From 72a82c41ee2b2217fcb96dca0a3cd4b9eea59c9d Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 9 Jan 2018 23:07:53 +0800 Subject: [PATCH 001/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=20success?= =?UTF-8?q?=20message,=20=E6=B7=BB=E5=8A=A0=E8=B5=84=E4=BA=A7=E7=BB=84?= =?UTF-8?q?=E6=97=B6=E5=8F=AF=E4=BB=A5=E6=B7=BB=E5=8A=A0=E8=B5=84=E4=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms.py | 25 +- apps/assets/models/asset.py | 2 +- apps/assets/signals_handler.py | 9 +- .../templates/assets/asset_group_create.html | 124 +----- apps/assets/views/admin_user.py | 35 +- apps/assets/views/asset.py | 14 +- apps/assets/views/cluster.py | 19 +- apps/assets/views/group.py | 43 +-- apps/assets/views/system_user.py | 42 +- apps/common/const.py | 7 + apps/jumpserver/settings.py | 1 + apps/locale/zh/LC_MESSAGES/django.mo | Bin 30609 -> 30126 bytes apps/locale/zh/LC_MESSAGES/django.po | 362 +++++++++--------- apps/perms/views.py | 60 +-- .../users/user_group_create_update.html | 18 +- apps/users/views/group.py | 24 +- apps/users/views/user.py | 17 +- 17 files changed, 313 insertions(+), 489 deletions(-) create mode 100644 apps/common/const.py diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 72d38f85f..3ff8bb900 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -124,20 +124,25 @@ class AssetGroupForm(forms.ModelForm): label=_('Asset'), required=False, widget=forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Select assets')}) + attrs={'class': 'select2', 'data-placeholder': _('Select assets')} ) + ) - def __init__(self, *args, **kwargs): - if kwargs.get('instance', None): + def __init__(self, **kwargs): + instance = kwargs.get('instance') + if instance: initial = kwargs.get('initial', {}) - initial['assets'] = kwargs['instance'].assets.all() - super(AssetGroupForm, self).__init__(*args, **kwargs) + initial.update({ + 'assets': instance.assets.all(), + }) + kwargs['initial'] = initial + super().__init__(**kwargs) - def _save_m2m(self): - super(AssetGroupForm, self)._save_m2m() - assets = self.cleaned_data['assets'] - self.instance.assets.clear() - self.instance.assets.add(*tuple(assets)) + def save(self, commit=True): + group = super().save(commit=commit) + assets= self.cleaned_data['assets'] + group.assets.set(assets) + return group class Meta: model = AssetGroup diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 276b767d3..3d3ed6b10 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -44,7 +44,7 @@ class Asset(models.Model): hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname')) port = models.IntegerField(default=22, verbose_name=_('Port')) groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups')) - cluster = models.ForeignKey(Cluster, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('Cluster')) + cluster = models.ForeignKey(Cluster, related_name='assets', on_delete=models.PROTECT, verbose_name=_('Cluster')) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),) env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),) diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index b21c0377f..9e6eb21eb 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -27,10 +27,11 @@ def test_asset_conn_on_created(asset): def push_cluster_system_users_to_asset(asset): - logger.info("Push cluster system user to asset: {}".format(asset)) - task_name = _("Push cluster system users to asset") - system_users = asset.cluster.systemuser_set.all() - push_system_user_util.delay(system_users, [asset], task_name) + if asset.cluster: + logger.info("Push cluster system user to asset: {}".format(asset)) + task_name = _("Push cluster system users to asset") + system_users = asset.cluster.systemuser_set.all() + push_system_user_util.delay(system_users, [asset], task_name) @receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier") diff --git a/apps/assets/templates/assets/asset_group_create.html b/apps/assets/templates/assets/asset_group_create.html index 3b951d4d1..36ca40fc1 100644 --- a/apps/assets/templates/assets/asset_group_create.html +++ b/apps/assets/templates/assets/asset_group_create.html @@ -1,119 +1,31 @@ -{% extends 'base.html' %} -{% load i18n %} +{% extends '_base_create_update.html' %} {% load static %} {% load bootstrap3 %} -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
-
-
{{ action }}
- -
+{% load i18n %} -
-
-
-
-
- {% csrf_token %} -

资产组信息

- {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} -{#
#} -{#

用户选择的资产

#} -{#
#} -{# #} -{#
#} -{#
#} -{#

#} -{# {% for asset in assets_on_list %}#} -{# #} -{# {% endfor %}#} -{#

#} -{#
#} -{#
#} -{#
#} -
-
-
- - -
-
-
-
-
-
-
-
-
+{% block form %} +
+ {% csrf_token %} + {% bootstrap_field form.name layout="horizontal" %} + {% bootstrap_field form.assets layout="horizontal" %} + {% bootstrap_field form.comment layout="horizontal" %} + +
+
+
+ +
-
- - - - + {% endblock %} {% block custom_foot_js %} {% endblock %} \ No newline at end of file diff --git a/apps/assets/views/admin_user.py b/apps/assets/views/admin_user.py index 713f1fc53..e3f6bc6b3 100644 --- a/apps/assets/views/admin_user.py +++ b/apps/assets/views/admin_user.py @@ -2,20 +2,22 @@ from __future__ import absolute_import, unicode_literals from django.utils.translation import ugettext as _ from django.conf import settings -from django.views.generic import TemplateView, ListView, View -from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.urls import reverse_lazy +from django.views.generic import TemplateView, ListView +from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.contrib.messages.views import SuccessMessageMixin from django.views.generic.detail import DetailView, SingleObjectMixin +from common.const import create_success_msg, update_success_msg from .. import forms -from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser +from ..models import AdminUser, Cluster from ..hands import AdminUserRequiredMixin -__all__ = ['AdminUserCreateView', 'AdminUserDetailView', - 'AdminUserDeleteView', 'AdminUserListView', - 'AdminUserUpdateView', 'AdminUserAssetsView', - ] +__all__ = [ + 'AdminUserCreateView', 'AdminUserDetailView', + 'AdminUserDeleteView', 'AdminUserListView', + 'AdminUserUpdateView', 'AdminUserAssetsView', +] class AdminUserListView(AdminUserRequiredMixin, TemplateView): @@ -38,6 +40,7 @@ class AdminUserCreateView(AdminUserRequiredMixin, form_class = forms.AdminUserForm template_name = 'assets/admin_user_create_update.html' success_url = reverse_lazy('assets:admin-user-list') + success_message = create_success_msg def get_context_data(self, **kwargs): context = { @@ -47,20 +50,13 @@ class AdminUserCreateView(AdminUserRequiredMixin, kwargs.update(context) return super().get_context_data(**kwargs) - def get_success_message(self, cleaned_data): - success_message = _( - 'Create admin user {name} successfully.'.format( - url=reverse_lazy('assets:admin-user-detail', - kwargs={'pk': self.object.pk}), - name=self.object.name, - )) - return success_message - -class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView): +class AdminUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = AdminUser form_class = forms.AdminUserForm template_name = 'assets/admin_user_create_update.html' + success_url = reverse_lazy('assets:admin-user-list') + success_message = update_success_msg def get_context_data(self, **kwargs): context = { @@ -70,11 +66,6 @@ class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView): kwargs.update(context) return super().get_context_data(**kwargs) - def get_success_url(self): - success_url = reverse_lazy('assets:admin-user-detail', - kwargs={'pk': self.object.pk}) - return success_url - class AdminUserDetailView(AdminUserRequiredMixin, DetailView): model = AdminUser diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 1c0e2ce1a..d251b7a15 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -21,10 +21,11 @@ from django.core.cache import cache from django.utils import timezone from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import redirect - +from django.contrib.messages.views import SuccessMessageMixin from common.mixins import JSONResponseMixin from common.utils import get_object_or_none, get_logger, is_uuid +from common.const import create_success_msg, update_success_msg from .. import forms from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser from ..hands import AdminUserRequiredMixin @@ -46,7 +47,6 @@ class AssetListView(AdminUserRequiredMixin, TemplateView): context = { 'app': _('Assets'), 'action': _('Asset list'), - # 'groups': AssetGroup.objects.all(), 'system_users': SystemUser.objects.all(), } kwargs.update(context) @@ -66,7 +66,7 @@ class UserAssetListView(LoginRequiredMixin, TemplateView): return super().get_context_data(**kwargs) -class AssetCreateView(AdminUserRequiredMixin, CreateView): +class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): model = Asset form_class = forms.AssetCreateForm template_name = 'assets/asset_create.html' @@ -87,6 +87,9 @@ class AssetCreateView(AdminUserRequiredMixin, CreateView): kwargs.update(context) return super().get_context_data(**kwargs) + def get_success_message(self, cleaned_data): + return create_success_msg % ({"name": cleaned_data["hostname"]}) + class AssetModalListView(AdminUserRequiredMixin, ListView): paginate_by = settings.CONFIG.DISPLAY_PER_PAGE @@ -147,7 +150,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView): return super().get_context_data(**kwargs) -class AssetUpdateView(AdminUserRequiredMixin, UpdateView): +class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = Asset form_class = forms.AssetUpdateForm template_name = 'assets/asset_update.html' @@ -161,6 +164,9 @@ class AssetUpdateView(AdminUserRequiredMixin, UpdateView): kwargs.update(context) return super().get_context_data(**kwargs) + def get_success_message(self, cleaned_data): + return update_success_msg % ({"name": cleaned_data["hostname"]}) + class AssetDeleteView(AdminUserRequiredMixin, DeleteView): model = Asset diff --git a/apps/assets/views/cluster.py b/apps/assets/views/cluster.py index f8540bed1..567e881dd 100644 --- a/apps/assets/views/cluster.py +++ b/apps/assets/views/cluster.py @@ -1,17 +1,21 @@ # coding:utf-8 -from __future__ import absolute_import, unicode_literals from django.utils.translation import ugettext as _ from django.views.generic import TemplateView, ListView, View from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.urls import reverse_lazy from django.views.generic.detail import DetailView, SingleObjectMixin +from django.contrib.messages.views import SuccessMessageMixin + +from common.const import create_success_msg, update_success_msg from .. import forms from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser from ..hands import AdminUserRequiredMixin -__all__ = ['ClusterListView', 'ClusterCreateView', 'ClusterUpdateView', - 'ClusterDetailView', 'ClusterDeleteView', 'ClusterAssetsView'] +__all__ = [ + 'ClusterListView', 'ClusterCreateView', 'ClusterUpdateView', + 'ClusterDetailView', 'ClusterDeleteView', 'ClusterAssetsView', +] class ClusterListView(AdminUserRequiredMixin, TemplateView): @@ -21,17 +25,17 @@ class ClusterListView(AdminUserRequiredMixin, TemplateView): context = { 'app': _('Assets'), 'action': _('Cluster list'), - # 'keyword': self.request.GET.get('keyword', '') } kwargs.update(context) return super().get_context_data(**kwargs) -class ClusterCreateView(AdminUserRequiredMixin, CreateView): +class ClusterCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): model = Cluster form_class = forms.ClusterForm template_name = 'assets/cluster_create_update.html' success_url = reverse_lazy('assets:cluster-list') + success_message = create_success_msg def get_context_data(self, **kwargs): context = { @@ -43,17 +47,18 @@ class ClusterCreateView(AdminUserRequiredMixin, CreateView): def form_valid(self, form): cluster = form.save(commit=False) - cluster.created_by = self.request.user.username or 'System' + cluster.created_by = self.request.user.username cluster.save() return super().form_valid(form) -class ClusterUpdateView(AdminUserRequiredMixin, UpdateView): +class ClusterUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = Cluster form_class = forms.ClusterForm template_name = 'assets/cluster_create_update.html' context_object_name = 'cluster' success_url = reverse_lazy('assets:cluster-list') + success_message = update_success_msg def form_valid(self, form): cluster = form.save(commit=False) diff --git a/apps/assets/views/group.py b/apps/assets/views/group.py index 5e87d9dc7..0ae65eaa0 100644 --- a/apps/assets/views/group.py +++ b/apps/assets/views/group.py @@ -7,42 +7,41 @@ from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateVi from django.urls import reverse_lazy from django.views.generic.detail import DetailView, SingleObjectMixin from django.shortcuts import get_object_or_404, reverse, redirect +from django.contrib.messages.views import SuccessMessageMixin +from common.const import create_success_msg, update_success_msg from .. import forms from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser from ..hands import AdminUserRequiredMixin -__all__ = ['AssetGroupCreateView', 'AssetGroupDetailView', - 'AssetGroupUpdateView', 'AssetGroupListView', - 'AssetGroupDeleteView', - ] +__all__ = [ + 'AssetGroupCreateView', 'AssetGroupDetailView', + 'AssetGroupUpdateView', 'AssetGroupListView', + 'AssetGroupDeleteView', +] -class AssetGroupCreateView(AdminUserRequiredMixin, CreateView): +class AssetGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): model = AssetGroup form_class = forms.AssetGroupForm template_name = 'assets/asset_group_create.html' success_url = reverse_lazy('assets:asset-group-list') + success_message = create_success_msg def get_context_data(self, **kwargs): context = { 'app': _('Assets'), 'action': _('Create asset group'), - 'assets_count': 0, } kwargs.update(context) - return super(AssetGroupCreateView, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) def form_valid(self, form): - asset_group = form.save() - assets_id_list = self.request.POST.getlist('assets', []) - assets = [get_object_or_404(Asset, id=int(asset_id)) - for asset_id in assets_id_list] - asset_group.created_by = self.request.user.username or 'Admin' - asset_group.assets.add(*tuple(assets)) - asset_group.save() - return super(AssetGroupCreateView, self).form_valid(form) + group = form.save() + group.created_by = self.request.user.username + group.save() + return super().form_valid(form) class AssetGroupListView(AdminUserRequiredMixin, TemplateView): @@ -54,7 +53,6 @@ class AssetGroupListView(AdminUserRequiredMixin, TemplateView): 'action': _('Asset group list'), 'assets': Asset.objects.all(), 'system_users': SystemUser.objects.all(), - 'keyword': self.request.GET.get('keyword', '') } kwargs.update(context) return super(AssetGroupListView, self).get_context_data(**kwargs) @@ -77,27 +75,20 @@ class AssetGroupDetailView(AdminUserRequiredMixin, DetailView): return super().get_context_data(**kwargs) -class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView): +class AssetGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = AssetGroup form_class = forms.AssetGroupForm template_name = 'assets/asset_group_create.html' success_url = reverse_lazy('assets:asset-group-list') - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=AssetGroup.objects.all()) - return super(AssetGroupUpdateView, self).get(request, *args, **kwargs) + success_message = update_success_msg def get_context_data(self, **kwargs): - assets_all = self.object.assets.all() context = { 'app': _('Assets'), 'action': _('Create asset group'), - 'assets_on_list': assets_all, - 'assets_count': len(assets_all), - 'group_id': self.object.id, } kwargs.update(context) - return super(AssetGroupUpdateView, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView): diff --git a/apps/assets/views/system_user.py b/apps/assets/views/system_user.py index b79827598..6fdd8825d 100644 --- a/apps/assets/views/system_user.py +++ b/apps/assets/views/system_user.py @@ -1,24 +1,25 @@ # ~*~ coding: utf-8 ~*~ -from django.contrib import messages -from django.shortcuts import redirect, reverse +from django.shortcuts import reverse from django.utils.translation import ugettext as _ from django.db import transaction -from django.views.generic import TemplateView, ListView, FormView +from django.views.generic import TemplateView from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.urls import reverse_lazy from django.contrib.messages.views import SuccessMessageMixin -from django.views.generic.detail import DetailView, SingleObjectMixin +from django.views.generic.detail import DetailView -from ..forms import SystemUserForm, SystemUserUpdateForm, SystemUserAuthForm +from common.const import create_success_msg, update_success_msg +from ..forms import SystemUserForm, SystemUserUpdateForm from ..models import SystemUser, Cluster from ..hands import AdminUserRequiredMixin -__all__ = ['SystemUserCreateView', 'SystemUserUpdateView', - 'SystemUserDetailView', 'SystemUserDeleteView', - 'SystemUserAssetView', 'SystemUserListView', - ] +__all__ = [ + 'SystemUserCreateView', 'SystemUserUpdateView', + 'SystemUserDetailView', 'SystemUserDeleteView', + 'SystemUserAssetView', 'SystemUserListView', +] class SystemUserListView(AdminUserRequiredMixin, TemplateView): @@ -38,10 +39,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi form_class = SystemUserForm template_name = 'assets/system_user_create.html' success_url = reverse_lazy('assets:system-user-list') - - @transaction.atomic - def post(self, request, *args, **kwargs): - return super(SystemUserCreateView, self).post(request, *args, **kwargs) + success_message = create_success_msg def get_context_data(self, **kwargs): context = { @@ -51,20 +49,13 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi kwargs.update(context) return super().get_context_data(**kwargs) - def get_success_message(self, cleaned_data): - url = reverse('assets:system-user-detail', kwargs={'pk': self.object.pk}) - success_message = _( - 'Create system user {name} ' - 'successfully.'.format(url=url, name=self.object.name) - ) - return success_message - - -class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView): +class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = SystemUser form_class = SystemUserUpdateForm template_name = 'assets/system_user_update.html' + success_url = reverse_lazy('assets:system-user-list') + success_message = update_success_msg def get_context_data(self, **kwargs): context = { @@ -74,11 +65,6 @@ class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView): kwargs.update(context) return super().get_context_data(**kwargs) - def get_success_url(self): - success_url = reverse_lazy('assets:system-user-detail', - kwargs={'pk': self.object.pk}) - return success_url - class SystemUserDetailView(AdminUserRequiredMixin, DetailView): template_name = 'assets/system_user_detail.html' diff --git a/apps/common/const.py b/apps/common/const.py new file mode 100644 index 000000000..a28b0f1db --- /dev/null +++ b/apps/common/const.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# + +from django.utils.translation import ugettext as _ + +create_success_msg = _("%(name)s was created successfully") +update_success_msg = _("%(name)s was updated successfully") \ No newline at end of file diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 214aba3aa..ce2d06ed0 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -374,4 +374,5 @@ BOOTSTRAP3 = { 'horizontal_field_class': 'col-md-9', # Set placeholder attributes to label if no placeholder is provided 'set_placeholder': True, + 'success_css_class': '', } diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 4eed75216922ff6a32dc99b7386330c0373b1c97..5bb422501d5c1e84b09d0fafbf0347cc7c371e46 100644 GIT binary patch delta 10967 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 T{{B4X8#A_*Z}yc58-4!|qc!tm delta 11242 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 ZV0h!e*lSBRUt75GA550>Xw=5Q{{^f+Riyv` diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 910a4fa02..b34a774f4 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-01-08 15:58+0800\n" +"POT-Creation-Date: 2018-01-09 23:01+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -76,22 +76,22 @@ msgstr "端口" msgid "Asset" 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 msgid "Select system users" msgstr "选择系统用户" -#: assets/forms.py:158 +#: assets/forms.py:163 #: assets/templates/assets/_asset_group_bulk_update_modal.html:22 #: assets/templates/assets/cluster_list.html:22 msgid "System users" msgstr "系统用户" -#: assets/forms.py:160 +#: assets/forms.py:165 msgid "Selected system users will be create at cluster assets" msgstr "选择的系统用户将会在该集群资产上创建" -#: assets/forms.py:168 assets/forms.py:243 assets/forms.py:302 +#: assets/forms.py:173 assets/forms.py:248 assets/forms.py:307 #: assets/models/cluster.py:18 assets/models/group.py:20 #: assets/models/user.py:28 assets/templates/assets/admin_user_detail.html:56 #: assets/templates/assets/admin_user_list.html:22 @@ -120,15 +120,15 @@ msgstr "选择的系统用户将会在该集群资产上创建" msgid "Name" msgstr "名称" -#: assets/forms.py:174 +#: assets/forms.py:179 msgid "Cluster level admin user" msgstr "集群级别管理用户" -#: assets/forms.py:195 +#: assets/forms.py:200 msgid "Password or private key password" msgstr "密码或秘钥不合法" -#: assets/forms.py:196 assets/models/user.py:30 users/forms.py:16 +#: assets/forms.py:201 assets/models/user.py:30 users/forms.py:16 #: users/forms.py:24 users/templates/users/login.html:56 #: users/templates/users/reset_password.html:52 #: users/templates/users/user_create.html:11 @@ -138,19 +138,19 @@ msgstr "密码或秘钥不合法" msgid "Password" msgstr "密码" -#: assets/forms.py:199 users/models/user.py:46 +#: assets/forms.py:204 users/models/user.py:46 msgid "Private key" msgstr "ssh私钥" -#: assets/forms.py:224 assets/forms.py:284 assets/forms.py:345 +#: assets/forms.py:229 assets/forms.py:289 assets/forms.py:350 msgid "Invalid private key" msgstr "ssh密钥不合法" -#: assets/forms.py:235 +#: assets/forms.py:240 msgid "Password and private key file must be input one" msgstr "密码和私钥, 必须输入一个" -#: assets/forms.py:244 assets/forms.py:303 assets/models/user.py:29 +#: assets/forms.py:249 assets/forms.py:308 assets/models/user.py:29 #: assets/templates/assets/admin_user_detail.html:60 #: assets/templates/assets/admin_user_list.html:23 #: assets/templates/assets/system_user_detail.html:57 @@ -166,23 +166,23 @@ msgstr "密码和私钥, 必须输入一个" msgid "Username" msgstr "用户名" -#: assets/forms.py:291 assets/forms.py:351 +#: assets/forms.py:296 assets/forms.py:356 msgid "Auth info required, private_key or password" msgstr "密钥和密码必须填写一个" -#: assets/forms.py:306 +#: assets/forms.py:311 msgid " Select clusters" msgstr "选择集群" -#: assets/forms.py:311 +#: assets/forms.py:316 msgid "If auto push checked, system user will be create at cluster assets" msgstr "如果选择了自动推送,系统用户将会创建在集群资产上" -#: assets/forms.py:312 +#: assets/forms.py:317 msgid "Auto push system user to asset" msgstr "自动推送系统用户到资产" -#: assets/forms.py:313 +#: assets/forms.py:318 msgid "" "High level will be using login asset as default, if user was granted more " "than 2 system user" @@ -260,12 +260,12 @@ msgid "Hostname" msgstr "主机名" #: assets/models/asset.py:46 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" msgstr "资产组" #: assets/models/asset.py:47 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 msgid "Cluster" msgstr "集群" @@ -287,7 +287,7 @@ msgid "Asset status" msgstr "资产状态" #: assets/models/asset.py:54 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 msgid "Admin user" msgstr "管理用户" @@ -443,7 +443,7 @@ msgstr "运营商" msgid "Default" msgstr "默认" -#: assets/models/cluster.py:36 users/models/user.py:259 +#: assets/models/cluster.py:36 users/models/user.py:263 msgid "System" msgstr "系统" @@ -468,29 +468,29 @@ msgstr "ssh密钥" msgid "SSH public key" msgstr "ssh公钥" -#: assets/models/user.py:216 +#: assets/models/user.py:220 msgid "Priority" 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:61 msgid "Protocol" 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_update.html:11 msgid "Auto push" 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:65 msgid "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:70 msgid "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_list.html:30 templates/_nav.html:26 #: terminal/backends/command/models.py:12 terminal/models.py:94 @@ -508,15 +508,15 @@ msgstr "系统用户" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/signals_handler.py:31 +#: assets/signals_handler.py:32 msgid "Push cluster system users to asset" msgstr "推送集群系统用户到资产" -#: assets/signals_handler.py:63 assets/signals_handler.py:125 +#: assets/signals_handler.py:64 assets/signals_handler.py:126 msgid "Push system user to cluster assets: {}->{}" msgstr "推送系统用户到: {}->{}" -#: assets/signals_handler.py:102 +#: assets/signals_handler.py:103 msgid "Push system user to assets" msgstr "推送系统用户到资产" @@ -552,11 +552,11 @@ msgstr "测试系统用户可连接性: {}" msgid "Test system user connectability period: {}" msgstr "定期测试系统用户可连接性: {}" -#: assets/tasks.py:372 +#: assets/tasks.py:376 msgid "Push system user to cluster assets: {}" msgstr "推送系统用户到资产: {}" -#: assets/tasks.py:393 +#: assets/tasks.py:397 msgid "Push cluster system users to assets period: {}" msgstr "定期推送集群系统用户到资产: {}" @@ -570,16 +570,16 @@ msgstr "仅修改你需要更新的字段" #: assets/templates/assets/_asset_group_bulk_update_modal.html:12 #: 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:67 assets/views/admin_user.py:88 -#: assets/views/admin_user.py:115 assets/views/asset.py:47 -#: assets/views/asset.py:61 assets/views/asset.py:84 assets/views/asset.py:141 -#: assets/views/asset.py:158 assets/views/asset.py:179 -#: assets/views/cluster.py:22 assets/views/cluster.py:80 -#: assets/views/cluster.py:97 assets/views/group.py:30 assets/views/group.py:53 -#: assets/views/group.py:71 assets/views/group.py:93 -#: assets/views/system_user.py:29 assets/views/system_user.py:48 -#: assets/views/system_user.py:71 assets/views/system_user.py:91 +#: assets/views/admin_user.py:29 assets/views/admin_user.py:47 +#: assets/views/admin_user.py:63 assets/views/admin_user.py:79 +#: 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:144 +#: assets/views/asset.py:161 assets/views/asset.py:185 +#: assets/views/cluster.py:26 assets/views/cluster.py:85 +#: assets/views/cluster.py:102 assets/views/group.py:34 +#: assets/views/group.py:52 assets/views/group.py:69 assets/views/group.py:87 +#: assets/views/system_user.py:30 assets/views/system_user.py:46 +#: assets/views/system_user.py:62 assets/views/system_user.py:77 #: templates/_nav.html:19 msgid "Assets" msgstr "资产管理" @@ -620,7 +620,7 @@ msgstr "如果设置了id,则会使用该行信息更新该id的资产" #: assets/templates/assets/_system_user.html:16 #: assets/templates/assets/system_user_list.html:16 -#: assets/views/system_user.py:49 +#: assets/views/system_user.py:47 msgid "Create system user" msgstr "创建系统用户" @@ -657,6 +657,7 @@ msgstr "其它" #: assets/templates/assets/admin_user_create_update.html:45 #: assets/templates/assets/asset_bulk_update.html:23 #: 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/cluster_create_update.html:54 #: perms/templates/perms/asset_permission_create_update.html:67 @@ -675,6 +676,7 @@ msgstr "重置" #: assets/templates/assets/admin_user_create_update.html:46 #: assets/templates/assets/asset_bulk_update.html:24 #: assets/templates/assets/asset_create.html:41 +#: assets/templates/assets/asset_group_create.html:17 #: assets/templates/assets/asset_list.html:55 #: assets/templates/assets/asset_update.html:56 #: assets/templates/assets/cluster_create_update.html:55 @@ -711,6 +713,52 @@ msgstr "详情" msgid "Assets list" 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: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_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_list.html:43 +#: assets/templates/assets/asset_list.html:96 +#: assets/templates/assets/cluster_detail.html:29 +#: assets/templates/assets/cluster_list.html:44 +#: 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/asset_group_detail.html:26 #: perms/templates/perms/asset_permission_asset.html:35 @@ -760,7 +808,7 @@ msgstr "任务已下发,查看左侧资产状态" #: assets/templates/assets/admin_user_create_update.html:16 #: assets/templates/assets/admin_user_list.html:14 -#: assets/views/admin_user.py:45 +#: assets/views/admin_user.py:48 msgid "Create admin user" msgstr "创建管理用户" @@ -780,7 +828,7 @@ msgstr "使用集群管理用户" #: users/templates/users/user_detail.html:338 #: users/templates/users/user_detail.html:363 #: 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_list.html:184 #: users/templates/users/user_profile.html:181 @@ -817,41 +865,13 @@ msgstr "比例" msgid "Action" 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_update.html:33 msgid "Group" msgstr "组" -#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:180 -#: assets/views/cluster.py:98 +#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:186 +#: assets/views/cluster.py:103 msgid "Asset detail" msgstr "资产详情" @@ -937,8 +957,8 @@ msgstr "添加" msgid "Remove" msgstr "移除" -#: assets/templates/assets/asset_group_list.html:7 assets/views/group.py:31 -#: assets/views/group.py:94 +#: assets/templates/assets/asset_group_list.html:7 assets/views/group.py:35 +#: assets/views/group.py:88 msgid "Create asset group" msgstr "创建资产组" @@ -1062,6 +1082,7 @@ msgid "Test assets connective" msgstr "测试资产可连接性" #: assets/templates/assets/cluster_assets.html:77 +#: ops/templates/ops/task_list.html:70 msgid "Run" msgstr "执行" @@ -1083,7 +1104,7 @@ msgstr "任务已下发,查看左侧资产状态" msgid "Settings" 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" msgstr "创建Cluster" @@ -1180,85 +1201,86 @@ msgstr "连接性" msgid "Connect" msgstr "连接" -#: assets/views/admin_user.py:28 +#: assets/views/admin_user.py:30 msgid "Admin user list" msgstr "管理用户列表" -#: assets/views/admin_user.py:52 -#, python-brace-format -msgid "Create admin user {name} successfully." -msgstr "创建管理用户 {name} 成功" - -#: assets/views/admin_user.py:68 +#: assets/views/admin_user.py:64 msgid "Update admin user" 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" msgstr "管理用户详情" -#: assets/views/asset.py:48 assets/views/asset.py:62 +#: assets/views/asset.py:49 assets/views/asset.py:62 msgid "Asset list" msgstr "资产列表" -#: assets/views/asset.py:142 +#: assets/views/asset.py:145 msgid "Bulk update asset" msgstr "批量更新资产" -#: assets/views/asset.py:159 +#: assets/views/asset.py:162 msgid "Update asset" msgstr "编辑资产" -#: assets/views/asset.py:292 +#: assets/views/asset.py:298 msgid "already exists" msgstr "已经存在" -#: assets/views/cluster.py:23 +#: assets/views/cluster.py:27 msgid "Cluster list" msgstr "集群列表" -#: assets/views/cluster.py:38 assets/views/cluster.py:65 -#: assets/views/system_user.py:112 +#: assets/views/cluster.py:42 assets/views/cluster.py:70 +#: assets/views/system_user.py:98 msgid "assets" msgstr "资产管理" -#: assets/views/cluster.py:66 +#: assets/views/cluster.py:71 msgid "Update Cluster" msgstr "更新Cluster" -#: assets/views/cluster.py:81 +#: assets/views/cluster.py:86 msgid "Cluster detail" msgstr "集群详情" -#: assets/views/group.py:54 +#: assets/views/group.py:53 msgid "Asset group list" msgstr "资产组列表" -#: assets/views/group.py:72 +#: assets/views/group.py:70 msgid "Asset group detail" msgstr "资产组详情" -#: assets/views/system_user.py:30 +#: assets/views/system_user.py:31 msgid "System user list" msgstr "系统用户列表" -#: assets/views/system_user.py:57 -#, python-brace-format -msgid "Create system user {name} successfully." -msgstr "创建系统用户 {name} 成功" - -#: assets/views/system_user.py:72 +#: assets/views/system_user.py:63 msgid "Update system user" msgstr "更新系统用户" -#: assets/views/system_user.py:92 +#: assets/views/system_user.py:78 msgid "System user detail" msgstr "系统用户详情" -#: assets/views/system_user.py:113 +#: assets/views/system_user.py:99 msgid "System user asset" msgstr "系统用户集群资产" +#: common/const.py:6 +#, python-format +#| msgid "User group {name} was created successfully" +msgid "%(name)s was created successfully" +msgstr "%(name)s 创建成功" + +#: common/const.py:7 +#, python-format +msgid "%(name)s was updated successfully" +msgstr "%(name)s 更新成功" + #: common/mixins.py:29 msgid "is discard" msgstr "" @@ -1517,6 +1539,10 @@ msgstr "成功" msgid "Date" 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:91 ops/views.py:104 ops/views.py:117 msgid "Ops" @@ -1544,7 +1570,7 @@ msgstr "选择用户" #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:187 #: users/models/user.py:31 users/templates/users/user_group_detail.html:78 -#: users/views/user.py:348 +#: users/views/user.py:345 msgid "User" msgstr "用户" @@ -1658,47 +1684,32 @@ msgstr "选择用户" msgid "Add user group to asset permission" msgstr "添加用户组" -#: perms/views.py:27 perms/views.py:77 perms/views.py:103 perms/views.py:128 -#: perms/views.py:165 perms/views.py:195 templates/_nav.html:30 +#: perms/views.py:28 perms/views.py:44 perms/views.py:60 perms/views.py:74 +#: perms/views.py:111 perms/views.py:141 templates/_nav.html:30 msgid "Perms" msgstr "权限管理" -#: perms/views.py:28 +#: perms/views.py:29 msgid "Asset permission list" msgstr "资产授权列表" -#: perms/views.py:63 -#, python-brace-format -msgid "Create asset permission {name} successfully." -msgstr "创建授权 {name} 成功" - -#: perms/views.py:78 +#: perms/views.py:45 msgid "Create asset permission" msgstr "创建权限规则" -#: perms/views.py:89 -#, python-brace-format -msgid "Create asset permission {name} success." -msgstr "创建授权 {name} 成功" - -#: perms/views.py:104 +#: perms/views.py:61 msgid "Update asset permission" msgstr "更新资产授权" -#: perms/views.py:115 -#, python-brace-format -msgid "Update asset permission {name} success." -msgstr "更新授权 {name} 成功" - -#: perms/views.py:129 +#: perms/views.py:75 msgid "Asset permission detail" msgstr "资产授权详情" -#: perms/views.py:166 +#: perms/views.py:112 msgid "Asset permission user list" msgstr "资产授权包含用户" -#: perms/views.py:196 +#: perms/views.py:142 msgid "Asset permission asset list" msgstr "资产组授权包含资产" @@ -1716,32 +1727,28 @@ msgstr "帮助" #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: 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:320 msgid "Profile" msgstr "个人信息" -#: templates/_header_bar.html:34 -msgid "Profile settings" -msgstr "个人信息设置" - -#: templates/_header_bar.html:38 +#: templates/_header_bar.html:37 msgid "Admin page" msgstr "管理页面" -#: templates/_header_bar.html:40 +#: templates/_header_bar.html:39 msgid "User page" msgstr "用户页面" -#: templates/_header_bar.html:43 +#: templates/_header_bar.html:42 msgid "Logout" 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 msgid "Login" msgstr "登录" -#: templates/_header_bar.html:60 templates/_nav.html:4 +#: templates/_header_bar.html:59 templates/_nav.html:4 msgid "Dashboard" msgstr "仪表盘" @@ -1775,11 +1782,11 @@ msgstr "" msgid "Close" msgstr "关闭" -#: templates/_nav.html:9 users/views/group.py:30 users/views/group.py:48 -#: users/views/group.py:74 users/views/group.py:91 users/views/login.py:193 -#: users/views/login.py:242 users/views/user.py:55 users/views/user.py:70 -#: users/views/user.py:95 users/views/user.py:151 users/views/user.py:308 -#: users/views/user.py:322 users/views/user.py:366 users/views/user.py:388 +#: templates/_nav.html:9 users/views/group.py:28 users/views/group.py:44 +#: users/views/group.py:62 users/views/group.py:79 users/views/login.py:193 +#: users/views/login.py:242 users/views/user.py:57 users/views/user.py:72 +#: users/views/user.py:92 users/views/user.py:148 users/views/user.py:305 +#: users/views/user.py:319 users/views/user.py:363 users/views/user.py:385 msgid "Users" msgstr "用户管理" @@ -2152,7 +2159,7 @@ msgstr "Agent" msgid "Date login" msgstr "登录日期" -#: users/models/user.py:30 users/models/user.py:255 +#: users/models/user.py:30 users/models/user.py:259 msgid "Administrator" msgstr "管理员" @@ -2191,7 +2198,7 @@ msgstr "二次验证" msgid "Public key" msgstr "ssh公钥" -#: users/models/user.py:258 +#: users/models/user.py:262 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -2293,7 +2300,7 @@ msgid "Setting" msgstr "设置" #: 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" msgstr "创建用户" @@ -2304,7 +2311,7 @@ msgstr "生成重置密码连接,通过邮件发送给用户" #: users/templates/users/user_detail.html:19 #: users/templates/users/user_granted_asset.html:18 #: users/templates/users/user_group_granted_asset.html:18 -#: users/views/user.py:152 +#: users/views/user.py:149 msgid "User detail" msgstr "用户详情" @@ -2379,11 +2386,11 @@ msgstr "授权资产" msgid "Asset groups granted of " msgstr "授权资产组" -#: users/templates/users/user_group_create_update.html:45 +#: users/templates/users/user_group_create_update.html:31 msgid "Cancel" 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" msgstr "资产组详情" @@ -2395,7 +2402,7 @@ msgstr "添加用户" msgid "Valid" 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" msgstr "创建用户组" @@ -2433,8 +2440,8 @@ msgstr "用户删除失败" msgid "OTP" msgstr "" -#: users/templates/users/user_profile.html:100 users/views/user.py:181 -#: users/views/user.py:233 +#: users/templates/users/user_profile.html:100 users/views/user.py:178 +#: users/views/user.py:230 msgid "User groups" msgstr "用户组" @@ -2458,7 +2465,7 @@ msgstr "指纹" msgid "Update public key" msgstr "更新密钥" -#: users/templates/users/user_update.html:4 users/views/user.py:95 +#: users/templates/users/user_update.html:4 users/views/user.py:92 msgid "Update user" msgstr "编辑用户" @@ -2592,17 +2599,11 @@ msgstr "禁用或失效" msgid "Password or SSH public key invalid" msgstr "密码或秘钥不合法" -#: users/views/group.py:31 +#: users/views/group.py:29 msgid "User group list" msgstr "用户组列表" -#: users/views/group.py:43 -#, fuzzy, python-brace-format -#| msgid "Create user {name} successfully." -msgid "User group {name} was created successfully" -msgstr "创建用户 {name} 成功" - -#: users/views/group.py:75 +#: users/views/group.py:63 msgid "Update user group" msgstr "编辑用户组" @@ -2655,37 +2656,36 @@ msgstr "首次登陆" msgid "Login log list" msgstr "登录日志" -#: users/views/user.py:56 +#: users/views/user.py:58 msgid "User list" msgstr "用户列表" -#: users/views/user.py:66 users/views/user.py:335 +#: users/views/user.py:102 +msgid "Bulk update user success" +msgstr "批量更新用户成功" + +#: users/views/user.py:207 +msgid "Invalid file." +msgstr "文件不合法" + +#: users/views/user.py:306 +msgid "User granted assets" +msgstr "用户授权资产" + +#: users/views/user.py:332 #, python-brace-format msgid "Create user {name} successfully." msgstr "创建用户 {name} 成功" -#: users/views/user.py:105 -msgid "Bulk update user success" -msgstr "批量更新用户成功" - -#: users/views/user.py:210 -msgid "Invalid file." -msgstr "文件不合法" - -#: users/views/user.py:309 -msgid "User granted assets" -msgstr "用户授权资产" - -#: users/views/user.py:349 +#: users/views/user.py:346 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:367 +#: users/views/user.py:364 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:389 +#: users/views/user.py:386 msgid "Public key update" msgstr "秘钥更新" - diff --git a/apps/perms/views.py b/apps/perms/views.py index d3759d064..6fb2cbca7 100644 --- a/apps/perms/views.py +++ b/apps/perms/views.py @@ -11,6 +11,7 @@ from django.contrib.messages.views import SuccessMessageMixin from django.views.generic.detail import DetailView, SingleObjectMixin from django.contrib import messages +from common.const import create_success_msg, update_success_msg from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \ Asset, AssetGroup from .models import AssetPermission @@ -31,46 +32,12 @@ class AssetPermissionListView(AdminUserRequiredMixin, ListView): return super().get_context_data(**kwargs) -class MessageMixin: - def form_valid(self, form): - response = super().form_valid(form) - errors = self.object.check_system_user_in_assets() - if errors: - message = self.get_warning_messages(errors) - messages.warning(self.request, message) - else: - message = self.get_success_message(form.cleaned_data) - messages.success(self.request, message) - - success_message = self.get_success_message(form.cleaned_data) - if success_message: - messages.success(self.request, success_message) - return response - - @staticmethod - def get_warning_messages(errors): - message = "WARNING: System user " \ - "should in behind clusters, so that " \ - "system user cat auto push to the cluster assets:
" - 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 {name} ' - 'successfully.'.format(url=url, name=self.object.name)) - return success_message - - class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): model = AssetPermission form_class = AssetPermissionForm template_name = 'perms/asset_permission_create_update.html' success_url = reverse_lazy('perms:asset-permission-list') - warning = None + success_message = create_success_msg def get_context_data(self, **kwargs): context = { @@ -80,23 +47,13 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, Cre kwargs.update(context) return super().get_context_data(**kwargs) - def get_success_message(self, cleaned_data): - url = reverse_lazy( - 'perms:asset-permission-detail', - kwargs={'pk': self.object.pk} - ) - success_message = _( - 'Create asset permission {name} ' - 'success.'.format(url=url, name=self.object.name) - ) - return success_message - class AssetPermissionUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = AssetPermission form_class = AssetPermissionForm template_name = 'perms/asset_permission_create_update.html' success_url = reverse_lazy("perms:asset-permission-list") + success_message = update_success_msg def get_context_data(self, **kwargs): context = { @@ -106,17 +63,6 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, Upd kwargs.update(context) return super().get_context_data(**kwargs) - def get_success_message(self, cleaned_data): - url = reverse_lazy( - 'perms:asset-permission-detail', - kwargs={'pk': self.object.pk} - ) - success_message = _( - 'Update asset permission {name} ' - 'success.'.format(url=url, name=self.object.name) - ) - return success_message - class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView): template_name = 'perms/asset_permission_detail.html' diff --git a/apps/users/templates/users/user_group_create_update.html b/apps/users/templates/users/user_group_create_update.html index 5c388e668..6baef2a94 100644 --- a/apps/users/templates/users/user_group_create_update.html +++ b/apps/users/templates/users/user_group_create_update.html @@ -25,20 +25,6 @@ {% csrf_token %} {% bootstrap_field form.name layout="horizontal" %} {% bootstrap_field form.users layout="horizontal" %} -{#
#} -{# #} -{#
#} -{# #} -{#
#} -{#
#} {% bootstrap_field form.comment layout="horizontal" %}
@@ -57,7 +43,9 @@ {% block custom_foot_js %} {% endblock %} diff --git a/apps/users/views/group.py b/apps/users/views/group.py index a26cfcaec..4b11e7901 100644 --- a/apps/users/views/group.py +++ b/apps/users/views/group.py @@ -2,17 +2,15 @@ from __future__ import unicode_literals from django import forms -from django.shortcuts import reverse, redirect from django.utils.translation import ugettext as _ from django.urls import reverse_lazy -from django.views.generic import ListView from django.views.generic.base import TemplateView -from django.views.generic.edit import CreateView, UpdateView, FormMixin -from django.views.generic.detail import DetailView, SingleObjectMixin +from django.views.generic.edit import CreateView, UpdateView +from django.views.generic.detail import DetailView from django.contrib.messages.views import SuccessMessageMixin from common.utils import get_logger -from perms.models import AssetPermission +from common.const import create_success_msg, update_success_msg from ..models import User, UserGroup from ..utils import AdminUserRequiredMixin from .. import forms @@ -39,9 +37,7 @@ class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie form_class = forms.UserGroupForm template_name = 'users/user_group_create_update.html' success_url = reverse_lazy('users:user-group-list') - success_message = _( - 'User group {name} was created successfully' - ) + success_message = create_success_msg def get_context_data(self, **kwargs): context = { @@ -51,21 +47,13 @@ class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie kwargs.update(context) return super().get_context_data(**kwargs) - def get_success_message(self, cleaned_data): - url = reverse_lazy( - 'users:user-group-detail', - kwargs={'pk': self.object.id} - ) - return self.success_message.format( - url=url, name=self.object.name - ) - -class UserGroupUpdateView(AdminUserRequiredMixin, UpdateView): +class UserGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = UserGroup form_class = forms.UserGroupForm template_name = 'users/user_group_create_update.html' success_url = reverse_lazy('users:user-group-list') + success_message = update_success_msg def get_context_data(self, **kwargs): users = User.objects.all() diff --git a/apps/users/views/user.py b/apps/users/views/user.py index ec54e1d0c..89ed460bb 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -27,12 +27,14 @@ from django.views.generic.detail import DetailView, SingleObjectMixin from django.views.decorators.csrf import csrf_exempt from django.contrib.auth import logout as auth_logout +from common.const import create_success_msg, update_success_msg +from common.mixins import JSONResponseMixin +from common.utils import get_logger, get_object_or_none, is_uuid from .. import forms from ..models import User, UserGroup from ..utils import AdminUserRequiredMixin from ..signals import on_user_created -from common.mixins import JSONResponseMixin -from common.utils import get_logger, get_object_or_none, is_uuid + __all__ = [ 'UserListView', 'UserCreateView', 'UserDetailView', @@ -63,7 +65,7 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): form_class = forms.UserCreateUpdateForm template_name = 'users/user_create.html' success_url = reverse_lazy('users:user-list') - success_message = _('Create user {name} successfully.') + success_message = create_success_msg def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -77,19 +79,14 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): on_user_created.send(self.__class__, user=user) return super().form_valid(form) - def get_success_message(self, cleaned_data): - url = reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk}) - return self.success_message.format( - url=url, name=self.object.name - ) - -class UserUpdateView(AdminUserRequiredMixin, UpdateView): +class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = User form_class = forms.UserCreateUpdateForm template_name = 'users/user_update.html' context_object_name = 'user_object' success_url = reverse_lazy('users:user-list') + success_message = update_success_msg def get_context_data(self, **kwargs): context = {'app': _('Users'), 'action': _('Update user')} From a4d02adb223eb63ffd0cb8e0e1c84d86e44d602d Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 9 Jan 2018 23:14:25 +0800 Subject: [PATCH 002/197] [Update] system user form add label --- apps/assets/forms.py | 12 ++++++++---- apps/assets/views/system_user.py | 2 -- apps/users/views/user.py | 7 ------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 3ff8bb900..68aaddc43 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -258,9 +258,10 @@ class SystemUserForm(forms.ModelForm): # Admin user assets define, let user select, save it in form not in view auto_generate_key = forms.BooleanField(initial=True, required=False) # Form field name can not start with `_`, so redefine it, - password = forms.CharField(widget=forms.PasswordInput, required=False, max_length=128, strip=True) + password = forms.CharField(widget=forms.PasswordInput, required=False, + max_length=128, strip=True, label=_("Password")) # Need use upload private key file except paste private key content - private_key_file = forms.FileField(required=False) + private_key_file = forms.FileField(required=False, label=_("Private key")) def save(self, commit=True): # Because we define custom field, so we need rewrite :method: `save` @@ -307,8 +308,11 @@ class SystemUserForm(forms.ModelForm): 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), 'cluster': forms.SelectMultiple( - attrs={'class': 'select2', - 'data-placeholder': _(' Select clusters')}), + attrs={ + 'class': 'select2', + 'data-placeholder': _(' Select clusters') + } + ), } help_texts = { 'name': '* required', diff --git a/apps/assets/views/system_user.py b/apps/assets/views/system_user.py index 6fdd8825d..f7e7eaa35 100644 --- a/apps/assets/views/system_user.py +++ b/apps/assets/views/system_user.py @@ -1,8 +1,6 @@ # ~*~ coding: utf-8 ~*~ -from django.shortcuts import reverse from django.utils.translation import ugettext as _ -from django.db import transaction from django.views.generic import TemplateView from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.urls import reverse_lazy diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 89ed460bb..6b86ddc1b 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -329,17 +329,10 @@ class UserProfileUpdateView(LoginRequiredMixin, UpdateView): model = User form_class = forms.UserProfileForm success_url = reverse_lazy('users:user-profile') - success_message = _('Create user {name} successfully.') def get_object(self, queryset=None): return self.request.user - def get_success_message(self, cleaned_data): - url = reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk}) - return self.success_message.format( - url=url, name=self.object.name - ) - def get_context_data(self, **kwargs): context = { 'app': _('User'), From 22af44211d3d6a8229e92395b725cde26d8ddacc Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 9 Jan 2018 23:33:14 +0800 Subject: [PATCH 003/197] [Update] set default cluster --- apps/assets/models/asset.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 3d3ed6b10..e340f302a 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -18,6 +18,16 @@ __all__ = ['Asset'] logger = logging.getLogger(__name__) +def default_cluster(): + from .cluster import Cluster + name = "Default" + defaults = {"name": name} + cluster, created = Cluster.objects.get_or_create( + defaults=defaults, name=name + ) + return cluster + + class Asset(models.Model): # Todo: Move them to settings STATUS_CHOICES = ( @@ -44,7 +54,7 @@ class Asset(models.Model): hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname')) port = models.IntegerField(default=22, verbose_name=_('Port')) groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups')) - cluster = models.ForeignKey(Cluster, related_name='assets', on_delete=models.PROTECT, verbose_name=_('Cluster')) + cluster = models.ForeignKey(Cluster, related_name='assets', default=default_cluster, on_delete=models.SET_DEFAULT, verbose_name=_('Cluster')) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),) env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),) From b4080be5beda3324314ddaebc99ecd363d53f88e Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Jan 2018 00:38:21 +0800 Subject: [PATCH 004/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/templates/assets/cluster_list.html | 2 +- apps/assets/views/cluster.py | 2 +- apps/jumpserver/urls.py | 2 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 30126 -> 29961 bytes apps/locale/zh/LC_MESSAGES/django.po | 153 +++++++++--------- 5 files changed, 77 insertions(+), 82 deletions(-) diff --git a/apps/assets/templates/assets/cluster_list.html b/apps/assets/templates/assets/cluster_list.html index b6c0abfb2..e4743b346 100644 --- a/apps/assets/templates/assets/cluster_list.html +++ b/apps/assets/templates/assets/cluster_list.html @@ -8,7 +8,7 @@ {% block table_search %}{% endblock %} {% block table_container %} diff --git a/apps/assets/views/cluster.py b/apps/assets/views/cluster.py index 567e881dd..e1a2f6fba 100644 --- a/apps/assets/views/cluster.py +++ b/apps/assets/views/cluster.py @@ -40,7 +40,7 @@ class ClusterCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView) def get_context_data(self, **kwargs): context = { 'app': _('assets'), - 'action': _('Create Cluster'), + 'action': _('Create cluster'), } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 21d42d0dd..839492e7a 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -36,6 +36,6 @@ urlpatterns = [ if settings.DEBUG: urlpatterns += [ url(r'^docs/', schema_view, name="docs"), - ] + static(settings.STATIC_URL, document_root=settings.STATIC_DIR) \ + ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \ + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 5bb422501d5c1e84b09d0fafbf0347cc7c371e46..0e2eafee39d6a5ed51982f7abeab6202ca62870f 100644 GIT binary patch delta 10755 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index b34a774f4..a997277e8 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-01-09 23:01+0800\n" +"POT-Creation-Date: 2018-01-10 00:30+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -48,7 +48,7 @@ msgstr "默认使用管理用户" msgid "Select assets" 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/asset_detail.html:69 #: assets/templates/assets/asset_group_detail.html:47 @@ -60,7 +60,7 @@ msgstr "选择资产" msgid "Port" 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/asset_group_list.html:16 #: assets/templates/assets/system_user_list.html:26 perms/models.py:17 @@ -91,7 +91,7 @@ msgstr "系统用户" msgid "Selected system users will be create at cluster assets" msgstr "选择的系统用户将会在该集群资产上创建" -#: assets/forms.py:173 assets/forms.py:248 assets/forms.py:307 +#: assets/forms.py:173 assets/forms.py:248 assets/forms.py:308 #: assets/models/cluster.py:18 assets/models/group.py:20 #: assets/models/user.py:28 assets/templates/assets/admin_user_detail.html:56 #: assets/templates/assets/admin_user_list.html:22 @@ -128,8 +128,8 @@ msgstr "集群级别管理用户" msgid "Password or private key password" msgstr "密码或秘钥不合法" -#: assets/forms.py:201 assets/models/user.py:30 users/forms.py:16 -#: users/forms.py:24 users/templates/users/login.html:56 +#: assets/forms.py:201 assets/forms.py:262 assets/models/user.py:30 +#: users/forms.py:16 users/forms.py:24 users/templates/users/login.html:56 #: users/templates/users/reset_password.html:52 #: users/templates/users/user_create.html:11 #: users/templates/users/user_password_update.html:40 @@ -138,11 +138,11 @@ msgstr "密码或秘钥不合法" msgid "Password" msgstr "密码" -#: assets/forms.py:204 users/models/user.py:46 +#: assets/forms.py:204 assets/forms.py:264 users/models/user.py:46 msgid "Private key" msgstr "ssh私钥" -#: assets/forms.py:229 assets/forms.py:289 assets/forms.py:350 +#: assets/forms.py:229 assets/forms.py:290 assets/forms.py:354 msgid "Invalid private key" msgstr "ssh密钥不合法" @@ -150,7 +150,7 @@ msgstr "ssh密钥不合法" msgid "Password and private key file must be input one" msgstr "密码和私钥, 必须输入一个" -#: assets/forms.py:249 assets/forms.py:308 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_list.html:23 #: assets/templates/assets/system_user_detail.html:57 @@ -166,73 +166,73 @@ msgstr "密码和私钥, 必须输入一个" msgid "Username" msgstr "用户名" -#: assets/forms.py:296 assets/forms.py:356 +#: assets/forms.py:297 assets/forms.py:360 msgid "Auth info required, private_key or password" msgstr "密钥和密码必须填写一个" -#: assets/forms.py:311 +#: assets/forms.py:313 msgid " Select clusters" msgstr "选择集群" -#: assets/forms.py:316 +#: assets/forms.py:320 msgid "If auto push checked, system user will be create at cluster assets" msgstr "如果选择了自动推送,系统用户将会创建在集群资产上" -#: assets/forms.py:317 +#: assets/forms.py:321 msgid "Auto push system user to asset" msgstr "自动推送系统用户到资产" -#: assets/forms.py:318 +#: assets/forms.py:322 msgid "" "High level will be using login asset as default, if user was granted more " "than 2 system user" msgstr "高优先级的系统用户将会作为默认登录用户" -#: assets/models/asset.py:24 +#: assets/models/asset.py:34 msgid "In use" msgstr "使用中" -#: assets/models/asset.py:25 +#: assets/models/asset.py:35 msgid "Out of use" msgstr "未使用" -#: assets/models/asset.py:28 +#: assets/models/asset.py:38 msgid "Server" msgstr "物理机" -#: assets/models/asset.py:29 +#: assets/models/asset.py:39 msgid "VM" msgstr "虚拟机" -#: assets/models/asset.py:30 +#: assets/models/asset.py:40 msgid "Switch" msgstr "交换机" -#: assets/models/asset.py:31 +#: assets/models/asset.py:41 msgid "Router" msgstr "路由器" -#: assets/models/asset.py:32 +#: assets/models/asset.py:42 msgid "Firewall" msgstr "防火墙" -#: assets/models/asset.py:33 +#: assets/models/asset.py:43 msgid "Storage" msgstr "存储" -#: assets/models/asset.py:36 +#: assets/models/asset.py:46 msgid "Production" msgstr "生产环境" -#: assets/models/asset.py:37 +#: assets/models/asset.py:47 msgid "Development" msgstr "开发环境" -#: assets/models/asset.py:38 +#: assets/models/asset.py:48 msgid "Testing" 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_group_detail.html:46 #: assets/templates/assets/asset_list.html:31 @@ -246,7 +246,7 @@ msgstr "测试环境" msgid "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_group_detail.html:45 #: assets/templates/assets/asset_list.html:30 @@ -259,116 +259,116 @@ msgstr "IP" msgid "Hostname" 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:218 assets/views/asset.py:258 msgid "Asset groups" 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:219 assets/templates/assets/asset_detail.html:85 #: assets/templates/assets/asset_list.html:33 templates/_nav.html:24 msgid "Cluster" 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" 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" 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" 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" 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:190 assets/templates/assets/asset_detail.html:73 #: assets/templates/assets/cluster_list.html:20 templates/_nav.html:25 msgid "Admin user" 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" msgstr "公网IP" -#: assets/models/asset.py:58 +#: assets/models/asset.py:68 msgid "Remote control card 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" 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" 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" 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" 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" 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" msgstr "序列号" -#: assets/models/asset.py:68 +#: assets/models/asset.py:78 msgid "CPU model" msgstr "CPU型号" -#: assets/models/asset.py:69 +#: assets/models/asset.py:79 msgid "CPU count" msgstr "CPU数量" -#: assets/models/asset.py:70 +#: assets/models/asset.py:80 msgid "CPU cores" 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" msgstr "内存" -#: assets/models/asset.py:72 +#: assets/models/asset.py:82 msgid "Disk total" msgstr "硬盘大小" -#: assets/models/asset.py:73 +#: assets/models/asset.py:83 msgid "Disk info" 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" 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" msgstr "操作系统" -#: assets/models/asset.py:77 +#: assets/models/asset.py:87 msgid "OS version" msgstr "系统版本" -#: assets/models/asset.py:78 +#: assets/models/asset.py:88 msgid "OS arch" msgstr "系统架构" -#: assets/models/asset.py:79 +#: assets/models/asset.py:89 msgid "Hostname raw" 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/templates/assets/admin_user_detail.html:68 #: assets/templates/assets/asset_detail.html:149 @@ -380,7 +380,7 @@ msgstr "主机名原始" msgid "Created by" 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/templates/assets/cluster_detail.html:89 #: assets/templates/assets/system_user_detail.html:87 @@ -391,7 +391,7 @@ msgstr "创建者" msgid "Date created" 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/templates/assets/admin_user_detail.html:72 #: assets/templates/assets/admin_user_list.html:28 @@ -578,8 +578,8 @@ msgstr "仅修改你需要更新的字段" #: assets/views/cluster.py:26 assets/views/cluster.py:85 #: assets/views/cluster.py:102 assets/views/group.py:34 #: assets/views/group.py:52 assets/views/group.py:69 assets/views/group.py:87 -#: assets/views/system_user.py:30 assets/views/system_user.py:46 -#: assets/views/system_user.py:62 assets/views/system_user.py:77 +#: assets/views/system_user.py:28 assets/views/system_user.py:44 +#: assets/views/system_user.py:60 assets/views/system_user.py:75 #: templates/_nav.html:19 msgid "Assets" msgstr "资产管理" @@ -620,7 +620,7 @@ msgstr "如果设置了id,则会使用该行信息更新该id的资产" #: assets/templates/assets/_system_user.html:16 #: assets/templates/assets/system_user_list.html:16 -#: assets/views/system_user.py:47 +#: assets/views/system_user.py:45 msgid "Create system user" msgstr "创建系统用户" @@ -1104,10 +1104,6 @@ msgstr "任务已下发,查看左侧资产状态" msgid "Settings" msgstr "设置" -#: assets/templates/assets/cluster_list.html:11 assets/views/cluster.py:43 -msgid "Create Cluster" -msgstr "创建Cluster" - #: assets/templates/assets/cluster_list.html:21 #: users/templates/users/_select_user_modal.html:17 msgid "Asset num" @@ -1234,10 +1230,14 @@ msgid "Cluster list" msgstr "集群列表" #: assets/views/cluster.py:42 assets/views/cluster.py:70 -#: assets/views/system_user.py:98 +#: assets/views/system_user.py:96 msgid "assets" msgstr "资产管理" +#: assets/views/cluster.py:43 +msgid "Create cluster" +msgstr "创建集群" + #: assets/views/cluster.py:71 msgid "Update Cluster" msgstr "更新Cluster" @@ -1254,25 +1254,24 @@ msgstr "资产组列表" msgid "Asset group detail" msgstr "资产组详情" -#: assets/views/system_user.py:31 +#: assets/views/system_user.py:29 msgid "System user list" msgstr "系统用户列表" -#: assets/views/system_user.py:63 +#: assets/views/system_user.py:61 msgid "Update system user" msgstr "更新系统用户" -#: assets/views/system_user.py:78 +#: assets/views/system_user.py:76 msgid "System user detail" msgstr "系统用户详情" -#: assets/views/system_user.py:99 +#: assets/views/system_user.py:97 msgid "System user asset" msgstr "系统用户集群资产" #: common/const.py:6 #, python-format -#| msgid "User group {name} was created successfully" msgid "%(name)s was created successfully" msgstr "%(name)s 创建成功" @@ -1570,7 +1569,7 @@ msgstr "选择用户" #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:187 #: users/models/user.py:31 users/templates/users/user_group_detail.html:78 -#: users/views/user.py:345 +#: users/views/user.py:338 msgid "User" msgstr "用户" @@ -1786,7 +1785,7 @@ msgstr "关闭" #: users/views/group.py:62 users/views/group.py:79 users/views/login.py:193 #: users/views/login.py:242 users/views/user.py:57 users/views/user.py:72 #: users/views/user.py:92 users/views/user.py:148 users/views/user.py:305 -#: users/views/user.py:319 users/views/user.py:363 users/views/user.py:385 +#: users/views/user.py:319 users/views/user.py:356 users/views/user.py:378 msgid "Users" msgstr "用户管理" @@ -2672,20 +2671,16 @@ msgstr "文件不合法" msgid "User granted assets" msgstr "用户授权资产" -#: users/views/user.py:332 -#, python-brace-format -msgid "Create user {name} successfully." -msgstr "创建用户 {name} 成功" - -#: users/views/user.py:346 +#: users/views/user.py:339 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:364 +#: users/views/user.py:357 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:386 +#: users/views/user.py:379 msgid "Public key update" msgstr "秘钥更新" + From 7e23d2c234536c3cb8a7e654354027f5ff398d3a Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Jan 2018 11:31:25 +0800 Subject: [PATCH 005/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E9=87=8D?= =?UTF-8?q?=E7=BD=AE=E5=AF=86=E7=A0=81bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/forms.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/users/forms.py b/apps/users/forms.py index c4f008663..c27bd4378 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -29,7 +29,7 @@ class UserCreateUpdateForm(forms.ModelForm): model = User fields = [ 'username', 'name', 'email', 'groups', 'wechat', - 'phone', 'role', 'date_expired', 'comment', 'password' + 'phone', 'role', 'date_expired', 'comment', ] help_texts = { 'username': '* required', @@ -38,13 +38,16 @@ class UserCreateUpdateForm(forms.ModelForm): } widgets = { 'groups': forms.SelectMultiple( - attrs={'class': 'select2', - 'data-placeholder': _('Join user groups')}), + attrs={ + 'class': 'select2', + 'data-placeholder': _('Join user groups') + } + ), } def save(self, commit=True): + password = self.cleaned_data.pop('password') user = super().save(commit=commit) - password = self.cleaned_data.get('password') if password: user.set_password(password) user.save() From 99cd6851943eb99bf3a7ced490f13c222e241547 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Jan 2018 12:03:35 +0800 Subject: [PATCH 006/197] =?UTF-8?q?[Bugfix]=20=E9=BB=98=E8=AE=A4default=20?= =?UTF-8?q?cluster?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index e340f302a..c83443077 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -25,7 +25,7 @@ def default_cluster(): cluster, created = Cluster.objects.get_or_create( defaults=defaults, name=name ) - return cluster + return cluster.id class Asset(models.Model): From b2b123b41ee03f5416974912fd603e271c832d99 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Jan 2018 15:15:29 +0800 Subject: [PATCH 007/197] =?UTF-8?q?[Bugfix]=20=E7=94=A8=E6=88=B7=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/signals_handler.py | 2 +- .../assets/templates/assets/asset_group_detail.html | 13 ++++++++++++- .../assets/templates/assets/system_user_detail.html | 12 ++++++++++++ .../templates/perms/asset_permission_detail.html | 10 ++++++++++ apps/users/api.py | 2 +- apps/users/forms.py | 2 +- apps/users/views/login.py | 3 ++- 7 files changed, 39 insertions(+), 5 deletions(-) diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 9e6eb21eb..63dc3e882 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -37,7 +37,7 @@ def push_cluster_system_users_to_asset(asset): @receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier") def on_asset_created(sender, instance=None, created=False, **kwargs): if instance and created: - logger.info("Asset `` create signal received".format(instance)) + logger.info("Asset `{}` create signal received".format(instance)) update_asset_hardware_info_on_created(instance) test_asset_conn_on_created(instance) push_cluster_system_users_to_asset(instance) diff --git a/apps/assets/templates/assets/asset_group_detail.html b/apps/assets/templates/assets/asset_group_detail.html index 9a691036b..4a9b719b1 100644 --- a/apps/assets/templates/assets/asset_group_detail.html +++ b/apps/assets/templates/assets/asset_group_detail.html @@ -17,6 +17,11 @@
  • {% trans 'Update' %}
  • +
  • + + {% trans 'Delete' %} + +
  • @@ -212,7 +217,6 @@ $(document).ready(function () { addAssets(assets_id); }) - .on('click', '.btn-leave-group', function () { var $this = $(this); var the_url = "{% url 'api-assets:group-update-assets' pk=asset_group.id %}"; @@ -225,6 +229,13 @@ $(document).ready(function () { assets.remove(delete_asset_id); var data = {"assets": assets}; leaveGroup($this, name, the_url, data); +}).on('click', '.btn-del', function () { + var $this = $(this); + var name = "{{ asset_group.name}}"; + var uid = "{{ asset_group.id }}"; + var the_url = '{% url "api-assets:asset-group-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid); + var redirect_url = "{% url 'assets:asset-group-list' %}"; + objectDelete($this, name, the_url, redirect_url); }) diff --git a/apps/assets/templates/assets/system_user_detail.html b/apps/assets/templates/assets/system_user_detail.html index db9cf9670..0bf591bb2 100644 --- a/apps/assets/templates/assets/system_user_detail.html +++ b/apps/assets/templates/assets/system_user_detail.html @@ -25,6 +25,11 @@
  • {% trans 'Update' %}
  • +
  • + + {% trans 'Delete' %} + +
  • @@ -259,6 +264,13 @@ $(document).ready(function () { return $(this).data('gid'); }).get(); updateSystemUserCluster(clusters); +}).on('click', '.btn-del', function () { + var $this = $(this); + var name = "{{ system_user.name}}"; + var uid = "{{ system_user.id }}"; + var the_url = '{% url "api-assets:system-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid); + var redirect_url = "{% url 'assets:system-user-list' %}"; + objectDelete($this, name, the_url, redirect_url); }) {% endblock %} diff --git a/apps/perms/templates/perms/asset_permission_detail.html b/apps/perms/templates/perms/asset_permission_detail.html index 942013852..5540ca515 100644 --- a/apps/perms/templates/perms/asset_permission_detail.html +++ b/apps/perms/templates/perms/asset_permission_detail.html @@ -237,6 +237,16 @@ $(document).ready(function () { }).get(); updateSystemUser(system_users); $tr.remove() +}).on('click', '#is_active', function () { + var the_url = '{% url "api-perms:asset-permission-detail" pk=asset_permission.id %}'; + var checked = $(this).prop('checked'); + var body = { + 'is_active': checked + }; + APIUpdateAttr({ + url: the_url, + body: JSON.stringify(body), + }); }) {% endblock %} diff --git a/apps/users/api.py b/apps/users/api.py index e1d38f572..326f2c3ec 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -128,7 +128,7 @@ class UserAuthApi(APIView): user_agent = request.data.get('HTTP_USER_AGENT', '') if not login_ip: - login_ip = request.META.get('HTTP_X_REAL_IP') or request.META.get("REMOTE_ADDR") + login_ip = request.META.get('HTTP_X_FORWARDED_FOR') or request.META.get("REMOTE_ADDR") user, msg = check_user_valid( username=username, password=password, diff --git a/apps/users/forms.py b/apps/users/forms.py index c27bd4378..651df78d3 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -46,7 +46,7 @@ class UserCreateUpdateForm(forms.ModelForm): } def save(self, commit=True): - password = self.cleaned_data.pop('password') + password = self.cleaned_data.get('password') user = super().save(commit=commit) if password: user.set_password(password) diff --git a/apps/users/views/login.py b/apps/users/views/login.py index fabc39b7d..493e4936c 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -53,7 +53,8 @@ class UserLoginView(FormView): if not self.request.session.test_cookie_worked(): return HttpResponse(_("Please enable cookies and try again.")) auth_login(self.request, form.get_user()) - login_ip = self.request.META.get('REMOTE_ADDR', '') + login_ip = self.request.META.get('HTTP_X_FORWARDED_FOR') or \ + self.request.META.get('REMOTE_ADDR', '') user_agent = self.request.META.get('HTTP_USER_AGENT', '') write_login_log_async.delay( self.request.user.username, type='W', From 6d7b5968543aaff77e6a7c2f08307720b59fd6ca Mon Sep 17 00:00:00 2001 From: q4speed Date: Wed, 10 Jan 2018 16:42:30 +0800 Subject: [PATCH 008/197] =?UTF-8?q?=E4=BF=AE=E6=94=B9tab=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/static/css/jumpserver.css | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/static/css/jumpserver.css b/apps/static/css/jumpserver.css index 5e9360545..28fe88b68 100644 --- a/apps/static/css/jumpserver.css +++ b/apps/static/css/jumpserver.css @@ -322,4 +322,20 @@ div.dataTables_wrapper div.dataTables_filter { .welcome-message img { margin: -11px 0; +} + +.nav.nav-tabs li.active { + background-color: #FFF; +} + +.ibox-title { + border-top: none; +} + +.nav.nav-tabs li > a { + max-height: 38px; +} + +.nav.nav-tabs li.active a { + border: none; } \ No newline at end of file From 3223d3b74d66dce21d93033748a9de7259d5a220 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Jan 2018 17:38:10 +0800 Subject: [PATCH 009/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E6=98=BE=E7=A4=BA=E4=B8=8A=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms.py | 2 +- .../templates/assets/admin_user_assets.html | 2 +- apps/assets/templates/assets/asset_list.html | 24 ++++++++++++----- .../templates/assets/cluster_assets.html | 2 +- apps/users/forms.py | 5 ++-- apps/users/templates/users/user_list.html | 26 ++++++++++++++----- 6 files changed, 43 insertions(+), 18 deletions(-) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 68aaddc43..824ef3fcd 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -93,7 +93,7 @@ class AssetBulkUpdateForm(forms.ModelForm): model = Asset fields = [ 'assets', 'port', 'groups', "cluster", - 'type', 'env', 'status', + 'type', 'env', ] widgets = { 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}), diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index dcaaef312..c00a56e60 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -34,7 +34,7 @@
    - {% trans 'Asset list of ' %} {{ admin_user.name }} {{ total_amount }} {{ unreachable_amount }} + {% trans 'Asset list of ' %} {{ admin_user.name }}
    diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 0a19ba3de..2c562ffa5 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -178,9 +178,15 @@ $(document).ready(function(){ var obj = {"pk": object_id, "is_active": false}; data.push(obj); }); - APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(data)}); - $data_table.ajax.reload(); - jumpserver.checked = false; + function success() { + location.reload() + } + APIUpdateAttr({ + url: the_url, + method: 'PATCH', + body: JSON.stringify(data), + success: success + }); } function doActive() { var data = []; @@ -188,9 +194,15 @@ $(document).ready(function(){ var obj = {"pk": object_id, "is_active": true}; data.push(obj); }); - APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(data)}); - $data_table.ajax.reload(); - jumpserver.checked = false; + function success() { + location.reload(); + } + APIUpdateAttr({ + url: the_url, + method: 'PATCH', + body: JSON.stringify(data), + success: success + }); } function doDelete() { swal({ diff --git a/apps/assets/templates/assets/cluster_assets.html b/apps/assets/templates/assets/cluster_assets.html index b194a5034..88969a000 100644 --- a/apps/assets/templates/assets/cluster_assets.html +++ b/apps/assets/templates/assets/cluster_assets.html @@ -28,7 +28,7 @@
    - {% trans 'Cluster assets' %} {{ cluster.name }} {{ cluster.assets.all.count }} + {% trans 'Cluster assets' %} {{ cluster.name }}
    diff --git a/apps/users/forms.py b/apps/users/forms.py index 651df78d3..018c39a18 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -156,7 +156,7 @@ class UserBulkUpdateForm(forms.ModelForm): class Meta: model = User - fields = ['users', 'role', 'groups', 'date_expired', 'is_active'] + fields = ['users', 'role', 'groups', 'date_expired'] widgets = { "groups": forms.SelectMultiple( attrs={ @@ -172,6 +172,7 @@ class UserBulkUpdateForm(forms.ModelForm): if self.data.get(field) is not None: changed_fields.append(field) + print(changed_fields) cleaned_data = {k: v for k, v in self.cleaned_data.items() if k in changed_fields} users = cleaned_data.pop('users', '') @@ -186,7 +187,7 @@ class UserBulkUpdateForm(forms.ModelForm): class UserGroupForm(forms.ModelForm): users = forms.ModelMultipleChoiceField( - queryset=User.objects.all(), + queryset=User.objects.exclude(role=User.ROLE_APP), label=_("User"), widget=forms.SelectMultiple( attrs={ diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index ab9bcf3c7..24ec5b226 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -161,18 +161,30 @@ $(document).ready(function(){ var body = $.each(id_list, function(index, user_object) { user_object['is_active'] = false; }); - console.log(body); - APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)}); - $data_table.ajax.reload(); - jumpserver.checked = false; + function success() { + location.reload(); + } + APIUpdateAttr({ + url: the_url, + method: 'PATCH', + body: JSON.stringify(body), + success: success + }); + location.reload(); } function doActive() { var body = $.each(id_list, function(index, user_object) { user_object['is_active'] = true; }); - APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)}); - $data_table.ajax.reload(); - jumpserver.checked = false; + function success() { + location.reload(); + } + APIUpdateAttr({ + url: the_url, + method: 'PATCH', + body: JSON.stringify(body), + success: success + }); } function doDelete() { swal({ From 576ddbf6731e04146eda4999193b948315139cba Mon Sep 17 00:00:00 2001 From: q4speed Date: Wed, 10 Jan 2018 17:56:50 +0800 Subject: [PATCH 010/197] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=85=A8=E9=80=89?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E5=9C=A8=E6=90=9C=E7=B4=A2=E5=90=8E=E4=BB=8D?= =?UTF-8?q?=E7=84=B6=E9=80=89=E6=8B=A9=E5=85=A8=E9=83=A8=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/static/js/jumpserver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index a76c44187..27b73dce0 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -310,11 +310,11 @@ jumpserver.initDataTable = function (options) { if (!jumpserver.checked) { $(this).closest('table').find('.ipt_check').prop('checked', true); jumpserver.checked = true; - table.rows().select(); + table.rows({search:'applied'}).select(); } else { $(this).closest('table').find('.ipt_check').prop('checked', false); jumpserver.checked = false; - table.rows().deselect(); + table.rows({search:'applied'}).deselect(); } }); From 6aaa106affc25691a062f9aec905bddb594eeaec Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Jan 2018 18:08:27 +0800 Subject: [PATCH 011/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E4=BB=A5?= =?UTF-8?q?=E4=B8=8Bbug=201.=20=E6=9F=A5=E7=9C=8B=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E5=BC=82=E5=B8=B8=202.=20=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E8=B5=84=E4=BA=A7=E9=A1=B5=E6=98=BE=E7=A4=BA?= =?UTF-8?q?message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models.py | 4 ++-- apps/ops/serializers.py | 4 ++-- apps/templates/_message.html | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/ops/models.py b/apps/ops/models.py index accdfe78f..3c96f5146 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -218,8 +218,8 @@ class AdHoc(models.Model): history.result = raw history.summary = summary return raw, summary - except: - return {}, {} + except Exception as e: + return {}, {"dark": {"all": str(e)}, "contacted": []} finally: history.date_finished = timezone.now() history.timedelta = time.time() - time_start diff --git a/apps/ops/serializers.py b/apps/ops/serializers.py index 86a029c12..a395a427e 100644 --- a/apps/ops/serializers.py +++ b/apps/ops/serializers.py @@ -43,8 +43,8 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer): def get_stat(obj): return { "total": len(obj.adhoc.hosts), - "success": len(obj.summary["contacted"]), - "failed": len(obj.summary["dark"]), + "success": len(obj.summary.get("contacted", [])), + "failed": len(obj.summary.get("dark", [])), } def get_field_names(self, declared_fields, info): diff --git a/apps/templates/_message.html b/apps/templates/_message.html index 29a25e963..046f48ae6 100644 --- a/apps/templates/_message.html +++ b/apps/templates/_message.html @@ -1,6 +1,6 @@ {% load i18n %} {% block first_login_message %} - {% if user.is_authenticated and user.is_first_login %} + {% if request.user.is_authenticated and request.user.is_first_login %}
    {% url 'users:user-first-login' as first_login_url %} {% blocktrans %} @@ -10,7 +10,7 @@ {% endif %} {% endblock %} {% block update_public_key_message %} - {% if user.is_authenticated and not user.is_public_key_valid %} + {% if request.user.is_authenticated and not request.user.is_public_key_valid %}
    {% url 'users:user-pubkey-update' as user_pubkey_update %} {% blocktrans %} From b49e3b8f844c8c09a296e8f89a09cbe3338741cb Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Jan 2018 19:39:22 +0800 Subject: [PATCH 012/197] =?UTF-8?q?[Update]=20api=20=E8=BF=94=E5=9B=9Eplat?= =?UTF-8?q?form,=20=E5=B9=B6=E5=A2=9E=E5=8A=A0web=20terminal=20nav?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers.py | 15 +- .../templates/assets/user_asset_list.html | 11 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 29961 -> 30048 bytes apps/locale/zh/LC_MESSAGES/django.po | 160 +- apps/static/css/font-awesome.css | 1672 --------- apps/static/css/font-awesome.min.css | 4 +- apps/static/fonts/FontAwesome.otf | Bin 93888 -> 134808 bytes apps/static/fonts/fontawesome-webfont.eot | Bin 60767 -> 165742 bytes apps/static/fonts/fontawesome-webfont.svg | 3230 ++++++++++++++--- apps/static/fonts/fontawesome-webfont.ttf | Bin 122092 -> 165548 bytes apps/static/fonts/fontawesome-webfont.woff | Bin 71508 -> 98024 bytes apps/static/fonts/fontawesome-webfont.woff2 | Bin 56780 -> 77160 bytes apps/templates/_head_css_js.html | 2 +- apps/templates/_nav.html | 6 + apps/templates/_nav_user.html | 5 + 15 files changed, 2779 insertions(+), 2326 deletions(-) delete mode 100644 apps/static/css/font-awesome.css mode change 100644 => 100755 apps/static/css/font-awesome.min.css mode change 100644 => 100755 apps/static/fonts/FontAwesome.otf mode change 100644 => 100755 apps/static/fonts/fontawesome-webfont.eot mode change 100644 => 100755 apps/static/fonts/fontawesome-webfont.svg mode change 100644 => 100755 apps/static/fonts/fontawesome-webfont.ttf mode change 100644 => 100755 apps/static/fonts/fontawesome-webfont.woff mode change 100644 => 100755 apps/static/fonts/fontawesome-webfont.woff2 diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index 401aeac2a..cb939fcc6 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -191,9 +191,11 @@ class AssetGrantedSerializer(serializers.ModelSerializer): class Meta(object): model = Asset - fields = ("id", "hostname", "ip", "port", "system_users_granted", - "is_inherited", "is_active", "system_users_join", "os", - "platform", "comment",) + fields = ( + "id", "hostname", "ip", "port", "system_users_granted", + "is_inherited", "is_active", "system_users_join", "os", + "platform", "comment" + ) @staticmethod def get_is_inherited(obj): @@ -214,8 +216,11 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer): class Meta(object): model = Asset - fields = ("id", "hostname", "system_users_granted", "is_inherited", - "is_active", "system_users_join", "comment") + fields = ( + "id", "hostname", "system_users_granted", + "is_inherited", "is_active", "system_users_join", + "os", "platform", "comment", + ) class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer): diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html index 20c7d4d50..4a91d4f65 100644 --- a/apps/assets/templates/assets/user_asset_list.html +++ b/apps/assets/templates/assets/user_asset_list.html @@ -24,7 +24,6 @@
    - @@ -61,16 +60,16 @@ function initTable() { $(td).html('') } }}, - {targets: 9, createdCell: function (td, cellData, rowData) { - var conn_btn = '{% trans "Connect" %}'.replace("{{ DEFAULT_PK }}", cellData); - $(td).html(conn_btn) - }} +{# {targets: 9, createdCell: function (td, cellData, rowData) {#} +{# var conn_btn = '{% trans "Connect" %}'.replace("{{ DEFAULT_PK }}", cellData);#} +{# $(td).html(conn_btn)#} +{# }}#} ], ajax_url: '{% url "api-assets:user-asset-list" %}', columns: [ {data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"}, - {data: "is_active" }, {data: "is_connective"}, {data: "id"} + {data: "is_active" }, {data: "is_connective"} ], op_html: $('#actions').html() }; diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 0e2eafee39d6a5ed51982f7abeab6202ca62870f..5b42f9d18af67e2dc07a01530827036f620857cf 100644 GIT binary patch delta 10792 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 delta 10721 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index a997277e8..4a7c99298 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-01-10 00:30+0800\n" +"POT-Creation-Date: 2018-01-10 19:19+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" #: 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" msgstr "选择资产组" @@ -43,15 +43,15 @@ msgid "Default using cluster admin user" msgstr "默认使用管理用户" #: assets/forms.py:76 assets/forms.py:81 assets/forms.py:127 -#: assets/templates/assets/asset_group_detail.html:70 perms/forms.py:34 -#: perms/templates/perms/asset_permission_asset.html:88 users/forms.py:239 +#: assets/templates/assets/asset_group_detail.html:75 perms/forms.py:34 +#: perms/templates/perms/asset_permission_asset.html:88 users/forms.py:243 msgid "Select assets" msgstr "选择资产" #: assets/forms.py:86 assets/models/asset.py:55 #: assets/templates/assets/admin_user_assets.html:61 #: 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/cluster_assets.html:53 #: assets/templates/assets/system_user_asset.html:54 @@ -77,7 +77,7 @@ msgid "Asset" msgstr "资产" #: 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" msgstr "选择系统用户" @@ -98,7 +98,7 @@ msgstr "选择的系统用户将会在该集群资产上创建" #: assets/templates/assets/asset_group_list.html:15 #: assets/templates/assets/cluster_detail.html:57 #: 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 #: ops/templates/ops/task_detail.html:56 ops/templates/ops/task_list.html:34 #: perms/models.py:14 @@ -153,7 +153,7 @@ msgstr "密码和私钥, 必须输入一个" #: 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_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 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:14 #: users/models/authentication.py:44 users/models/user.py:35 @@ -234,7 +234,7 @@ msgstr "测试环境" #: assets/models/asset.py:53 assets/templates/assets/admin_user_assets.html:60 #: 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/cluster_assets.html:52 #: assets/templates/assets/system_user_asset.html:53 @@ -248,7 +248,7 @@ msgstr "IP" #: assets/models/asset.py:54 assets/templates/assets/admin_user_assets.html:59 #: 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/cluster_assets.html:51 #: assets/templates/assets/system_user_asset.html:52 @@ -373,7 +373,7 @@ msgstr "主机名原始" #: assets/templates/assets/admin_user_detail.html:68 #: assets/templates/assets/asset_detail.html:149 #: 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 #: perms/templates/perms/asset_permission_detail.html:94 #: users/models/user.py:51 users/templates/users/user_detail.html:98 @@ -383,7 +383,7 @@ msgstr "创建者" #: assets/models/asset.py:92 assets/models/cluster.py:26 #: assets/models/group.py:22 assets/templates/assets/admin_user_detail.html:64 #: 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 #: perms/models.py:23 perms/templates/perms/asset_permission_detail.html:90 #: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 @@ -398,7 +398,7 @@ msgstr "创建日期" #: assets/templates/assets/asset_detail.html:157 #: assets/templates/assets/asset_group_list.html:17 #: 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 #: perms/models.py:24 perms/templates/perms/asset_permission_detail.html:98 #: terminal/models.py:22 terminal/templates/terminal/terminal_detail.html:63 @@ -472,21 +472,21 @@ msgstr "ssh公钥" msgid "Priority" msgstr "优先级" -#: assets/models/user.py:221 assets/templates/assets/system_user_detail.html:61 +#: assets/models/user.py:221 assets/templates/assets/system_user_detail.html:66 msgid "Protocol" msgstr "协议" #: 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 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:223 assets/templates/assets/system_user_detail.html:65 +#: assets/models/user.py:223 assets/templates/assets/system_user_detail.html:70 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:224 assets/templates/assets/system_user_detail.html:70 +#: assets/models/user.py:224 assets/templates/assets/system_user_detail.html:75 msgid "Shell" msgstr "Shell" @@ -718,7 +718,7 @@ msgstr "资产列表" #: 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:172 +#: assets/templates/assets/asset_group_detail.html:177 #: assets/templates/assets/asset_group_list.html:42 #: assets/templates/assets/asset_list.html:95 #: assets/templates/assets/cluster_assets.html:170 @@ -742,10 +742,12 @@ msgstr "更新" #: 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:43 #: assets/templates/assets/asset_list.html:96 #: 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 @@ -760,13 +762,13 @@ msgid "Delete" msgstr "删除" #: 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 msgid "Asset list of " msgstr "资产列表" #: 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/user_asset_list.html:22 @@ -786,7 +788,7 @@ msgstr "可连接" #: assets/templates/assets/admin_user_assets.html:75 #: assets/templates/assets/cluster_assets.html:68 #: 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 msgid "Quick update" msgstr "快速更新" @@ -819,10 +821,10 @@ msgstr "使用集群管理用户" #: assets/templates/assets/admin_user_detail.html:101 #: assets/templates/assets/asset_detail.html:230 #: assets/templates/assets/asset_group_list.html:85 -#: assets/templates/assets/asset_list.html:202 +#: assets/templates/assets/asset_list.html:214 #: assets/templates/assets/cluster_assets.html:104 #: 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 #: terminal/templates/terminal/session_detail.html:108 #: users/templates/users/user_detail.html:338 @@ -830,7 +832,7 @@ msgstr "使用集群管理用户" #: users/templates/users/user_detail.html:386 #: users/templates/users/user_group_create_update.html:32 #: 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 msgid "Confirm" msgstr "确认" @@ -848,7 +850,7 @@ msgid "Ratio" msgstr "比例" #: 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_list.html:39 #: assets/templates/assets/cluster_assets.html:56 @@ -934,17 +936,17 @@ msgstr "更新成功" msgid "Group assets" msgstr "组下资产" -#: assets/templates/assets/asset_group_detail.html:49 +#: assets/templates/assets/asset_group_detail.html:54 #: assets/templates/assets/cluster_assets.html:55 #: terminal/templates/terminal/terminal_list.html:35 msgid "Alive" msgstr "在线" -#: assets/templates/assets/asset_group_detail.html:62 +#: assets/templates/assets/asset_group_detail.html:67 msgid "Add assets to this group" 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_detail.html:153 #: perms/templates/perms/asset_permission_user.html:97 @@ -953,7 +955,7 @@ msgstr "添加资产到该组" msgid "Add" msgstr "添加" -#: assets/templates/assets/asset_group_detail.html:173 +#: assets/templates/assets/asset_group_detail.html:178 msgid "Remove" msgstr "移除" @@ -963,13 +965,13 @@ msgid "Create asset group" msgstr "创建资产组" #: assets/templates/assets/asset_group_list.html:80 -#: assets/templates/assets/asset_list.html:197 +#: assets/templates/assets/asset_list.html:209 #: assets/templates/assets/cluster_list.html:84 #: assets/templates/assets/system_user_list.html:129 #: users/templates/users/user_detail.html:333 #: users/templates/users/user_detail.html:358 #: 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?" msgstr "你确认吗?" @@ -1043,22 +1045,22 @@ msgstr "禁用所选" msgid "Active selected" msgstr "激活所选" -#: assets/templates/assets/asset_list.html:198 +#: assets/templates/assets/asset_list.html:210 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" # msgid "Deleted!" # msgstr "删除" -#: assets/templates/assets/asset_list.html:206 +#: assets/templates/assets/asset_list.html:218 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:207 -#: assets/templates/assets/asset_list.html:212 +#: assets/templates/assets/asset_list.html:219 +#: assets/templates/assets/asset_list.html:224 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:211 +#: assets/templates/assets/asset_list.html:223 msgid "Asset Deleting failed." msgstr "删除失败" @@ -1104,6 +1106,10 @@ msgstr "任务已下发,查看左侧资产状态" msgid "Settings" msgstr "设置" +#: assets/templates/assets/cluster_list.html:11 assets/views/cluster.py:43 +msgid "Create cluster" +msgstr "创建集群" + #: assets/templates/assets/cluster_list.html:21 #: users/templates/users/_select_user_modal.html:17 msgid "Asset num" @@ -1156,19 +1162,19 @@ msgstr "任务已下发,查看ops任务列表" msgid "Attached assets" msgstr "关联的资产" -#: assets/templates/assets/system_user_detail.html:76 +#: assets/templates/assets/system_user_detail.html:81 msgid "Home" msgstr "家目录" -#: assets/templates/assets/system_user_detail.html:82 +#: assets/templates/assets/system_user_detail.html:87 msgid "Uid" msgstr "Uid" -#: assets/templates/assets/system_user_detail.html:142 +#: assets/templates/assets/system_user_detail.html:147 msgid "Clusters" msgstr "集群" -#: assets/templates/assets/system_user_detail.html:150 +#: assets/templates/assets/system_user_detail.html:155 msgid "Add to cluster" msgstr "添加到集群" @@ -1234,10 +1240,6 @@ msgstr "集群列表" msgid "assets" msgstr "资产管理" -#: assets/views/cluster.py:43 -msgid "Create cluster" -msgstr "创建集群" - #: assets/views/cluster.py:71 msgid "Update Cluster" msgstr "更新Cluster" @@ -1555,8 +1557,8 @@ msgstr "任务列表" msgid "Task run history" msgstr "执行历史" -#: perms/forms.py:16 users/forms.py:144 users/forms.py:149 users/forms.py:161 -#: users/forms.py:191 +#: perms/forms.py:16 users/forms.py:147 users/forms.py:152 users/forms.py:164 +#: users/forms.py:195 msgid "Select users" msgstr "选择用户" @@ -1567,7 +1569,7 @@ msgstr "选择用户" #: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: 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/views/user.py:338 msgid "User" @@ -1782,8 +1784,8 @@ msgid "Close" msgstr "关闭" #: templates/_nav.html:9 users/views/group.py:28 users/views/group.py:44 -#: users/views/group.py:62 users/views/group.py:79 users/views/login.py:193 -#: users/views/login.py:242 users/views/user.py:57 users/views/user.py:72 +#: users/views/group.py:62 users/views/group.py:79 users/views/login.py:194 +#: users/views/login.py:243 users/views/user.py:57 users/views/user.py:72 #: users/views/user.py:92 users/views/user.py:148 users/views/user.py:305 #: users/views/user.py:319 users/views/user.py:356 users/views/user.py:378 msgid "Users" @@ -1830,6 +1832,10 @@ msgstr "离线会话" msgid "Command" msgstr "命令" +#: templates/_nav.html:56 templates/_nav_user.html:14 +msgid "Web terminal" +msgstr "Web终端" + #: templates/_nav_user.html:4 msgid "My assets" msgstr "我的资产" @@ -2090,47 +2096,47 @@ msgstr "" msgid "Invalid token or cache refreshed." 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" msgstr "添加到用户组" -#: users/forms.py:71 +#: users/forms.py:74 msgid "Old password" msgstr "原来密码" -#: users/forms.py:76 +#: users/forms.py:79 msgid "New password" msgstr "新密码" -#: users/forms.py:81 +#: users/forms.py:84 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:91 +#: users/forms.py:94 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:99 +#: users/forms.py:102 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:111 +#: users/forms.py:114 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:112 +#: users/forms.py:115 msgid "ssh-rsa AAAA..." msgstr "" -#: users/forms.py:113 +#: users/forms.py:116 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:126 +#: users/forms.py:129 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:130 users/serializers.py:42 +#: users/forms.py:133 users/serializers.py:42 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" @@ -2418,20 +2424,20 @@ msgstr "用户组删除" msgid "UserGroup Deleting failed." msgstr "用户组删除失败" -#: users/templates/users/user_list.html:180 +#: users/templates/users/user_list.html:192 msgid "This will delete the selected users !!!" msgstr "删除选中用户 !!!" -#: users/templates/users/user_list.html:188 +#: users/templates/users/user_list.html:200 msgid "User Deleted." msgstr "已被删除" -#: users/templates/users/user_list.html:189 -#: users/templates/users/user_list.html:194 +#: users/templates/users/user_list.html:201 +#: users/templates/users/user_list.html:206 msgid "User Delete" msgstr "删除" -#: users/templates/users/user_list.html:193 +#: users/templates/users/user_list.html:205 msgid "User Deleting failed." msgstr "用户删除失败" @@ -2610,48 +2616,48 @@ msgstr "编辑用户组" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:83 +#: users/views/login.py:84 msgid "Logout success" msgstr "退出登录成功" -#: users/views/login.py:84 +#: users/views/login.py:85 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: users/views/login.py:100 +#: users/views/login.py:101 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:113 +#: users/views/login.py:114 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:114 +#: users/views/login.py:115 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views/login.py:128 +#: users/views/login.py:129 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:129 +#: users/views/login.py:130 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:146 users/views/login.py:159 +#: users/views/login.py:147 users/views/login.py:160 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:155 +#: users/views/login.py:156 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:193 +#: users/views/login.py:194 msgid "First login" msgstr "首次登陆" -#: users/views/login.py:243 +#: users/views/login.py:244 msgid "Login log list" msgstr "登录日志" @@ -2682,5 +2688,3 @@ msgstr "密码更新" #: users/views/user.py:379 msgid "Public key update" msgstr "秘钥更新" - - diff --git a/apps/static/css/font-awesome.css b/apps/static/css/font-awesome.css deleted file mode 100644 index 4040b3cf8..000000000 --- a/apps/static/css/font-awesome.css +++ /dev/null @@ -1,1672 +0,0 @@ -/*! - * 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"; -} diff --git a/apps/static/css/font-awesome.min.css b/apps/static/css/font-awesome.min.css old mode 100644 new mode 100755 index ec53d4d6d..540440ce8 --- a/apps/static/css/font-awesome.min.css +++ b/apps/static/css/font-awesome.min.css @@ -1,4 +1,4 @@ /*! - * 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) - */@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"} \ No newline at end of file + */@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} diff --git a/apps/static/fonts/FontAwesome.otf b/apps/static/fonts/FontAwesome.otf old mode 100644 new mode 100755 index f7936cc1e789eea5438d576d6b12de20191da09d..401ec0f36e4f73b8efa40bd6f604fe80d286db70 GIT binary patch delta 84561 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 z�=-sMe6tk7vLw+GWRwQGHPubM(&rLyt73(qw(EP?~g~c8ySz#ZpCIEsaClw?Udh z!sAGgR6(}(J_J(0&4655;OO~M^}rnMV10|*kgy@%nfIn6yy-}90Ge5k#BI608fv#4 zy_yvncLm^~Vc!TvHM>l^G}p2=tr5)0?E|>oFj+1L$m6rboie)LmMT^(rB;IKOMM1Y zLq7flmP6PbjUXXpXfm%lw!=fO^APB#Yv@w?J3XSN7s+fu&(CK!WG3bYCOt;Q zF`CU`4UL$Qjr=M-S5Q4@N~L34Tv3aQ>58F(>J$J+)_A}c0*<>Dc>b76eCe%%J7u?6 z)t8w?8QDcS>_r%Vm4`?#&FxDO@wbHRR3@)5FHh*+*Bdn04$LvKx{LXq`A^QzFE%hj z@Y<5UGhuc{~ zE-`wL3~8LVc*`4{j%+xulgbv^S(c_UYd5*C)eBDg_BZ4<+24~!=NyhZ61c~n4kA;%#Y5Zox$d&3fv|@ZM1*CBYUa?* zoL@y)y>7qmK<a+@)AAx0U;9ly@k{n?AtJqQS6y8X1@hf> ziP2R838aIM`yKJv?`}^9(W$$93b&=Y+5dI^Kv_+F?qU08vNNwa^Njv9nvTlQw%NqJ z6If2jfG&WU%;&Q3FB<_G&053Zz$VWyuT@oIPm-6Q3;yaFjl4$*&Odv$OLH)MX!%iP5`wBD#DBTmDEW{4Qe)D{{c3Rhv{3 zTfvhHz{@=#Tj^WPqJ_Fxgw*yV5lo≧~*WF?vm0m=Lc^iWAilztD-r2&!K&6$C?l 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

    5q!RY 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%0DKehr}RFC`2XTB_?==0w^)u1m5PYii@6f)6_5Ydu zv+NIZ_(Rt}Q++LT5!n8!J4x!>sE&v_3*cXat{Zq5;17w;B6$epw}$Rg`0nFJg5D-L zYvw@(goc5TeJjM($AJAZxZHZN}RzBcP0=_>ZI6WVGU zO#Nk-YqZTa3{!84P0K~GsI#32<+_AsXU43wILwZS(8n%S9)lP!Dg$$e2$$9$E?^Nj zql4do#+a8qEP(bD2)DpP|$dp<`TZ#bY6^~7Xv_Lle)77^OsVhMOm(@ z??8O8kA%}ZWpR&2v!7qFSw@TF6d*=9YT^Rtk(n8p=CQWvt1Om=n&5uP;GiT6 zMRvbm39kbp*KB`qoVg12w52Z)T}`X41P>D|q_%K#zuhwb+BpEogY0E)KnSy#@+(m5 z20@LG@LUEvk`I|OIUV^^0_YtG9AElBS!Dsh%k^P9r0moJ25Lkm-gh#igwBDhAOj0!EF&8MxV^-m1U1MEd?H7} zL;r;tfFIT|ei3-Z@gyM=!%Ba7Pa626JRAA`V<2D<{RLRT@0o=bE)XF)nFtUL67`2L z{?_Qz_`Yy2t+I)?9&z#z__Q%L3pnhN}U z_rN#WU)kD59D4whbSYERHY01jM7id50EuI1ctl?<_IT=Y5vP>(sNNkB&U5&F&^kBhm5y{o!y!F+4wdxXoy;!4$W`?_nL(+bK_QDAMUV1O0AwZ| z6j)s}9YEZbY-C^Y)9Ej`aS&~{sXCG2SS3ce$EY;Yv-c8TlrD$C85ATlLZpGP_YWfi z`RQ?z1@zIfa{yqfsUDMEPpwuX%XHdO+ASb3EPi1fBPocvfgsC0xa^CG2SWBPWQ&GS zpCXPti8b>WkYbf#Vg%A?&_UwUsUQE_t4GX?7QqUpKJ2Iw#%)Q4Ft(`9Ja&Yk{C@38 z@%T`)#wWy(kKfEH;ZBQ(m*Iq&L=<)4D7tNO{SsA4Fp4D?(Ex6nQS&f3TK|atgj`fE z2|OX0(&(ZqxJd~IANX&dvX?U14_<~h2(lP6k^H8ep;2HW6oPo?U%v{M>|{sU~;p zLTv$OTx3H^4zNUn4wUfo>j{CEvTC@C+cw+cW*ABH6u@!M2EdBL?1GbL_#e;7YDBas zic?MTazk(khXSyPeDom_I~wkLv?Wr8<%egEfM!*M9^kl$>zsVzaP}S!gcD3;Czy#58RTm?`p)RTS8I<-sC3+*n{A)P*rU!@Npj`e{x9xsif2v zTW`{q3p^?A!Mk60Q{(FLt(&TVe9z z0-!PiOV02JcNeq?AbJaI+B9xC;LB=}Ho0vH(@;Qe0zq~-8ckOa!(u@Wou`p_TR|QT z38H`lJE$G{q1egUX@&v$x7wNLWD#j*!D58GLv^bT+jpdKBrK#SsQsWK(+RO40VA^w z0nA7MN1Y1Fc#5JkwD5TtHG1t;lo=i)U+kFG?1Jh11h9382!marrRE2eZh;JGh`wNO zQA_~n?%97HOKLA^#oG(5*bgSllS%rOc(S%Yj00cYR;!D9G_90{pfq7D4I*$k?byOV zR|epi%oIJ{ou`5zS!-_dnxOa{uNv)(luMo^5TCOItq}2}sxCztLEzBGS)Mf6dzaw< z!GweAgvFYJu&mH(Vl9HJBV%=Jz~~i%nDGIF9ncTET-AQ=fv{L11&K_;ei!iht(!De;ym|y7ksL|^5Ko~B-vSh80++s?unD}bZaYa@ zPH4M$&fw;xEGN3_H1vHW><%-+dg7dfW)F8$bB+h7sThoOtteO(v{&-+iK}r$%G))# z*Nhx^!ZMj1VeG?EkWg+0CYQSX1t96fV9^3c+9C393LU&CHsFCa1q99$`zTMsEWwLc zxsw1|A?k8-m8HCrk6;K7dhNDJN3R9iws%6vTq_}PtR2CZ8TG;ltZ4I}sU+^s8`P3F5QxrypG1-{ zGlr^7$Wsy(lo=xfC~BpKfg<2z4OEeEF@~x{Pi7O#CvqMJy+f+}=CB_$&IuEslB@s# J000000038FvZ??8 diff --git a/apps/static/fonts/fontawesome-webfont.svg b/apps/static/fonts/fontawesome-webfont.svg old mode 100644 new mode 100755 index 1ee89d436..855c845e5 --- a/apps/static/fonts/fontawesome-webfont.svg +++ b/apps/static/fonts/fontawesome-webfont.svg @@ -1,565 +1,2671 @@ - - + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/static/fonts/fontawesome-webfont.ttf b/apps/static/fonts/fontawesome-webfont.ttf old mode 100644 new mode 100755 index ed9372f8ea0fbaa04f42630a48887e4b38945345..35acda2fa1196aad98c2adf4378a7611dd713aa3 GIT binary patch delta 54090 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 delta 11716 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 ZfSEq4enHn$^T@LbiNK5zCp@ze`X3sO4ix|Z diff --git a/apps/static/fonts/fontawesome-webfont.woff b/apps/static/fonts/fontawesome-webfont.woff old mode 100644 new mode 100755 index 8b280b98fa2fa261aa4b0f8fd061f772073ef83e..400014a4b06eee3d0c0d54402a47ab2601b2862b GIT binary patch literal 98024 zcmZTubC4&$(_Y)Q?OXfSHg9d)wr$(CZSQ{8wr%e%e)p|<|9eyQq|;BjCzE7qGMTiS zyqFjeFc1(BuRO}xo^G_%I z2O^L=ATW7lM&^H<^*^2eAN0eSJq3(x4DA1L)&F4euaO6sK5joV1E+r+DAqq4sQ>Wu z0|aVj?P25hA?l{GgpFa`oP%>HM?@(=7t5y$lA|Hyyb+&}%lcF7Py zVOq>>oZbI%cmJ;c1Ox&!PmnY&6cmq2?4Nt?RBbj#@*S#u% z($dm;AKJG3Yv)w@yrS19dscW!&dp@T$utcaiktwRu?l%Fgn7##v*Q%&IaI$|O!P}5 zE!tXI-Ss#N&%~+2xwep6)=D=@bER^nrNZX=A{Jq3H3E=sm}xcLG|pUA-88}8wRPyv zPnoSTxscjcm{McuVx_s+*=h#*Xv3UB1T}&E{uxPi!CD1QZy{>6F_-GvT;_v+@h3%S z3~p6JKLUMaO+O0%W$iTHs4{|UN^?L;ts#@G+64bnV>gujTO1A$SfkJKhUN{&{#iBu zbrz-NBAI4CWjjIN*&fwVu4RubbB`IvgcJ!WV;{$}bpWy2K1lw(2Xe|eWcN9U#V^J= z0v&sgD$Y5Kh^J4utKJ8w`)YkScnEwZDG=2~oYvdtqau)|6HAhwqW$r>MKydMdi-xf z|IPEi=Mls`ySoS4Uu8Lk>GP(?uENKw#l^+NO;vrl>caNS*3!n4J~PMG6%1?`Lo`8D zP!I`IikK!Gm+D~0Tx5dT2;-4lEPJvvNz@Roxn4bK2&F(-3ukKoTzvdLw9r!ZsOd)GFakMtPqh`I$P>j#E63N~^t! z8t)N`OP-Ey8cNVPKsgcS6B*&w9LA&4rPERq64J$9K^)cnN)EQxZgj#nJKXDP(AwtHNPvj4d!y|3WE|h>aXutjp#eR1Va1(D~!1cD@#G$XK@| z8ScdxW>*_WC0A}fCWQ_Gk+039h^tbyU`-AaRQXE3C@|xuc#bIvB-u`7jVA9qExYjR z=L}OyA;5`@PuJUM+d|rr+H3CQORerU?U9!{Bot;XUqe}i%R=!=DIcZf5IBHt${UX7 z$u&nXerDE=@3Wd|0@Hz$q*rpVDJ+Wsi!-OJ!$UKaeXQAz3oz@z3unQS7l<)x)linz zAH493JdOfC{BNrjX7CVfZBLDtgiqO>03bm9Y%opN;dZI*d!CgC7s1So zx$n!T6vhxG4g7BozT_i+(EXciSh1 z*WKx5dLayUw$Hadz3+<5D}%BZCKe`cE4yNK&2O zC_2B@YGbYTJ=@>6O14_I7;gA)sBiMPW}zMqr`$mljy|@#K)X4 zywlOE7bt(D_<9aY(j=81rYh}wpQBZ2>BFX$_0y{XD7Q1jV-(PFSPU`4DYgBSjuXGW zB&TypZ4-Ia;ZDv{*YiZ4BK%bLvA^d#3^`kw)^(lO=^V#PS}I{JY8vD2<6?gDUgByH zoos%w5n5SA70~&_wmZ}=sE_CH+$5D%I~M^tEkJ<ZQI7BsvH)rso$j0Tno$9{71< z@V}SCAhApjLIvlX0Pxk%zZqkf%M1LSF2n#NI}?5xPC=! zobSQlu20xcw~DY&-wOel-n@?qJ&by)A02bP=f7VUb$6h9A&zxij{$poi1x&>usk&q z)o~Zd^jeapPeoI1Jmh>Rc-6+ws~2@GiSZz{hBgw^soz#me0J4++L57M=6^+@00R~q za2yth-1NjYw%qz!q2gOQL3>x?qI6L_n5iR9jUE#0ppndAXQSaxXgAAg+?Y2ZVSq`= z9KUjbab4|QH-zBoMtL>BP)ja&OJ4O?2yYF#*>9aH4X@u0(otsJ5@}kXX@!4~Fy4Wh zDN>w`7i{CSlIi9?H2YDBB_h~K`_cJqA-9`a@G}pVc;w6b)PGdJz9MqO5mS;`wb~72i`W#}dhh!aglheCet+(79kLz+P{)7XRuyhb{YxtDFZ#1N?6e^# zh*vvtce7F3I~yiY){1)rPtn#OV%8zxe}b9$IU5=66PVl01yCBSd^dXUKhK1G0R|IV zcvk_Ac>q2IN6uR13{;c-_cRbEqYJTB_{Fr4IijaDP_s&jXx0$`sG}^H^o5 zz-Q`#Xift$p?Wb<=fxuzXVyNKg#>QnXBe)ocjuyk{hgW=c?V zRs~?RkX9n-Kuh2ogdASyGctZ-79U~PP*d!u<<~CRR3B7LYtxF8T{?!Nye0d%0n1-I zI4RC68nKpBKg^rfqiJ-i4HXbQx4>=dyxjLao>lA4TIu938pOX`7jX~@WPeN@jr_P# z^lTrnNnS5FJgePCzFZ$yZEE2?4_z#R){UKOsw3qqM;Tb8H@A2_3MP!1!fsit%Vn(B za_2OfhiiPV49y_-YDhUHAURUHq=tlP%rx5l^&mD@G^8z-Y=Z-tIt3L`u!>WVQxz;^ z&9LZUjm7~;VIecrymMSz9sAiMQWB|u=tF>$?NZ<_+~80;Rt&KJZ1cdqEdhb%EWus! zdJaxE0R*U{g1~6{#~l&e3R1mY+6nb{2=-5{7mcd@paR4GV(zxv{CelE`s$Ei#`XXd z)c6s?t)+nM8@GOItmYqze$tkR-@pNBhUdU3!dN9ILMYJOj4^aUvZMFQFK=P@cL1r6 z@U=sJ<=N(Bq`QQC3-wJHuee;+1OIT=^WJf^vichJbLK-(8A>DTum-ya`_|C7PvY^V z-X#zAoguBv{!+QTW6rx3-!1S_UiFDt_}ti$D*F?fI@AHKaETKn;7R7C5HXlh^h{!o zsrxdvVOX}7A?4Tr{6o+@q_3pMQZTg)Ea1)Q8|O#l$}N5<%GqV~ZE>N)M!~x7JUKA5 z9t(l39F)9Tiu!T`O`2ZQdW$v?+Qe4m558`xNHnv~bX8j4G6ay*PnvTLCWgm@K+IP1 z^SI~_P^NN)(Qy;gv`8wrCM0r zdu^7~mAS%W$G8dDhB^z`1T=lN-^sNz%Wcwkz4|)K)IQg@u1iEb91XhJ5xEwYDfvM6 zkLOfT>Goml>)dkK7RrcGd}4t$1w4`Vi@x?8r-Xz-T@erhoTTvYj;62sm##V72KMKy z7jCvo37#eEob8=(e^%k-w*#CwiWcoBL~yaY-mZ;3#7$hwrE0n&Z&_iqW9;qZ8h>;~ zOjAz(rmb4$^7bp}HHOIkg&1oXJz&O9f5ETRc`KDiwH!c>87$jXR}9R=#e{N-{typMNosUZX^8aPu^3Zb=_A_|$kJ2>CKI25a~u?@$|xUD0E z3rV0H2Dkhmtcz}Bqr1R;PGC&s1*q_(cw=w!eh^JIxmYy6ip|~R@0t~6h9kSKF8k`r z-rmZ)soKb2jgHIODnmo-1=6%KLu=Va>yJSJgYnC@P2eB{+<2U~g=4b-hjNb|x!65z z5!Z3c@32#?=kl#m5f8>l8a@f=Wi6&X>j+N1+ruaQG?CtDV~PXb>@WWf2Q($z>z7U+ zMBlz(Z=2s-T8$d;Ue6M3l3xRuVhSxm5s{3BKIpgmi-?-oisza zkmgcLp`Vnlx?L~qe?(H=WYV)H)PPR{pA7{5h`m_l^X{d`q$MOR49YduCf{c>9PI^G zU)!twAe$_^TtGrD{jAw%Wfw1k)5`DgJXWP`-7XNQ20MryLW6t0#t42k2 z0hnOio5PA`bpihQ)A=v&;|;YU&l?F@fC_Npa}OspB^Vr!zTb{NLwi)Hy`}19z@fr? zU3Jh7xd)*wL=El;v+()ck_u(iI_w^muPd_R6?OAcCyxtX2(vAWE-tjbs3u$PJ&jfGp*j;7`8P+@e0HF88@NU#6t?jH*EMz0L$My9PHiB zRVebeoyHC8Wl&pm$IT(G**{Utw9Bh)HAE_^TCH*ta-8|<-fxJ&aV4hWUSV75)+$)r zdIu%X^B9`Hh`wv*IW6Ho^#zL)v08Di99QNKyQ4Ex^x@3G;Cg6K(hX}D-{D_(j!D%6g}xd;qA)E>mv@<*$ZX$rUpcaK+~5kxF2pAac=%N>3B`6+-EO>fzLHkzfcD>r`}fy+!N&}- zUH9`HP&unio@pV+24r=ON7xE68a7?3>8!kAzHyK4Lb=YbvQ+HBn+||W{Eg?GVcYQ!l ztSPK!t!;Un>i4P0$ET?I9pdIh^EU0+RcYthPqRm& zPB}LVBWJC5;`qzHr{VN*QZ9;5?qvVIY@^viP)2>OQxb+mdkWDzLq#%PR5z67y??M+ zSjDiw%%q&n3QENt>Lwj~Ps8*c{0xvFm@csrU=eyiH}Cpb=6h0&O92O%dTc0WV%R`6~bS z;QT3eZTz7V7f#K|S{Kj{_}e_u;Joz^)V0uvH!H@e3WnVKG*Y;R5RQx=UKb=?4!qeb z=_DKa-vz<$?}ZxrbHii^hC> zLN`k`gS9^kaeye-(%)p=Q!i(kFa)B=q#!VbG7-calS3zKZMl8Kg`I^HD#h_iN?($! z>66rNVaPiYq<@#JX$rYXkw1$h7(yVDzNky$V^i%H!;0ZYI+ZXhW#@zfK7#lXMnh2Y z^3kcr0*7W=&Ss!urbd>4di6HWv0K><1f+uu%DQIF7AJcpusQzmE==J_e z-fwZbee~KU31mUe(k?U$jD<>ni>OKvN0|-t=m-(#j;6O&G~<{8=r6^gv3$D&K-xY8 z-A~Ae;#6^CAZ`&J{>W;EQAqsZ`r@~1+yiz(zXcIDK*GBO!0caA&f@eEcUcd0SLAp% ziK^4%9xfj7AK-j%&m}#)l$Krz(B|KAu~u{JsH3mYsRF-@7#pkE z;OJGjbEEV%#{Qt8>G*G(Vfh9<)rQPk1eaSAEZCJ)F~PoR(h+g}tl-VX($ zYO0R@KF7}dH^^v=pHnQ9YSNiTJWm+f!v@BwqQ$Y$ei`a_1{_|I-ss`3Ry;b`bNIE$Rnb+z+c*ky}aexvI*zKtJjccvTTZIqk!Rw!$+NgN&BT7q-IM^YM>9lAFF3qsj z{Ui)Y_-SRrj^=N_HhESJD-ltQtL~Y=Od(%jfPRpq8P9`F;O6pc)s_oF{z{=|n6er5 z!u-{h;{bvm_L%5agg+m)4aA0YAb@K`Qv~YLWx~sGmt6*V!|?F z%7PdL2(eqp+SqbvQ;>6xmHK-4tnG6El;(blqDJ+}Q2=*wlRYGBr%&K>9+K^{Aa z9GQ#O*$%Ki>UYmph71RnuwA?#!9vfTIuG|p%N;AWWwB5C+IE2*>xGPGkT?t@?Dvhd zt%Wpg_71*1_@0kBba@@FZN^TvjpVY+rkq1h2gtm zJPXCjvMjf7K+`s#pH$0kv}>*SPOV2H-e;NChSuuNAtqhRtEe-DVqBG7vr*enVEmVd zAv-&^RqMyAthD#nN)(w!Yp^GI_VB1e$~skiRlP3K6DJObNVTJM{r0E+{x$grTNFbh z_uBsc88W7$jtTI-pPGD>}Uj((F_m&nMmhI4lhx z;SZUOC;SP$w;q=0ux8Ozq190iFGeAoD%-HBSfOO9W&PK~Tem;KeV~3gA0dW>Pv6I1 zYNn)N-+Qq-I+AJB!=V9uxeoR-tL7t;-ZGy%%>9l;tMtQJm7z}(vh)}z8v;!QqkT%c z`Pr;kXU{<7gZGe(<&Zjp1|1&SGt0&iI1JiBIdPElDo}oD(oS=FPy1_j?dy9UkEB(@ z9bfbpt~myqXy`*o?NPpA2S*3Iq3$t0QzT^=d^GlO7pmjpsXe^IwU{J-P?mtkdD4jT zbfg}pfa66t&>R@5s6DBCTElqWD~=VAB5A$Y$g3nSX4Ol}s9ozugn47sFrns|d)D7D8mh1^h>F8%3W z2a5TI9W)%RgrtE1+L(i!DwwV@xZ@VytBSnvu3ay?9Y$%KBd@=bFp#4X>B};lBl^>;B5%>LW8TFDeNLsW?@@;#fCxMm!*pX9lfHt)uuajgiV$d zT#h**{Ipyhjltvp#_fvwZ6(9T&)Rb;VTsa~=gJDe$;q~EJzFO3Apn2EXrlA~F^1;i;H_jG>WmV*SvFHky zf3twjY=>%B`6@dr95pk37;>@x#zI%UP>yJ?6%2RCAY-s(SLIof9c#sG+>FEDjD6gU zD+r3UOyZKt5Q%XW6oZUQHH@|K!@vgu>y(j~#NpH5x9l+GPE6*P91EzHBE}krNo7~5 zb|0;8aj<>dJDCakJW=LK#vk^V^`8D9UP$2lLk&K$X+Ag;(w#ZeR7?dFGzJkJMi;Oc zoicM8#T@0|)<b|u?YyW0!6Ew$>Y~pX2XU`J zDYoQ`d*fm7~YwxoZtL1W7$X*5n>+fi8oUqvJri& z6nm&FFcO9AAX=7k9_;yussklMDtxu6t5OkjY3tvL7s1PUqGstoYssPT_ItLMXX))Z zJ03DK>_IPJgIKX7x8Rw<+?!kIc9MEA5hw)}5-iqzE8VFOr%mr5VC50inCtJ#tAQL} z1%tXg16rH5cZ?pPJcaYO6~hh*gGh%x5*s)RLDozXG<$(Q=kn_7fh78e%R|8C^X%4F zm9*vMr4{4*^7ibRo5iK-C*+ed7*^J_i&Im+>V~x=%ybD)(9wLptciZLN_)YB5O^v@ z{$Ja{Qtd!!GiH0^v6Ue$NG8nsD)~)N*JjWChU+1?Ny%198}eb+iG#cLFl;OopkF>K zIJg1zG{!THV!AKNdnO5aW zt-47+g@#B%3Z{it%Q@M`87PUsQr8-l>(V z7?crSbh@OEA$m#}=67-ZTp889W3?AU=1tjMdw;Ne(Izfm0-RQ+6jH&8gwGA_(Q}sf z2cqudmvKpmxhIPXLGEOm41F$3^s>mhI5{xLs3uHjw&8hlNfyhYWJ>LMMzm7Au8{{4 z-78CWHW(hd0`W;PqChl|g^3)t!&RZbm@=i00BhlV_)wg0=hMU42F)9g3L@3ao5I}H z8I}fZ8eb0a?<61oj=9=X+T!Eq!RN*aH=0Y9i8s}rg8IT>C(zNJ!Th>8L<=0PZ>~y% zhz0Bh?ag(U19g*K4YsztBIx+FBiiPs)+@S)uF6ph=|=6xgUL*jcixtPvskp*56`B0 z={4aNiYE!i0tq@Z1;pR-k?I3o>lQ~?sYinu)T9ag!9h~z6;ikT8&2oT|A@)-z( zaQOIKXY~=W6~KLycubCWOz(G95I!BBDB0Pny<_|zlgVmqx-mrqM_VmHhiBtJ`$Z5w zCPrd45%V_Ko8gYvDbKOB4l<(Fy#)}+&?NnmY-1A}rTwO$s?$(4W6U5%XfMI)w58zk zbnp#zcaX9eQujFlW$d|exgN>CX+D9ODCFX{GoRcYei!0W`_4DPA4@ELI0BSq?GTP9{qy5{Jp>{!$ilU=1r*;&BcRg z$*q-IA(UIbR;y$MuoVtrm}_sru-Iv6QF-Z$*v_HQLPEzhFGyrl8>MSf`fNpzygHW~ z_QJA574ufXwN23TR!mhNU*^BKQw@5<dJs*_=x{mDYt5qy%uW6HuIrYQdUw=BHHG z5Nt@%wEdaq4{)mv_E2B_!pNn?M`+Gf3%JA^GCHQY{6Z+#==o?VMBVKN&I-5tw2=+-ea|`(iVDzDkf` z_o4ZdXMG*j@}fOMk`);6@zP0?jJxg|pqYLnuYp;NEjq=E37d$523+{9c|=_m;Y=FC2zr0q z9ABp`#xa?^D8x?{^m9Pb8P5(LYi&GbahTA*2ISmx(8c(0gM7mGV0*-m^P2+5>2y*D zK>!ty(}TsN$-pvPyv8MaFTTJ&O7I6s@>;4;BIl36G56wWqHwlP{~pWLHf$Uy#0Puy zeV;G?gvis^Jxj`$>M5o?zm}_}UVzVP!9jt89Pwn(1x#nRAN`d2;9sJ`tk0AOz$1+E zH{8RxgaNe%M&|1hrS+*9C*P^Q=fDJ&p_?m6QWaQ!V5kK*vuF%HaecM^I*D{f1%Ubp+IA5m}APs2n1ZJu)J^J{Rl04s^nuyFN`DfFR|@!RJFA-DyQV<_xaV4SNKY62@hT@DgkLAq~ zhG+%xacHfgNfA`ZaU>zuj+4n`fU3TLj}&960XK1bcKm{wvmh9SVn*;5QgF*KxDXp> z;Zr51Q6HgH%jqJevB^Jiu6LMSlE`WNR1ubZUzzA5+#sU+UBVg8!D?yT@>=FvY+EEQ zC!*yn>I=^d@TLt~CRiEKJXWgp@5P+?!Jd%4yZjSDVZ z`OkMD7`^B2*g{%}qlKpgf7Zmo0$lvg7&BQ)Aza@3G~b|J$Ysk*P8I&CB}bAMZW-~Z zIR_wi6Up0t%hZXSOGa=}k*;=(xjt200^6TTRMf=`GX0xknXv$dY&rT#xsb_X8RNyA_$By$)d>6vNs2f?oR!rfdl)uT3^wm? zQwUBwSI&b&0r(I>$MjJH`fi%N1_>bz?&Ie_?js~TGj-`X%$+E9%n{r<<}`S$e`-p) z=*`trS)6S1Q%@D>CURjquWCtl()2l|<=i+Y;!j1i7jdhWpckp=OwWUJ0MIi}l3TJ6 z%ie2wuVKrrw_6uhff+-6)=_Nlw(qWRJwWbgGK?~1p|U<-iQ8R_>vJhnE;jiLPcBi1 zRW@hF{B?5XRh6|AR&h%$^yWc*ouol%@U#QTr4H?XOSYZzd|Vm2@o@5F7Ops_jl7Q) z_!ybL>GEq;&gio9wM`Qi-TlKa5EY2IY0@jteHNx%WR6`sJuJP1f$&aYFSPnLp{u4Y zEC0QDql)X^>kq8ecE4t_gb{C=2=3N2Gdry^aVqO$<8QdOeXI3e?r5`^^}Z(42qSR{ z0UzZY8>scj$7ip(7LQ+vQ=uIKkHj_~tcpcgSP5 zl5+MbW(cv;e_PPRsa@@MkrcgqMx5Z%N!L9-bn~Ur<+53s7!rjk3?KlB}I?)Qdv;%ICl2PJN$ftp)ow;+k%4wA>Ck$|vtQ zY_;32dscrw)Oop1ekSSV`gS{<%RUw@3VxU0lDzU1SQNO$YkfWP$ke$i6f&=S)<#|) zlsaMpADLw$TU8oa^N=>@h~Cf?=Nn=+j|^}w(vlxqQu54&1r>x{W^6ldqjSsVb<$rwy}rmwYQ01Baz>U?dDE) z6Enk8YWv#EPCC25t@EorUGU5O{POaAz%~D^imu19F!K|CcOQ6u9A(3jzt&6Lx23hJ z_sY^Wy`DrdJCS0duxEW>Bp16>_r;eS+N9O(hQNvjVv4ZBkPTG)KZS(quq)nebe34H)H7M%ti+!MZpA9N4oWcss21+ zAQwnD0vc>}2(d1Q#3z7x%6;?j6E#S26$>I+F1&^X5Yhyy)jZx2)-|Upucn@=gqJ|1 znjL{ulPOb0eXL1wk8Ah>PJa-YixeC}tZx!&A(kWBz|&k)2zfAfgt^NQ;Olk0Vk3P% zSYd$?<92$LGI`4r+F>*)w>2H8@J!QRnSiB-i2PD1f4t*yB0TW=VEPmk1ex?YExNMN zI9GtnDg}xUYG}IWCAHvEm4{~@{-51el6Asc*;aKov?K-kv&2q9S;tVToYnO+c-B=` znQKkgiC7CwY$Fiqj<-%#M!D%}%W?y{P=lzvRFF$pViFDB=NX-O>E6kM3WCB9`o^B* z{MM$j4lm`~NPO5-ia@%@awPiq@h@2GFf=ysU@*00s(yk}5oIaOg0TGff)nIUWYyxN zcEn}cZ}y^F)#s&R>KDsgsBwSUKb9_R?p87K-R`$x3itD)iTviK$x&+bcHFT*Q!eFg zNcceU!8YQz_sVsSd;ERa>;c4~o)C6(H5wX?RrI-;Mgfj(au5r*P)ju{uKG+ds!M@l zW?klvU;Oq*8pDCohHSQ24f7DeFk&%(PZcU>rFa>O6fcD4U}U3XS#+b?NZOc2maoDf zS5>B4E6*}7JnfMM)^Z2!u|FFCSETDqB*+}eo{nd-W7`sNQ!;2e+6~Ni)KbM22iZWB z%yRrZnm~6U0RBToY0kZLy)+s{VKacat74^qa)$4)&Ph1*?@Ov-g?MMEm?8Zb;eqt! zLvhaQgRdzKuk?`*jXV%Juuj*{CsQsj!V&}8J|X^iw$%6jIW)vwOI{HkFX{!z0lWlKgw@5_{( zOMVy%4F^Dsc0R@>XubIc?i6ec|UaBw?M>gea5yPFzj5S zT>m(ee^IdLw=-~?{o7xKpf^)qkrM(2p!((az6XGrED0(FM33D<0}i-zg79zA=DNXS zEsb+Zs~m#O<|j?o&r=|HRfL83{B0M~P{4zigdGU_Y0sk`&i#!eN@q9FI$Eh0D@$c= zHCwJI_FH!WbsFo5orbP4n^#UY>8;Ped9MS08=u=>R+PXtTkh6>nUbtX-mk~TlT<&} zv`4nQ78`LiHas=DuR9r3LjJaDID5~MGzV7ac6>D$N#lJ)K*b$#vtKZ<$~-Garg^@I zP>8fe%19Y_zr@ojHZ~{hg_(b+=~elZnQQ=ZFK<0h^nP0I2;dD#pcOcEKg%FDH|FA= zgCO~T$_6o8I$2SShA9w6s>(w(SXOn4pJ?h|oFzAC(qSCg$%!_$fG;Qnflw=yLUdWW zA)3k1AMBe)===HMKi6Z+RK3K-|6!Nf$WbMb-SFwgWqST%&t-)@hRVSed2jSKYbX^_BIu^IWwbNF9 zpJnu1Rn|Wqa>o_q$=jWj4UQukG7HKuhoijLbIp1FaSe$CRlFxs!%%g2>DL85wjvj( zy86kPCL7BS#|tDau=B}#QE|ffG7?kw$s+S;oe~>*PDr08^U!7HjxX!ohnTQt-D1S< zv>{kD2r9{5>ItH#v8$A+WSK86m8%+ql61HsP9hz+9q#mvT0C!ly1bL)-)G``ieJy& zd%tNl6e$!ua=U}>dM}XA>NTG{gA*PE_J3EIFWC8k4~p(C2wkZV>yfP7W~hmm#ntLo z8zO~R9Z9@lS@sMv$@L065Op;&QPR1FUw{cSF>(@B%9&rewXJ#8_cAc=o6*#1DT$xOzeycmC9E)Kw;29{@u_qV|P2(ZS zxS}xa+vYYvo$*1@$w1$QXeJ2ZsA|VX769oq82C&5=~|MRo4VlmF*%RSB7`4{P#pDd zHVO!rfZDXw4$Zpt!Il+oD?D$1+{uEk#nJjBK(eeJY%HhD`*}7)n_Btv{`Im!O4a(D z%EQ}+PvTbP=WADI;~|5XOqn2(kOqamX)kKHqw#y&_tnem731aRZGz5@?m$TdETNl9 zYS>UXk-v4THB7I;csa~%`a0{~6#Le+(mw=byX1PI&dDx!XDsGYB|_m zcnJe4os^9}S8d;{%WfLBg;;#j0-p7l;vBtSuFqcnEiu4ur+K*sVg3u1YtU+w(t}S* znYH047Q2SAnx}fb`rn$h^+M=ct#RG8&mx;^A;cRG6M`R-O{L-D%KMi~ug2yjTfo~> zH4VQ8Mvs>gE0<^aSeNJZh7>i+(1$u(`q{(nwWQK^YY{7>(QcDGjqqfWJw2Vyf}@0< z*0q@`%Zi=ABF2bB1I%U^tnxIB&zV$RNhKpCH@w6qHX=p|SL^r?GC$PTAhC+K`1sxu z=1&f_c)8l2Cc3u2W@J%(6;VRUbf0Btl2F`Y)VYf`m|vxeoTi>`gW96 zdvwr9$IR>Y)MUHq$%$rM=IkMf`b<@d5=nY#^q%C`fbwITF7v&Kd~K}4z;F$*^rQ0@ z4Sj#ac5hQzCLMN`*^3>aRyVd2a?)5z3k(T7strykphhh$nsZ>Qc7_&FaAzY51H=Kq zn4HbEn!l9dl5~X1xNQFng5l~P)~B!E-}j`fMweF^Ns421yno{$UANe9e-h$_dT3dQTzRcqepkzHk^z|s)HyzqDH#~EbY*nE z!3acTnuFHKm4Be2=5dmGaC(Z~Y(EH2Sh?kod(}((&UA6`XTR-YOn2Lq=K8Ed9J;;w zkQ210aTLZ=kK-~tSZUlpgbb=&zrtSoh^z`D-34aSz#KFN6OkBL#w9Qm3&c|6wm}xW zpST@|N0Y+_&$;v!^lp@ufMv?cYmi{r4I{lR1#NwKkwjJrH|5aRv8PE^P+iKQnnsxV zp9t{@(G&~gYy7pdSBcci0$eh7${KG?ZP|P5B!Hh!V~Ydjpyepjlz9e_y56W~f?UN1 zT}>?Ii^u;+sVa<|K{^5K$KG$V_fNK*c-!7`SKC-ilQU~8d^Yh?4bl^Be3ZK^lT{8= zS8p}8Foc24u}xec3~k@==9w{AJZg;u$Bsi94Ws6U%vuicdGkP86 zxPP_v64Oubdj3pnSIZt6EKDi*gaANFtS^9aDeN6?*l&Po^l(+nHNdVjB*mkA<#9R( zcBb{DRXMY=mRP1rN=ufcI?i2TqDX}okf?on<4}r zl;fjdikvb6STV!q@K~{=8VjL*l6Q)k40Kr!tD_9n-j}cIQH4J3L)rJNMja`rb^JJA zOox=e;F?5I3T&fsrC0_^(Yus3APsM;-FFE!Cx%+-tsa;5@zPj%AVh-)t$ zF+X@&4pt>X7%PsBv14&KggqdqHG1W^!jSt~HJUay?gXlvWsLkQPE0grR#Im*_Tl>X z$Zi}x0nE$Bk%)~}`lYFe!RX7JuD=ox%p`whlQ6|bqgsXfHaF81jT$YIL9{f(HSak? zpn0T?m@}WjLFh8hI=OyV6rERA*m#w}U1h2qzjXGbsml6#Jw&N*zdT-dd=15Ie+EtT z*#yE+H{;eR8(c31v!LGR%vg8(nR?iWQ!X zgB&?&SyDYVk5FD=GAgy6YMPzYc)U?f6w91AysneldB*ZfNwqr7o)r^k6yycj+5=oG zIsm{uOIXjQV$7>=Gfq1Zc(Qc~$x7f?D4xDB3DhOeHps*Sz*-D^I+uTCI|L@ z!^~0YFTBJ!r7pCmhdi8L0w%yf7id5|2Cex45Bt0=AS`Qc>_st%GM2eiFurXA8)&vn z(v1_c41I0zS)vsNNO%C$bu$RG48L{WZ2&C)?)C# z>17e@z3yu@{by7YpJ=5K$JiT#A#la2nF;S3f; zDSR=#+R(v$PoqqAEtF7EmCxP>bl;Bz4el=aO=r4jf0+oz{lpsf`JTJPo^$7U#Lirz z*rL0Ew*_?NZcc0iwo4?}+q1LDEVUGyv&xom@Y2<247cIV0>W%XhlS_CXn+GXfhKB1 zlkLEMF9fYoKw9yoIFBEbwmtAoO2?fPtK2%89$@3BqiiYqJ(gJ#O3CSZtS5)QCq#Td zD;_7RGd7geKFUW=+l}kCIyx@xSzhNHB=BU*rOC2NCU#BeGr7%XUc3KTRu(22MeP|OfeK}h6Sw$9 znybF@fKbPT$!GsTdDghElPCbj>FE=w$Ot1AM3OO`xCeU~O~LnREf(PRSZF*d#^Q?o z>;6J)+eJi7qg3szm{M%>vS1BMpTSV>egNC$?5H3hAr1~m4Pbo}?=89Nzi~9tHbPTP z;2V^AM16l1wX0b{vq4OIUpnQ|fwiRQ8kTb|JSWSTROq@C$lwruW0aX#qk-YnxK8H> zHw!#`jFjBf=_XQx5f~Oa{a_)-ei$&AuTgrk;Fu{BoqrAlS)sby2vM(P>jNt|rNgh>#=@{8vwQ;2CN+C+RNN7dj;t?ykeFtlMtesE?J!WjV9* z3rus4%J)WW(aIZ8p^48E4n3tHQ9k8b_cpaLHU+paT&KQ&zhG@L^d~+YM|w33YEs); zo?4rq3NcCzHtF8B$38y_U>LwR7r2++O5|Bv z#$sZ13Jk+K41jjkomNzn@>A+j*ifN0KeIZ^$OW<*yfL`NGz?~QZUTT{3buT*ARp{p{y4spA`#PCdq%(!t zgVbI=WSZrJZYhdd&(h!^D?ghV6EWy@F=6~$$K`8cR2A~~Yg!i~=>Q|o`GeD>@AK1s z*Uv*oP}N%In7?%8Abm7D=%i3{BPIHITKaU$uuS!$8KP0af*C~(-(~u;_{URw3*`*_ zdq{v!3xx93adJg%>3)ftaFArB(~d`3U&FxMhmx>t4)wF+v~l@12ZgHeOpelk^&}8 z>}dr$wl6ypRB);DsHO8~b^1t@aoA=_md7tRbz;K2)jSa&9J7=@>-9u+J;6&>r7Fe} z1Q+j@6rI;ze+5kFhp}4Uw>xg0GSfUi8Zhbz}Y@6}@->kHZ+jo_eNB zh(V%q_s&vwdO2BFfGpWxY$G-%v(_2hc5_AcDm2Jepu?qKUkzVEKPk4WM>j+2dM@ow z8vq`m^&8RJX*`fav$SU)?UJt_67BmEgZxsQOvV2JJV3+0J-Z{8?Apzzotf{|zIMm{ zv!jhM>cxsvuURNkE@|ysfs8o<_zT7QN@VBJQPZ3}3lcCuLXJ*(Vf-n-Y6LJ=XrD6d ztc1sN0qxRH0G(w}9yLBmu9JSRk?N^2Appkvq5mzs20=JsXT)mCPH|p0tTyVyWvdgg zFNy5FhuyPMb=0E4S|_06JTmFIA{Aep?DP~m+37hq-Z^Hn+1lxt zjM>@#ipY5E0K9@)7GY0>x+%?jWiTetLN0y zEVe7E>1ZOYDLtsHRm(ok5FV|sc~;NMl_AU6R$a+j>o`YW3Kwcu3mdMoaHyt8>hvJi ztWh>ls2=G!J$JBCIlEm~jLh;lFuvFj6jER{Lt;v4rIl!cMM*%Xx!m-4piw}Fxh>dAv%`Oh{%GoMl%m&=Avcrz zha=aWj=EV2(W6)pt)ZS4nWhCY?9WY&>4|QM(#Dh+q|(i4CW0erg?KVggqHH&GZrj>>FO8onE`P~>Jp5+Qe*(xghpone*3 zu1DM1jR5gVrXYiMOB;=6>H$|z)2x)cOke3Fn~-#fv72Fx=vyIaCjK5x7wtYu7UH2y zLT24kfdm$wx}YVs4BMkNA>nVV1`C;nts)i#B-$)Wy&Zc9@e*t@B2jO_27`#O6(d3f zQ70iH5)l(4vDyrxo=5_+I*Bd`ZwZPf{sW51Mjs9JdX%( zA>}GQiTJA7Gl{)M} zh#*o$5avbfvtlA(tb<&{U~yv6rqjDcLB!Z>auT6hXE50Xt6vJsSTIUh@ClI6sk78M z1cEWI$09;bEVuyMDLC~9Yl2At^On5i86XGx%Y{aA|c5HRqkDqve$iyKc zNpBn+=_%prn2e*^$A7B%LVg zWb8%&7H(uS14v;QdcBtj&=W}%3^t`B-iD(fdyIE)BbuN+J z1Hjl=s|20iY}O0NVkM%7POR0$TLmwSrGY9}IG_Rm2jl^`t3p2+aIGK&TbgU&-=>v>s+%nlBRP1Tm*_D-F+c#|3O2I|S|Agvju6c28f}K4-G;3MQTwF;jYKaR z&B!iPI|xqze2HK&#K2`YN;M;x*q2|8Z3>7gbgv0;-zr;{WR!>9^6WaP0KdH^d8 zVS^|P-yVJh>H%cIL|dzaX{L}ypaNJ{SQG$?t3+72Myw~i4LU;%adVx$%IfB&Y8}&# zaGi09w=$Z^MKvKyD89a^kxS)QYXQue!~|#K*taO0lHl@apQF%FEBv{_QmUi6UQzI| z=)?FePs_XaXv#qCyC&Fd>TkX!Jb07dYA@b}{2r1=Hc~BCd~D6bXn%C-9nWb@rC_bG z-gs|kjzX! z{0(PIY%gm5;t%KYP}*An+WRJfV{)o)schzsDjc(KMa6}i>~*TltlOR8WL2ggffBez z{#Ok(s$B3f!*-nPLw`W;*ECS2V!nLOO_Z@re6@? z_~N%!=oLKu5cbuSvwSa@ilceTLf3Y;3y*eQdwYlAQZRPiL&yIL~}Uiw~k zk*Ck;F=Z3DM!pQBXD3jJ@sy@YK~m`>Mw-nmD+EQg@t_%5tU%N!(B=0-r%N9Ux?g=l zed2yPK*f&%-H$GZ0NH0U#poRxOM@mT4EL^ow@$B$T*xrLR{r(-BNu zi3t!xUR+Fp7e0N}9g8;KEcWf_nA$7wxdS&2AG+~?jy~~bP52Q56fT^HE^BP^L~8CXSa#ff_m0%s zZC6}6HP)1Bg1^|*ORw0rR){m%Lba~=sqDg2^A_GDY`eQA;%RC`>se$;Pwjqjv+yAo ziw2^{|F1O6x^s;(QIsPOiO ziw`Wm=*Nq9+_ZH0awvJUw`k)s$839Z8eDMHKnpdgNI!_BUBgPXNXota)ag8Im-lYP zXu`=S5$c#Ru>MfPZO^0JQ*Xl_y5~1(zx5=V@WQ>_ht~J?)cyqMjq72}nVEilkXn6b zP?ymp`-_q`P4pNDqG-w$F1Vlb33>@xcyw&=D&a#f06BR3^}(H zmpa4Q6HG9d$!ONIZ^*FgXohW5A>rbrQ|4ltnc-&SL?TYQnaLn1i~6Xw6)1#RaYqv5 ziXxZ9jQN8*Lu(}(;|y&?r~O2z&6#a>OJUwMIv#N1HH-H=aM#imMrqBWJqH#~)0=nh zH0!4=KCoxe8cAqqx@hkMdls*eAf@ga{AG*XX3o_L#D98Kb9~{dE9OMCSM$Pnb9BxX ztF#xg3wCJlJjwJ9RBSVgs}Y{d)jsv+BYv13Jv}Hr}V^v*_?X!fW?1+PP83)pHRp zLBA|9>K>+eLYA~uT=sNALP0$W%JdK^exfs(E_=km(v47Ih<*_Q(N989y8_cXbL!7g zQ-M9di#kxZRP5S**amTB`oZKQK!7WL!IZ zmDlV1z-YA3)M{L-%V2h6l@rl*#YLhM*Bk)7r3FnQrOd zxmsB9{jh6qm1n_Ui5W^N*NwjuIh zDv_kvrYJ=-3Ht>H;g(Gc*Y{4IG`XhfYM*XWShh{Etw(b&O>|=Qkl51O+fq~29J&RV-l}mAJ*F{yQYFKdO6j$mz5UH5H9OeJR^BrqBbCImq)JXt=8jaZOE($K+EIK zc*=uC)4OH&$jE7TSg_$lm9cgWTO&GRuI^0ksb9KiYi(OC!kyVp*^H1yoEYj_e(}0x zZB4EAu-zqDf##O$o360nC9n7I09t=ybhcawZ^`QQRhApfQSlx1PdCr&2)6hg!LYxrefHz?*Bo5hG1V19m@G9A zGgi!!*My9s)hES_vU=xtHuX18X`dVjHn;TkZ(r~Pn)`B9_|)yCxp8oup)A8O_L~Ct zaZhO$BP#oDALAc8HviN9vGtApMkxJGdBrE{E8L@FRPNkypFCxyo07Xs7D1pQab=r^ z=-#qZ9dQ!Nc%c_eP*E6~SNVlex(`>Md8}xULT37sP1M2%5WXnP6tILut>#!upXKY!LZ!58LIB^o^PRM0)Iu4MVKth5Dp^$Ke0O2O) zD$tNZxp@h#+5)BA;e}FKXiZCb3oS?6mjbc1`OnO*4j&=B@BjNgh_$o3v%531vop^# z&-46#c%*0p;51w2hak8?{yi)cPo5NG;)|lla(H|4m6aKt6SG&l{pcpHlmZ}-lVPS&85{;Y5Mk9GhZqr%A{xj4Dn9cH)-#oi+0E$s3k{i#|D_Sb=hN>&lb+Gqn>Haxk@WWbpmY z%4P7Tl=$Iv`Fw}A!nVHoiN8$V^<-b~6T8nUpEbj1V{|NMseR-A8}GlouNha)9<6Da z?_BA$Je40~ymOKN;cz_&|7qSG7j`!E?7D2?+S|RXPN=Xrq}D};-?{se2mZdW*}r{Z zam|FybEnqGD_7r|4Mfh_w%kNs!`O*FTSQRd1Zo{|Txv5Gbb^s+Ac|xhTf`O_DWTFg za`NH#X!rQ}u~k=HwQ6Zg?>RU24-E9*_X=2i?z!io|A3e;!@?b|&^~8fEO5)?qix0UoTI_``5>_HnA!vfJrG-6}# z__6%cH*b``e16-u=Yjb~;Cby=+aKO_V&~2iyXIbbR(mmr^s2`V^r{nYojCCp-1w&a z>{B=+CNHoB>wK0 z);6*cMUUX2|$Yqei7s%w7PUQH4LMqk(gY+B9 zn2C}hcm}8#3?<14jMkZu2w4(+7D-DWCDmnc9+28d(Fx^RQUw(O0RxZ>5zK)U#vDii z;wvF34*ANp2`ULOLVz*LtgAvBV9h@FASRK2A1TA9oP-G`ugnUNpaZ}JDYNn{9Db82 zd`Nxn@YtFnii-G%Z)6bjL5`kV`(aNyDY56Kldwmj&d$zvOmeW_D0!Kl!KB2zmd`_i z`)7(#u;<((TU8v|y8dfXY`-LM;}*V2?)#xuM-dgOC+@x(5S zMw0vP?GDD_flZLuzJoCg9Y*m2Qw~XBK?$+qsx(o`LU~04=)1gO%J~rhBIi$O_z{@e zP`s>^o$ zAq*DGIv9}$6MS`1i71v7Rr86@oMqRy&Fo!H-uWYFJUfTP{gtcu7Iwu|7kd+u6@7)G z-e&QM=4#-x1xSb`SSCLSR)BT$;GEU#ez=;sR(@*sg0}fKz5Ems`#~qPmQ7jLcJxj9 z+94nPM^M|ja%JbVv(Fy-ApH^)*YB7V@kG+^f@{H-a=m#o>i z^L13l(o;6>Z|rZePn&NTXe|y-^>8@emsO9oG9(NI)f*T0$?v0`HQ`8=zRDd?d%xLIB+O2nqE@Nq-+*_#C+VvjV6VjP2Ityoof&i9| zl@;7PM%F!mD#xo-8-mf`Il&;nma%exo+UslhccOUA#{P>uGNy2G9$W`-i>amK{vNS z^ceK4(OFTc#>l$o6jhGu63$_GDE`Ely%k$Frsra-v%;Jds{%NRo%nlTF5!|9IWit` zz|1RlA4`V$9V7`0GSDlVuh($y+A4lc^K!Gb`_=r^H@@gq?@&^Iw zYK&$D&H-ItUIWOP=}@IdJ_7c*Dh0Po-pkHto^hbGdq(pXLCNt7*=$$xrR2ds6cv2{ zxF_*VuK7}aJTopRm|J!{|4~R#L$VKsq~~J_8huI39Aa`{To`^}I2soLiSCkn~*E4ZCWUitU^n_ih#+p}bL+c_al zbLHQG`1fDsfV*s#F>t$n48li`=GGu^>_#KCI=>d#I@E>mTlfwX1@PVY2}t~-7t629 z|GuNI=j?#Lup&Bh`Yk|r#~tZAF>b=~GoUN5jo%AZ;Tk5{`{>#^H`mwCvr5G}q4&{O zAN}k8zn=kWVep$Xqb%&Y-~<{Uz$uEp2#sMr#SW_&AmS3M7$;O`cr;4TK^*Y1UDT&P zG8Qp9i-mbX?qf8fQDlG3IL% zSqbyGKjsf#4@F83l21pHBaeBE7;Xc(30}eTvH4UKL7u8FRYD4TWQwfFj=9%W2bFyi zcv#v4F>+sNeSSD%DwWAS#$H`lDswG9n(C@c)#qfB6w+pAQHxc%DC6*sk#j7uT4j|H zt4&40@vkDydUo{!gz0#)12MAWfB3lwsfB=hMe~ zZ@#$~i!ik_XV$_FeaI;3s;Z_n>qkNRp}%n3!eg(E4r`$^8pCoS_$Dw zER-@?yNU*B#BQvCus+3>;v2PC;>*Txw+tsmA*=T^l5Fw1yPU-AjA^o(2~(&J6eyS9 zfmF`eQeVoTl+A?af+Swb2mQdC#fnXzi}KG;lXu>)EYoAtiqVATgPyEhNw{FlR4KKT z*d|F>xvDdv=2xQ{tO`?hBu4bzxD|W2WuY;!W=I0I$eYXjVR!Nmy9I4#t+{P;P1n}i!dTGl z4%QVpoK>|Ib#)cBRZd4y9X=K-tlipGv-!4FM>kKHu=yw%{}t?67l}b3%hWmBkisKL z+$GF;xRjw>pt=HQW<1$184U*c=UOdD5UR)?Oom8MCQtSgl;0i&MH2L&TA+VAln*m5 zCNM&z1brE>NV2q?g@nvt1QKqdD2V|s&sl&nwk%8#$bN@inWaQwfZTWhlTr3yGRhS? zn6Wlrbw0K>-wx=eDJ%L8kK21c>=8uJL+m{LgaNZ3RcnReZDNDo`+nSGd>d5!_+abd zzOL5d6Qj!*CXUMrK1J3KH=-g!oVJYkF{l;p(&ZKQJIdHE;F_TP27@5Vq>Vw3B!70A zLT38A8vnJ3>d9Gj*sQMx9Y#z@|hsip2 zD5hQ}q_}P9gN?l%_QuJZ`ZrB!DA)%k?{M>e)xX^R;-NiUAnAB&aomSDmXm12~beaIJq-laFD z_~Mf_A?5AiaABKrhDZ{%*|3Ev4GMhpz3+!yoX*l5z;5rp;^RPbyx51+fo6-2bA{f& z7awYvf?9`GoDLGLD{b=jBOiWvWS{l72MMHxrvyoHqI@1%y*nhLoe~ek{9p%vYu!f< zUTIs|ike2{`c&+ySep$hzENxr9v$gUk*q6}ilH9Kctpwl1l5u0AEJ_q3lyaGElr?< zOcH~}?ORHt^dOSA6wjxDq14iSEVU1{X)Z=AG9p6k`$vV*iSHQ*_PqkX6xlGL%JzQp zrb%UiPwDii!92B z#X^zeXqY&@54+m2sdN&37DHd*kAT*r4+Sdlusy^XuYY9vTf&(E(dbQk_Z?U4zDoRx zgk}Q;19vWAG_Z{{vhx-n=0pYR3~$K+}5} z|Nr{>GvyyyUyKND$#`3i!eYX_(pfPrhu2Nz(x>v$^l6TtF8zNaKRnIx;bq47skm+g z7>mkhe;>%!^k1VZo_8$$uQ3jemHI!GQ6B4H?&sw77<6<%5#aLNf$<9DcYHHXQNO3Y z`hWkG{BL?`)-NNkzZQTD-#{Qb+}o%HL~Nt+?IXUd2J?TVcYojBcM5C5XdJ|8r5BP@ zdF4r}_sjH6kU*m(=D|t)AM2xM=ut!0Gf6KVu)Tvx(y!>0QqZ2BtYejuuFQQtfLtLD zgpkmY$nuzD+iNpM2Fka-5(w9fI46!In^P>%&wH`W8EtD9STd{d-A;M0*;e zifKh!OcLpbNe!m@bJC(09R&Sj*XHx@6e2VD90V60TPips-~);XUQS0NmH;0JW2;~^ z9F1c`W;7mgprg?ysQCJVh=WDiI-dmchjRZwLjL_E-26TLi9~;@$Lmd|Qc173Cx!Qk zFf<7S69b?pc~AorUi3dw!vw7t^bdGbUX3&9)S&GE==W-|BADjV~aZN6xnv}ZW(i~Eq6gz>hgM;SCRB$G!zOnAY7mri*TINstE6`d|8QmNF3M?fNx zOs2d;1H(8|G4n}|E_H<8qXG{?@DE4f01-bvnac6j!VGh2zU?-p*sd@IM#hGP2Lu^= z0nq<3!Z&e5xxNpV>saNIQ%c!V%CnSGB}SG^A#+VAr5k<$Y#d%Nh~(@U^uL%0lH$f; zjdmm#F0Td5SO?)&U9HZgldE((@D@tc>U8oBupb;4^YAf}B1h1Vl4XayLpSzeQZ6GZ z*MDZpMdf^3a-6!%SO?);{BY&I`_U7~O~G5JTw@)EGnBHDz5QUnTH-3**oSesW>8l% z5oYeN_8QI)A&zyBiJYm{!w!Eos;Kz+;QTQUQ%bpxp>l1_Z?6#?6XIA0QMpcA-7yZs zW20X#%7F_u#$h}bq5cK8lJ|&9r3EADmQhDia}Vn`^k-u?78&1A-+*(o_x#?S;B;@B z+;avnG7);Na?k(43k2t$?w#O!R-$`u&6V?eHa=Z>n&wpP(2Cqxt>C5Rqx2}Ye5)s` zk=M0?Xxg4n85#2U!4zHy z?N?x%`sqz(bHCXPC z_aNf{KQ}za}--K*7MVC)=<*B%t6N9($#_rVs$xPB$sFlj;+&^LXkdHKHO%l9!~s-|}Z z&}{F%rI__`>Aqj~O~)DK|5BuN#gLx92H$Y{bow9o(&g!Ul#@zGg1kk!G9$-k`z)1@ zbis{8B~g7F^E%@&{#szAF{FYDVv7C2+4AB3S2jz;E1}WxV%lWj4Q7*tWdp4%H{WvG zN=#ZSQxeu8(FYHIeRmY}|4{xj?{{e}R+Bcsb;Q^7Z=WA4HsF|Dk`4c06j%A&A7rs) zDe~RbP>b+PAOL?As3R*|A8y| ze63fwBj?<^;rhF8*th=P4H5ShptpNoN5{P3KNnr_fK9KrJ#fLIOQ%-~Lgn;Jf#!{i zW^8H>XgO(I>*@)+-u&#yoJHH#&YBnS&Y8J(+rruX!@nyBehccjhrgQd9DNnGB&3R` z6FKuUCXF3Mpfmu> zxte_XGQMnW?lx$+9`W6dT{k;{@l)*m*y93!F8_nNX`Hp=)ml{-xSSeXS2_Mat6QX? z+MKDD2Hgf#6>9&tb<-2y{c>#O&-fwYF82MalnlAjMBju-mmK<^)kHB0f+zk*g;(V~ zv{7c6_V2es!i@0mDlt<5e>lJ?5D>mvIw1-vQAi4+67i5p!h~8GbtAw1cIwdkhf;6L zZ-a`r>EzoWHR>9iTt}*-dUz3>@?;WJfCm6(F*jw`MetaR{iyL=IhR^NZJ>5gmy(s& zd#J~V6(7|J4F{+m@w{|6FOBk`_lDA_7Qxf!IpguurP=(nC7X`oeTlG>jkF1vd(7xx z(mY^B|I|H(G7lkvk?t|4v**bMjJ=!L%9OgF+oIcU!WVptrq$`uZwYoLM$iPCNRBV_ ze$!u$IwX&=qi%q*QUA&PB%c|_pAIGQAAS&xe-)8Bp{~{0sWNH-mew-9LA-_Vgb-{1 zFv4u8S_d=HaoEw6$)ZQZiQ8)?Vhj!L$p`n(XhCY(`;B|nQZ~V=P6v&sMSb8_;J8$D{l$4 z#-&XL)+}0a>`$idEb75!R4p}`+Je7Bj<>}m@{7{pC>koYs5xw;QVtuc7dnaRYP0|U zY8E>2#4E2o_R!n!(x3e8Mytfu8*8O1S4E)0?r=$KpV%N-%W5t-_Tc_X-wlHg{jb^z zI#cE~&-8#tUeKKX+(x1~w*oR%)+oV>*88HWBtV^qr>w?O{6C7S2Uz~}$FhQw=2 zNG>7k2PFy{=ZN(KyLDvzDeN3;K|#kl&d58OO<*DoWxy)ze z`3)+^=&IGc)4@sdm5jsCYBVxnyOMxck6D5JW3NOp zzLQ^}i!F@9$m*3ux_9i#<$U9xrEC~e2iP+3G`K<-w~_$XVIm5}Pg2D0dLuH~&=Zg- zOAu@nal2?-Sl%j0oY7w%E#x#-jxK=ZHzwY>Yj_@T+wlj%i<2?BiYj|!NAOAV790sM zqw%KQyXy@WpmBkN_f45)92}8PK3VwlV~VT_PaWg-umhBiDn)guL~T!794sBy0*T@4)%W=^;2Th|FW3vyNlPiKv%AwNdq5{zS;}a3izc4AXOId&HeiPdcSWfV zCV5F1m%-Y^vN=SfNj*XE*8-nn0nD2De5x;nqUh#GsN<;j;dMOX^im1urjzLJ7?aGH zDu()pSuW_g|3>{qtNof7c2L&ep}(Fy>jvGEXW{r-t3|p0J#A|1LRVSXLUx_x66R^LnM!_p>J}HsA6^_PFKwOVDp*{H6?b%quFIumldITL5G-q+ zr5;qU?vo^z(}=Y9Ad+;KQoYnRYOl%=tgbxTtq#Q}miV}Y^5jJ}8>0}$;96)0)6zg*EG!EZ2psuQ zo9zo=anEsIUsx!AE(UC%dtUmcFXS&&I2|COWAY;^Vh)&TgV*HUCjC$4*5IaL4+Pp% z6zK_oY$AE#xC11A{{0#OCrkw5>^hKjV{d~$*O z6We-)G>Xc*<$c2*hR1^*^pOmab||9W-f5Tsj=lv&2GD6 zUV)`JC{@nAKHzSwE=v>@oMqPR)_IIT*V=niM%RY;d-h-+t$gGQg{C(%k=gJ!OOKr0 zlFAxz$dyQBsIXBYsc_LKKxA3i3y@R|W9d|gSxXE{O5iJ`R-zwImUm>tLnKWb5Uz5o89GOdB; zwb1H3c|QmM^8+6-A+14cDEsIE`78Oi@c!4`g<_(wy{)R%7pe*C-AjW-6LzesU*6PM z-t6mE<{=jQkkNZl-8#Qt-PqIDjsE_1`+Hhu=;3wiKIgnECaqdMjX87G-h16$2}aj! z;`;W+j&L`r7eKn##jJuiM+LDDyB#mXkRA~t^B7(^O@i(;B|pM_WzrW6B}0vAD%561 zX&R+zlqNWPOw>QUaEPiH=SN!xZI$)D_sLk=t6*di^lXeLYxDD%6ebj{%f%jJVjneb zpc?qY{-_0GWMDxT2QX&>mI*Bqri!uQ=EqnY3IPyO5EjoG*IC&SJkJa4djG|}RW0)Z z;{xZ*o_D?{=&1^JuQ;p?YK;IwSRAAeujmd|q2uSz?>-0Rn%9!}Yc*h5;0#n$+8b)R z%jYZsPtL}tE(+fqW|7#Ti#7y1Dm%x`TD)XVd3Q~Ny|NqsL}HZIjRC-J|FYIZVdtj1Ra>x;1CUFy?oR0eeqb&+2=e% z$~&q)yU&x+xIagyW8NZLd1w0iEzZ_yoa4bRW|Nh>@_e#OrLeVvlUDzJp`GK)pdB;>@7<$p`HuiC$DPtZWNvO@KGlI(6RZ6DEme z6}VQuV!a4^0I$V$D>>!m6uV?)u5Q4JrB@oW@DT(bq-tbSxcu>02{u0U6G0U?Z+dk0 z7Aq9wB(F8-6GnEv{9p3lX-?24EQSG{8SLumJ`UyqRLh$cqmmiEds=*T<@xB* zVHJ?xp;f`(^Pdl2LyuE#hi(fZ@@u3Z^yHDx$ECtWQ;PW-%7?Ew)AK<*mWg&zAn>&# zp3hvJR~so;NiebjfYJgZ3kyaTV2pQ=X?|^{Ax6G~%2D-FUc$(w<p&={&Y211-(yzcTTRn`)<;I4W|;^f2$aBJ}s1dJd5rt`Qknxu^-C+ z9(q4Lc?uX;1bzrU?iiff$UGAooQj6GSLCmN9<09puDifoFz#n+TbX%j92DwK-1#wM8;kZc8hOXTWOdlrk!v(g2;SK#-^cux!keFA4IM5Sc;|DiJ&Mc}6jWbN6Y^+S9;oR__{BE9E~mL0O5f<*Tuox#%@ zr7@25ogU>&ovbe_mhk0T9_E1gk&^W^o|L?To0L7|qZK6_;V~BcuGxCxX>ty!CxO z5RFNr6Q(Vo7)uyI2+byk4`} zVj6{$eA*oOvW%srAmjK=LgF-BiGv^}^XxTk(ofBo)YkiHV_?8ZBLf=sjg zd>Uh|;;ZU#ZhTc8z8+pXv@M7(>feO&Z3xl_g6JZ&vpcw9Si2~?|HzQ#F??AShgo`* zUoG)oRhAfrd#mR7_wxGouoZ?g_;uk0$|17mLn}ybIft%fKJO_U$gbDRwS*Q`$w}|c zr$9yHBq|YolD(KJ#D3Q0AO}{Cy}<)H`d|8_Sen8?S2m5t(62RvM5Ckq~2E?EaN1Epf{! zbW=IyvY5gAqdUm}}cfVfXIXhj^SM|VEr3QlwhK4oQV<1asbP(k8~-7Cvm)go_7q?N7BqPS)$?!|4HXXLz(F@M zMSJsH3`aR2f>bgIW~Kjhib5Ls2gFHH$qiSGn38jNZW!^ZQpM{~J{r^vBS(snt;Ad? zI^>izQIb;*(NYSNr8ld7o<{8RIsDDh%L2u6!tDmB;y@tn9p)4|V*DCWCS|x#2Z=M6 z$x@n5mRdvynk6PmAmP}4`Z9rg0)ap=NV(l|qFDaj_b(IiQ&#N1F$XwfnG*Q^0p(f0 z&$oq+=-hYZHKhf&ZTjyt8Hvdi^y|ZUj$FCrjxFn{oZky-NFdo8;7(Dv8@Eg0 zEEz8q#6KSW!){H1?qWTFTDGucdDpw5aH&y}FMC1(H3n4ODT;mz=?^Ovp7pGViM<%x zFz}OOyaLgS*IVgul?EH?vTIG4rCY6rN+pS*h3L0_bwm^{H%b$Cb$1l77SlT3Y|_Hb zdxOE*yF9_}x>&e!X7$8zRRxyk?~sg_3u42D_GXc@7-nlsf{}K_TNjqCxWG~toL*HO zt?!9X3cA3GTRw0-j9cSjZAE3oiJo=24njR#<<&nx)lnU4ov=uKXM52*Yt6{u0^sc`Q*f9H zXPt-RSpg=Lk;5~g;N`&Xz}A|*qVRy@?H}C_N(7z8_Di!?ejQ_dY}$91U7k!b3mW>GYNjjw8r7aOGob3_51*en?@!+BA%Wv)m- z4UwpU%8R6RUqA)&S7A!B-AxfWYB9nxQeP#KM&oKE)6HzT4rk@yl7~>IATf%-t89NG z|4gINiNBC^?@B@4IR0lE+s`aItw#RUyQI(k0r-_IstTAU3hRv0d{O8%N^qjtY!>B( zp@q&x7I3d*7A)!KBxA22&Xnir!IAbamYEF;_}{$+Dd>_vvI)%BaRj zd;4%yS0C7zeo1}^d`lKAdC7Qx#zdX5TSNCt^tzWWk`v%AdCz~JKhlv69k>ydeY+s$ z@egSz1Cn+M&}e%e>KRf%vRfT>F)8kI_#)u|K7f=U<$$6i(xk`G0a{^_rn9BZjfZsR zz4)YITRTr@7aVwOtB13XOa}mL3&`(#!ChAdCW9k0@1Bj0Z1lf?;3+#Ur*XLp1HF$IGVpgX!?{~3hfpur|&OJ_kB{+8(>)LPD>DVP3ahB`+kD)PR zJ}5`(GlLnv9!e&YX{1Wa@1PxY=vXr8MZGkAv(pKC(XXI`y+qblR+hmclhNRmZw9?i z<=0>|$q%R*uzp*AiemnX+A%^+C745YOnf3Rye$y*hiw6iAALq~Bn4R_p@0QDC^~B6 z(TFXEflxg(U022U2?%LzD~ET`)PQzcIp$jN#_ijTd}QXfi|5?hU3RNDReGs-W39%_ z>5N?)-%j{$ol|=2tew3rCp;BXnitj1(r6k(9W@iGYCO`Ef|BOi&hiO7+vJ~E(G)5X z>Ex4Lg@>=4a?a#xJ9BCf3{j`RQxR|ofZ~pO0T}ukel^4wH=Uinqols1z`#NI$AD%H zW|zMTeB+Dw96AmF`86~>Xaq-bm4b^wuqD)ZNo?eIuu9Be-jvKxb^+Wh2gkVTOWmfREs<6p@(we=^m8 zsqmQempb|9I-@}^r|?Q#iukf%x0jCe(_phfi%HWA;$JU-ars)#q!+ZdZ{CszrdR)~ zdb<4K!>_Q8W5G+u?iE`;K9?lTOBOM{mv=0Zyt}^4zUs=Gaev)+L zB-xQk=L9LTbBZE6=(lIATIWH(|MLtNc5A@? z5p^Ec8o74zW~;Jgtfl~4&fEZ`&$F+qeZC!g1P6(cpIGis-{*r?4DB5bh2x4G8V_Jz zLN)3Me*hT30Lcj0?E>?WuoD+G)wOnZ)J{&{d74Up?yB$JKB=|JDTYnvU})YNGqlaF z==;IJb9deAk<0G~kk^Qx#q1$aOy!qYT=4JK+-Jc#O>q2yHJh8xu%E495x; zL|>Z~lY&7WFE3Fcmpd4AyF&dTmrQKD!0QSz{c#grWwDsT+Q!6XC0&+@w=bNrE8q&1 z6gYcpI((u_tL62DR>@V>S?x1vfh38vpkaV*<`!bLLHC62Yyb!PUC>tH?P{rS06jp$ zzi9|=n$!i0-L7%~f-ZPTK@h?%iG@C~Ian61XtqkW;@Z+?k2BO&;pd!IVT-!vkH-B3 zi7|7lIE>ksH&TNS+HFJ|h7RlmL*R@t`7cyxjMXN=?a@SI4mI+}TTj;z>*HYaO!;q& zMxaH}3bZC)b!U}JvKH!jt=1*_I%;~I1tlR@VAqU=w@GAhvNl(Q%Yx0KZ((8!guw!Mi7N;|xyxM)yC!W4 zHlT*<@?sSF%vy$)*pbSq7StN6sf($rs5_}gsb3IY6YLp}SIHt6S}lkKM)ZG_MSrRh zFQP8rTUgac2xYu`^LYt6sS1AS zCH)ME_k1`&z%XqQOms>-wvf1_EZkur4vSijfLe}G3wSpbSRy%0p4dVj7_I7W{I0HWjX@fgjS7fsmt##Wj^E){pUy?{bo1~jqeueyZ z`Lio3Cg`kI-GuV}FtooMrPIctuN`xPS5<`MT1|LQ4?%<$pS%sTepn9;&mIjVl44-Bns< zds15@*u~P2yXlf9cPLcU&^00A0tTC&uD?AJxxFq;|731O6KgWDO%)4|Ju1Vj_1;^;2^ebV9-R=m3 zIcJ?U)VM)@Y5i*8UA)-i7HP0pW2hP*1IM(MSZ(>@#g*e@7A=^w1PyCdkGaF`9pS>F z@T93oQGx0H1q?V!@$QB~D(c=_`5ufXT>56Wz`7n~zsSmO+~EPtWX zRUdmVy?%T=?w)Im=t?FnTsJEii3DdILz}4Et)+kQ)}%>qO-?WTbX!w5XR~qLO`AT) zY2Iq(QJN9t&GJ8hY1)Bx^W<+QKRg><9qN9#8{cG(Y>c-Coe^+AzRm~jY`uP>(gI? zZoN)t|Dwz(9}^)c2>-)QuMy>GResD{fL@`=R0&p_Z9`{)^etA4sS=*&rLU>XjM2*2 zBxU(U@OlrnAlPWmfxWQefE)pKK=xu`fW&aeDC5f>Tk+GPhS%(VUaQrZpDC8;IB$8@ zBgt!!x^4A7E%F+zJOpmh{C?OXH4Q%S>kXFQ0{Mr6U@W0$8v^MtlzjoDV1xGo{7>^0 zqcLkJ9Zxa;MyXD+hA-7J#Q=leD{S^f08?|CfPnM_U#O%SDl-Y{*)1SM_~u)=NDTf8 zd?Xh>^8je*>;zuH=k$66P70$^0wD1vf*^RjP9GW}2IVW>klz?zQ&JL~;2fPp@Pa{b z^T{+=r)3$M=5%I;Yn1#SF;BXjouuz!v7CAnHK>;x?@TDeRxiKa%Zig=|OqxZ`@T006KsJsT{LMft~U z6__JC>l7)U2!vf_^WZilWz^0DjSle^NVcG0`i z7x%zRPTqCo$QZsCv#51BFP97$Z3gGI#2-R(5tfcW$k&Y#4@G?$AJ8|d$_bN~Mm^>tw{GPWReo8)X^!-VC*mrFr zI3FYZWg^+g*G#kup*m8&G;r%hk6d)oBk&Qj$?zB{U*OOK_?Y@H|2YuNUYG}5^05&u zh{S!vT(ziQ%jdz^aycqTm-j*)7#xX|a7ccA06vzU(GP0IicjulFJbRN`UH-yY{z{8 z*tsx{Gm4>iSB1%P(Mv>cQ$p{#ghjmpJ5D2MQ6ljWNQR`*{M81KxZ?qw#1Y(uAUe$8 zGng|YUczGE54u{jJsK`543%`oHwrJVY@1Fq*DqbN^CRojiW>O?`Lpt>gy>lsZ~o~0 zw&>CY8k4c2WWgIRtgD(bCt)q{a^fFhe89$;pK#4*E6ROC@~z(-GTDqQ548cCOG_8| z>q|VlkAq!c+-=Qf0Pkz-@>=H1v51By%Z4o#g%?g*lGJE!hCAH>t){w$*ZEzA0WDut zsL=$5MAw@3PV4w;+M==gqk*31&DtAo;QaOU)A!3xPhFv9PsqK=P&Ce6r>%Wy*F#fX zl^%~tUnK??R&`lh2@b6Ct~6w{Z$vsdVYdzuD&kn2gtL=SeF?V@9y77>fksuSE*1)- zkH!QDhaqm*80J%8IbLaN4~>p9SXU8835MNsO3Fcbc-}P4qJ4cdj8{&+_DO4dxZ<`4 zD?;ryW0l|Y;#GoYqfHGfmL$yNU>n~ zf;7#C3z)t>&Twn}YAKo4q1 z%tL_cz%gK`S^d}^h=-Lb8cAYN)Sn2#pwH&BSUso(=|{R9k1XyzwrQsCfvHpy zGye@{$d4Mm?c-;@@mZi1!1|>ZT+j%;@46N)+qkfj<>f^~>64zis0YA&JHNsp8%9%G z6^vSZQS8ux20k7Mg!oylV3aL%Q)@+2NnL>sfK$|Q4PXnRYdZFpFT8Elq|3qG`RzCT zDLZhKj&p!(egP)yDi-uED7a5v-mtB20tDlk>fyFf`cwj@QQa|Wk9};F9)4vu%6IFG zf=<4}sL@(gyg;P1ndPKT2a;wvarc>G+beh~VgMy#Iz;`I%89aqcFrrX!VE8ju3Zw># zA2Oi1lzLCaEQPnau&^HR(=e(^ z+gN5N8lS=u3NqZP3elazYG*fx=UtMlS+Zb4%k0^an{T{+^X8*d*Z2A>SFWA1V|iWO ztiXf=@`pv9wpc9KPEViq2%ymnGhz4c=e=H^AMLRJ{OHg@kH_zyP?BhmEZ=<5i_FfJ z>C@X{qMp0)oDJh>GtC&X{`>@sT#*haUSPB0t zeJ+fqcMN^L8{SBtH}o;Q1G{xAxU=jYGT#>>NpuF%fhejrM&>6*-LlForgUxv%8~?B zwqSLaEG~qJjSvS~V()tF$y$uv7;vCCPreNG!>F}`54;YC*A9+*?RKwYXt1ogX+d){ zGb>R!y?H_Nf#&kEW-zTP0e`$9IkYNy&J^BYG?W zDsO5+^C*_Pz9pO+Cdv;qNEHZz2Z0f{=dcESr;P*gENxUn`)gEYzp&14Z zSmQcXDhvO#Dl7$d^9B)U z#}&}PU+6A^Kx^T39HZwg09c(CD*$$_CJco~5-0Yp1rtRS-kd zg1Ml~67u`pb|Zuwr{|4y;jEb5R%WMxr^qNeW@#YcG&U~-IfjL>q>3$NtPg0-bg@TM zCRBwPBL`@!uIhrzDja$PM9<`Gv;#s5w3|vm`^@xRw4T#KT1V4*8r%c57LL`j9HfOZ zQLBGkXP`NTp#??*W2})jX|*g3fetc^M$iDW0OM9WI$?pu?bLIcYHKTZ3smjs-vCpgN>Y0;{? zaC}Flo-2Zs>Jxcg!!kMXdnsA<=A= zboFPIHnns{$LqshpN|%RU~-w=%o-p8&VY7JwBE?cbAZOevKl>VUmdN%FC5CZicV93 z+gzmc^X2UL^Q_jkySJ4>rgCRhxVcy~fYv#l61#1JUqgEUsI3F^!~)60GYQsHYSYr1 zJtm|;@(mLKXec&S6hm6C1x1qG1IkJmlVETF!NqDECOv=_V9;8$0*6XMbH$9rAPJOV zOb!4HX33;ww2);Pj^=^T>@w(Ei?uXg&^ErKh-$YhZMu-{0x8vb51u#yJgky{SX6Xt@Fn=M`wKqHaRi z^3%F$ey!7NFT!-*YhxYOYwI?>c-F3R8z^#@9qCxHWApl^Hy74SDTUAwM?7x5NsW)kvY0@5ksMt`)l#k00_;^34AB8>^v4`y zbSTXD@GR|6=z!5!f(8mN8{+XG2mE}D#q&GbVWdzPUqwcfR#59<9I;^$1Z68BG{8MZf>nuNIEmc*D>?(4-D$J@ZZ1 ztV_2}+Bv1!^bvgsXszwjcTXz7s}LnKCU-PP%RRcCBlNHmd?ja_vGAH1`or-0n$~5! zaM6d07vHwLLofpNH}Bjx;h#5s(Omq+$J75pp9{cs_ewu{+chcHY?J+eeH0i95)GY& z(K6PFx)+VK0~WqC79OM8ey!AUtbbI|)c|uRM`}H^;(LXeh#`)LEe3>J9>>kn89PcV zREW1Y!ZfR(&ta)3h6x!(j6KKP7;aoNqo&tWSSFedmUonvRJf`eHa*nSk=)oGnzo?% z&{=kG_k_sonzGuW+Q@%D*!hEv6TyZLkL>N8(Rr;r_}oTwx4HvZyaV2=og1rg>YY4q zHoGh{oIbxZQ5j!cRou3*vt>zhP$;nr*3xjqTUqICu3UO)aPszpM?UN}Z+s50*LKe6 z-K*@#gLsGN=M_kIc!k8Wv{4--;wobgi4%PCT0&DC%CmCD;+zhK4gR?~c$EF#r49D5swLbYDMy*C(Ztpb2 zyXMdrtVr1JWLjr1Gk@Xm`>lhIp$GK1Ohu->EjDy*Sy9mad8fQv{*}dUtFT*jTG?H| zYwca^-uQ~XzM)SopaEP;jaYY3G?h`FnrFZ`#dc{TGlK!uVw>IT54lbflMIV~Qw*{9 z4pD@d91=?|vFFl4E>kEISBCws1_=M7VucFR0h?qeeoVv2S?c0aG(f9tZ6x*^$?}<) zAC{^wjTHU4@@s9#m6}-9Uo|o13TeNt{Bu#HwB8J;&UGNUt`ksZx#!aVxb)Kh00X7< z(mnWsOO>)RxU50qiK_~` zfzxc2Hp}9(QT5&RiHS=ml0TH*)D4r}o8$pf8ag2>Jb67sn@CCCl*i*OeNZMCf1tm6 z(2Ah)QMOA2w@u<5NcaN5DhCh z&Mh1yG1e?`3l4^`3n!K{<3Zvh%*F}XJi+i`i6gGV&Zd^!_Rgp8+_ps7fQ^hA2(a7=X5$VsO@1*7Q;8+7|rM`s8!Ay49Z#gb#&Hj{N@{js{8$vy_gbF52b>5 zT*Jc}M@GO%ZAp-0)S*s{l@Li8LwsPzVIqk$pU3K-lwW?l_t&S^9{p_ZK{Q{6mdlq7 z+>R+`x4r{|Ty1?8(%9&GL`m-TT?mwYz@#%D;BL4hnC- z1vp;a&B1Zwif6vD^@fv&B4V*ns$iRODb=Q3u6i&MbG~nsAOEP>mP8(!23(u}1*0=3 z$r%pwVEs^m|D%Qo(g(4^f*Ox0%oRI1yNqT`bkMp`PIGj5i zHVSXp%wp8~=PmuXVj<;1x~Aa&WZ&!P|f)F}$^yO}A}WyEI?uczUqORQNyr0TI; z2+fT&8ucAkLV?J(mJPP0zAWrfvr;xZ(ims z&;`!vy}FsB8B-Y$4R)3_Ypiu9b5X3kw9p7SQLAI2z;gx7M$v4K{>PlC)h+N43G|#r z(1`xB)?jlrgG6%3S#`i0uI1=&5+8e`k+KGN84_vXrDw6Gkf(rQtpS9(o9;I1~?Sx!Q-CPV9OwHpeHnitg+vOrVP*xOk;(P;2%p*dJXR7!dM_Fkacr%KcCk9>!A@(~D33l{qFO=^ zPys_@NV`;2${;yL4xtlRWydNyya$_pXWHyy$Lwtytx+iAEgr%1MCG40ZkSzNeWGvU z3Zx_U%cli>FPfWH`aZaaaDPs7^`V7@;|;}yyZ$-kpKKCb zKK~@I`!=JSW%b5lfz>Zx+f(9yX2r6l?xH7}dv2I4I6gb1Y_93J_R`+g_8m{1vlTGO z2Y)avah+g5y#O|~v~4vCdeosB*TWUdch#e(qcXJh7}3+6<5=UYp7d6?ORROzdAws% zROE{5t2x*7eA!|PrKKdy7f<+Yk*4jzYo3tDq|7D2%%g$QVrN9=+@mi%fAqjF{efS~ zx20cw;(k!VM4xyy{TL{@-@knM!fy^9{Dy6j-9z%(tKJ39XThZ3q|4;LzPkz>83KRt z{6>COS?fcx!%ifpZNO_UG!|7kiYF)^Xe<^WHXi`=am8?&#c8$}#G+L!()$?!X*g(j z!fPV}{*XDGWOsTOE$>~md{(pBvROXzrsQ%-$3XeolBvrVtz0nIx8RUA%ot z$BH=%5|!NKi&rjaiTLa+W6-##)Yl22NawlDB`jwZH9S&}gzDI$6_<3taLdg3^SYWW z7Dp}ToZh`-+cn@P-P>BcwBRYw={}Ob1+Gv5c;~nvYK#@r_ROue24;3uT-pz4NLz~P zr)`~FXpzP>wYAll%sV?d>!fL$HecOQ(Aj;~qPde}CKI#N#XH)fjm6M0^Wr%z9ua*$ z^z~Qpj;5**tU+Rn4aqKlV=3ZEZYA+mM8X1!&pxpEEch>I%P=xAf7?2{K^{tfF?%cX zo58Zo-`3gm%-LIkd*b{Z^1py_$NY(4@+s;Rn2LU`YHy#nV@IBxi4n?b)cBw=X-w^> z3GQN&Dv@c1WK$tBeek;iz2G%t@R=U{u7Iy$GO=3L;cTq=WUS(8%ZfQmaRGBwteDBP z|2qpipcWCdVP;f?kySqRouwTmzbk8|xnho#-$z*+sF2HQQNqqFRvbh79RX@7>|13} z!^RAup%=eLJQ$C@{o-64zIYnO0M(vb_FcRIYIHsDekXl^>f^o)$>cUFh9g0VIEJOM zxC76vR0Ip94l)|i3XoWwkc(nVgXFXMaI}|1pIX}}zxnL#^4GVW_>pDjA;3Sg=bi1) z-FS*JnoBKT$feF8-2*kkg4o36y&XYtzr5ZIepPDu2rPT`u|M1fw6{M2%33dt{qeGA zH|Cme$)G41-hGa{u1nugYic%i^xW~M_fHOcpL>7H zY2<%NJq_P+5Z|Rao!031B(oI-bP((?xg7Eib#ojr7YFw-a<9LP%<6pO8eTynea1~H! zjj@kC>McGZ!4Owez{k<#=D?A@K92Vz@e~N49MF+kIv`<)Uf^LOtS=N_hot2e47n?6B961WqG6M}P#$nCuIyP>bjKY< z%X+F7xqz1us%tw-z)M5gZJ3D#B4VQL{7}iJ63_S> z#>>A6m5p~gu~#T~6AXYiv4<#Q^cC2;6YBSYu|(z&|785JVhvHTA|a(Rm&_0}v;jJo z46AOeNW;t}Rd_qp5K=q_f;7v1(K>h8L-qW;rs^4{xcqWlGq1V2%M`z*$ksADUUB>S z+g$}(Kz=?aJ+U^!~?f*yHcfdzgW&gi>-+S|>w>Q0J`lKf_nVIxXfRKa`dT60{2_PL| zXkr5urKl)T5gT?aD7snuT2L3a;Ln1)xVyHs7a()_-}~N72+00)KmY$fFz?;^%6+$- zbI&>769Z*&=?HR_*glK7a&$buXKoKElE}L~AsJqgKU5P(FP2Kt>A9d{{)Kxr*@7n3 z1v(-?mv&@d2GXwVL+Kuy>A-2c3`wM#O$4gJKqV6TgxlkNDK@RXep=ykg~}XxX_&4J zmnO3Ndc&nvfx^c_v_tLSEk=XU!s8GP6uz4CbxqEk0Ec`A(>nj4L0PM^q(LcaA10Id1)q5Mpm{izktGVY2Q2Q*gQ*eJRBACr@puIbLIEL@7DPWm zjku>lcqhI;$s6>={lta0XyS>feU>+wg*6a=TgdV8SP7NI;H4T8kewi2ZsJsyKaS%; z;sXT7P3s%Lq8I`ZsuTP?D{`?0p>G*Nj%v{AB_o@h2R&;uI_84kDJ2!8iU{(6(UE2|vUSj0y=3{EPz<3MEAZkh4?@ z-}u~5geN5)?UET^(Mg$TyH4l@-XwIC1kaixiL}410I|9?8aO_!p4Hbli-VRA!v8_#;~WRI1yY20!=v6?X8MN?3Zmg^1^!cmM}mWf2H#pUM_M2ST>zjS z{Qe8iCfOTAofg0o0R{?YAoqc#xc_go)X4~&` z0@ru0ER4rW%N@18Hu(Ae>YSeNB8%V0-zi?j;{K{A69Jq2>txg#-bq;I|8C!nK(}n zyH_vOCP*VpL^&`hDAAMswTM3r*c@Tg6sIXcfNg>y-b_4v3)rTZo}wjO+R(#{4@@-T zkCk9<&_7_7z_Wvi8LZV-qkmUxwGzFgXw}MMi5?v*X^zF3!S7}-%aE$MaE}!Oy$jsTzR>bSvL0Td++;NVs(S)dH55%@kQ}9 zC6b&R$u4(6flxDj9-LF@ZezX+W#!?k=jO0_^u44tt1`zGQCZEaA9!H3)uJi}Coj&I zxbW;l5SbHc@Ueci6yXI$l@ljmV`)W|D!_$|qywF&CONJ1(w<8lLHq8d9V3?74ZIy( zxr>}SD=)ocDHw4f|8m$~J-mC-aP*16Za1u4-LYhGJHU&ngO7i-dY!@U;Mdq3YucAA z0S{cr)sQ*rPA~X_C50G888F~QV%`c z_X4;U3_0`YBYm4*z$tX;a-trS+WXMYXC4J|bUL@9A{Q>W|J&~mUQvEK`ti{-ryd5% zs&e#gPDMq|Kz@bbeNX}7W?XcSdJ+1V?M>C9tVx?-FE}x2Q|-X-+XGI(-c6HGR;qRr z<2+wsPl|swDaHH)_h=cuk4~_54+yw9WO?vdflmkUNCHFa?10A9=U@nWiX_|&4LD~oIt&J{VgAvV4G-hI#pqgGW-vSqTyMOA{?^xV zXUBdqu|GIqe8~iC)FR?rh!WUtV)HQ|q)h{PbGihv?SMkuCq{n3h?`nsxpqfR4E>M} zz;zE_X5h_o2?ek;|GJo<5eSx{NlTr$pJ9?9>3G4va`nAm>yuP(DYul~0kR zHfJB@;anW`_dSJ!;OFz(S59T0m2q$4`E(<7gnErSO1)40o%$#BDfK1w72!c$G*Qr3 zL#}}J5lvDT=LRMm4T=UNC5dW?rw78K3Ys^JNNkfO5zqSqM{Ukf*ie#2=^%oV5Sc&( z8#!}AO`8)1T&Mu%5Z5c1EOo&eU^HXmPFf@CED?oO%%#!fg7}F9$}VB%fCx+-s)kWK zG)X2O#i=o)2Gl_2&$M4#E4vOtwpB>|Bxz-yq#st5{-?!Q>L@(G*198G`hylksi z?Nj7RIhZ}X?~uAQPefLxcyR$w0~ljS=AUV)}eG5SO1d|eseqLIbM-1TxU zEtAXmIH%|vWy^KP3rg911?^WpQiR^t08XQjav&F~IC!Z+2b8I`BbAb30E8=xJgy#( zv42x$Op{HbHsNJ0nBEN``ms8qxjEnENpAGphYlatomjdb!WL&kQ`xTNtFvrvb%PDQ z!Yqd~w)SoGIeHuY<4?&@MaQs?LSEhMt8)4Cq#Mfe4(1yDqZ>vhLJ?kV@)lzb!ywOc z&@|(*bIQ$yYK>f(XE8`Q15`0`MnXf4TBDONN>FIZ&v%R*1;XX!VE}HK*mRAlM^*GZN`LxS7LC}Tp=s~i2@Nv2#zU{1ib`}XIQdz67W%>n10p53?ab~WbNn>tsHZds}vbw53O<>=-m>M_qWDs~HH zTzh)(KWA;Bv1KNl)nY4XP~wc{IYP$mdz=kVjZrLZ8@&>|)w9P{TVQPJTs3+~w|2~f zb;>=8z?@)!6oh(m$L6`@j`*Le;qX`uey~;3nhk|#c8*>(d9Wj|Q7AGeeM4961EUp7 z8FTBUiqTItq@OpP)sSx+HfxpWw?o9t7(|VuCQwtT+0;DhO6pFspA#$;T-Aj{WzJAq zLopE~)1ky5Dstj~g3&S2y~JaI$b|$QPf=x)78Epnq*OwXh9x4bIRpYa7MSS}o_5WE z)!|P_ZXqDTi2EW!U1GY82N%!@qU=yfNGE8wBy?;f4`&*6a62#?40*X+Bh%0@!os*| zNsDoVTGt4rv!o#xgn+e~EqXZvBmqTv;S4CRSIDdk18J*+wwBZ?FJl?iTQsK(x?DE1 zngO)OP~_)z@VT0+&-@IZNHsIZXFWdSue0)xp#oTiPTv*}Z`@Jt88!Ty8mU~$I6TbI z2L?~MZnVZ7kb|9lr`4$fPQ?<1Xbon63m|56D;NWKjpn2>gOiQH*=@$F~Vxs zSpv|}e>?!{|1Q6)CtR9JGRevH=e#T5>0Lf3Ma|naxn4qrOT+jvy259Y{ndc_VnKA# z)c>Xc*bb=Da1Wx0H*catFQL-1n;L33o&y$9>je*j4^h9P-l9Ijl-OCI0d7zTYA&+l z*Y6}zYof%~zv&oRLGG+Fo_tUy{=zWL7Ioxp)bf0vzI~=G-RIqy= zz2En$pjwwiNkO%)6!=L2$H|kV!Y86`9h>&OO!iZpg4AdPk$;JN52hUnUjjs5F(AE! zvJpm4EGqEq=kwwW;xr~Opfte-2?)MnL~;t#XUgEXs+P5t_}IFp65ThdwPjP2Z~#{= z2l}VHHTAiTU)9v7nxE{x`)x3!YFw~#O)ELB1v6SlHEn7k2PRxOzisK>q2zc=>R9{o zMSGjuS1h`<@CEeg(t;|dqI3L?F~=TUeynYNW%Dgd@p0(hrE^xaH}74vyuJC>Ma2H< zECq=#aHEL1$eYr}?&8DaXNSE@rsPAvt=Hy<`BRpR-gV!u(e&5XzZB?uUC;!J1zx&7 z`Q5Fzes>O2Bx85v##B7ev7vmRA|FviQcYup2%D&wYDvOmDp?DkPBo>P*wcP@s@75O zNY%Ri1wq(r$}_>glfT!XaQQlzB?e2 zCx#EB!DujhD(FGA)>+X^!jqaqyC((UQoWj`+)}@NNvl6 zR^A2V`@5fg_SsYw>hf1>PpH)=ApRp~ZM7ft1Z%ZVgX{3IS1#|>)&^1c)7n~5rh=pt z3-No)aJvVo0;-Pe)*3xDK{gH2n8J%fj~6pPl-MIVkHHl1L}DdAPs~Gjb)P3dJdfcV zp~KQX4_Ar+INR6REdhJ<2WpniW!WVH;E z8#X_3aO2kfzw?H{C96y8fxI=tYjGKz`w&5A?e|(B?7^Bd`ez|RnS%icMF|7t1Hv3q zh{u(nK0|HEVc<@4&PhSvv_e2(q7t8I@wxMP`T1-iB@%(3>|cz_$3Y+ zZkRIXW;qzY>)5efH~tZREaQh&qrZqB=%?+kZre6v<~BOJXYrEZ?TgW?2bPu>84UOu zl`AbC7A_P&=1qepuDoV;-?5#$j=ggudJY6ufOl~^>Y1@^+pF8R5w!8MV> zh*J`DAVCz@*f^%@O?0CMqKSCyD>#kJ3)}Jz-B2^N$W1fP=^!Wd4ZlW`JfbY-^@DGe z{^J;T-`~nop~Cmj3;f51_OPYcS7a%IyWiC-OscTI%G0Fq{u7j~-TpqBwAr76%EMPBf_D|%LupDifIOO`dql`u{(^jd|*IYIx^%=U!>7yBr-47Ol zc@Jn!Ci>ADbj>qLFvIO&puv=9jiZ;)&On>b;5C`#dU^<0@WPiP(ba}A<8PkSpi%+a zuF+J9eWX?@_Ia|e+i(sog7@IoB19zDpEA&J)RQqF%{UUl?MJ$YnW!*;6O%Vjp1gS@ z{quNek)I`m?`CX zY04@_DTGP(Byqi&6pxsmOXAXZPF}x$GMcnWw5yep={8DLU_QQe0I&AHJg|tf>`8mX zGV>X`S#a*%(a_T{GX}gj;}Ozea?>R861C*4G@- zhW-T8O%{g`xo3(k--|pwtyrawaCHlinyNY~P&b4|2Fu!9_TYU?{>(HYQztLlM zXS)^7Ef4Mk`Lm6@GxyC4;pdyO_@!Q1uE8m_&sNyK2phNMsG?S%)U#IQ1G+-<&|!sK zz~#=71{$lB*%K}h1_9BRE&e7vp@xZHHjd^nj~&9H1fTFQ6ne)3%!tj~?n1{vp#^;k z&fqY}XWmIY?M72w=qnc}go9mRp9|<*cJsh1dyk{KIEaWj&(GgPXKMwPM)$JG*_y&p8DY%xvJzCY}QIyR;rbx zo&}!+Ij4|uDzG5AP9|HIlr_Eex=jAsTQWQ{KmXxNh2qN}lx*MkD%JOWD)(nUYGvGy zpGjoM1Q(*sKXMBFk6^7{F&yQ6FIDj0gLipF7Lt5xG=2+C%T%hA4t|Eu zAI5e8fs~@M{0ThOkRAFeVEW%SNqDs_(u55s)(=!sOsnQjFo#fc;#avQa*2G9EjZ;<2+8&q=@BuQPKx z5AmlgC|eT|E)b+;WD{4y8O1$w4hnwzh&?+X)*(i+2TN=YDquvgzsIkQ516u010XTu zNsgGj$MC<9ful*$5V?wk4f@EKEMbp0!ubw!ugd~p9w<25P^VC9T#@@TaTmLwYe7L`ijHUhI!FC)hA$^^2PjE)Wk8#F5X zI08b260F_26PnnTsJ+w$S6D7>DN-}cW?_ph1H&A4G@>hHXet!F4=&~}=FBWy0N z*o2uY0D@tUr2?Jilz@@j!n5;b8VE;sU$L&^mPlA*ER;Z+b*&k+AK5LJhsV*Yb2_;I z9cCDS>zZ(Tq~^x$m?&;oIA&3)!r}mcI9h02<@gk44GmIt~kvezZgb zd?f|MH5&m|C$yapw>TY*{c20kZQ8#t$bU5|I2n5 z`P}r}VY68|i(i_7EJx380lvoG z7aGu~&9fOLje8d(QOs*WA2vSw{BLN6&*sg$o#Um9gyCe&?epdV9k9)xzmMY?8ed1b z54XwJ=#z|&%)s|A6?B1rYYSkGQuNb}DGh?`2z)v+atYYtufKB^7(D69mYjy+%{4_G z=(>r3U9qynU0Ut_Z7+DY#+>XJvC_`ZPyGp4fKu=281L3x?45F`$Zwo^be>qk3>Z;e z%J8eNz$E*qUb6Yo-qVd~(%(FGHR;K{X2~>oK2^jrpAE zv+>v8!AHQwbwIEX7PO$_d@M?wB*HWq4U&S%*M_TPQpf#DaA)DZzv0vwPz_%)+S_Eyj-?UB` zGhQS69XBN61n5y45|PzRS^;$>6d_(g3jj$m2r0kbIWdt#d`BMGL>Plj2ejajo8PcO z8#fqP-HaJJ)~J8hZWudO9}hylq=bjO;kV3A1yWP$1aT#Kx3F(~wr0{Fg%}A( zdI4z`wG90PWU}A1j?u|XU4V}ezke@ze<1G!a@j?`e}WoD@RNSin^hCrQ9!iciG`_P zzTz=)wBWZ05LI_#zKE$@OepYTS&|w0^^e~rwJD+sTKdEjQW^(r(!Z(k%c|9XyD%Ls zS83o?(4?wKpMO(};41|2mA?B9Um=LE1oCqyrUYv^s@O1^zH4o{32a!$+aH?4qWoq zduTWM>gBF`zZ?R>hkJiG*1K;#V3eV(*(1hwPM`4fU(zytPMp^ylpJ$Ydd!(x2{r%^ zbOAOIl7T>G!x{5#IyQi56rCaMRE)4BA`AUjH~~G19{>IC=_n3;haPPOTD*9DeKlxH z-Nn55d-OO^rS77m-o7`DdB(msysRC zbP4)u1AzWRUH}zq*IrX7R1-<5M=*>1mFQ()_G-vQy@r$r4alafZ_DNya&gaR6 zf`p?Vz=P=B>v1L!m}jD`kiiRgvC;G{9+%Mp^La(DTGB;VesMRWq0bBkkiGAVOC~D! zFPqXj41^v#04#Tc({J3f_R87X8f8OkqO~=aH=?d?=!nI2tM0yM&9&1e)wh(iH<#rO zud5&0v8ZPCeXy_KmDT${1@eF1b;;B5Q0~$@%5Oe$JNn{Ii3NSVdi!+4P<35HJl2@g z*wN9LbM1;%+ovw5t&f%s5)-zaZ+{?SZxXAT1mQo66Ce>RNrWU?DhnUI zAx@ta7ktaIW;_9NCIfu!m#Y7;7j3@(`HuTKoFgOy@x^>#j@0j>6WU8IGv@p9InlG8$3E~Z0(A*-Lpql>2xaE>8+2n zH_w{0aWG1u8UMKPXV4+iJwjhoVm>!awNsO*1=K3)O6n%!ZzJd@o)hqY%+zuC7}O@r z5{{@{6Dvk87EgrY33Ht0h#{ARsP33?7fb|0L~EOLOOlI^5qtrB89Y&@i-qETN{f%8 z?j^2}AXS7~q$^MZjA0njIOaSxczWL3=(c&~&b+!C-`CZp{x;HNFPk>4%*A*3SZVn@ zblcmdb-MR&tjk;dsapLncf;Yb&Z3fuB}JWOha24gQma4p)E}-GSCqFPuV`Gw;d+!) zS4xTpeP#1N7o(k4W;c!W`#N}6nW@YdBsVFodk1s@)z*{fMRWkYcyjC3lb{lGg36PR zU1WgFs+YWV&|4fSyC-jq66ze4C7wgz=0l#+Qpb$$h3H@2gKtUdfpSdVJ!KI%p*?3z zPW!~xI~w%g$mQSY8}0x{K)AnXohT$tYPq9P|FvBHwZ8F=78tCDiZMC&mgbat4!)JT zAI&=CDXDbKUf4auQCjK=dT_?QIb#$M-x{x-1&uuKcKakd(*p1gSF_@q9MhRreZi_ph)aweN8Rc zIeJuQG;o>IxnxXaj)vAX#w>JTR(^v|d!(UO&AKglQq3j9Ee;u)YEOVo1!i**S{ae8 zGIo3nmvtB{?!sj>fX4&zil7C)=TF1~{#bnE1sJaqsu9maM+6LPt+0o=fLcMkdicD= zzXDBGBoZJaL-3?7AhWPWt;Z{)A6bUpwwBFrzN?bS9=*`PSneHh_2I(4=kmwH zsgu2)38`DgKk{NIT-i0Q0!(3`IC2e22S2-b7G}cyxrm>U`g`WoIeo75t5y0#=X+ z4#q(u0VCU9K@qu;n4}O3aRD1ffSn}TyCSd<*<=>LkBMRhCPL`uCBrMD)v=%Qf!)aB zVWKt$n;OGagSCr$z`ysR?{2GYFq&D`Z;X~reKgt9l6>@ed@7Nvg4y!gNqhgg{5GIs z3_Xi|4a3nkWHEW5-LUSv-#xyuvU8X(r+sk&9@yXSRkHznXGWE-j!#pU%rS%wYJSc3 z6@T43aW7s6_33qxAT_5IWfKHigjjA%+(c`gjALL-Q&j|o(#H{aO|yvBly)g2DB9xQ zCOVcO`{@Eu3=vg`jTF-YwbY~nI`!epu0FhFOL0eK#OpRFK|)V6tz$!enNep{XaOd& zDuxW5|nhM~>yJ>Fv| z*P5!8SA*Qj`h+oF-qtj|y__A{pe|7YmIX`xupoDd#*k%nL%`fT$Pg&VVJwoVdK1q= z27vr9t+B-e;gA!W0ECcMJX=j0vKtr~h!+4pLw8kUI`eq}C)|T+tF>^Y)+pr{*O zJQ?61L;8a-I73{*Pf$e&vK-M~F^iycT7gnE!Ny2-Zhd`jHf@cD?fLokaP*5}F$Eqh z36Ydg3Hs3;x)+_i)9mxuimL4$veXdt;R~SkrH4V;F}Uc;Wr{0#1IPW0 zydx3~hoWeTBQM|X$j<{`U6^nmb2B=%x2>6`<%|xlfA4kRz85&|-27>(X4#*{KE5!p z?OWjbcH6e^MEnxTS==4ZV`22CoP|Si+|%r&h`yM#s$z=P`gujIVF{9qQ~bPxs2s;U%19f5Mz- z)_HdYnY*U%33$NDz`*;azCnN1JJmAYgu(%u_DPaH^!f*Y9-<#O}NGCH3wut&Th zi$u;iguFbP%MK-S0l&aUkUm8X@H;{@h#RQE znA$OVVu4?13VUL_(HA3U`og>m_sVcN;-(UGp&lr>*Gl8M_4M_eI3b}@StrgV(#dmS zSbO3`Uk}+K9RMO11UL?$cnDcTFH87SgCd#+dzUhfJ1@Rt&+mPVw;h7w-qXE)6 zvv4||omk8Xv2mt%%QMfQAD@9}&%|{&xMkf$Fb5L2Hxfj9AOv$JLW&f5W{c8vXbj03 zbI7C=tKpCZC!RM}15}Kn{GttP9J5TOsJNAkml`hP94{dl#QwsRkEJdfH>&Cz2*0Ts zHSV&@9$p8(sUC>~<3?701J^waE*nTHr5;{azEZ2!t}I{oFfPJrSC(D&@MUEywcNPN z=o16!Ca#}%)ZuSkO|?+ts2P}hpeSM6SJ>ed1QUrkFcX|Tjevk~j**KJT=j?>@WSSC zT5HyXm(GE)xY&1v`7@MOT@j?}BDPD32#scdgA7I11qbrv2CGVuqxWtYWu>1g_`Z?n zYsVAZRP;9j%PPRBK5=_3ALAR($dxMj1er{3lXuGBS6CFCa=FYdn;^^5s|DbbF7<K-!j}4CKp$084w|1zSKMPRxLLb1-CP z0|^P2;E7SNIl=OrDUt~B0XP-7fqNmkmHp)&5VLUStgmY>-}O}teT+VieYI-nBo3Cjq;4%G}^0bPvlf+D(p$Du&<5-GZhJQswu7fnt*?+8K|w8OLiO)Zd2A+!-~ zOd(ygecNL|1*(Da(6;ud?p&Fm9VP9-6a6~y1H6l(B^OKG5wvgEU=ODLiz?tMm3$5a zGvz8>Nz1U-@<5=xby!OY8hft9D11qL;eNSa8W+JJXz!GzalrcLC7vJ}5kX%jK@cTG z%%C6IjqMM?-k>dLLwG_y#aZCL2)wNr#WVRm7Ow9&fjRbVnD97eky2lLhz-r2JYTo;_z96;Tlf$M|wn2O-sAnL|t3fBrn4uh9Snd<}1^KsqJ zz;yvZ_HR9_l>Afh+h?T81+PQ{Q4lWT>(a$y>LxD0d&bQX7p!LSsMm|ucL`b$`=|XS z@PhLN7ci&S0HZDuH_>y~Ke`_O2S2Xs9KU}3_|A17*A72(&&Z1034tw~QUyI59QF>@{g{P2iBwR@(%Enomm}-b2j?>p~b$e z!sueq1fUe42bV+&v;0dA0sHKoff75E)9{HQvt|uRHEZl8q|IjF^>A-mPD}74aL*Fl ziRt(RvB5VcfDU*#B7WuRf{q?CcV?fh!Of(|#TZ=7r$o#!tSWp2blXPuda@ZB^YKbns?YJMo*kSw%50^}xO<}koBF;&HLLR#f#t8aNgb(9wxYZg zT`sj}gVyq}j1IzEXr~6f++YFb0=3HpnlFpU9D$-;lH=>q`>HIdY;umqs8q|FA8Xg}8fj+kZ8je}!+_S{Jt zxlf<^{i`8^yhS60m>?+(gPHf&OL(36gEGOsUzFn{&$E57Q$9?$5}!5r>j_kzPJnrg zo%bU&tguPw(HXe&ARRn0hC)P=pAsxJSPEgH>D&(!dBKvPBzc-ru&-m9uDktIvb`Hn zq|#YT-O-d#kLs7l3%|Zvx>p1eW@^v$dfY+gy)%NYDpQ-pRdXm6_h$ib!Hws(5tuGZ zk6NQ4;l<2K+KMJY^!)@NFaiI{=OxaF1@arOEkZhvDHt41t~ch-7fiNuo5J}%FXg!NTGNPtw*J3{bLG+ zZnyjy$Uqxpo{{fX-C)Sd%gZvXjo`msdX>C&+_+Y`O1}$erE{m}RafWj(ktbgckI|K zSK>sC?ACqzZk3UOPrvcT)1)BLf)ng!gni6`QmGnh7&VfbPR*y*;K6x;PdMtoJQHk4 z5!EgdADA`}>rOjB2YVom3zEZ#UIchuI3e*w4;vV}Xd*qVWljtJk23W$=6EbV3Q4cG zl$;hM=PW+P=83h*fAG3+Laz^uT{JP31m~pp@T{2CE5K5V{06#9NTaFK6e%YmN8%Ch zEX95$A-H;jgnba`@e!Cj0v{k4L6MEg3Lv<@5hf6#WFfkAGWbH638aN4N@O(BF;V)J z-ZU0@^Q=LZNkBGaJ!7=cGN0ZrV}qNv%zmhQR?MORG{X$Psi6JC#aDNB&d|e=K!J{% zob6FYLwKlUJ!rXhumZPj4(&)S~YpNC3?pI@|IgTOR^!;J};%aL=Ij zHG2WrQ538UjcGEOn-^`o6<$-ES6t8(*MQz+o$1F1eebfGo0BaiKMUPSijUA6*e;W2 z$rCFJ{n}>J(4_D{j+D&$fSpyu%{jq_SHZ%<}*f(6);A8OBE z7^9&`G!ZW;1m0X6iADV-{X%_z#O!0lxfsXd>5$j#4S9otGzCwy#gUkx+FEQjnv9%- z_>1>R0#PE#@^Yg0V|>+;Xv7JGlhGU{P)r#%y9VGp2T6uGA@2MN`{rI4lxD2nh00UqpUOeS7$GU<76S0&p7wwf?~!|P9*{bsX& zE76%G<;b2pV4zS5g40J_PHUD%?Y3xKE|1IUaUF0vbvEK?#G!e#P;IuF4N8;8<|T!BDN>wVpsL17T6dGqbgCUp4q}Cg~+)V!_v(n{q%B3=yKIC!oYQ0WxHtTt< z+TidUb-6TlXDH-!sJEDvPA4fQUGH>iN<$%sQ{6^1h9RLyAwx5e#Dpg#Pd$6!0AlVR zjhkvVX_nFRK^3SRIUOBC?@pf%@<9HY`RE1o!aP!9&TL$w?>J5C3@VjDqf((VNXuD3 zT0zC;1ua%RZyB5A76Vqlm7JV_5uO5y?L(Aq$ur=G7>)BR7K3){Fu#8o`876Z4dLpr z!Qz!bMy^p<)E0w>1a)e&&Z4$*rYd`Ow!JE{J?zd3@g|K&nH9qITYQXz!4IfwbF zZXbFP-HQweNj$b--vje@&6~Fi!0QHgjvu`J?Wa~OUAp2au(f?|OLghgIvMb^CVrMC zT3Zv`&xuy}Q`BR7-|kkG%v{nu2|X5!jt8y(3g;Q*dbQSQ&kH2NzHF^ZqBI%odEwfs z?AAbCq^Kd-YM8lWX6i|(36I;c;hLf#e39IAo)nBZaRS{ZEA1?8E<=x9qiriJL62>L z{xizbwzg8{dweA1xW50}K}?aWF(2x{^mq_+qr<5Q)KThhcm`*I4ER9}m_|{2Gz1c4 zGRE^-z#KD|km)xP5KllnvC$B5>dyH>MqkLs`FOm_Ma>CdP&3{jo)AMECiKk-T+Qgy zMUCRc`i;1BcwsaPb3G>e6A`i(m^ea$q*sW{;LxORazRK5@u;*nDbG_@JdYbxm&W z%cgtV#BR7U>Utz$MlZTc-!V6S7LTAi!PrE}F=K`ML8+91x-$1Ym8pD-$*Qljcn8(p zTvU!ew;FA_I)Is0v%abJree&O{PnN9Z@dwGSr31jwQil)TO9G0gg376`-+QwUs-A| zyUb$^)TD}e@`1>mWtQtujE1{DXvgw9T&89%NKVQ%FEH^6&2%E zv!*lBu@=i2b66(xI^+2s<8+{LfqN`C?s3IrK8;DvO#>R>OkIlaT8i%q??vALP3qDy zKe1?IYZcwCO8E}^zi`=|%0!_*(r-l)?1M7T@)IKmMS#D{_D0_X@wO9!65uyq$spF?VB+!0C$w906K~nN=NB=uI{Ym=g6n{Ur7DJ+0L}Jgfs!Ns9sMfl{wE(PO58ST;#f z)Aq(8GY6GBD)o$N5D%W0vaJekULLC(#!5r^phJbD)LF2uwR)dHxJZYR`Q=4ygUChj zdO$AnfvQ;{6s_mssiABRo=KpB5Bs?#=h4;61I1a6K-9A`#|7pq7~{SEh!Edi5#!Mu ziJZSgDyQMpzX4Vv_kBx0{I&ZMSp?GDXB8@9<$!*C<9MiB8fy#eNo@&&kB~;>l->+3ySI*Lhd4Ghg(0S zYeZ2LGh1C7^aZ-=yx`ER!YpMDxKg9aDwNAN?Xs0>3wP~;m*j^B*T$rqclonMMypU> zL483%J^gS|WOCP{n#8=B722}Fxdt=)Gd!P5S~V!(lbvvlnf7T#omFL0+dSP_!BA6q zokeZdx~=-f*@0}}TeQ`(z9Ys}yB}h#Nfw{_^4KvXaum)Eet< zMQI&)k=(fueZIJ+cJq>CWges8 zW0|Znz(in52pU_Q_@}C7h#QH_<`Z7L%tX~*VygPGr3BUPdUq!PlvZ0YI%_r)l>+(C z56kV+Q8@54AL$rZ75eNsX=!_@bnSC7a0kwT2hrYFOIqgb+Bxr`tkD%(?aOLuyci{rJXL)lb-f-WySMLF=gEtWUdIPWDFbT}Z1w?zcbMIlobVM8373zQZs0^fC zGipKq+a)|fI-w`l1HbxWjQA=;Q$NuQa~|I^>88#irZ@AVJK+xpsuop&hEc!zq7SEE z4tx%O9=EJ!+JY!bqFV9AH#`HhQ_)`Lp03~e;{6!MY_ea@l^~i!#CM@Eh3Z7Kr(cT$ z4;~sG3CCvq3W@{7m+=9S5chH1#M29;E)LT)Fq}F8dW$$YdO^<7i}dO)(Sd^?a0Ia? zO&O>8FI-+#M(>3EZt8fMuK~ zXgU&I1OhokiI6U|lTc3Hs)5>48L=AtPdX^fx}i%~mA#3+1lrfVBWHJ%YL{y_4Y}r# zC$~3VBa^I<$oqaxM+F>R7-`GJKP47n%7)2Ou}&zCxkDuV54~zr%z*7rWS1mX&wR`oJS9FUG zPK!bi^F->${qDhAf&7-iwS1{WsbCeUn=O`*4ah=O%iA#ZKQYrp*U6xwSgBOWMs|`* zf>Pi(x*Cn^*V_{I^?YPck1}bAO^`tYh&-Qo1Ytuw@rs!i+7o{lG7thrN#l{pAJ37? z|0uV~=ceuo#9lv3)g}XQ!dx+J&PS8_UV^o~sa^?n1pPGWqd7S7k8+`GvKCOU$Aq#% z+MJIkpRN_k_NMj7kRXT5PW$NKsLWnFhzpJzOq7pk+7eylL^UHB-ZVEK9ojN=)w;(g z!gUpWPlvXS1PuD&FKeD#TFy0=R%^1=*1G0db0pNHrkZi7tJh38ygoS!HpI{T*s{Ph z_)qBjNq4-loQ;IMf%-`me$9FE(ENThJprLQB4B8W5SK72#31Q5f|trPV6hAGMxui$ zV#jgj967v#75T}E@r z;>&e8g6*ARrdNpMr_1CQwELYVQ<#+bWfdV8*XeGrC4Ldaf3@x1XQ&~iv0=Q!>)?Z( z@IOY9M5yDiTkIyambcm*POFvIs!ce-A*2c+P}?i!I&5O@1qE$ZyQ#Om8}y>u%&(i) zwvHSYbLLsH+~vU=TmEB29P@&_iY0Wo$4I{Wi|=p(wHkFosZ1fUOh}*hx5QD*SgMOqk_5My5p{+o zA>v)RAGAcY5y5L06xE@L6BH3`TOxqE5-F$817<>IIbH`pcdu(|{PPwh?$`MP0H63He zHJ2*rhZePsE&@uEi`igvn4626=vs--nQd3eCw#Nx_ksA7_VvRrcZ`@jF1+Z`uAZ-^ z)Wr69{b0{+0PL9i+U|+L>S;4BU%Dgy>eTj}$}G1zzhZ8aR(HvMhBoIY?D_2UVk0ot zpSKo_6=e2A_b^nF*}n3bFex1p@kk5;@-1HYOoHMnOWMe66zBd#KXkD$%(>`AaO(Gb z=JSVT3@rA?b-=(+3duc#qU~#;cIpggIARAQE2cJ?%R+;OCr8eFVjj&*dT`;>lMIT= zoF(Iz?%6-5`_clb&y?*?l(yu|-!tbtKL#fssF$k(4yaN9~_rE4NKcOZPz%b zRO86DvE@zI74Dq1Vn}iKQ!~JVCl+5~w=8TQ^5C+$_sm~moKilatTAN28h&!V!2_L^ z@roFtQR;lpyMD5rz+^wR*QU#%ar zzWw)^)qij1(ev&IQ2Npt8shr%9!8k|iHZk45$j6}rj7_I7yiyQL=+;?lCcqrVlp3i zIFp$XK>3O7f#460&<$C53dtfq$`T>6jFNtXQwYx{xTlTc(H}~O2;f>Y0#Bot!#>NA zx*?m79NE0|;X9w!mx09~3uR58Yh>9Yn=7jx)W}U5qfh_fq$5BID$yyl9i1B9REPHI zJujL2?m3K30q*dUnO6#`l^_Wo8~vfE80j$p#e|uML9!|9jQa@s`N;KOjjp*7Bsb6A z`67@Wv7kP4iCWUL?x6+jm$tN)vGxHhwFeA!tokLikxo@7?#|~kG zE+*&-{?lPdB@GUT0VWOLASs-p@F8iPEqesm!5CnFL^jt96a(bHPzjP|r_+p*u7U!1 zN!Z~CJ5m!;cO_%PhQ*TN5l-k{1YT}iURk-k4VBLl)`cr@-}@P_3k3vQfD(ti@a-@U zE#g>3Jp=_xFeC7Yf-H}TA(Amb7z0s>68C|SIDb?Cf#CEL=pa0ouun$(sd|4T;)l=q zfz;fWL&Eem!nWF`=M5?XLhO@vou zU6Igfkycz+Lab5z;zoswNkjzrBoUGvj}s$K4u&MYwCgoY%(nLudifI0jKD=bvUBNPRjf)O=l{r52=007PrgGJ=BHl23_GYizoTUnu)jJK* z+pHC*ZvFc$d+>KEMSoZtP%3j9$Byf8YB`Hm!#EnNvTDZ%Xy!_p)B{JvJMQ(ANLx#l z&WD`2@g<`tJ62aYv+wL^+w{ByN(!z|E^3pnu%_kTNda?+Jyzm8ye-9Jm$s%Cy)quw|EUkM>eecFQ4nKX(jrXWtXRD%RHF8@# zGzI?osQR8v`WsAjgrvtp#R;&`oiEWi;F#2{scT2GR-Gi@<;s`n&5}H@74UG{Sk|Ir z3tYWFQ&4-`XdWMB+FRXuEra0DT?O3T3|T?m3erAr`acTTcET=Ds_y zi6i@eXNy+77h9HP$+9F@xyX`igJs#6Vr;;eX1eL7n@)g$=p;ZwPk=zU5K;&!dY-#w-%u2RwxZHj3`~Bkw*6!@=?Ci|!%$qlF-upaI z6WM{D(kdBY5lRFpuAIJ3MICZ4hPU2> zqe)9idMC+ZL5CD*tn_WHwpgmy`6>+o#JW#NvKahEOVT97-3JWxpei4{=Bq-%w2D){ zs?}SXI?gw3+0w)oG;N`uTZnVP2iWebEH19}wHu9JFb|rnN z>*+0tz6)tIHDfJ8dkV1Q|B{>R3U|Ygc3%Yn_zD~VUjYHIhMskNX(Y7t`0=Go>(b-k zb=n=d2XX%tD5D?hia(CKgQ*jbaS%0vnnX2IbE$>Ya#Nd_@&<}LQI7%0zZFWEY39u77f}@L$ zsA3L)?f?>N3TWIS9@tGzlqZG()`D$nzZ%@7#dm*ivhgqLk|S=g5gxxA z9tX|Z?8sO^pI5!|vO-Ni0$068XTxvRx%88O4QZ^#2)tAQmZ>Y@2rx(-Y2m;~xRpht zWLF5jd+7AhM_3?!%(@?BefAl9_LPWOrjG8u2>*z_XJ&Ne7VvfU2;lr-0|SiWOPmPGhk8#Rf!?e~VsM;Fl=FeOt7ufWi<8O-lb zKe74XTrluGLwzMT>o%AQPmdmT9!xrWXXTg$(bI6{fH7blUDnYXOr`Zp$IVy{gYaXe zzNm7z=`5(7ckhNLW3)j`vHu{tznGHi1TQ~iha?B+{D{r=du>>`lZnSOc%h3J8NoRn zPrO5!{3d?d!S$=poc?0Zo-a1sZKkT{p)2EIsT=o8v_m7=;hh5$wE*-mP&)8D-+L~FjIvy&mWTJz&Zyy|C za&jGW=A<)Q*?SIFMTU8crqAXCKKdA%o5yzATa5dk%b{<&?gCg%Kw2TR#R|A9R{eOr zl^o!gR{b;_MhAH1)?seTcMo-BJoMe_nbO}Zm_9fUWWTyMvRk?N#4-94gVkz?I&eZ- zhmX-+lMc;x~%Y-3xxx=lMVHj_j=}v42cqZAt1zP$byS z2!7fO#8aD{_-f0e3Mn5|N|jTUR9~tF(dD6tGLNRlBkDYZnoZ587E#Nnm54%bL=<{E zqS1S){nRn)A{r4`^y4H)pWT41*GxTs0TZA2!!C&ue*oix{mKvD_ZkBKt&9Q|&Kog)MWkAKq7!fTs<;DFA zEJEXNJHdO%?y-iwm2qCojVxv~Cf?t6_;4Eo54YWae;a74$h&qauc9IkJeeD!e+uP- zC-W-67JTn8PS~>GFk908N^V6(E?13@zxfS1#`w@oM87Vh^B6?ExH#Mq-?cwa1kD&9 zkQKZ{P>B#pG0g#=u*nfuWfvasbNc|h=Yx+9k2tVmVe^cI%kLd_;J4@RpL%HoXS0Zv zhThZQ&ucb*z8R#PTYmBI&W)RnjhVi2?L_MgjXq8D$NS4>mluguhU8vPO*jSFQs%|? z-q>~M{lK{88#XQ<7kGaEp_gjQ*;JiDndEDnv-rbJXMuXu)`uV2I%?&#iD9QzuN|zv z|GYETX;A4>`qXs1=1f(^cvP}zj}RwyK@ec#G8HR}m*FgS(2J!O#D^~lM86hv$OTpMcWucX-vORWV(!IBB9z%> zbkZl^6T~L!WR;BN0ejNyV!G#o1JOjqa;6nhNls=3pPD397hsG&v(j75G657+Xw!^N z-qnR`kLxYy;|~*hn<}nGPduQRfUzh5{?j^hl&e^`8@+ZnVls7r!qC`MboYN;Yuzs3 z#5dr_yL2e$8@6t>KXXAg{1 zU@y8r&xaSlRWLr-6#W;1BeCFb1~4b}$-*m9#n%(w1o>AvLW8 zVXd7F+Zif4gWeyBFf8%65&4GRPXZu39a7qSO@z|xSxS?yr73L3i7Lr|kLIEp>K?@D zQydn{^KJq~{p*K-U>y5T56;9y8U}BhYrNRar~yNOVjm5RrYrTodL=M8IUk;8cpdu4 z;W5L8Y5m$^!%+C29&n;xyFaWwFCkUv1C8E#GAwKZg-=@bnh$h|IsNMEKnP$HABg&k zkfH9M{eI={ZTN0OgHG2F0!~n7E|->p9Bdp8FP2Hm&G1e5u@>EI_|;5UvjDjnAAelj zmrEaNDMi_Js3mnO0Afxc(__9M1vico?0_0;XE7)s77U|1#~u@KdoiIEh%LrvF%}V! z7C?Ypjl7q)GIXe^2{%Nz2~adG9ocUZZ{a8P8!07vx-#^~$T@{fqctfqJUXdDCYLFs zI!}heq}9k2oSc!7RN#SKw?+2dwo8)g8R{GJp^<+515MuyTds9Z?>W|7TSi~a2e0!f zA2w8s&Q^oga0r`7g~D_ZON(_htrOF%R>JT+YZsfvdS1@5$&U2ojLjN+=}PXO@&^2X|yUgF$EZj$n3aN#@WYpWD|QxjVLR5Jj}C z4son4*xE%&W2*`m*(f0*P)CB`+tq0kZlz6jFP4M`$X+|{?lGYRV%1G}uL*Im0lVNL zorv2rf&V5MyErPZUib2h-+Zr@4;j+GX`VCX2GzGy3|?24wDMVE4i+A~X-aM?O)VPn zsnx}?uB514-*2HVWg5QuUyIi7xci-J7ZyEbf^RzXTFvhK+zqe1!i9nOmF_Zk@b?*~ zw$$;mFOSTBtN-l!FW05GcXjYlM5K2$}DXvGpBKE zuDSp6#Z@ruGKT~cC)9eiJ`ncRHW6P}71PSo(#oe*6b|t_`~(b3w;g@| z6d?F=(V2_@&3PD@R>aHDjDU9&>@kc;+7x840G$GboRnpvJGI5y=nhT|78o5|zt=?R zMnk%2SBaK(&wzK&7dv!$vbDbxIdapv#c=ct*cMznzdj?Qe*W5E8>A_bgkhtPXtneh zTAN}3$P|sjC*H2c18CxXmepq9y(08u!|?Luwl2^ZA-L~vYvr=7pKm-4 zvY&`hLXX3HKTPW<@I};@5|Rq)M6CJ=pgp+h>s>0{F8F7yu$zOQO56vwYW5ra1 zP!e7gFEkU}c@j0MfY?A@D+DjY%O`gps}SileGTH=*6&(##i`{Qov0%EU{@vB-wl9& zc^J3yhJ;5+a6=O4|H;F^FrewAIz>Ng-MU%&6!poDD+yI1{ejFiRn$Pd=Nwabk5>bO z$Nh`?;V$B*FcEO#@g1)eOJSS&_}5r{tNQKz+d8=#*xp@wrIEU^NvVx)PWU#cv!Jg- zy3D2Xx21RXp(e`)Jzd!NL*y%1sW`q(|{rrM)N0OOGHq<_HX+VC<&8gBCf@Y?Nj$kQ1X zEi&lfAENK92Xof1hkM{JrN_Q#d$?3+a>S6csv$#EFalzU4JMVRrAFrr3Z2#e`8Y1%Xp}t**kD27h|~19-I0lJmRk#gaR}*u3=P(WL(*rt6jd+%6IcDfWSn&|f6{ z=`jW<-}Qa688sx+iW(3_z@JbA+mzVXCjJn94o1wWADt4-IQr?b&41pj62@RCG1b6{ zl0_&E9?`p!+aD%}Mj$91xqKJA9^nxegkmgdAHdTn2DPCmwy!Y|wc$9b`B&Ny z^_hQ*FcEhnLQ|5yM_9dpOO1P9XP;A}E*I|6gf{q(XFq#s$<~|3?7{1|o05UzrM8!L zJ@IyIR8nCK6@aREIJW{E3UdKCgbbO=?C7CEJH|pI--`5aLf<{3r7)eS;s_^BRwcm~KY1Abd6!PL>+4Mif%XZt@Y#-y6P|fnr+Zt-XxuS!qa)mX9zrWR zKFqF;*M*><3#CpVmm&)5@d@0P(d6~TH$m-jFsk^s;pggf@FPizBu^@R5q=b-@&BZZ z!1bb3nuij1gu1Fk&qWo69|<>J6sRDYhn@i0o$Vt;z9_sU^8HQoD)}~8J|ysvoj`CD zUJ)Rcx04OP>>?=%dO_^tNBM--B@ANpKB5yo70*<$UJ`w`$2$>$4YL?e7=yRRm{F>; zJ7X;`3SRHzBR6;TR&)Xhb0+QUibp3Z0f#Lk!Pln78^DUM-T+Z0!~nxyO($^NV~(OC z2fXbq>sR^JD=HRkIeO+y)Q;o0aFL_^xTA<3_U)dM67YM;kzJ2{8+{zz80jdYV(;QG zeXGMeVR&7@8i~`;CXNl010GkWDwjQQ-!-+R%90uy+u7;&2 zW>jxVm1fAS#_S@eQliQk!`qtc%c~p5gaQ*P3R4sxKXnHFJvlYmYNS=(Avs3ou{o#i zYA)Ugk2Jk-eC?o6iFl$?f|B2IcJZQNI2jJ2|P*sh_$s`g;Tu%eO8OJ?Rjei}yK z%55mfkyyqss)pHf<8tX0sO>hP^+XUOmQVsR3DG?#>+FEwj?7535doEh46RpbqecJ z<6oG7(%egKu(o)J7E(rSSYSv~UB}LSM}ozjgDqz$n@f#x1wo93P0%8V&ja?j_6Tus zZiow$IB$FfgEdmIXS|8<_0KUnKOF*13Y|^?kLVPw3LQLxFF+Hyh}!Ck0aZN%i-vfE z&EIcYxlTXio~Q2_qStL0@mX;l9gYF~!~1W3TF5urT3q)-(Ve&XrY)H|u}`L^9R1TY z)fLBeqWOQ2`gy653H8H0Q3V9F3;_$!S6o4c7)DzqG97%x{gvYh+(KeSjW$wE!hChr z^V#bX$rg!1DY<@KqEw(D4)lnL8lH7JhZ#)WDtrJ8JfPQEQY~g@XMLle{qsz^VxD#S zea>M_SLIi%(1=nzcE2-0FIG#L3H>6hlAxy_`-JhXXYbUc0h9>M?>DG+M97H{hz{+$ zuy5Z5Zsh0pM?>fmBcX)=Ci4XA3>xv>eWCk5N8xZ6mM*4aMxy1ycnx;mZm>&mUw7Mm zUWTZ==+Laz+6sRNfEqXr9z_4AftmpPp|urIpbuC9`ao*VB@qQft>M;4D}zs}WHp)fb=XKz!Mc z#EBEi8PWQeH%7wiUf|wQWoD}0;a*tBgg3t2-b#Enf%6#NsS|H5;oUicG~(9prxV^! z{mZg^A^0o}McWuCxHJu6E0kLnOK|lHUdP3XCSJt%YVJgIXesf(Vj-9}8Ztq|+<9Xm ziP0pXu@8B-6VKHWAVkt5l9M!Qm~Tkc>y%b-g9*{b=%3lymI4#(PbWujj z`092|PfYc8st1xfdtA_dOQMF~5Q!h;Zp7@A^QmfT5ETI;pam(wiRgT9&>sv16Tlp> z4Ez^(9b5)i0i+e^^I@bk7r{w0a#-4pJu$moq5ugKr)DA{4OT$#8-X{SkAdsBW80a< zF0|C*gR~U@BjTNnLXNDHIH|_i?Raq!I~EJ;Tazy~?cu#p#Kz&NE(oyr$6Xxo#GXT| zKE0JOVSptUPcW7|tUCk4ECswl23vQT1d%G>4Oj~ml^7@T27#5_AtGWz7+KJz1SaA05QSa*6k-yL1a8WK%4A}Ri+T}x#$hOO;%f1Jp8%JK zeL$kDIKO}ms~3t1J{7yP$vzr1q@YR_^DbSo575I>jK)&MsPw#nn+r1Y+ZQTE3PBJ3 zHpp_Mr2AdP7OrJTeM?K*l)tS?nScAzq4ZB;9S_Ea{RNH2=+NlzOrr`%z6@wiCl)0u zQ+SEYl4@0$EDp0)FXMfUGKoYrm`-a(9$faN@c1B!37qZL975qK)JsjXewhE zn&r8a!h)jA75U}Uciy4TF182d^f2I?+GTk#L@aOgNqL~xnjIFC(r!+XNyQe03H~f;u(Bx@y=|}~S<%O;;FuDxYM@n_ zEi)L^*6XiX8zgp}B_%VpT9NExUUgQfO3N@(uJ7xNa|19vbOIO-+8ID=s#N9@ zZyLw)Qd%V8vfWY?4w37?mnpDM_Q%^7sDhO}dF| zT%PUft6`)gz5aDu)lOcLtTR?|tk;kbZcM3^C>(arT#g%&o)BiMRN}l8M^TPRH*n_6 zJu^R=o7bmzjVN<&`xRN5NmH_*A5G_HCnskW(9FSMMs1o*Dlw*}N~B7?GF2?Mpiic% zp{0F&uAHD<yL>9Tk zqSh)TQj66fW}Zw`SmwNg{LYCenFa`bG*?b@!>@?!n^-ZZ`b*y1I}jxAXXU8p0bEJcG##ti8565H5_ znq5DE2f=N*0tCZ<)kOfQZ)WOfrRRSfBK> z2E*<`hmm0nmfm5I@2_&%!JsbgbM)%N@x{Lm!w=p?SN_vl)0 zrb)?3O}6}!0Yj(FsXR2syLjUCq4mAJX=;X6TZ_E|dkqf^jq4o5{BorcRM1*#2KMGc zb@x<+5goh1H0z2GD}wlTG|zikvRLFh#R*vXhPJWVxXrW9An4o)AlHcNk6*cLqMlfY zY!-Y1zW3RN4WEHx&;W{YC_49Mr00cdwN0%CD`(X@QpplO)iG4CY>t~se?X$wzqFp5 z&%rC_m?oDw5{?6^bFCXbgYWft+wX3H3mqM-hWK4=>QJrEQKngl9^e7@K4n?=t`g#;0+SI*_!1jMp9tJIK z|9>hEjX2W(v+~fLgOybeR74!UV zV&@X~AM4(h>XS|;7syV*Gdi*&RNw&8I;}O)&|Z{OAr7g00~&2!%rM$CeiOV<-ed;V^7P zXLU;pP=~m18*B<(&q8E{zVq6%ah@`!HEh&G+I$9i9g+#!8$$@`*njDjaV4&pdfZ`8|Em0v3jvcMTCAG!Wp92 z2uj6-v2)ZY>cKZqdh82Wc#5S!+&^wR7W$(I!RG@GMJdvQ!Zhwh_yJ15&OsGJbxP}$ z5qV=iEJk&&Rrk7S9Pt{0#9BHGUZ=gQs@Qw59sN*0^Vwrrq1CugLh6cZg8qb}Ggx$l zHJ(tdqg1#ZMRMrZfo`BG2!1JWMEntkz!(e9;vY@UFyM}FU5HF}+-rH3iZo#W6fTrmLR=Js+f_v`6g2=FY!YHiG9yhT0~%1I zib}M#5fQ)26m|kv0sPLm^aImw>~OK0rO@(gsqz=)@F!sFKpndToXNDjU}?&XQ1Mp- z>Y5a#IK-e10c@Ei%n@|22_?#m6$1BDQ38He68ff<)NpDlvAXO8B=mQNjb0;1oTZ>K zX~5tRHm48ceHWAUB6fG>B9_bnV!GxNJZ@t@q#FCprcV6*X(q9B|9+|1q_CP8`PQwB z4467*ep%ON&TYOeS=nF!{mztWb5^XFGi^#iv&FLJ`N_Gtlb>HRjj0(~RT^rjLhK|g z1%DYhu{%Ujaj}!5x6#~_Md>V93)nVL4BsoO>D8iA17KfJ%!?<#G+E4hTjVO57G>5q zEpDpM6tQ>t`*Mu9k0(&Ypmlc*>j2_2-A0 z9)KUd^cej3__RmAV?^C?u$XSV8saUv9<==?{Ah!t%Ye;DaQnKjslqx%M=O?YvLS^o zJfW(Cka`wP2WafX?;SZ3k8HxpV$tlNuEY~S@W_$)op3BJ=I>REX*bqo^-<;22x=~t z#b7BN#*x=_%6~hhzG(T~c|lOd<4M@KOiS2tA&Q0mB9oQndPay^5$&X|V+u-vXO$J1 zG~vS9$?QfqWmYJmfy`ikF-%@H*#Q1Rwht?+^7E_m*&XBW+Pz`-UE}*LoZ8H4>$Gh1 z)P?;zs9VLdA?$r28e+mI%l4nU;E6aHdMOE&_U~Ux0_uF6ePmM2;wrnnYH^Kh+xySG z#M|xsOV7Q(O?J!JL>XruH3;=uHO(8fag~QI7hGy>z(s2kHu1@A5M+FIG^R~fY;mV# z40hDD-5!*L3tv2PVev5Vt(wR&;e8tAExG?O1^JmS1 z^I=By3lO3B* z({2Z<-@mL@TZED@KS-(;8IjO;T`r8v-s?Xr zJA-<=1C4`!r|2V?kt0g|&(HXJ#`FGvzvSnhembJu{&sfu+uOVMr~d!D{v_h^*&Mi4 z9M+YIKa`+5L7`cE7Wyt^w>RceUE>x4sMIFBPef=uDtbWYj{%MeY2ArIcMcg`MaGG?PAv8eV8gY(@c4p0RUSCZdIF!@@*VJ!y87;8^o;sgl!5xb9h{p zt!iA=0awUZi&b$$^i%16zK*LB;%(1tS(K(TP1!#49&w%W_My@G-g7fx*t>7m;G*qQ zOu95KT;++j&}wWR8vXGGb=F(!%SnfnH#Z&ZwWWZch~4Oq@dWe^&+Glm+3iy_qHQyw zGBXFx8PXicr>W|Zv-YKfr>AUZ%j5e%f)20?&7uRT$=HuEhu2qvm?dBrRK`1zrn#89 z63>Yk%zp~-MR-GobQzu_7`-?u2pDG^mYOrfFh>G-dy*k{1si`p=DVUCc!_Bw7W8mz z;mM;FreF;RJ7(?MH)}!ez_I&gdGhGRXaMhN?(Ty}tr=AwvmP`QR)7!=!A~vP z9JRWlNUsG=){JkXOOuSg+B_$%jFJ^8ZMy22Kc}Gv49oGOCFpxwGH|<>7WehI;5*^% zg+9)@q_0c5@4`NfWqtjueVV`Sn-!hfxYaPiM8DO4pfX_hR7np=>x*tsD6l~xHXEGA zqLAc>GQeoAiEDkCRmwA=+F7-;-mJ)(9-(w2WPNk#`+T*l?S=4?C)m$({(Qe&@lap( z0L}K!zDL%B83Z2>^(4^g#IGDUJDC;y5!^x;Xo^wSA}klin8o0R273%O$!jNC6|q$T z9@emk55x5>@QdiD^(~Js0}p0L8>a3SSGLrPTE|C!>kdUK z%`Qf*k$TgZP^1-w#RKx_@Yu`}E+j2VgMF(eps`%2R)F%PRIF5Pc8REx!pPt5KLZb8 zk1r?hZmG8|do;Xx%8(hh`j+dhV9KF2jH1|OwmCfdG?&d~&Q<1?m1L?^t*OolRW`GW zKdkViyg>w50wx~j?TV5oA!MlTQ(@j%wi}_XKHS0$WTc;m3L%(j==#9#8 z%lVbkfUzLGFnQ*_(jv%Jk0^ANOCDUaQ&R3K2r(PXQzSuGeigHrXT?*+#di9+>~zpk zQd^9M>e$8V92m@{K2d=Q)%I%Cl&>7C<~ z9FXF3)K-~n&&*(p3vTd=!UeAANP3K`pekRbh<*a@b$Y8jN;yooEVjb=wk$JPnbW7Z z#{Bi4SReoVa)XcGC#M*2d`6S^NH~**B|xy+wlvRf?hSl9%iO<-q=d zqIyJ|s-84D4Q8=ogS5(nqK`;I9hKs1({n1`L{zCZbVgZ~>8oWexqW3LblWupvVB9v zx&6+c_w);T;H5(Q>RKOjo2laH$qD1&<0I$nL%b5bIL|X{-`Ih<3os#u9b8Qy!+P{! zMImU=n>|&V)#@Cr1%8Ud8CKAw)fZKO8OEgO(!TROS7{TbyU{SMbmrBz|HYpJhSfBT zh3~jLeTz%+te3F`zUQm$#DU?TVJRw^@Q;RDYwi>oIh~Owv2Gd0^-4!4;@HRS^63QN zP#xKn)(My}qjd`Sp;ob3p@V-^=(I{ES)pTC)WInq`TjE-Fmg(I)!HBTWOK4YZwxpV3F?Bhe;w4cegX zG_W_pFx`fQocIPwhNIJPqF6Hg*yl|kOm&kR;diTXfV=ddwK<0+H`KNv=jRDn0q zqyLSvJB6}C4>p49x9F5uR((Z6aT%zbI?59Bve}m!hI(kYyH|ktt|}K(FY^;8!o*h! zNrkC?Ml9qN)a;dj0I&fJ%~fQj4aGq^uF0#jD~WnKmIh*t4zx5U@Wr%`sLj}k^K*J@ zz~v4E+^zt-E-*L{7#wjgII;l!v1=F94_Ub2NTl!4MT?I<`1MhC-OJ;k5(vB*9!TcQ3f_i#Bj4og%zGK;yUjC*XH3SO7>FTFHx#0`&X(D9i+_foj#o z_KT}n+5CB94_sKX=>2;qM0p&IJ_C9!%X-&%?|JDycx`{nl#-Rk+niGt><8leUb+Xx zPhHT0`ponj6nlWsMIF``CSZ-|V9<9d=Kw3f9?5xAO!*zHK4Z$|0jzc8VFW!SD~o6; zRxGjtrZ?OIe*sdk97y557uK(TVLixIu!_t)_o6d3KxVbd(?+KCIRk%A8;OExKsMmr zh3>pelth|Q5VCXnssSyfV;^$5?4g1TdI^xe{0hqHmsef}2iK1uw|@P&@zIA<@-njQ z$u))nBo~F%T73ro-HHMuaejuHWP4UdUW(qT)S6kP!)){>C!4iOYXW{4Px+}J(N>M` z+IxVASJLUOd=kQ%M<%Q!gq>ue85LckqrW(x#{4g>cG*N~qwOZ~@%`gBj32)Nc%>P= z(xk3c>z1aZr1i>>8Z-M0yW4wLq0uNYmK#qk9E6S%qw!Sn_Thap`@aVN{@QCmPOnIW zI%OcvX?*k-eG-=}PRh*CYLmGneO|9zpR)L_f>;KN>Vzy`D^~h)djTzwzlL)I-*(40 z6=V=Epn7Wszjb(#Lo}fgIfywg@8rlOppz99rB;sF@)bP&l!G3+Vptp~Y%5xIHiJBctxaRM$}&^zLJ@ z&#}#`NUEL)LKk=If(z{z6<_h-MP>h9X7C;WTZ7S`>@(=+3!^tS0su}k`ge*JjpSV7 zBHB{s=oQ&9wHzGGc7rc{ed!{QPkTK5{#yOv-asMEXNUkOq=QAUpFIjS%yn0x5+JIQ z%Wm%o)h6I+OQ|GkA>wLxB~U!P@>H@s2(nH+kFl{)`=eTtRY4lrZpDB&1Tq`ZE3#fv zVLm^AF$vK{KJn~_Io*7+E)Ws-ZC30L7!BnLG%y7XkHi_f+ibu*Yfm=2(u+{G6C_JE zZJo%#qx|v>+a}O=HZzuFR?%zVC+pRSArJxefPrs44w7^VG)U+Lhtv8>Wn8s#E^SX? z70G)2ptcPvT7lB3`d7U7q+2d?&flL_B9*bF$`NZmgqPq;@Y08C)_e#uK|hfB;b*s) zVCeN`7cP!{7~NMqch$PFqUbC9yp`+6_I~>~tyL+c=`DwBeNdLws+qLY$|_PbncB}c zs2DkZ?SMY#9tTFXT%?oBTMk%JI<87Fw?v`{)qc88PU9*l27E(az9z9i^xA*MM}gSf zYNXOJIu5`)YfcyXT>cCRFtP#0g=P}9)2O8p#c%>Y?asjXB#5vuxBvKuZtM|lAPek+r{E{iVH=h7{Pmz>spuqr2#+fo_b={kvYTL|+%6g| zteGGdQ3UW9Vu;Qs&70gJD>ekeSQ|vy{$AD*?-FhF`(HbIP>+ z?wui%EmUNGzu3Q?Pp>J19yU0V-^gT5eVJp4w+mA zxGX1z;~xEQ@`6)mQKU|pLVc6MT=(_@qid%F{lV9d-3HG-nyP#f{_e|7xNkhiJOT>Ag9o-WFTG>wfw$f~ux#_P*_-d- zEc14)8Q;D=dwcu%HM{1`Sq{W|egM@cpTj)~EQ?%gg^#VS7+wMKxBSc z!4=raq81Uwjrz!^N51l zY5ismpR?<>cl&y;zd32-qI*_6@0kp)(U-VOcklQkJ*uQ&*Bj%9-~acG!xjU6(UIPd zg63a_!0*w7GZ8E?2PRi7KK>kdYS`p{`H#-u+_7rp_+bM+-E@{7c-L#M#pP^aUhp%5 zaRF|*t7*7tztESsF-_?d*U65hNZ8Gc+5p*zh>(p4&=j@d4NFm|Y67q^Bw+;aXEJ9a zg8oZwF$1T(Wr8| z?tG(PNrp$sBx!Xl?X{Lpgg+KkSF_)OVst8a`hptf(E98_ft7W(?DBMnL8{e{=$$vH z)a%fI3)NgWG@@kb#@UA^j@C(j82earbpe-zA8h}&p!x$aWm?|AeuZ*#RZ8`1M~|Kv z?8*u$67u!unQugW_%@@{)ekW7HdHR^3k<$~1;&hUU&q4Arc{MSMD?ybVMW%r`?6KgBNfSeF6E4vj61P_DGwQMB zTMQ=#mw_?rJBx}_6U}xq5K)a5>^gAt*u8t^F9>GK*ij%6;v{qbIrM7AnBEGUxYfS-fdGdzVfB4gf^$j^HASo`AI(q|V z%FI2x&%eK`%x_Vt(Q3~nYu+)SfAj4Ap?Mpcp59cmecM}Sw)v81vD9ufq!~2KT&p#5 z5oE6N%w2KYhxJ4AJZTb{%&d^`v!;djY+Re7MWj!$?$HPDy+bBi5DbMXT3U9^7-?Bht`i9SKrWV z=TkIl%am#`jNZ~Tc z3kY8x4HPFaK(sOjpeM!%{&JvXL@Je0r3kLw|Jl-IKRk16YPy&eNflh{9Iz1_cn#bu z)9BN^8m+{Tui*@KbFMB2h?HUpC&K!_qFF_rRd7R!)1_4WDRZz+CsVqXZP~HDIatzo z`|@p5iVW$aM26nQy|wV8+%c<9PM`X~q{`%IQ@^U3;Z|j@=DC%Px+V{k+WF|ia* zHxeB%C4|{!nPZhpptDzWhB%Vea z{eY!fZ>qBp9(?PDs_Wh-+=z1_eZtuVapodaxzqPh%nsdT)c>Eg!zgTJ{>m$Yjrpsu z3RdUw>sMZpL~Q?A)7*3G>^iSu+yAb;^k^NGNtIx%Scw3d6lZ)%K=05UblPYKcq&}w$kNg7l9 z=rUg?dh#O5WsYnFk1JhfD4aTkcytuximb5qAznwQqClsdJPv-~Bs(RYA|pR|Z9|Zl zeGUhYfLwS1Ho^-ug)6h`oYta!6tt?M3-BxGyV*kFHpm5!)S-LlcHv~p9u;JoPV}8W zCUcaN=-?0$RF}A=>tkW0rg*WssA&wi0ke??(fd;Ac1vbEu{Whdf>kP&X^Ff71QS(; z;H0&;W?HtBlr(Bv_K)bRZ?|ATNP-0BGKVZ3SBQ?knQ0XO!ccOYrnOa&w~HyRgXk6G zu}lej$vhCbom^aF+8;pN7w7bI8cyRx{{cGlUs{aXXgDb;dT;bzsZyswmo&Pho9Sj- zM-muvlEN+$c|7fz>DTNpiVo>z_Luf3`^)7H zX`*acgG%L#&o_9Zmb4@)kNp-g@r`gitZ=buN}e>;L&HxnP5YHapud(rXm}C1I6NMFGdw5id zp9Sqsw}=xFQ_Mh+4`3w;tm;V%j#I$9-A_Nlsehk0?Qz&%oG#ZhY!c^G+Er$yire+@ zkKjJ=Ex3=aO@Q?j{(uKQ2roaTeY`}<0HsW2~THYO4)HHTz#T=JNy!AVv{SIz@0yT#C$v#RkqBE?TRUx)e>@$^k24s!~ zqJ8VWKQV3EiSNmGl&}={57Yxil$26nDy>0(AQ_M|HsgipKTUpUz>Nm(=t+2qSr$DB zGTFm8Ob>yVaV(J=Hr!|xJ918d&pbCiUCL8X_ zyi+V$yA^&u^7?OnGh(Y5+#wTpu46?4E`yXHYuf>%v!f0yqS`68{F6_jn?Csjl%t7( z0>|iOAPfF6dIvlo@7M8XwNxcFBKAB_Ft-ElfEzp7=FmzvfYp>^pdi==3$39Hb{|@G zVvQYdz>$tQ>Ea*_d_+mlr?I1zTr3?f2eVCHo0dF#c5+&+e4@|hgZpgB;0Z_7fWnO% zn(FjYMGa`(E8=JXPPx7ju`DA`p_lr3j)vcxhMDBbez^E-t9{tQ8F)OCd%sqQ%pUydK`Al+coq zLfxkl8ie1L4o zaoLDri`yRF%pFF9oVM)ckQd*)=GeezuD3?*efiP2YPx%t~4S7i;Y?4`JQfYQ(X0}u+ zO_SvmNhC$r@XJQ6B7M5=4O;XvYL@~meF!pm8wzVW*sToe)Ebc-v3?koD4+zq-S1)Z z(F&?BP>w-4zlRTOfAwdY`SK41z18$eu`M{Hq1tHN zeErP>^jE9Dd3W!~KfL+!jaTL$ZLpd9c;V*2K-ymentt~a7(Ti8`U!(p4=ORM0N{qK zyC>dXiEh1sMxR1asHeqP3fv*F5lJVr~ojb1Wn)lYu5x32`{n6Id7vM*TdY~*mr2D}mQTS08t%N^c zg^P~>VorkE$%g9D7Q@qx;SmJvz^wskh|bY=!0nD67{`oifA$6Te*Ny~cVHZpM;--J znOYQe`N>8rB@1T2BwDhGC> z$;uJFJ`VCGtRzuCy-sS}9lT( zC%4Qt+b}tZD;=C{n60s)d^Bp0lO1DI(;tgn;#Q88YQtr-of$z}hPo-9xmMYvPw~6z z+*!WTn)Kmw_FdRFXLx!|sV~c2=kllMOZ%g*(!W%lVGCwBXP1SwdRcef03MBEJK;%) z@(ZQLHb7ny>Y>!KdPqq$S_0_j*TW&tMAy-qZ>6mgY#9s`@E?GEArb}(F!L6hCzys@ zM&HGaxZyHt5H*STAa;x5_)T~pOORC?O_ohuCjK0(amf7rZ{OAN=SP1$ zvo{EWzx@jsYg)X&eUd3FNoSU8`}fz%iz~E~0JX`KWzv}y+BtKy3bQ$=1<&=GXvoV? zvM|z8YySZ&-(RuoHp^gBDA!oK_rl)!gYP=?*GKn%X?)>J_}g!iU%u_h9d?DL!rTn# zW^*t@VZN&xCcTxe&<4#9zW&<>%oQ4~JO%L-88;~I3fYIBhuBCm>*28~;4)$l2pl$l z!Gbibo|^`UPg2&6x8Hqn5gWnya%2M!ODw*KS5qrvvWmGYtDjl3=9$%37ag?kx;poT zm6QDrxx|t;Y*s^Vir8eCPuWEEUtEXg3UDc~c)!jb6rXXD>r4^&stQkFK&6-oHCzlQk4bJW}a(IJRsmrhQ zW;pVDxs~bpDOMUxZ!qWOx{C7B6?|aK!aF7m-m!jCX>r4>nO;v#PO4O@b@@m6)j9xz zgPln(e?hO*8~=(u8s5~B-CUT55_15pzt&bawGY#y zeg0|d1QKmE|5a#EQHpb2{FM>(l-#B1n?K{J6@2Z(_uTHJyXeCN5yh=oIfCp^+d zLfCIJiav2LI$i4ZaH>wnI7H(|ULQV^$w&qiSv27Tm7D?ByNX?iMx!H!;|jyKEJlOD zXaS{6|HyTQPqHU^+_eAZ1||5Oz!WMTzW?*jV|I4_2BzcCLO zXzp?|9>ft5HEUIMa_wI$u4@Eac|-^CZ3Tn8V2hM0yO@K zwIv#)1Z9({*|T@=p7r27JO_$k!Hw}C1Y5^bH|XDo<{v-(%jx6uL-7Fk)1JM|w!M2I zlfZdUg#Mq89-?lHho|5v^Z;l|<+7!F<9!^)skmPkREe`D0s@JxoPHxs~IdpnC7ERM1wbJtPyQl+-9AV_Ar70GnWV^lS|vXXoTK-^=b}Hp35(to z7jXsCc%?RSACp8b#Y`|Fp_eLh44^n75si)BM^80HH^TP}Ig03=%s?FXJL&|G@t2-CND>*niCpz+$CwJ?)l z8-%BfhS3*RoGa7S>B`QncmYO7Px%oX0$+neKhmvj(F@};XfUz1seTdwx3{&vd~Euf zL!ZuU1fX%|r-#-|Klbwb!ekJ~ZivfIgmspV%0&EtVDoKo_;kb*nZ4^rME$_c6XTQE z6o*!39Qx~_w?{LPNQC(bJ_bf$wcKbETrOrWiP4hnML3Jz`UyIG zF*4YZ85}t>$X*JLq!)z4)QvT3AVxo+gmC0R{KO6FvB%Ju6nA8zJlF~Q_U+SmJvOqN z&Pp1dl|XF6UX%u~wvNfl;(b#bLjw;-yKQn5kHOgtzyXxBhi1afC0oy@XN;D*-N9*% zzFY~LTfcbG?%MqT6!|QJ-h&Nw3x@S7^VGW0FgguOqM8f)ndOUTjLk2 zbCr^0qf}xsr_gg>H^b+NfRo-j|5fzl7qH{i`SV`|9IyiJRagtpz%S3OSaA+mKnbvr z(3xAUe?}Cih=M^;N^zdZBR~A<=>CS}0x6rN-@1JHR(%#LEl4)>AN}cJxkq%Ah*KBz zcoPoIS#b`2+2e(<;8tpAsMl8``u%dOjR&9@BQb{|s~;VKwRgufI8l3|ZZGlxqLYge z8qwtDqy?pEJtzv0RRy*!#Cn28ZdEmx%a&(}nA}pvad%+P9b?b#+%)};KN zWt{D==4vbWHbbt-ISUqL?P+e_Gc)qhtT9`6y}GAk*W#_c&(gp2%a2~pE&)uRT=2Mf z!J13=-7#&`&U54LT$loKNBzdiRW+twH1S&al_9@R(YJc=Xfw{H{k8I~i+8o}d1cSm z#<@GsQayeA4ko_fdieOoC;_~Z7B;&{bddRf)qM$k8^zi8&g`Z8T4`n7vQEo~WJ|K- z+luWti5(}7bH|C}-1iANNr)lj;D!WJAmnO*aJD7Ta1|P$C6pFOxf@!V1m3ok5-60m zkZAMG%*u}Kgwnq6_x^t0msmSHv$M0av(L;t&&=~Y|1|MyL12rBHcM1iGJ#$lG`OL+ z4kDJbKYvRv&p{OL$8LGtwM8MX%SvJvN5bPOFP@mJ2)hzWgIcjz#qjGtyz2ck(z#C` znmhNQPXR+haO+^ExV^VT6F41juX0;VW~ZL)<2CuK1Ac?n7Vs2SJIwVOu7kI$jy?t& zQE~l?m7W;HN~87&pQqW$L_VxTTuV2$k?md0K`ju%2w|vid4NC@T@4})JFs>S>2pX( zqy^b0rw8!Z2criQ1SXHLAN%qlfO=S^1Bh5Ps2u#DXX@0RPH;m_qfWY&*D*A&UJnj5 z+Vt9Zxywew7uoTCMrAVdyx=jandqC=DXm^`KhGm(N?KCXnU@#f)G>cu0rs`Ff!^t% zm1;A$Qu-yWplLPpi_RgL&d$t`tUvA-t>B1;hqOX_y|hcpbuJ@(3Z>UwNVoN-AIasf7?=*A8z}FaxKP@# z61PV39-vIg`@r2@c!eWKTl}GF(mqY565$tQ=$q#4edL7X#g07oGs+KYdq*qUh;4 zJzV-crO4*=Eap)^BK&;L@||$IDeQqOMyzXc;EH(m(Gk;cJ}#@o;ueh)&3rW9g~CA@ z>JOu23Mo@M<;JE-d@6^Dht7z{{2+16M{}|^J6;7(_kJsKF7t?WM9m=W>${N1C09ey z%HlzpQB>QEb;0u1fXY`ItTWo+WxZ$Bxhv8H<4Awq@I)!CrKj#GFggMzi^UXh7z_4H zW8(%ldUOjZ25j`8#Q&pmhn_4$WM{y46tKHIPvqis0&H+jT zeK`W(QuY9wV}WWyJnU4w-%YfmLf$?-Da4!-Yzh)1JrRj^xqiwK^?$ja(s+*qaq+!& zcNlMn4u!F*8{@?tMEdP(D7fayYv$uFgbAKNn*_oIzCgmdYayoLeW&yxm&YGST03`V zUpSq8R^!v$uhDQBbokgltl_H8*R?))G)L|`a^w#_#Be+~BKMQ@jAS%iI(|mwLb9y6 zFVavK@<(EmW>ur!lf3~Ki%RurI1U}PAKQlAxuElPP5(7~Gc}2zE@21{+0S@xj|Xq@ z=U9O-X5}$U0Ez9stcC9P;k^ztKjI#hb9z!oe2M22#uFENN26zI5krW$LbJLm+1%u` zI*s5DqqG)n=Qc=}eUVq(b$iQ!oi@OTy4I3Hi_0zYc|$$^O541N9XlplIDw_rtCy6H z1~jXDa)5DO*3lS$Ij*JwoRyjMa7dRgRqC!_6>U&FJ>+A~cUnNsAZmXcs4o8m`6!lu$p=Ob>CXLBvCyV9!%F#HUikUmcQYAO>bZ4TP<9 zOfvdvSiVA9k@oxgVA9Q)fN;~$X+&&=vPu_0(M))aX2{E~f!qN8iP5^O;qZdR#=y`R z~Cl}lmm+I+Zs+rIF`ROlX%AB}qRy(R7CMIy_qR4VY{ zH$$&@c4;yNR*z)qIR__*9$`K6dY;Rpw^m92xVCugs2BjOM%4z&+d8v{crBm}%4rHA zaJ{GV(L1^hZ7=Ux(C7r#aC~?uzo35F>h3}%q`_CG7oUFNMnNgvF;n_}fUd05@;^m1 z1kn7qi9JizQXPnop)hJHUPi!DFe*7mNZ4l!_E1s++*?&ah99J1sfm70fP$|cy{G1LP{S9D%Rd0UUud_KUPoH1| zX8;ZI)Lu`E<0i-fuZg}_&*)1v>4h+|qdfD0uP_n(#HRD*x8(tq^o_+5^tYP-x?OMa z1xFd5pQCW+0S&B(ge&OjrrQcCAB@&Wv%E!2g}0(0m}0#(k#G`Z*i6Jv<3tiByJigOz~oF zBt@Ss7`B4ZkeP6ArG;TsypA)$CxK?E@p6qxwPEUPpaQS&G@Come-9<81=WU()Wlas z=zpG3YO5=0sUlpI2R5j6*D?!F7W<%={}G)m1I9-mmp*PB-X$${nkTGx7B~-IX$Boi z{&86Oqp9w&(rhqmM1_?;yYeNipvoBjOOQVOlV_yorr&2?(wdbhVGW(+^Q^3tl7`br z=H=-T&Vr(BBcm$jeh&7Om(#@>=_%FR&Sk&^EXy+wOkMaatS)e_pI~-6%~u{aGJLNd z+4mTUU4Xd!7{SZMqp7T3N(KQd$LG{>y;yQerNyur>VYqeVV=Tb*b)l6kzj=v-LP7b zJpAH;R0dXJ>^pD!!=HBS-2TPR?g?JLq3zIzr$EO^Z$o9|SNrzqT=`=+4KLBt>GX&# zla^%1ww)L*z`_?7`F-~2vg$5JOP+TH_`$pT4jkC`?#_Sg@YH3Tf4~31Pd|Nda+@|V zv-PO-+HAmjZ@mAFA9fD)?f*V}=XCXX>8aMWn}R~ut+rHkaGbr^Z5Us*;I<{TZHs#S zW0ASTPDQ9Fnoq|O4<1B)jLW$Tz&IHMCE1&z3E&kkR)drg&lX{kO%ja*0& zN)IPvdExaS?3oG@g&!Oc-6}G54&3fNFE-9~@!?oFXx0>{83k($Y#o1Wq>*J*ngW%@ zkFM~Ut>U#%p*Ls}I)A2kSfprpQO2)JXbn0AycU4Lt6|rOtbS5P;Pj%#B?>kJoGy&^ zkD7R|f3z?i>hsJNmqyfc!gVfIjEZcbpmh7)=ucrTU`23t@H!Zv^r#(HpmxBmkdkr0 zWJM-|J4hUGS#$7UP}Xb8*)z$_BsZH(>R5vU%8n)y@f>(L-M;nhN{3RXGc}l8sruG> zO>pyQXVUpTuP|H9+qP}nwkDp~wrx8T+sP9@v8|nV zYv1>++O68%`{DGdb8mm?TXpa0?thK(sW3*xydMYL%wnEf8l88wnXm4nLs1$VF1F5C=m< z^0OsOTsTCI{6`A{st_D%kTm&^5=GJIW^Y9UkVbiu{i@sYG83~Ws2;<>qZe*P#G8E- znL~<9SX5X;dKeQTtz6N(br))Mh6VdCMgMcO#W zmlgCpAM%=GCZR~HrO(EF7dpp1UIy|O*d`jiF?{_kL z1iLIm-L>4YyV1XBb&_g~0#eCdAnMD8i*VTrp|`PkKI|1gfG%-7F4~ly&yMp6J@*j^ zgf%n|udr@K609@35ia==-(d&*d}L_dE}ZIJ4*uIfC2j>*fw}99)|254Hj4T&b3Rv# z0$21kaI*T-bA#ZnQ`R-QX|8A3&U@YXWKfAy0>@^B*~B#zv2wIgjsurBM#+4jTPdC_ z2>zH!lg84RpfJejhbqpwUihLt$mrnM#k!Zwb9I)v9bL!X8q?eJcfyu>K&S8F+K3wz z&9wRHP<(CyMfQ7L{*N7ws%>_QU${8E9;Y1_51SC~FOwW|5AY0mFUQdvx0B*=RFe@5 z8`tuwWr;T)>lFQ%7KD;nSlchSy0N`u<@yHKTzdR0DGDiyDVD6d(lsUa1z(;68z8@> z3bLPtSQquUnQ!nMxj5FXSXI-#d;V&v^wf&W8PO&0s}Oh?TMy`5Ow!K#9=gNsf>B1mqqc`#*k+b^Ux~g)Sd(nm z$5~c5?)IWe*|rJdwI;g^4V#6z`I*J)kXp@d*1Ee)XS0j_>tP_1(oAz4)XHck^{Fg{ zie54eQLKMM6jii_f()4k++#RJ8v)%kOA4IUmLeUDx@D=_6YtP)UE4eUGU}LmBMu!& zT7r>6(6m8f?%+oSHAYpGAB%lSSNV9)f}ZZhSDM95%IDZIpR4m_F|>g1^ZSC13-!Ta z-q;F6=$JOw-XwGt$9C(v$8^b!qwfRI)A+&i)b!aeI;-lLE~8HoK%MCBvKUR1CY8r( z`m{Fiw=l*xz{E<02Z?w4-{XIyUQC*D)}wPoQ$Go1EL*$TMoB6D5=ANd~KUtR;v!IxSJN+jziV| zmS!+_d%q7SKA*o(Wc3?OsotPuLo|Q3lkd7rk56#)xw<@NuWR=0$Fj*tjV_0DfbnvG zyBwIM=Pwyqi-q7hJm3~_Q3PQPi0d=`%7TrQ<*K}ZdX7op#|xOXc|VtU!aK#*`rgWE zGC$RqZIx3tuxO3II@?ky=`?k#cmQ)xwDVH2P*AW~bkDdjC6o@PHM(I8eC5 z8I&o#Ev{7R3FC&q{x{q#q1_uPteoE)z%kk|3)1)+%QR81$CeQ#vJyHUzr9c(yH*S; zXHLZdSwyZ2FY-5u!p3V)G=fi)m>%RoZb#D%+YQ&%(PgdS4gXT#p({qULZMb`r%^z-PN@ZHb(2E7iv4!K0)6>CNc(zsDhH6!AvTZT6rmJPP_DWbA z<{-5uZf0^$XDPj8qJcJ-r1G=wU7Mmj%QoY9+Cm zchaL}2pl7Ue5Miam&AHWELLunG}Nr4fjwI+!$>&!F36<1!w`^^vBS#M7O*wtpkhb~ zEvWUsQ{$fY?5Z6jlTxrWIZ*40yeg~qvSdZlw3RHZ?DYe#mEFCqeAIk=soNfQ9;c^M zxx={MY5G0Nt;8gaG`^j$24K&1CQYUVIAFsI4tYsRF@FEPdGmIC~zQRn?X4RF=L} zl@4f-N7CE;^LI?Jm*dDB6YfEailXZa(=H}RB7Oo(tBBQu5Q|j`4MiDnWA=4TtMFR} zMt*{0eRU)3hU&l-s(TSv=c|cD)S3>473l@#AB`e`g_X_5Y#im(eBKSc#gnwTp&~ zlF!RU3z|d$#`ZKws~>EdQ0&?#A_%mdDaM355}(EG)PU;IQD=d;9m%u2vb%`y+?bO5_m`8 zIV$y4{W($SWX(qM%LY!3X6gqGKBN#%7!zxm^O`try(?0&7mbvBgjZq2pOqoTcsVT- z&7z#6kAgeLNQ7mu3sVjL(hw&a8f|c6pk0G8A+D9}WR#wrp%BJ4oVNaL50q?waq3Ru zjIZV!x-p53+rR10fh#AXu=$cFzYbzK`KgI{?H3}W4@@;m@x+7P@!|~z!W~E_Aq(sf z+EkvGKl!ZWHH+dca#Faj9VQk6x}J_9hib5d7S58hx&31bZCBjU==_BZ-a9(jqxo?e zp63aJgUoMKgC5w{Uik1&YM(d!xravA`p>3$!Mft4X}qm>=9kA`7KHEje0f9Y41r|` zxjx4SSs1bwYiue4z*ovXTXY$Lp+*zL`iDGXa0ABvah3sSy!4qSvL zi4oE93d9LC*i5>_a_+(tc$zzf@x10>&N0em3BhB#c6tT=^LWnn*6%L>WKwNc)t+rQ zkvX0nkc1p}+fPDKlgnqO9))~2p-lM*`z|BV$i-YEE}aSNO5b-3KN@q}DT4K_e8v@J zcLrrGHc51`i^5~-k|M!FRatDw)EcxQZ_+9#A36He4}Vxf4U7Y~&V>G!-fxDO-rHqT z49hO&!@6W1nW-*_a65r-gHijG7F%WJ&PnDs4N6qIG_BK1dj2Ij$ls2GK=nD86DlE} z)ch#Ma*jpZxhi_$I$FNdDtsm{(_*Kc?$L#rFgvNyqE_m8fvOEKtffn6<|f~ZUFvqm z)b^(V^&w#d3JKzS(pSqET;bRPbt9iW%8Mcp$(^51!Dc4_W$#ZX+`eD*3W!IIiy+2l zD?Td@N0H288#Eot5>7@&Mh!*DRkrcz+R6#ivDOeX$ z)r)yslFRGsKoOETT0CzL#$Jp0YU$Am4w@A6o}`NGmU0W;>aj3~KVNevfj`oz9VcEu zmN1ni_8b=S$d9fU$xOiXxBPV?NrQfa>+JujpvU(BTkFc>9Ve7{^%xEVZFYmkgiY&j zF)B|@7A?`Hw_iK|4j~sqdvFsUeY?8O0~PTv$~ZcgHMsBHX89__fSgS@o_2p`JIv@^ z`K)BP)XgRa|6S1?fC@WRh3PH4+TVd?V~LjU6~amUI6>4ADv_EatsJgD8`DD_XAqUO z%F6$^p%QDu9t|r5+m6z#o3+RuUS|I$>;3Wj7Z@63K<~Sn$mCiBUATtF_1hleo)I?u z2b!c*o0P!UInl@<>?5-xXl44EbtHN8Yj7r+J6whffhCiU9Q1rvT!eE6qqxD&WC{NmYTtXg0En8yr=}tO&trS7RpmF} zm4iOSkheF&p*0^;{Kzkz%|K8Q{Z5Ub0pn818f8dO2Z(;g6L=R>%s*bN?Ecy!x04*X zJ~yLj(YU3t@v#Ih+f8G6|K>o6oThpgg;KcB7u{-|Z!0-I?DD~R=h7DTUM}}~*L?x2 z#~f`_w99r|T!csB9MikdVOx{FE@#Ibd7vzPR;Uc0M@=0Z&#zhLW&yD5f8!s$-yg}D z`15IuLN;VTcpeL^5P&cy)Em1tby%qDy_X$!o4H_6GX?W0sU5{Gp(~6Tgd-2JlHS6z zq0oHM78NAiE$jba(d6!?1zqlIe{F6@c)m?u52=}_ihpo4lLROP&QO;Sy^|q?rb-fC3u?Hum6}s)Tmt{n3h{6Sd{7)xQHHS!S%gy8ZU&)D*t)a|wNOZ$`f=!i|Ni>o z!3?37a%L9klEJSXt3OyDo8)`&^$AeAA6X_>bdmEw?6{i}Yo5Di2$~{3=t~y}yxZp4 zxoj2h!xhm=u&n(4v;?VJRf(n+^c1LimCvDbfEe!M*<4ZLuIQS(aD_^ClPjaT0y2u{p+(<*hh?%h%(_ zK#dOnhyax5Z8}}xp2j=G*;58Nz;x)LbTgGUW>?McY-p>E25LQQBjC%U> zM%^=QTm=pXCbK=zY1vHA*;G3|)tJCu9-V8Dr{89Jn`!D*yp+F`t|$BthDSB>Rs2s+ zZPgOX!V$mKC-+a(zw>0(LJ;D=ruj%HIB|Rsy+T_+hf_6Qjdn-4M(g+BX!QLU&dYob zTY(fG%8A@n(HO;B4(^NR6WB5S^L;1hZ~gO@f7(dGGtW<2Ykj(DLA1sfQ%L&WP`<%{ z0Yc0O)&&#mvRFbG95)zsGQIadoZmYjTYgj_KWb;&l2R{7DSjeQr!0QTl*B?8;c7BP z720x2N={`-XZ_B*VPy(!#u6j8@Cpe)il?1c<5QdFlVbxmm!4whdzVV6-<=bm@JUPv z*na4&(xb8K}*;B3G0 z%6Yo^-@om)2Obx`rMD+hQ@DkCi#iSk>NwusJ*@e>N22Dx zonqnruw*?;pna+wO2w5>%jvD@TavZq^rY-c>HB6k+N8O+$ApOAu5)oZd-O*-2pwt^oc0$s$ehCgF^23VTTP8AltR8*&y@ zX{3Sf@nyAAuLnCzB98C!h)-v0ObGJrxV|e`eXmX}?F@SmP`Pkq)tk}a4{#7otu~VQ+i4YY*KcJ@` zf=7@mnTkFSK1|$ss=)5_=PlK_x8`Huw8yDd!aYt?fK&#)0<(F|iDfE1n>?v01h44d z2Wq#&*Oc4T9$$*Q3xl2jJBJW?`AoP)+xs`TvEV5j`ClET-h+hXJDtW*g>m$_rKTtyg+W9LQRHvN%fB< zwg}ZRZ_z`aN8%2ugfmIWXlrk?}X-m{v@I0SmU z?iT@oLMxczO-(N~wV}#1bz81VH8upLTQ6Ex%2I~l2R1@ozexcHh$M1aACKc?DwbV6 z?puFBKYF`#L7U_f@;ZH~c+gu4LMXE5s+W=Y52u5qh4Uh-5;6tsMM^f=?L6NdpqBO*+v+=?4;;Qq< zO5d?>(xm&yk4(g$neRl&W~{Q=V!I+cu?a`!Z~|M~2Ku1RTp*it${|M_{{1}^6aP|l zqsXiKYe5wp))f_G!x%wU?|-rYF0@+M<qQ{w`ezR;XuXcRGlEj- zJrJhYv9mija`6^MNF&d{{o`tFl^$KT>>nNyfjEyKRK%14g@VrweM}>od3JkU`wdw154l}2Th+A32y-zT&N$i4k5(th4d*~>pKcBZ#rz!x)e$@xayog3zro17Sh z4_m2sCTc}db1WZ}+>C^~bgj^j@#$yP3Z~^!XR%ObVf`HpgoE0R&nHeFd-44E0C)B< zjVM_AP8$n)6f>P&1`?WA(BeGpbf2V74}Y!Uf?|PUQ4lD?oU0NcUpT*pv2jcr5rgVW7ji>ZjPw{= z09}|c@xBHM&xf|1h__r<;lbOq+6kp6z!Rh zak@|q(|V<7k>YuHHcGvBDwHp&CV!jj&QYy!+`+-0x3f`5kH5Jm@?lXu)|*E87xMO% z>FoZr@B^JP8~GuGhZte780f!AgQHB6E|7KC&ecmY$HJ=?OPON5Sa@+OxDNJpI!mhe8s!VE8o>vVW zDLkZzK&(EdtJ0jn5oAfUS{utL;JK0sQ9pnt@r9g)paR(*m;RNw3oHo>scyh;qdi&Ueddl z6GS9FX$2Zt9Q#Ft!&^9nF`~z6N&}1Y7ll7eF@OLJAM;m#1#b5V5wHn!P~I~ zp&O_>{Rt=6$rYknGe4aEnVE3~wisT{wlYUs4@%kAf}h6UL2F>AF>eSn7yL2`k>lP~ z%H?`FodpY9Am%XZ!pTal5IgAe9$SakZJWAS=1>70+bL@;zRTdLKh!h!728;-pHM)K z60cIB$O#o2j?VvrHYY?L*fGV;J-r?TNu-{{A;NM?EXr;Qf(tPM`~g)%tT~3{>%}b= z)?h%!QB*V!WnrT?M6PO=WwHSLR98s(rD%XQ#bUEeT~G4*VNlFa?7$!3O91;&iIkN7 z4S@yKIgtF1iZ#i!8Q}au@sDxy#CzfiWoQ1VQ6D%sT)gYUK2RL1}Qe!8lCUuDg@ z(Dkhz*?kX6*3Sk=%0&W8qjfiitY7# zS|aE%cYJtU`_jp(igde#%Q0SLQgHV6Kgo4@x4)PiBZc>|)gs{YO~G9@{A!&?KkZR!982U0^cF{&Z~jzY+)mifl<-j` z3We66@JaEvr^H1E^Q}NE;&IrVrn;#A(Hev$iT;;B456MqC0l;q(JnHxKqV!o2im)A z2@3>zB-7iKj^xjBf{+1#SYN=i?KcPZ2Ns6FMfH!ee44xf3CeS%(YX(HNWUx{#yYCa zz0rDBbeKho@BIyFSo(sxqv}@??{kUsl5f^7tzPz_U z?(cqu9~GEdb`U4#LBWre^vx_IMB6MX=p1m@ti1h`5b0?Fe^C8^dxa@-eZlGi!!%Wh z>TnMHLOBBY%y-6fA3afIUZ4SAWIm!+-54175ZeevSF_&xQWQo9AMubGn@NY^3m#m$ zM_7UIEgLIF;teZh$-lEdt;wfG-snS0F_*K%JaU=W48o|g5E37Fl zexM%cm+P?W*e@%rt&(-egFq1_9CjEq)o>TL6j#~txmn$UL`Zl#-5UR z*Z~btbX}lpktV87Kn2416yyrcm7^=zmeiI+mQerEZL5}imL!(2AL7;^%Me1%B#m%% z_Vc}PqOqDUu3@tHTtq{Ol!MihHOQ1rnFetv?)h@vlw&9v43&Ix8ndQrASFZYsLvQa=k&x5{9vkjk<6^pWHP87tNU<<#jYv znbf(9aSU~ix?wq%gfg$xG5)z_n3hZzD7^msX3Hfi57UBWBt(qgCYjsFr~$B(UaklT zGvK;~>r*jyCsP=hU>vuZo*4}lZ2tB?E#}T`S?wGLf8*?6&X>;<+dwZBNo|=5OQa&R zqKgRQM7WHziA-WDXc_lfJJdiHfY^0~_ymDBepGuYnQZ$AU;_cmAMqMRnoqn|IN za~5cmttM`bMh{(>n++McGkmb4wQi_r&0YN68-%W1mvG?TRPjH;nShV&IOWU&^E6^i zN9yQlA(pw=hwCN^d^ovaLCC^_V3`F4scH>)@R}j$Krd1guI5t9g8NbUw!nfWY|Giz zU^SSQxYY<*gGv!08%d{c{u0CEmC zqok%mO-#iVmW;4C=~~2oe2uyG*T##|jMb)Jk@DM7S%|93wgz14Twi~sZ8ioGGkWbp z3yORQbnWRE3);vfRE5%n84FjZFsWX_(j~acSh&Lb9Um+ zT(o7eA1e2gH68;%RAKj8K|nw}vrP<54Gj&Ac=`5x#Y}norZph#-64_MjeS>sihqB9 z=LIGGfge6HG&BY|0|7Dp1-ts6eN0|v`}_MRZU}#JVq*uAj0alLfcU^b%>26_t1e@M zCWKV$^}rjGMH`OJ2Cgn8n@k&34ir1CC+LYJfQuyA7b6L#aIyZt{z4om>XYuSQDaf# z+igy&mf^4L>g?QEPMTV@*f)4fqu{ah)-Rb*R5{YA;H^=x4L}?7bWTJM#gafp<|CtL8URQHJHfb(q8bfIkzRjPi8E zbMR8VCO%i53l-dWqL7W)!85X@iGZepxh#AXr{ft}G->vWSuNRN5^Sw(N`&AoGqn9r zW?ij-z1>BhXKWad5}>P%oBA zee$ustjIrTy}3#J#9{C~Y)5W=Y{|Lsq2}=SZQL~v=p;qh+u$8)mV&;8?DObZjaP?d zlSB6~;@#)mi!BFgbrwVU_U8reVvKW{6N?`>pSwu^2S(U{NFC~>B%(N9H}Y74d)g)3 zZJyx0)xE9r9{sy>F>AL-$z3zT{X(7kOKIbUt*QE8b(Ac`mrjq_)4BW?`0gpA#!?^R zkwYi?Y|@*RgA1-ktcN#ujrZ5qnNnSaRw&rL)@L3|>%ge;r`OcE3{eEXz}`L0uWR9$ zs+ecrFX_+T8gJ`TsFpW^kRx`87d^oqHBq`g#R&IletSSyj9WiXNXv@G^Ckpvi9n&I z4$vcKCa%>x*Oa_^sk>$?m=jV1}dKxp*&ViPG*)QjrQ0uzjuF1Jv zXGJC_;B;)tT=x;mtF7=;xK9G%(raUopur&}_j*-Cr>VT}>l7Yvy|L{Je$yw0GAkws z({puNd#LNzjcUrfjpn^`&F~20d+V89lIo*6Yk@bmJ9{8c-w}?4V>K=O$21DbnD_uG zx`U<3DoZZ>w^kZ?h1vH@zsRmWeMk51_3XW$ z{6b#f#CIbAjt z6P>vW21pQAs1%~f%33&g=J&z!b^+caq?CVV3j*9fQAU+`x8@}IG0l)>+R6Fti~k1A0lx}g3RIM5(;_7glACnP7_}~@6adqq0^mZA6_}&IxmpA;=6qmVEhr4nnmS-`F-5tm1q#+j|T$?PMrAf4f?AwxMiXNosq8}vUMXb zO`+a0>pD>$lj&N#?|pz-XI2J@AsF-4AGtIctJG(tjw|X1J|rzDx6bg_HqON@584r< zZc|Lq_EOpBkDkrB*Ct?F95?v3fxF_~cBU9v>67Lk8?xJUOB=z2I$RMtdpWW@?E7s4 zRz7b!7l9HmnI44>nA{#J4u~vU5rpqI)&d{OrzugpP&YRq+=%-DI2Ppa{1HI6NbZOV z7w~^1K$(ciykWeO6D3!?kO0V*xT0^)d!C>bR9=OJ1JZMfd0!X>`KADzz8Szf_T3C~ znXIct;U1pN3BZlOVRmTmN3U+a1V(og!1vEuG_X4~b@D>*III1~NmaGMP};d=`%K4p z_yPRB1M`8-@OGgG!g<>(#&uv95$5idQ|kA=?2g4XXfLnm;xA{ydwjlu2#OnDX@CBm z6P0spi+!#h{kf(v3&y2fMW^`Xc_EpyySuzem+avva!P373*kzO% zl_qADVt-W;Q=It8RE7v|s-@)V&Q^_Q!@4(ySBYEcx6a~{oy=xa2p%K;wjYhRLrr=r z77@>iBZKV3){V2?f=e;$Lo@GGbC8v0RKa-^SP_sOL=)`tW?($rhr}C{%F=MY@l1lx zHMwQV;v%(cmeSo`3ck-X3-R*wmleSZnow{;6?L)nx(bQ>1kkf=1LpV?$&=d&9N#JN zkT#PDdb&ZFdgd2!uipR;g!@BtTbKl&Yq0T2rwVmnRLo$2S7@2RsvD@tE+Kwr2f|e81 zE+oC^^0xGLvMDEMoV3PPxY<;up%>MRqbW0p9*sgXbiaTc%6nWs6u>0DDT?#%zDM^< zh)WBOgN6$R%B>l^?#f*+M$b90FYcN2Lvr5_mcU-jgn7qtHvRI#VQd#aI|3gl6Qly; z=ds|hid)~BrR{SQz<~EW=pexLp5a05jgbFJ^ock~2EP;0Z}f&|#DG67vF97}hW)@h zW2^9wR74!uvp97M*E8dsI;kB;w{2;6uscO&$Bo==Vl=lyuYwL=8lCv-==e5ZFR zy!huiUgZs5Qt=-RU1QtKdIbboKn$bhhxrV3AJTRgj%B^?yMef*`D&QH_A62X}V0M)&MAU{=7&Be%INeD`-&=u28+3{x3agKlm6|5oa`0x?IBu!8}8&wv||)m$zgk@UH3RJ<@01ORv*&UQkbKZ zZfy{tOt4F&Jx3=#pY~UA&gvR}OT30%#Xtzm^tUHcX(ijzM!xP7WCy{w+cyKNn2&qT zcNFx8dVwhWAp8I`>&bKdul$mGigY4>2IPmV;MC7hI5-4DelQSxN>I6fxnfGvt~II< z+GyW)v7Ak@;kwz^R<2@y`;CGj<-SRPrt(_rwGn1Hl`JVH!fg zZp`inHE_ZK2MQC^24OkLV-AbskJp)Xi26(3u#nfWG2BUnzb~fiV$i#^n2v}7beKx+ z1lsxor7CUR((g;o&WoEq=slB!NlQ#ikGxR3$aC@ytiRrm4@;Gf`0*F6 z2Rn6_6BSmEXX&E2NVFqL?KGOhnypc<6EAf|rP`0X;wmy!tPo7orDiHVlDfB8)wZs14g`Y`>YFE8D+t!j+#PKjUg{YS{_IVdIx7*Li&5~fuqR0}m zzAGQmTp66he@C8Tn*nY3D&PF|^*Q6OM^3**Z@4PFG*A}3z6qH=LB+^39&TZ0qt}o< zv;8z6To1+@-PAISDX=w5+oqD&QnP6l3^Ou%8n;{7Qt4ue7$>LxUGW)DOnrV+Q}yu~ zmBml8#~&{K@(ZNfz1w~c8dOxWpM3%^IG728XeIX2dU>7nZYF1`OEnd^%55d~kl?|r zrbMt@<3mVj`9Fske-zcjr4GSpLgNmM)xpM!UhllAr@tXx~~U`uE&^(fCUJ*|D+F>0Vub_ z(MQk#q}yR?!)*ZC?Fh9IxB&5XX!~#-fOaQlMw zLhlAU40!;$ZunmKKS2C{3Ir1lDFDiDSYEh3e)vQ81se=G0NQRKKM?#80|EsG^8m9q zm@hOR@LveufdPYkfZZFy7lu+Kq(6+Y*i*&`_Z9e#KVdb8jqnDPbi*f|AZmwW9Zj~t zIYy=(UABI-4c9o@Y(egZZtlCc^IZkaTm^US+qd&v1^Mjjw{u*DyzgVhnLtl! z3W3R0?}N+l`?m`a1VZf#c`_0NS2@CzIYC<7D)Pc1j{Ulkb9hyV;bA#OM^}k_s)b)6cL5H!@E`bJ1pi*tu)tp4EyIh(2ksaCchL86z+T_2z>9%2G7^eXCUbHL-jP)# zjB2qFPJxp4zZG|gn&MbXlZ{aJl4(nqjo{Ye8cUmv@Ey_31@~sYOF^Cm`DT_&;jRVy zW}ZtSp9TG9j!TjE1*}+=-+xt!Lu4x#z~vVFn+5O%p%#Q(8S#ayETc-T!p%<=xnmH@ zegP%9qvA?UfSTNKab>7LQSRUJr7A#G?pXOU7N9J5^h~J>P`7g4%Ty@`XNgpd&RQkH z_Marcxm?1}d7_BzP(_efj8)>kSunaeb*2m!DBKxIUn&Ds?u?-?qX9~HM%9+u0JS^g zYRhne;+?4oAQcgO!-c<^e;jOAp@-*WH(wHowq-r4&E}|dwA5}^t$+IJb}32PSEayTxbHfb z@3pcNI6&mMj$Kyp&X!uIqLzwul`Ztzutj8D`R?w8!<|6o*d9uyG`zcc6acwajBAYE z;U$>L%BmSps#5EM<@Hlh6oBoq_MJzXmp>dzPu;e9VPITpQ6E)fS5=neh_Mzf|DBY) z#kE&CI#btGv20oVz$`wm-JF)0Z~Cwwy}$HNx6|Z1(m74tM11X7oZ2WjT8lL<#~9R> zSih9ljNH6;XSqOo(dsgAQKi9?&xBt_Ofit%fO6p*q$JkM887nJ=fm-`sDDg`61e8k{}G z`>9v^#``})6gz_nC!#`fF-pL7zinD_@~BO&Hr&-;HY6hwgPf=E>z}Dv{lVdNssh0F zy~uE~+JE(Y7O0nMzVfYJdwB@!iqcsR)DDx}4^K}Te(nE4A-r||;ZsxDLNbQEa+zmm924D!y}qE`j0(cw%8g>VjGXG;^1eHX19qvnK|DWGdK8c;mYF~m^km2)N0G# z+acU}PYg(|{q}wgT&0F;lYKVrSRjl7lNxi@9^vdHWg?@vcaFqzy6{h%&cHL9i4I0^ zunBdDzvHr9I&{JlzVJ_-=$SEYuwxP7yA?vg4<$dSM|^QS>cupPrVuR(napy9y@iF& z*m3l)U$td+VLy|BqiP&^Sr`Z9m_Yn-#`>yUkNa}-cG~HjZ7dSkG6IELDI8(8bQPDi z->SP6)om(@U@EphzTquVyJbk4Yq$<6@~4ehvUCsYYDLX`=Y(f>B2;}2z7bE!i$%n3 zSG^`2y*!wcqk|%&^;%qCdxm+4;CJSFXCtSu;x8C2>3D^aJLB&)eeU{WRiT+Ob&DeR zb*I`{|G{yg)xF5QO+9pX&p~$!%Ki4k`{t-sMGw{RX&VmCDT&xCq{;E~y>p(jCZx9f;keo|<~ zil$7BWv7x}^->yY{Ab&MC zA-*>H_b7*h`X`Tzw!zGC_{SwFmVX8BH?Qx_6Fpe6KXXQc5g>dSC)2|FIpOG_Llzjy zAr$P53h7~iWY=cF1Pr8$`&G+jxo3wPc;~!T87GXG?<5SnD0jz}TahBLT^$)GEXNmS zTvo5fSW%e6bzGAxBRu$loav+!B)xs7kP;2VL6V&p()C6fr8XsJrcP4kRFKHKlD)mH zW36##Qqcxkl!!j_8!gW6t=5$C`OF1)2f#OTy04qFwZB$z2qO;t&twuT~;5c*ENEE=ZfA)zq*8CZ8#0$}| zor^Y6snM;KG=gJrW{*Ad{?(bJZ6$y=Y{*8|KT-!_@pPpp&x8KY|ZxgYgGfzq(Ts9l~Usv*3=Q|~qX4|Ok4XkqnWEbrn~>>AO|v9ZsgUe*QZ5OCj3PM> z-8;ci^6--vmFzz01Gd}o;Wf#`_5Gks8WA$8zsiy7sNra(XlhjC#pzRGe(!U)Y9_ub zE1dDNFqVz9dZ2PJmdb)jKQhtg4oy4Nv7?dQtWt_8Wt61MvvAVlsKnHwpsB!F`N_k0 z@iFJx14n6;v6O!r>mnTlW3Ad`5iGU7pG)U0YM`u37CmX*QjNW-B- z!1H4e7ZZ^~5SNzA!WcIu+NT&}ucK{65&jgGHL9m-$4VtL|5vc?zk|>Q;#x>%Ldg)s1dM-!%YPPQiF<5k9X{l5jPOl+jaRu*E8bLP8QGBqUD665Mi zu%~&7yewF+|5wyQ{C>uAM{Am=%FBZ7y81Y0xw|RTL;ZdxN`;*5w3<9;xwt9QRXu6O SdSQM28?+M|D(2r_;{O0|uQ74} literal 71508 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 literal 56780 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 diff --git a/apps/templates/_head_css_js.html b/apps/templates/_head_css_js.html index 917e57f18..93ca99462 100644 --- a/apps/templates/_head_css_js.html +++ b/apps/templates/_head_css_js.html @@ -2,7 +2,7 @@ - + diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index f931cbb6d..633858eda 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -51,9 +51,15 @@

  • {% trans 'Session online' %}
  • {% trans 'Session offline' %}
  • {% trans 'Command' %}
  • +
  • + + {% trans 'Web terminal' %} + +
  • + {#
  • #} {# #} {# {% trans 'File' %}#} diff --git a/apps/templates/_nav_user.html b/apps/templates/_nav_user.html index cccb13fc4..f82ad6776 100644 --- a/apps/templates/_nav_user.html +++ b/apps/templates/_nav_user.html @@ -8,4 +8,9 @@ {% trans 'Profile' %} +
  • +
  • + + {% trans 'Web terminal' %} +
  • \ No newline at end of file From 121f56f44bd2f0dd5fad205053a70909ec77b460 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 11 Jan 2018 20:10:27 +0800 Subject: [PATCH 013/197] =?UTF-8?q?[Feature]=20=E6=B7=BB=E5=8A=A0setting?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/signals_handler.py | 3 +- apps/common/api.py | 42 +++++++++++ apps/common/apps.py | 6 ++ apps/common/forms.py | 74 +++++++++++++++++++ apps/common/mixins.py | 14 +++- apps/common/models.py | 47 +++++++++++- apps/common/permissions.py | 52 +++++++++++++ apps/common/serializers.py | 10 +++ apps/common/signals.py | 6 ++ apps/common/signals_handler.py | 27 +++++++ .../templates/common/email_setting.html | 73 ++++++++++++++++++ apps/common/templatetags/common_tags.py | 9 +++ apps/common/urls/api_urls.py | 11 +++ apps/common/urls/view_urls.py | 11 +++ apps/common/views.py | 33 ++++++++- apps/jumpserver/settings.py | 9 --- apps/jumpserver/urls.py | 5 +- apps/users/models/__init__.py | 2 +- apps/users/models/group.py | 6 -- apps/users/models/user.py | 3 +- 20 files changed, 417 insertions(+), 26 deletions(-) create mode 100644 apps/common/api.py create mode 100644 apps/common/forms.py create mode 100644 apps/common/permissions.py create mode 100644 apps/common/serializers.py create mode 100644 apps/common/signals.py create mode 100644 apps/common/signals_handler.py create mode 100644 apps/common/templates/common/email_setting.html create mode 100644 apps/common/urls/api_urls.py create mode 100644 apps/common/urls/view_urls.py diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 63dc3e882..171cfa6d9 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # - -from django.db.models.signals import post_save, post_init, m2m_changed, pre_save +from django.db.models.signals import post_save, post_init from django.dispatch import receiver from django.utils.translation import gettext as _ diff --git a/apps/common/api.py b/apps/common/api.py new file mode 100644 index 000000000..270c81095 --- /dev/null +++ b/apps/common/api.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +from rest_framework.views import APIView +from rest_framework.views import Response +from django.core.mail import get_connection, send_mail +from django.utils.translation import ugettext_lazy as _ + +from .permissions import IsSuperUser +from .serializers import MailTestSerializer + + +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)}) diff --git a/apps/common/apps.py b/apps/common/apps.py index 6664a6438..bc6db2151 100644 --- a/apps/common/apps.py +++ b/apps/common/apps.py @@ -5,3 +5,9 @@ from django.apps import AppConfig class CommonConfig(AppConfig): name = 'common' + + def ready(self): + from . import signals_handler + from .signals import django_ready + django_ready.send(self.__class__) + return super().ready() diff --git a/apps/common/forms.py b/apps/common/forms.py new file mode 100644 index 000000000..60cb1ae37 --- /dev/null +++ b/apps/common/forms.py @@ -0,0 +1,74 @@ +# -*- 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 + + +def to_model_value(value): + try: + return json.dumps(value) + except json.JSONDecodeError: + return None + + +def to_form_value(value): + try: + return json.loads(value) + except json.JSONDecodeError: + return '' + + +class BaseForm(forms.Form): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if not self.is_bound: + 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") + + 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 + defaults = { + 'name': name, + 'value': to_model_value(value) + } + Setting.objects.update_or_create(defaults=defaults, name=name) + else: + raise ValueError(self.errors) + + +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") + ) diff --git a/apps/common/mixins.py b/apps/common/mixins.py index 1371cf65b..2832424c6 100644 --- a/apps/common/mixins.py +++ b/apps/common/mixins.py @@ -1,10 +1,10 @@ # coding: utf-8 -import inspect from django.db import models from django.http import JsonResponse from django.utils import timezone from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.mixins import UserPassesTestMixin class NoDeleteQuerySet(models.query.QuerySet): @@ -113,4 +113,14 @@ class DatetimeSearchMixin: ) else: self.date_to = timezone.now() - return super().get(request, *args, **kwargs) \ No newline at end of file + return super().get(request, *args, **kwargs) + + +class AdminUserRequiredMixin(UserPassesTestMixin): + def test_func(self): + if not self.request.user.is_authenticated: + return False + elif not self.request.user.is_superuser: + self.raise_exception = True + return False + return True diff --git a/apps/common/models.py b/apps/common/models.py index beeb30826..3d6ee6008 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -1,2 +1,47 @@ -from django.db import models +import json +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings + + +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 + + @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) + + class Meta: + db_table = "settings" diff --git a/apps/common/permissions.py b/apps/common/permissions.py new file mode 100644 index 000000000..6a1cb8230 --- /dev/null +++ b/apps/common/permissions.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import permissions + + +class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission): + """Allows access to valid user, is active and not expired""" + + def has_permission(self, request, view): + return super(IsValidUser, self).has_permission(request, view) \ + and request.user.is_valid + + +class IsAppUser(IsValidUser): + """Allows access only to app user """ + + def has_permission(self, request, view): + return super(IsAppUser, self).has_permission(request, view) \ + and request.user.is_app + + +class IsSuperUser(IsValidUser): + """Allows access only to superuser""" + + def has_permission(self, request, view): + return super(IsSuperUser, self).has_permission(request, view) \ + and request.user.is_superuser + + +class IsSuperUserOrAppUser(IsValidUser): + """Allows access between superuser and app user""" + + def has_permission(self, request, view): + return super(IsSuperUserOrAppUser, self).has_permission(request, view) \ + and (request.user.is_superuser or request.user.is_app) + + +class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser): + def has_permission(self, request, view): + if IsValidUser.has_permission(self, request, view) \ + and request.method in permissions.SAFE_METHODS: + return True + else: + return IsSuperUserOrAppUser.has_permission(self, request, view) + + +class IsCurrentUserOrReadOnly(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + if request.method in permissions.SAFE_METHODS: + return True + return obj == request.user diff --git a/apps/common/serializers.py b/apps/common/serializers.py new file mode 100644 index 000000000..37e6555a3 --- /dev/null +++ b/apps/common/serializers.py @@ -0,0 +1,10 @@ +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) diff --git a/apps/common/signals.py b/apps/common/signals.py new file mode 100644 index 000000000..de8a84139 --- /dev/null +++ b/apps/common/signals.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# + +from django.dispatch import Signal + +django_ready = Signal() diff --git a/apps/common/signals_handler.py b/apps/common/signals_handler.py new file mode 100644 index 000000000..6b54706db --- /dev/null +++ b/apps/common/signals_handler.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# + +from django.dispatch import receiver +from django.db.models.signals import post_save + +from .models import Setting +from .utils import get_logger +from .signals import django_ready + + +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") + Setting.refresh_all_settings() diff --git a/apps/common/templates/common/email_setting.html b/apps/common/templates/common/email_setting.html new file mode 100644 index 000000000..d6d7fdf16 --- /dev/null +++ b/apps/common/templates/common/email_setting.html @@ -0,0 +1,73 @@ +{% extends 'base.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block custom_head_css_js %} + + +{% endblock %} +{% block content %} +
    +
    +
    +
    + +
    +
    +
    +
    + {% if form.non_field_errors %} +
    + {{ form.non_field_errors }} +
    + {% endif %} + {% csrf_token %} + {% for field in form %} + {% if not field.field|is_bool_field %} + {% bootstrap_field field layout="horizontal" %} + {% else %} +
    + +
    +
    + {{ field }} +
    +
    + {{ field.help_text }} +
    +
    +
    + {% endif %} + {% endfor %} +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    + +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/common/templatetags/common_tags.py b/apps/common/templatetags/common_tags.py index 9c3e58871..c10c228c8 100644 --- a/apps/common/templatetags/common_tags.py +++ b/apps/common/templatetags/common_tags.py @@ -4,6 +4,7 @@ from django import template from django.utils import timezone from django.utils.translation import gettext as _ from django.utils.html import escape +from django import forms register = template.Library() @@ -83,3 +84,11 @@ def time_util_with_seconds(date_from, date_to): return '{} h'.format(seconds//3600) else: return '' + + +@register.filter +def is_bool_field(field): + if isinstance(field, forms.BooleanField): + return True + else: + return False diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py new file mode 100644 index 000000000..81acabae1 --- /dev/null +++ b/apps/common/urls/api_urls.py @@ -0,0 +1,11 @@ +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'), +] diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py new file mode 100644 index 000000000..c2ea04207 --- /dev/null +++ b/apps/common/urls/view_urls.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import + +from django.conf.urls import url + +from .. import views + +app_name = 'common' + +urlpatterns = [ + url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'), +] diff --git a/apps/common/views.py b/apps/common/views.py index 4cee32ba7..f6e32af5e 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -1,2 +1,33 @@ -from __future__ import absolute_import, unicode_literals +from django.views.generic import View +from django.shortcuts import render +from django.contrib import messages +from django.utils.translation import ugettext as _ +from .forms import EmailSettingForm +from .mixins import AdminUserRequiredMixin + + +class EmailSettingView(AdminUserRequiredMixin, View): + form_class = EmailSettingForm + template_name = "common/email_setting.html" + + def get(self, request): + context = { + 'app': 'settings', + 'action': 'Email setting', + "form": EmailSettingForm(), + } + return render(request, self.template_name, context) + + def post(self, request): + form = self.form_class(request.POST) + if form.is_valid(): + form.save() + messages.success(request, _("Update email setting successfully")) + + context = { + 'app': 'settings', + 'action': 'Email setting', + "form": EmailSettingForm(), + } + return render(request, self.template_name, context) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index ce2d06ed0..5d265aed3 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -121,15 +121,6 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases -# if CONFIG.DB_ENGINE == 'sqlite': -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.sqlite3', -# 'NAME': CONFIG.DB_NAME or os.path.join(BASE_DIR, 'data', 'db.sqlite3'), -# 'ATOMIC_REQUESTS': True, -# } -# } - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.{}'.format(CONFIG.DB_ENGINE), diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 839492e7a..2eb54d87d 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -19,6 +19,8 @@ urlpatterns = [ url(r'^perms/', include('perms.urls.views_urls', namespace='perms')), url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')), url(r'^ops/', include('ops.urls.view_urls', namespace='ops')), + url(r'^settings/', include('common.urls.view_urls', namespace='settings')), + url(r'^common/', include('common.urls.view_urls', namespace='common')), # Api url view map url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')), @@ -26,13 +28,12 @@ urlpatterns = [ url(r'^api/perms/', include('perms.urls.api_urls', namespace='api-perms')), url(r'^api/terminal/', include('terminal.urls.api_urls', namespace='api-terminal')), url(r'^api/ops/', include('ops.urls.api_urls', namespace='api-ops')), + url(r'^api/common/', include('common.urls.api_urls', namespace='api-common')), # External apps url url(r'^captcha/', include('captcha.urls')), - ] - if settings.DEBUG: urlpatterns += [ url(r'^docs/', schema_view, name="docs"), diff --git a/apps/users/models/__init__.py b/apps/users/models/__init__.py index 269f68bd4..f3c9d1941 100644 --- a/apps/users/models/__init__.py +++ b/apps/users/models/__init__.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # -from .group import * from .user import * +from .group import * from .authentication import * from .utils import * diff --git a/apps/users/models/group.py b/apps/users/models/group.py index a0c7a8b0f..b4b7aacc1 100644 --- a/apps/users/models/group.py +++ b/apps/users/models/group.py @@ -20,12 +20,6 @@ class UserGroup(NoDeleteModelMixin): def __str__(self): return self.name - def delete(self, using=None, keep_parents=False): - if self.name != 'Default': - self.users.clear() - return super(UserGroup, self).delete() - return True - class Meta: ordering = ['name'] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 899cec4cb..031e4fa77 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -13,7 +13,6 @@ from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.shortcuts import reverse -from .group import UserGroup from common.utils import get_signer, date_expired_default @@ -35,7 +34,7 @@ class User(AbstractUser): username = models.CharField(max_length=128, unique=True, verbose_name=_('Username')) name = models.CharField(max_length=128, verbose_name=_('Name')) email = models.EmailField(max_length=128, unique=True, verbose_name=_('Email')) - groups = models.ManyToManyField(UserGroup, related_name='users', blank=True, verbose_name=_('User group')) + groups = models.ManyToManyField('users.UserGroup', related_name='users', blank=True, verbose_name=_('User group')) role = models.CharField(choices=ROLE_CHOICES, default='User', max_length=10, blank=True, verbose_name=_('Role')) avatar = models.ImageField(upload_to="avatar", null=True, verbose_name=_('Avatar')) wechat = models.CharField(max_length=128, blank=True, verbose_name=_('Wechat')) From abb1a40a4f7efdc7332198b25e0c1494aaadb216 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 12 Jan 2018 15:43:26 +0800 Subject: [PATCH 014/197] =?UTF-8?q?[Feature]=20=E6=B7=BB=E5=8A=A0basic=20s?= =?UTF-8?q?ettings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/views/admin_user.py | 2 +- apps/assets/views/asset.py | 2 +- apps/common/api.py | 72 ++++++++++++- apps/common/fields.py | 37 +++++++ apps/common/forms.py | 71 ++++++++++-- apps/common/models.py | 21 ++++ apps/common/serializers.py | 11 ++ apps/common/signals.py | 1 + apps/common/signals_handler.py | 21 +++- .../templates/common/basic_setting.html | 101 ++++++++++++++++++ .../templates/common/email_setting.html | 40 +++++-- .../common/templates/common/ldap_setting.html | 100 +++++++++++++++++ apps/common/urls/api_urls.py | 2 + apps/common/urls/view_urls.py | 2 + apps/common/utils.py | 2 +- apps/common/views.py | 78 +++++++++++--- apps/jumpserver/settings.py | 39 ++++--- apps/ops/views.py | 2 +- apps/perms/views.py | 4 +- apps/static/js/jumpserver.js | 13 ++- apps/templates/_nav.html | 10 +- apps/terminal/views/command.py | 2 +- apps/terminal/views/session.py | 2 +- apps/users/authentication.py | 2 +- apps/users/models/user.py | 7 +- apps/users/signals.py | 15 +-- apps/users/utils.py | 4 +- apps/users/views/login.py | 4 +- apps/users/views/user.py | 1 - 29 files changed, 592 insertions(+), 76 deletions(-) create mode 100644 apps/common/fields.py create mode 100644 apps/common/templates/common/basic_setting.html create mode 100644 apps/common/templates/common/ldap_setting.html diff --git a/apps/assets/views/admin_user.py b/apps/assets/views/admin_user.py index e3f6bc6b3..ce7b8bfb4 100644 --- a/apps/assets/views/admin_user.py +++ b/apps/assets/views/admin_user.py @@ -85,7 +85,7 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView): class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView): - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE template_name = 'assets/admin_user_assets.html' context_object_name = 'admin_user' object = None diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index d251b7a15..b2f57e323 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -92,7 +92,7 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): class AssetModalListView(AdminUserRequiredMixin, ListView): - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE model = Asset context_object_name = 'asset_modal_list' template_name = 'assets/asset_modal_list.html' diff --git a/apps/common/api.py b/apps/common/api.py index 270c81095..e8680e85e 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -1,12 +1,16 @@ # -*- 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 +from .serializers import MailTestSerializer, LDAPTestSerializer class MailTestingAPI(APIView): @@ -27,7 +31,6 @@ class MailTestingAPI(APIView): "use_tls": serializer.validated_data["EMAIL_USE_TLS"] } connection = get_connection(timeout=5, **kwargs) - try: connection.open() except Exception as e: @@ -40,3 +43,68 @@ class MailTestingAPI(APIView): 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) + diff --git a/apps/common/fields.py b/apps/common/fields.py new file mode 100644 index 000000000..36a8bdf9a --- /dev/null +++ b/apps/common/fields.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +import json + +from django import forms +from django.utils import six +from django.core.exceptions import ValidationError + + +class DictField(forms.Field): + widget = forms.Textarea + + def to_python(self, value): + """Returns a Python boolean object.""" + # Explicitly check for the string 'False', which is what a hidden field + # will submit for False. Also check for '0', since this is what + # RadioSelect will provide. Because bool("True") == bool('1') == True, + # we don't need to handle that explicitly. + if isinstance(value, six.string_types): + try: + print(value) + value = json.loads(value) + return value + except json.JSONDecodeError: + pass + value = {} + return value + + def validate(self, value): + print(value) + if not value and self.required: + raise ValidationError(self.error_messages['required'], code='required') + + def has_changed(self, initial, data): + # Sometimes data or initial may be a string equivalent of a boolean + # so we should run it through to_python first to get a boolean value + return self.to_python(initial) != self.to_python(data) diff --git a/apps/common/forms.py b/apps/common/forms.py index 60cb1ae37..073bb671f 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -7,6 +7,7 @@ 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): @@ -18,7 +19,10 @@ def to_model_value(value): def to_form_value(value): try: - return json.loads(value) + data = json.loads(value) + if isinstance(data, dict): + data = value + return data except json.JSONDecodeError: return '' @@ -26,23 +30,26 @@ def to_form_value(value): class BaseForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if not self.is_bound: - 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) + 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) @@ -52,6 +59,24 @@ class BaseForm(forms.Form): 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] " + ) + AUTH_LDAP = forms.BooleanField( + label=_("Enable LDAP Auth"), initial=False, required=False + ) + + class EmailSettingForm(BaseForm): EMAIL_HOST = forms.CharField( max_length=1024, label=_("SMTP host"), initial='smtp.jumpserver.org' @@ -72,3 +97,35 @@ class EmailSettingForm(BaseForm): 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 + ) diff --git a/apps/common/models.py b/apps/common/models.py index 3d6ee6008..091b8b83a 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -1,8 +1,10 @@ import json +import ldap from django.db import models from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django_auth_ldap.config import LDAPSearch class SettingQuerySet(models.QuerySet): @@ -30,6 +32,13 @@ class Setting(models.Model): 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() @@ -43,5 +52,17 @@ class Setting(models.Model): 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" diff --git a/apps/common/serializers.py b/apps/common/serializers.py index 37e6555a3..9d389776d 100644 --- a/apps/common/serializers.py +++ b/apps/common/serializers.py @@ -8,3 +8,14 @@ class MailTestSerializer(serializers.Serializer): 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) + diff --git a/apps/common/signals.py b/apps/common/signals.py index de8a84139..6edf140e2 100644 --- a/apps/common/signals.py +++ b/apps/common/signals.py @@ -4,3 +4,4 @@ from django.dispatch import Signal django_ready = Signal() +ldap_auth_enable = Signal(providing_args=["enabled"]) diff --git a/apps/common/signals_handler.py b/apps/common/signals_handler.py index 6b54706db..076ea7925 100644 --- a/apps/common/signals_handler.py +++ b/apps/common/signals_handler.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- # - +import ldap from django.dispatch import receiver from django.db.models.signals import post_save +from django.conf import settings +from django_auth_ldap.config import LDAPSearch from .models import Setting from .utils import get_logger -from .signals import django_ready - +from .signals import django_ready, ldap_auth_enable logger = get_logger(__file__) @@ -25,3 +26,17 @@ def refresh_all_settings_on_django_ready(sender, **kwargs): logger.debug("Receive django ready signal") logger.debug(" - fresh all settings") Setting.refresh_all_settings() + + +@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) + diff --git a/apps/common/templates/common/basic_setting.html b/apps/common/templates/common/basic_setting.html new file mode 100644 index 000000000..e24f1d6a8 --- /dev/null +++ b/apps/common/templates/common/basic_setting.html @@ -0,0 +1,101 @@ +{% extends 'base.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block content %} +
    +
    +
    +
    + +
    +
    +
    +
    + {% if form.non_field_errors %} +
    + {{ form.non_field_errors }} +
    + {% endif %} + {% csrf_token %} + {% for field in form %} + {% if not field.field|is_bool_field %} + {% bootstrap_field field layout="horizontal" %} + {% else %} +
    + +
    +
    + {{ field }} +
    +
    + {{ field.help_text }} +
    +
    +
    + {% endif %} + {% endfor %} +
    +
    +
    + + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    + +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/common/templates/common/email_setting.html b/apps/common/templates/common/email_setting.html index d6d7fdf16..7561849bd 100644 --- a/apps/common/templates/common/email_setting.html +++ b/apps/common/templates/common/email_setting.html @@ -4,10 +4,6 @@ {% load i18n %} {% load common_tags %} -{% block custom_head_css_js %} - - -{% endblock %} {% block content %}
    @@ -16,10 +12,13 @@ @@ -53,6 +52,7 @@
    +
    @@ -69,5 +69,33 @@ {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/common/templates/common/ldap_setting.html b/apps/common/templates/common/ldap_setting.html new file mode 100644 index 000000000..26f021569 --- /dev/null +++ b/apps/common/templates/common/ldap_setting.html @@ -0,0 +1,100 @@ +{% extends 'base.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block content %} +
    +
    +
    +
    + +
    +
    +
    +
    + {% if form.non_field_errors %} +
    + {{ form.non_field_errors }} +
    + {% endif %} + {% csrf_token %} + {% for field in form %} + {% if not field.field|is_bool_field %} + {% bootstrap_field field layout="horizontal" %} + {% else %} +
    + +
    +
    + {{ field }} +
    +
    + {{ field.help_text }} +
    +
    +
    + {% endif %} + {% endfor %} +
    +
    +
    + + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py index 81acabae1..a9075e66e 100644 --- a/apps/common/urls/api_urls.py +++ b/apps/common/urls/api_urls.py @@ -8,4 +8,6 @@ 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'), ] diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py index c2ea04207..ff8086bde 100644 --- a/apps/common/urls/view_urls.py +++ b/apps/common/urls/view_urls.py @@ -7,5 +7,7 @@ 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'), ] diff --git a/apps/common/utils.py b/apps/common/utils.py index 753fdd541..f366e6786 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -91,7 +91,7 @@ class Signer(metaclass=Singleton): def date_expired_default(): try: - years = int(settings.CONFIG.DEFAULT_EXPIRED_YEARS) + years = int(settings.DEFAULT_EXPIRED_YEARS) except TypeError: years = 70 return timezone.now() + timezone.timedelta(days=365*years) diff --git a/apps/common/views.py b/apps/common/views.py index f6e32af5e..ec9e54995 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -1,33 +1,85 @@ -from django.views.generic import View -from django.shortcuts import render +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 +from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm from .mixins import AdminUserRequiredMixin +from .signals import ldap_auth_enable -class EmailSettingView(AdminUserRequiredMixin, View): +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() + if "AUTH_LDAP" in form.cleaned_data: + ldap_auth_enable.send(form.cleaned_data["AUTH_LDAP"]) + messages.success(request, _("Update basic setting successfully")) + 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(self, request): + def get_context_data(self, **kwargs): context = { - 'app': 'settings', - 'action': 'Email setting', - "form": EmailSettingForm(), + 'app': _('Settings'), + 'action': _('Email setting'), + 'form': self.form_class(), } - return render(request, self.template_name, context) + 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() messages.success(request, _("Update email setting successfully")) + 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': 'Email setting', - "form": EmailSettingForm(), + 'app': _('Settings'), + 'action': _('LDAP setting'), + 'form': self.form_class(), } - return render(request, self.template_name, context) + 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() + messages.success(request, _("Update ldap setting successfully")) + return redirect('settings:ldap-setting') + else: + context = self.get_context_data() + context.update({"form": form}) + return render(request, self.template_name, context) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 5d265aed3..5bdc932e3 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -15,7 +15,6 @@ import sys import ldap from django_auth_ldap.config import LDAPSearch - from django.urls import reverse_lazy @@ -303,18 +302,28 @@ AUTH_USER_MODEL = 'users.User' # Auth LDAP settings -if CONFIG.AUTH_LDAP: - AUTHENTICATION_BACKENDS.insert(0, 'django_auth_ldap.backend.LDAPBackend') - AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI - AUTH_LDAP_BIND_DN = CONFIG.AUTH_LDAP_BIND_DN - AUTH_LDAP_BIND_PASSWORD = CONFIG.AUTH_LDAP_BIND_PASSWORD - AUTH_LDAP_USER_SEARCH = LDAPSearch( - CONFIG.AUTH_LDAP_SEARCH_OU, - ldap.SCOPE_SUBTREE, - CONFIG.AUTH_LDAP_SEARCH_FILTER - ) - AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS - AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP +AUTH_LDAP = CONFIG.AUTH_LDAP +AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI +AUTH_LDAP_BIND_DN = CONFIG.AUTH_LDAP_BIND_DN +AUTH_LDAP_BIND_PASSWORD = CONFIG.AUTH_LDAP_BIND_PASSWORD +AUTH_LDAP_SEARCH_OU = CONFIG.AUTH_LDAP_SEARCH_OU +AUTH_LDAP_SEARCH_FILTER = CONFIG.AUTH_LDAP_SEARCH_FILTER +AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS +AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP +AUTH_LDAP_USER_SEARCH = LDAPSearch( + AUTH_LDAP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER, +) +AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU +AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER +AUTH_LDAP_GROUP_SEARCH = LDAPSearch( + AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER +) +AUTH_LDAP_ALWAYS_UPDATE_USER = True +AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend' + +if AUTH_LDAP: + AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND) + # Celery using redis as broker CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % { @@ -367,3 +376,7 @@ BOOTSTRAP3 = { 'set_placeholder': True, 'success_css_class': '', } + +TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600 +DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE +DEFAULT_EXPIRED_YEARS = 70 diff --git a/apps/ops/views.py b/apps/ops/views.py index 4b090a8a3..ba9e2cfeb 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -10,7 +10,7 @@ from .hands import AdminUserRequiredMixin class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE model = Task ordering = ('-date_created',) context_object_name = 'task_list' diff --git a/apps/perms/views.py b/apps/perms/views.py index 6fb2cbca7..39a901557 100644 --- a/apps/perms/views.py +++ b/apps/perms/views.py @@ -93,7 +93,7 @@ class AssetPermissionUserView(AdminUserRequiredMixin, ListView): template_name = 'perms/asset_permission_user.html' context_object_name = 'asset_permission' - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE object = None def get(self, request, *args, **kwargs): @@ -123,7 +123,7 @@ class AssetPermissionAssetView(AdminUserRequiredMixin, ListView): template_name = 'perms/asset_permission_asset.html' context_object_name = 'asset_permission' - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE object = None def get(self, request, *args, **kwargs): diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 27b73dce0..119151897 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -157,6 +157,11 @@ function APIUpdateAttr(props) { props = props || {}; var success_message = props.success_message || '更新成功!'; var fail_message = props.fail_message || '更新时发生未知错误.'; + var flash_message = true; + if (props.flash_message === false){ + flash_message = false; + } + $.ajax({ url: props.url, type: props.method || "PATCH", @@ -164,12 +169,16 @@ function APIUpdateAttr(props) { contentType: props.content_type || "application/json; charset=utf-8", dataType: props.data_type || "json" }).done(function(data, textStatue, jqXHR) { - toastr.success(success_message); + if (flash_message) { + toastr.success(success_message); + } if (typeof props.success === 'function') { return props.success(data); } }).fail(function(jqXHR, textStatus, errorThrown) { - toastr.error(fail_message); + if (flash_message) { + toastr.error(fail_message); + } if (typeof props.error === 'function') { return props.error(jqXHR.responseText); } diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 633858eda..1493e55dc 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -69,8 +69,8 @@ {#
  • {% trans 'File download' %}
  • #} {# #} {##} -{#
  • #} -{# #} -{# {% trans 'Settings' %}#} -{# #} -{#
  • #} \ No newline at end of file +
  • + + {% trans 'Settings' %} + +
  • \ No newline at end of file diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index 2bd588ff6..8b0479d3e 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -19,7 +19,7 @@ class CommandListView(DatetimeSearchMixin, ListView): model = Command template_name = "terminal/command_list.html" context_object_name = 'command_list' - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE command = user = asset = system_user = "" date_from = date_to = None diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py index 087e16b68..22d04fbaf 100644 --- a/apps/terminal/views/session.py +++ b/apps/terminal/views/session.py @@ -26,7 +26,7 @@ class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): model = Session template_name = 'terminal/session_list.html' context_object_name = 'session_list' - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE user = asset = system_user = '' date_from = date_to = None diff --git a/apps/users/authentication.py b/apps/users/authentication.py index 09321953e..647f9a776 100644 --- a/apps/users/authentication.py +++ b/apps/users/authentication.py @@ -113,7 +113,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication): class AccessTokenAuthentication(authentication.BaseAuthentication): keyword = 'Bearer' model = User - expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 + expiration = settings.TOKEN_EXPIRATION or 3600 def authenticate(self, request): auth = authentication.get_authorization_header(request).split() diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 031e4fa77..4ae0e62c4 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -148,12 +148,7 @@ class User(AbstractUser): def save(self, *args, **kwargs): if not self.name: self.name = self.username - super().save(*args, **kwargs) - # Add the current user to the default group. - if not self.groups.count(): - group = UserGroup.initial() - self.groups.add(group) @property def private_token(self): @@ -253,6 +248,7 @@ class User(AbstractUser): #: Use this method initial user @classmethod def initial(cls): + from .group import UserGroup user = cls(username='admin', email='admin@jumpserver.org', name=_('Administrator'), @@ -268,6 +264,7 @@ class User(AbstractUser): from random import seed, choice import forgery_py from django.db import IntegrityError + from .group import UserGroup seed() for i in range(count): diff --git a/apps/users/signals.py b/apps/users/signals.py index 537cfb329..39e57f5a5 100644 --- a/apps/users/signals.py +++ b/apps/users/signals.py @@ -2,16 +2,19 @@ # from django.dispatch import Signal, receiver +from django.db.models.signals import post_save from common.utils import get_logger +from .models import User logger = get_logger(__file__) -on_user_created = Signal(providing_args=['user', 'request']) -@receiver(on_user_created) -def send_user_add_mail_to_user(sender, user=None, **kwargs): - from .utils import send_user_created_mail - logger.debug("Receive asset create signal, update asset hardware info") - send_user_created_mail(user) +@receiver(post_save, sender=User) +def on_user_created(sender, instance=None, created=False, **kwargs): + if created: + logger.debug("Receive user `{}` create signal".format(instance.name)) + from .utils import send_user_created_mail + logger.info(" - Sending welcome mail ...".format(instance.name)) + send_user_created_mail(instance) diff --git a/apps/users/utils.py b/apps/users/utils.py index 02ab846f5..685bf31d6 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -151,12 +151,12 @@ def check_user_valid(**kwargs): return None, _('Password or SSH public key invalid') -def refresh_token(token, user, expiration=settings.CONFIG.TOKEN_EXPIRATION or 3600): +def refresh_token(token, user, expiration=settings.TOKEN_EXPIRATION or 3600): cache.set(token, user.id, expiration) def generate_token(request, user): - expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 + expiration = settings.TOKEN_EXPIRATION or 3600 remote_addr = request.META.get('REMOTE_ADDR', '') if not isinstance(remote_addr, bytes): remote_addr = remote_addr.encode("utf-8") diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 493e4936c..a5b78cc73 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -185,7 +185,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView): user.is_public_key_valid = True user.save() context = { - 'user_guide_url': settings.CONFIG.USER_GUIDE_URL + 'user_guide_url': settings.USER_GUIDE_URL } return render(self.request, 'users/first_login_done.html', context) @@ -216,7 +216,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView): class LoginLogListView(DatetimeSearchMixin, ListView): template_name = 'users/login_log_list.html' model = LoginLog - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE user = keyword = "" date_to = date_from = None diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 6b86ddc1b..9110e2887 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -76,7 +76,6 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): user = form.save(commit=False) user.created_by = self.request.user.username or 'System' user.save() - on_user_created.send(self.__class__, user=user) return super().form_valid(form) From d48f8dceb500f43ff8c1d206c20faee1f1ab66a8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 12 Jan 2018 15:55:08 +0800 Subject: [PATCH 015/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/views.py | 6 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 30048 -> 31604 bytes apps/locale/zh/LC_MESSAGES/django.po | 230 +++++++++++++++++++++------ apps/templates/_nav.html | 5 - 4 files changed, 181 insertions(+), 60 deletions(-) diff --git a/apps/common/views.py b/apps/common/views.py index ec9e54995..9a0a314ec 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -27,7 +27,7 @@ class BasicSettingView(AdminUserRequiredMixin, TemplateView): form.save() if "AUTH_LDAP" in form.cleaned_data: ldap_auth_enable.send(form.cleaned_data["AUTH_LDAP"]) - messages.success(request, _("Update basic setting successfully")) + messages.success(request, _("Update setting successfully")) return redirect('settings:basic-setting') else: context = self.get_context_data() @@ -52,7 +52,7 @@ class EmailSettingView(AdminUserRequiredMixin, TemplateView): form = self.form_class(request.POST) if form.is_valid(): form.save() - messages.success(request, _("Update email setting successfully")) + messages.success(request, _("Update setting successfully")) return redirect('settings:email-setting') else: context = self.get_context_data() @@ -77,7 +77,7 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView): form = self.form_class(request.POST) if form.is_valid(): form.save() - messages.success(request, _("Update ldap setting successfully")) + messages.success(request, _("Update setting successfully")) return redirect('settings:ldap-setting') else: context = self.get_context_data() diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 5b42f9d18af67e2dc07a01530827036f620857cf..9b898fe2fa4192e3ad0847f32f3b2e9448a632c4 100644 GIT binary patch delta 12471 zcmZwN2Ygi3w#V@kAfXx{^d>`Z0fA5ikrp~cLK6fLLNbs*NMeeDiW7=7kpKe-(nJKk ziUEvBuNJ_HD2j@pm`MSzsN4%EmiPP5S-j`HpLaf=mG55b?6dbi`26O|n25bg6GRUPMaE5})kpI|mV80k2lhB(eQxB+*yA*VjHt>bX1 z)2N-}RKpHf9|vJ`oND?pjQk)npU-)VMrjJ)+KD3bDpsU^9ZOq%tiG-j+*#1s{dK6fL~)c^E*W}s^bmR0O1|nJE@K8*aEeXPN)F~ zp!(f!^+^~(o{4${3sBdsLG8>|)QufR?a(PKi|5d%j+baO#;d3Ss&;goI@knjU@z1& zeE@4?D(bq$sDYowUibnI#(dO*+IDiEbu?<6L8$s9)D2{HV*fS3Yzo@qC8(`hkF9Vs z>drqzt@t9|iN&aws7zRER{UEd8gUOx=JGo9Igy<87lhb+@) zE;9Y7t=oXwq3x)b>=0_AcTp34h`N!ps2d5O7IxXr|Ae~!CThI0zPsHDs~ zp{T7+L`|4w^(V0^`Ln2rx1;*KVfiuCPMos*3)F-cQ49DHb-nK|8hRF0ySUHNgBq|E zYQiYgyU-1_bz@KyO+`J+N3jMj#+vv7YO4>RCVU5V-Djx&1*rLcb?ZJSw5vOD4b;R9 zQ7deT8t5L>R`x|rJP5V0(Uwm{4LBY3F3mLOq59>Z7O)02{wt_Q^g342=l?hj4~5TB z&+>+K_|x+6d)x`CVHM6dKrN^vYJpu*6Zb()I22pr7|Z9P-hm~kEnk5x@Fl!MpZ`y2 z=#DR<2Kvdog<5e~H+SH0)XQ1}wa_|NZ-|qT|W`ELy4#bW?DWAeLAs- zh9+Kx+M4H4ceoQZ@NU%BA23g%COV7N@q5%x{DzvK)V=QYci@BM5qKA-q8`O2)V#ay zW&btNAqu_mIBI~ZYiHqEG{L$7bDJ5 z-)$a6-MH_48tQNc8{l`S6D51O3o3^ixH{@(s*8F@8l&#K1L}^tqZS@*^;pyb#$s)Z zM?JbHQRB=;7UXjl(a?n}QC|?7Q44wlHNab_xBdgvg{M&qI)}QW3#bJbqx%1Zy8c(x zBP_!Up)aVrQCl914RI2BnBRGdhVEz^*1r@{#9gSJK5rJIPj`5oh9>+S^&KA8*WK#+sF$-DYK!kiZEX+K zo!n>nSkz0IgzA@VF0k`UF_iiS)HmQ3L=Mbw1b%>$?#dJnbrUs?Ss>I=;GCyg>RA_lqx)IsHqFcc%P z8Ma5gjAKy?^r9x5idsOr|&LyHI$UtOKr|0XmvP$$%uMx*Y0 z7-}a*n-fu6nS|;$6E)xh)H|>oJ-7w6ptrFFoQScRM0^%~TG34! z8aQI8yYkvtj=VnVC2Wb|7;Wc=V+r!nsE_4XJO2=tC!dab)-$c1jru~$LEYdhSOE_X z<@xKGp0W<-QE&GR)DBdPbuVa-70LUczM@B>2h&h*^>WlW+fe=Xp%!uqHQ`y*4HlYL zQIGCMEc>r#6*A1dAPnyyk3hYY9@L$8LJiOtbtf^X9ZE#?&#-(pYT_lR>(-!N#+OhZ zx35vxUBycHn~z3i8s&$(E3bzdpfzfRT~HH5qXry?dKV_z`FR*Yz7(~v&8YDXq9*(Z z^%7q~z0_AwH}*4XJYVS%?p9VuJ=4ypj$O^3sAt&^wF5Dz3rArUOh8RA1NBU2n@dsG ztwRsKfDQ01Y>fdNsLy|uk?w%wQ5Pgzo`EqTe3#=)>ZeE1#zFVVnr8w=Fuya|3Ny`Y)YdOR-O+NZZ$!OZTdls^^5dv2 zJ!5&1oxf>$D8B-0oNB0v8=5VoKL2g4&>7XSyEzzjCu7WMsD(X=`g|`$4Y&*M#J#BN zzrZS(k9v1*qWXo6cfG@`iow7C*P)>co0}a`ciP+X;pPOZC!-efgyr+i)#f%k|EA?f zQRAOBzcDY5Xa9BL28G60>LIsd3#>!l3AF>mO)u&TX$I=L<>p50LH;WC$1A8Ch??N` zi$;wTYk9nxHo@m^$t()GU><6#R-)e4b*KvuVek&D{tc?%UuM`u+gen=s+KoEeL9+> z>g`bDbw}OEFdq$lg-%6nX^J_+oMSFQ4Y1nWf?B{%a}VnJ!>AiMi8_DIyl57n7VuA0 zKc5rl4qOTK($urO9cqQ$Egy{JOO7sPTNLev7QW+Wd#r4`M}q{!h`+1z)1R z;l2-^;3cy9E!3Td#=8SnFl(7j%_y^{8H2iRtmSc*r=UKTSs48De=ZGewck2yN3A^1 z@}riYK;7|YR?oNk74xRmLlbPFs0GwAJ*Yn$nqdR%g2B)KI2xKT(abXEpa#gXe6{6U z%va1^sDbyQ#(59*$iA@im(8D1JAD(?uY}h={}sLNiMps0O;Hm^VM*+2d3QVC7qt_^ zEuVz?V>K0Z-DBo#>_WZ}b;rk13(H5%bHmH?*NRF_=9dZ#N9FOT38rHh&O%Kv4@+Q< zxdPjeuQK1p0p!=PCw80SIt#wT|T<0$G5KQ%9)1}rwOqh8XINv?HK=R2bAthYJP z>akdc`gqGHn;B-dtIv6chF0<%w#3&^1D-Ev&v7 zi5kC)Z|n;)I=++zSi<>mcNGjg=4?vr%`YBH>m6WLM=Qj*|m~c+iZ*# zINt_?pZ|MkXvG6i1B^pGyGf{lrs18Kg%xlms^4bR#4lqjd=E9wb<~ZNo$me$)&%R3 z_r(erZ>CM>^RIzsQP4#5P+PMUReuRJV6NrIPy?Ja&tiGwYssQr!M!Q1xo4 zM^P8`x9!Lj_CL5H3YsVlTjE^Q1-Yn&?Y8`g`7Y|Z56!Po*B4s;8gu>U%7k%Cr!)jE`U z)cwY*gX-AT?2j5?q@AB)_4!u!TfWWALydnFwXoCXSEvQ%`>b)r`~@}Puc(Q_X1D{? zLVYE-Kn>I$wFBKykE}Q9x)J6?)Wp-VE6zfVw-Oa)-2T*@FO*EHa68T71W$NwZLN9g)raW3l6zoBFjI(iVF z1zY@DLhT;f!|@e-3VWhnS3Qp($vY7Z2_1UqIu4L$;|oM5q8alCj|ceKf|H*UtBD1K z9{5P&25lWB@o^%|?ozcc2t6nrbBH9)>kZt7>+vhRMa&_hh~~t5)OF0F9bxka3my1* zieeb?F|mn!4C)BQM#OyLKJq-Pe?@yR?W4q5+BJyIgpLbD0rkGP2#?@5#O>o3YQ9JK zahl2rtDZBzpnjBgUmQy4;1`DAp;xt+wO_*&&R-@D3pcr z4^Dhr{cZnW=Hycp*IVZ&X#YUGO+E=X;z~O=$fWGtLwhKeVbJgXR-Z@vb9Jy|JI*Hf z4srT$PE(JsNyAHH8u1zJj`$HVSszaS#73oj_fu_8bRo(zg$|$EZqunxdl{ia|JFE! z*zKlHE~b-LuzV}NK|X~TU^Vd}xsHWIM{DQc&D#%1ClmDJZ^wArzY|$>yo*S%3x7B7 zHRsVMoj6Edn%GDC58?))L;rxfg!qa0lRT7IMVu#pHH2N-LUNLLm%<1_M+tn0_$Te3 zaSx6Sp3o1@xj5f*?%zav^2=6x6t__0&(Yvfj-OAH?;vg;YpoHdr!HCY~n7s4g+;_k+RqdJ z(5EDiLO+tbF&bluCx|12j&gh|e!*Xfa1}TbU7V-!1^RjLUSc&7N$B|2t{+T$ptV(R zO1l(eeoK41K59CSxj5nEk6O)-4_f=6oxg6rVdj`==G*vyU87PdqBN78w%UC1LqtPD zhu**7e>M3|4US}@HJwxOl3mo_>`tvM?E#iYk-tw2BXl&e`V|~Qz7zAUb}iV0_W|n= z{fLi3-1(h4bgE1tnkXQ8kx#M)Q!Jvr2;;3r)0si6BY#;5M=Y_BycTht2n4JAuOMm@Iwl5x z@+EJ%(hTB9t6!ylRr}w{I_$xggub+^P|wCCI1zOuo3GO@CI%5bsbvtYX~z&7tPf4+ zIid{_No^`InOIC7fsOEM!WY8JQJ==+L=R4UO2pIFv4-}4A7!b{AnxSE0^(uX7m3Ei z!{o>DEn*q%e-nQZGszpEj-^Cp@|IXv@8656a#SVS5w$rv8lOcS8;Ja1>;Ha~XVICY zBkA)2@dvS&{1c)(?T>K^F@m_0ycw~M_FUBQHI^p|^+#843Ry&MNM3G>;pOYZWhBLW zGQ63YNhwpD-ro4s>0bV6FD1d#=f1qqj_-%$o$J&%q*8QNdb&3y(=%e=$iALY!-wSE z>ikeh_=v1YQ@!z-p4fEnyHro>H3_Iif&=@skgm6e(3f1*pR zP?Zw$R&;4rqW6fHk+Gh%)bvbGQii8Pr;d@H>2WhXle`R&%(%|L$)5k{((&%j|J5aL z_dQ!fYH0inZ~7zNbSF5M-A>+^d&h;f)EtSa8JUhdR9r^Jqp9f$mNB>6%HsOGUC~dJ zXvzk7($Z5ONlNghdnkJ{Q>S@TJl+}c-n2|lnq8mQs^6^={Zmt%k=~3p7CtwRTNtTVD{oEmFCk%txtj}@&G#=(IX|rE$i{-D zTb+X4PZ#Z270B7)<^|a=Ic`e}Em*iLuy~W>&Jfu1df|bbK+f{w?Ady^igxTN*pgka zX{*CSDOkVBzdp5P-j>wp5*6H=axeB@e0XY={6k0BU2U1$(n~fVy_5gRjIgM{vgLst z2lb-SrC{Nnp`)CPj6`m}c*Rzy*A%`Ff*TdswUM delta 11021 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 OpE-4\n" "Language-Team: Jumpserver team\n" @@ -99,16 +99,16 @@ msgstr "选择的系统用户将会在该集群资产上创建" #: assets/templates/assets/cluster_detail.html:57 #: assets/templates/assets/cluster_list.html:19 #: assets/templates/assets/system_user_detail.html:58 -#: assets/templates/assets/system_user_list.html:24 ops/models.py:31 -#: ops/templates/ops/task_detail.html:56 ops/templates/ops/task_list.html:34 -#: perms/models.py:14 +#: assets/templates/assets/system_user_list.html:24 common/models.py:25 +#: ops/models.py:31 ops/templates/ops/task_detail.html:56 +#: 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_detail.html:62 #: perms/templates/perms/asset_permission_list.html:25 #: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:14 #: terminal/models.py:118 terminal/templates/terminal/terminal_detail.html:43 #: 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_granted_asset.html:81 #: users/templates/users/user_group_detail.html:55 @@ -129,7 +129,8 @@ msgid "Password or private key password" msgstr "密码或秘钥不合法" #: assets/forms.py:201 assets/forms.py:262 assets/models/user.py:30 -#: users/forms.py:16 users/forms.py:24 users/templates/users/login.html:56 +#: common/forms.py:110 users/forms.py:16 users/forms.py:24 +#: users/templates/users/login.html:56 #: users/templates/users/reset_password.html:52 #: users/templates/users/user_create.html:11 #: users/templates/users/user_password_update.html:40 @@ -138,7 +139,7 @@ msgstr "密码或秘钥不合法" msgid "Password" msgstr "密码" -#: assets/forms.py:204 assets/forms.py:264 users/models/user.py:46 +#: assets/forms.py:204 assets/forms.py:264 users/models/user.py:45 msgid "Private key" msgstr "ssh私钥" @@ -156,7 +157,7 @@ msgstr "密码和私钥, 必须输入一个" #: assets/templates/assets/system_user_detail.html:62 #: assets/templates/assets/system_user_list.html:25 #: 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/login.html:53 #: users/templates/users/login_log_list.html:49 @@ -376,7 +377,7 @@ msgstr "主机名原始" #: assets/templates/assets/system_user_detail.html:96 #: ops/templates/ops/adhoc_detail.html:86 perms/models.py:22 #: 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" msgstr "创建者" @@ -399,11 +400,11 @@ msgstr "创建日期" #: assets/templates/assets/asset_group_list.html:17 #: assets/templates/assets/cluster_detail.html:97 #: assets/templates/assets/system_user_detail.html:100 -#: assets/templates/assets/system_user_list.html:30 ops/models.py:37 -#: perms/models.py:24 perms/templates/perms/asset_permission_detail.html:98 -#: terminal/models.py:22 terminal/templates/terminal/terminal_detail.html:63 -#: users/models/group.py:15 users/models/user.py:48 -#: users/templates/users/user_detail.html:110 +#: assets/templates/assets/system_user_list.html:30 common/models.py:28 +#: ops/models.py:37 perms/models.py:24 +#: perms/templates/perms/asset_permission_detail.html:98 terminal/models.py:22 +#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15 +#: 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_list.html:14 #: users/templates/users/user_profile.html:118 @@ -419,7 +420,7 @@ msgid "Contact" msgstr "联系人" #: 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" msgstr "手机" @@ -443,7 +444,7 @@ msgstr "运营商" msgid "Default" msgstr "默认" -#: assets/models/cluster.py:36 users/models/user.py:263 +#: assets/models/cluster.py:36 users/models/user.py:258 msgid "System" msgstr "系统" @@ -508,15 +509,15 @@ msgstr "系统用户" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/signals_handler.py:32 +#: assets/signals_handler.py:31 msgid "Push cluster system users to asset" msgstr "推送集群系统用户到资产" -#: assets/signals_handler.py:64 assets/signals_handler.py:126 +#: assets/signals_handler.py:63 assets/signals_handler.py:125 msgid "Push system user to cluster assets: {}->{}" msgstr "推送系统用户到: {}->{}" -#: assets/signals_handler.py:103 +#: assets/signals_handler.py:102 msgid "Push system user to assets" msgstr "推送系统用户到资产" @@ -660,6 +661,9 @@ msgstr "其它" #: assets/templates/assets/asset_group_create.html:16 #: assets/templates/assets/asset_update.html:55 #: assets/templates/assets/cluster_create_update.html:54 +#: common/templates/common/basic_setting.html:56 +#: common/templates/common/email_setting.html:56 +#: common/templates/common/ldap_setting.html:56 #: perms/templates/perms/asset_permission_create_update.html:67 #: terminal/templates/terminal/terminal_update.html:45 #: users/templates/users/_user.html:49 @@ -680,6 +684,9 @@ msgstr "重置" #: assets/templates/assets/asset_list.html:55 #: assets/templates/assets/asset_update.html:56 #: assets/templates/assets/cluster_create_update.html:55 +#: common/templates/common/basic_setting.html:57 +#: common/templates/common/email_setting.html:57 +#: common/templates/common/ldap_setting.html:57 #: perms/templates/perms/asset_permission_create_update.html:68 #: terminal/templates/terminal/terminal_update.html:46 #: users/templates/users/_user.html:50 @@ -856,7 +863,6 @@ msgstr "比例" #: assets/templates/assets/cluster_assets.html:56 #: assets/templates/assets/cluster_list.html:23 #: 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/task_history.html:62 ops/templates/ops/task_list.html:41 #: perms/templates/perms/asset_permission_list.html:32 @@ -1101,7 +1107,8 @@ msgstr "选择资产" msgid "Task has been send, seen left assets status" msgstr "任务已下发,查看左侧资产状态" -#: assets/templates/assets/cluster_create_update.html:41 +#: assets/templates/assets/cluster_create_update.html:41 common/views.py:17 +#: common/views.py:44 common/views.py:69 templates/_nav.html:74 #: users/templates/users/user_profile.html:20 msgid "Settings" msgstr "设置" @@ -1199,10 +1206,6 @@ msgstr "系统用户删除失败" msgid "Connective" msgstr "连接性" -#: assets/templates/assets/user_asset_list.html:65 -msgid "Connect" -msgstr "连接" - #: assets/views/admin_user.py:30 msgid "Admin user list" msgstr "管理用户列表" @@ -1272,6 +1275,14 @@ msgstr "系统用户详情" msgid "System user asset" 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 "%(name)s was created successfully" @@ -1282,6 +1293,82 @@ msgstr "%(name)s 创建成功" msgid "%(name)s was updated successfully" msgstr "%(name)s 更新成功" +#: 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:76 +msgid "Enable LDAP Auth" +msgstr "二次验证" + +#: common/forms.py:82 +msgid "SMTP host" +msgstr "SMTP主机" + +#: common/forms.py:84 +msgid "SMTP port" +msgstr "SMTP端口" + +#: common/forms.py:86 +msgid "SMTP user" +msgstr "SMTP账号" + +#: common/forms.py:89 +msgid "SMTP password" +msgstr "SMTP密码" + +#: common/forms.py:90 +msgid "Some provider use token except password" +msgstr "一些邮件提供商需要输入的是Token" + +#: common/forms.py:93 common/forms.py:130 +msgid "Use SSL" +msgstr "使用SSL" + +#: common/forms.py:94 +msgid "If SMTP port is 465, may be select" +msgstr "如果SMTP端口是465,通常需要启用SSL" + +#: common/forms.py:97 +msgid "Use TLS" +msgstr "使用TLS" + +#: common/forms.py:98 +msgid "If SMTP port is 587, may be select" +msgstr "如果SMTP端口是587,通常需要启用TLS" + +#: common/forms.py:104 +msgid "LDAP server" +msgstr "LDAP地址" + +#: common/forms.py:107 +msgid "Bind DN" +msgstr "绑定DN" + +#: common/forms.py:114 +msgid "User OU" +msgstr "用户OU" + +#: common/forms.py:117 +msgid "User search filter" +msgstr "用户过滤器" + +#: common/forms.py:120 +msgid "User attr map" +msgstr "LDAP属性映射" + #: common/mixins.py:29 msgid "is discard" msgstr "" @@ -1290,6 +1377,42 @@ msgstr "" msgid "discard time" 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:45 +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/basic_setting.html:55 +#: common/templates/common/email_setting.html:55 +#: common/templates/common/ldap_setting.html:55 +msgid "Test connection" +msgstr "测试连接" + +#: common/views.py:30 common/views.py:55 common/views.py:80 +msgid "Update setting successfully" +msgstr "更新设置成功" + #: ops/models.py:32 msgid "Interval" msgstr "间隔" @@ -1570,8 +1693,8 @@ msgstr "选择用户" #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 #: 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/views/user.py:338 +#: users/models/user.py:30 users/templates/users/user_group_detail.html:78 +#: users/views/user.py:337 msgid "User" msgstr "用户" @@ -1597,7 +1720,7 @@ msgid "" msgstr "资产 {}(组 {}) 所在集群 {} 不包含系统用户 [{}] 请检查\n" #: 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/user_detail.html:178 #: users/templates/users/user_list.html:26 @@ -1605,7 +1728,7 @@ msgid "User group" msgstr "用户组" #: 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 msgid "Date expired" msgstr "失效日期" @@ -1728,7 +1851,7 @@ msgstr "帮助" #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:57 -#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:320 +#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:319 msgid "Profile" msgstr "个人信息" @@ -1786,8 +1909,8 @@ msgstr "关闭" #: templates/_nav.html:9 users/views/group.py:28 users/views/group.py:44 #: users/views/group.py:62 users/views/group.py:79 users/views/login.py:194 #: users/views/login.py:243 users/views/user.py:57 users/views/user.py:72 -#: users/views/user.py:92 users/views/user.py:148 users/views/user.py:305 -#: users/views/user.py:319 users/views/user.py:356 users/views/user.py:378 +#: users/views/user.py:91 users/views/user.py:147 users/views/user.py:304 +#: users/views/user.py:318 users/views/user.py:355 users/views/user.py:377 msgid "Users" msgstr "用户管理" @@ -1814,7 +1937,7 @@ msgstr "任务" #: terminal/views/terminal.py:31 terminal/views/terminal.py:46 #: terminal/views/terminal.py:58 msgid "Terminal" -msgstr "终端" +msgstr "终端管理" #: templates/_nav.html:51 msgid "Session online" @@ -2164,46 +2287,46 @@ msgstr "Agent" msgid "Date login" msgstr "登录日期" -#: users/models/user.py:30 users/models/user.py:259 +#: users/models/user.py:29 users/models/user.py:254 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:32 +#: users/models/user.py:31 msgid "Application" 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 msgid "Email" 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_list.html:25 #: users/templates/users/user_profile.html:55 msgid "Role" msgstr "角色" -#: users/models/user.py:40 +#: users/models/user.py:39 msgid "Avatar" 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" msgstr "微信" -#: users/models/user.py:43 +#: users/models/user.py:42 msgid "Enable OTP" 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_update.html:43 #: users/templates/users/user_pubkey_update.html:43 msgid "Public key" msgstr "ssh公钥" -#: users/models/user.py:262 +#: users/models/user.py:257 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -2316,7 +2439,7 @@ msgstr "生成重置密码连接,通过邮件发送给用户" #: users/templates/users/user_detail.html:19 #: users/templates/users/user_granted_asset.html:18 #: users/templates/users/user_group_granted_asset.html:18 -#: users/views/user.py:149 +#: users/views/user.py:148 msgid "User detail" msgstr "用户详情" @@ -2445,8 +2568,8 @@ msgstr "用户删除失败" msgid "OTP" msgstr "" -#: users/templates/users/user_profile.html:100 users/views/user.py:178 -#: users/views/user.py:230 +#: users/templates/users/user_profile.html:100 users/views/user.py:177 +#: users/views/user.py:229 msgid "User groups" msgstr "用户组" @@ -2470,7 +2593,7 @@ msgstr "指纹" msgid "Update public key" msgstr "更新密钥" -#: users/templates/users/user_update.html:4 users/views/user.py:92 +#: users/templates/users/user_update.html:4 users/views/user.py:91 msgid "Update user" msgstr "编辑用户" @@ -2665,26 +2788,29 @@ msgstr "登录日志" msgid "User list" msgstr "用户列表" -#: users/views/user.py:102 +#: users/views/user.py:101 msgid "Bulk update user success" msgstr "批量更新用户成功" -#: users/views/user.py:207 +#: users/views/user.py:206 msgid "Invalid file." msgstr "文件不合法" -#: users/views/user.py:306 +#: users/views/user.py:305 msgid "User granted assets" msgstr "用户授权资产" -#: users/views/user.py:339 +#: users/views/user.py:338 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:357 +#: users/views/user.py:356 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:379 +#: users/views/user.py:378 msgid "Public key update" msgstr "秘钥更新" + +#~ msgid "Connect" +#~ msgstr "连接" diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 1493e55dc..eec72cf04 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -51,11 +51,6 @@
  • {% trans 'Session online' %}
  • {% trans 'Session offline' %}
  • {% trans 'Command' %}
  • -
  • - - {% trans 'Web terminal' %} - -
  • From f6056426dcfdcbb5e850d9d8dfaf6367f6c29e16 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 12 Jan 2018 15:57:24 +0800 Subject: [PATCH 016/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config_example.py | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/config_example.py b/config_example.py index 26fe13a4a..45d4b00f3 100644 --- a/config_example.py +++ b/config_example.py @@ -17,14 +17,6 @@ class Config: # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.environ.get('SECRET_KEY') or '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x' - # How many line display every page if using django pager, default 25 - DISPLAY_PER_PAGE = 25 - - # It's used to identify your site, When we send a create mail to user, we only know login url is /login/ - # But we should know the absolute url like: http://jms.jumpserver.org/login/, so SITE_URL is - # HTTP_PROTOCOL://HOST[:PORT] - SITE_URL = 'http://localhost' - # Django security setting, if your disable debug model, you should setting that ALLOWED_HOSTS = ['*'] @@ -65,40 +57,6 @@ class Config: 'port': REDIS_PORT, } - # Api token expiration when create, Jumpserver refresh time when request arrive - TOKEN_EXPIRATION = 3600 - - # Session and csrf domain settings - SESSION_COOKIE_AGE = 3600*24 - - # Email SMTP setting, we only support smtp send mail - EMAIL_HOST = 'smtp.163.com' - EMAIL_PORT = 25 - EMAIL_HOST_USER = '' - EMAIL_HOST_PASSWORD = '' # Caution: Some SMTP server using `Authorization Code` except password - EMAIL_USE_SSL = True if EMAIL_PORT == 465 else False - EMAIL_USE_TLS = True if EMAIL_PORT == 587 else False - EMAIL_SUBJECT_PREFIX = '[Jumpserver] ' - - CAPTCHA_TEST_MODE = False - - # You can set jumpserver usage url here, that when user submit wizard redirect to - USER_GUIDE_URL = '' - - # LDAP Auth settings - AUTH_LDAP = False - AUTH_LDAP_SERVER_URI = 'ldap://localhost:389' - AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org' - AUTH_LDAP_BIND_PASSWORD = '' - AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org' - AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)' - AUTH_LDAP_USER_ATTR_MAP = { - "username": "cn", - "name": "sn", - "email": "mail" - } - AUTH_LDAP_START_TLS = False - def __init__(self): pass From 7316661eb5636dc86265c0c49cb47e5a09595087 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 12 Jan 2018 16:37:55 +0800 Subject: [PATCH 017/197] =?UTF-8?q?[Update]=20=E5=90=AF=E5=8A=A8=E5=8A=A0?= =?UTF-8?q?=E8=BD=BDcommon=20setting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/signals_handler.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/common/signals_handler.py b/apps/common/signals_handler.py index 076ea7925..df2ea5a5e 100644 --- a/apps/common/signals_handler.py +++ b/apps/common/signals_handler.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- # -import ldap from django.dispatch import receiver from django.db.models.signals import post_save from django.conf import settings -from django_auth_ldap.config import LDAPSearch +from django.db.utils import ProgrammingError, OperationalError from .models import Setting from .utils import get_logger @@ -25,7 +24,10 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs): def refresh_all_settings_on_django_ready(sender, **kwargs): logger.debug("Receive django ready signal") logger.debug(" - fresh all settings") - Setting.refresh_all_settings() + try: + Setting.refresh_all_settings() + except (ProgrammingError, OperationalError): + pass @receiver(ldap_auth_enable, dispatch_uid="my_unique_identifier") From 39fff235d1092d46d75016b53342f5313d53a827 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 14 Jan 2018 18:02:11 +0800 Subject: [PATCH 018/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8Dcluster?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/cluster_create_update.html | 2 +- apps/assets/views/cluster.py | 2 +- .../templates/common/basic_setting.html | 1 - apps/locale/zh/LC_MESSAGES/django.mo | Bin 31604 -> 31610 bytes apps/locale/zh/LC_MESSAGES/django.po | 25 +++++++++--------- apps/templates/_nav.html | 2 +- apps/users/templates/users/user_profile.html | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/assets/templates/assets/cluster_create_update.html b/apps/assets/templates/assets/cluster_create_update.html index 38fc6e3de..66c36bece 100644 --- a/apps/assets/templates/assets/cluster_create_update.html +++ b/apps/assets/templates/assets/cluster_create_update.html @@ -38,7 +38,7 @@ {% bootstrap_field form.contact layout="horizontal" %} {% bootstrap_field form.phone layout="horizontal" %} -

    {% trans 'Settings' %}

    +

    {% trans 'Setting' %}

    {% bootstrap_field form.admin_user layout="horizontal" %} {% bootstrap_field form.system_users layout="horizontal" %} diff --git a/apps/assets/views/cluster.py b/apps/assets/views/cluster.py index e1a2f6fba..835229fc1 100644 --- a/apps/assets/views/cluster.py +++ b/apps/assets/views/cluster.py @@ -46,7 +46,7 @@ class ClusterCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView) return super().get_context_data(**kwargs) def form_valid(self, form): - cluster = form.save(commit=False) + cluster = form.save() cluster.created_by = self.request.user.username cluster.save() return super().form_valid(form) diff --git a/apps/common/templates/common/basic_setting.html b/apps/common/templates/common/basic_setting.html index e24f1d6a8..fb5039795 100644 --- a/apps/common/templates/common/basic_setting.html +++ b/apps/common/templates/common/basic_setting.html @@ -52,7 +52,6 @@
    -
    diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 9b898fe2fa4192e3ad0847f32f3b2e9448a632c4..5b01e493bc366126e93b5de9115b6256c4691b6c 100644 GIT binary patch delta 1192 zcmXZaUuey77{~G7+A-FSnZudMX|r9huvT)h%>_-F6mI;9)RHD;j-NiES>cCof-r0gUvcz^spSFi8$e4p?0eSUv_7K(lsiXQAwh4CN= z20DUZ4rXx;-a+NxU<^NE2~OB)`yEwRz$6xTM)gavl(-sot{xNEh^lYJD%{Z-g#QzV zoan{`8<*{EJA^tof@(PGcnnt(Pva`A$V9G3)u(O7_F*~sOSk~DnJ{WNNJ5QsE_`Z7 z?5OuA98Y5{^|Q_|>EbDgtFaE-P)~To_M-;MVgm1D1wIQ|)UbGsYA}OpP}v8L>s&LeI_HEKe=fmYNv+kzT& zuT7&a&R`4nqx$7h=ihpN%zm|hw9oxPSx?k3Y3oo88&N;m2Imhre;kwK&pN(p@1iao zLJgQl4f4wS<5)%f4OKVyOb~>6H49nj+g71AQmFh2RHFvl;(VLkj;i0~xE)s$r|lIy tOq@skg!R2qzuop=FW0ESiQI?fv2{gFx$&J3QzcWm;i=)9xq*(!(tkE#s;dA1 delta 1185 zcmXZaPe@cz6vy#9q@rnQW6Fd>nH#fgle$O@GJRop7Dkj%Q1t!veyh(t=bm@&dGEg8bBP~wiLu@3s4RqV zsV{_59L83>iBi12XP_!b6AbT*(h!}LPCx2y6}M= zw-erfm1N#359_0_yn}-k-G7_NVqaKa`$|8&=yis$mQ2CtK_MF6R$o9r*#r7ws+7 zfupDa3#dV!djBn^h(Dw15`)~oK20SH-E9)JQIEo!F$2)NqamHT2 nL&OEtPuP@;`)#v(avY-uhx6|mOWG41`42t!)A_4?(-r>!vMi*q diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 9b596a275..2c7f39971 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-01-12 15:51+0800\n" +"POT-Creation-Date: 2018-01-12 18:51+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -1107,10 +1107,10 @@ msgstr "选择资产" msgid "Task has been send, seen left assets status" msgstr "任务已下发,查看左侧资产状态" -#: assets/templates/assets/cluster_create_update.html:41 common/views.py:17 -#: common/views.py:44 common/views.py:69 templates/_nav.html:74 +#: assets/templates/assets/cluster_create_update.html:41 +#: users/templates/users/reset_password.html:57 #: users/templates/users/user_profile.html:20 -msgid "Settings" +msgid "Setting" msgstr "设置" #: assets/templates/assets/cluster_list.html:11 assets/views/cluster.py:43 @@ -1409,6 +1409,11 @@ msgstr "LDAP设置" msgid "Test connection" msgstr "测试连接" +#: common/views.py:17 common/views.py:44 common/views.py:69 +#: templates/_nav.html:69 +msgid "Settings" +msgstr "系统设置" + #: common/views.py:30 common/views.py:55 common/views.py:80 msgid "Update setting successfully" msgstr "更新设置成功" @@ -1955,14 +1960,14 @@ msgstr "离线会话" msgid "Command" msgstr "命令" -#: templates/_nav.html:56 templates/_nav_user.html:14 -msgid "Web terminal" -msgstr "Web终端" - #: templates/_nav_user.html:4 msgid "My assets" msgstr "我的资产" +#: templates/_nav_user.html:14 +msgid "Web terminal" +msgstr "Web终端" + #: templates/captcha/image.html:3 msgid "Play CAPTCHA as audio file" msgstr "语言播放验证码" @@ -2423,10 +2428,6 @@ msgstr "重置密码" msgid "Password again" msgstr "再次输入密码" -#: users/templates/users/reset_password.html:57 -msgid "Setting" -msgstr "设置" - #: users/templates/users/user_create.html:4 #: users/templates/users/user_list.html:16 users/views/user.py:72 msgid "Create user" diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index eec72cf04..39b14add8 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -65,7 +65,7 @@ {# #} {##}
  • - + {% trans 'Settings' %}
  • \ No newline at end of file diff --git a/apps/users/templates/users/user_profile.html b/apps/users/templates/users/user_profile.html index 99f5ca6e1..8b26c943d 100644 --- a/apps/users/templates/users/user_profile.html +++ b/apps/users/templates/users/user_profile.html @@ -17,7 +17,7 @@ {% trans 'Profile' %}
  • - {% trans 'Settings' %} + {% trans 'Setting' %}
  • From 59a69f02536d880b57ac72cfab432c6bcdb1e572 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 15 Jan 2018 13:54:19 +0800 Subject: [PATCH 019/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8Dtitle?= =?UTF-8?q?=E6=98=BE=E7=A4=BAJumpserver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/views.py | 9 ++++++--- apps/perms/api.py | 2 +- apps/users/templates/users/login.html | 2 +- apps/users/templates/users/reset_password.html | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/common/views.py b/apps/common/views.py index 9a0a314ec..4135ca82c 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -27,7 +27,8 @@ class BasicSettingView(AdminUserRequiredMixin, TemplateView): form.save() if "AUTH_LDAP" in form.cleaned_data: ldap_auth_enable.send(form.cleaned_data["AUTH_LDAP"]) - messages.success(request, _("Update setting successfully")) + msg = _("Update setting successfully, please restart program") + messages.success(request, msg) return redirect('settings:basic-setting') else: context = self.get_context_data() @@ -52,7 +53,8 @@ class EmailSettingView(AdminUserRequiredMixin, TemplateView): form = self.form_class(request.POST) if form.is_valid(): form.save() - messages.success(request, _("Update setting successfully")) + msg = _("Update setting successfully, please restart program") + messages.success(request, msg) return redirect('settings:email-setting') else: context = self.get_context_data() @@ -77,7 +79,8 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView): form = self.form_class(request.POST) if form.is_valid(): form.save() - messages.success(request, _("Update setting successfully")) + msg = _("Update setting successfully, please restart program") + messages.success(request, msg) return redirect('settings:ldap-setting') else: context = self.get_context_data() diff --git a/apps/perms/api.py b/apps/perms/api.py index ee2d12ff8..9165a9098 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -351,7 +351,7 @@ class UserGroupGrantedAssetGroupsApi(ListAPIView): class ValidateUserAssetPermissionView(APIView): - permission_classes = (IsAppUser,) + permission_classes = (IsSuperUserOrAppUser,) @staticmethod def get(request): diff --git a/apps/users/templates/users/login.html b/apps/users/templates/users/login.html index e9504d63c..284b6bc43 100644 --- a/apps/users/templates/users/login.html +++ b/apps/users/templates/users/login.html @@ -6,7 +6,7 @@ - JumpServer + Jumpserver {% include '_head_css_js.html' %} diff --git a/apps/users/templates/users/reset_password.html b/apps/users/templates/users/reset_password.html index 6c454ee01..cf8003a86 100644 --- a/apps/users/templates/users/reset_password.html +++ b/apps/users/templates/users/reset_password.html @@ -6,7 +6,7 @@ - JumpServer + Jumpserver {% include '_head_css_js.html' %} From 89e013e2e2c6e14c9da442d926f4d1b37ab94b76 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 15 Jan 2018 14:17:56 +0800 Subject: [PATCH 020/197] [Bugfix] setting tables not found --- apps/common/migrations/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/common/migrations/__init__.py diff --git a/apps/common/migrations/__init__.py b/apps/common/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb From 8b9e45ad31537331b5ed30a6ccd1c758322f9678 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 15 Jan 2018 16:00:26 +0800 Subject: [PATCH 021/197] [Bugfix] settings add option --- apps/jumpserver/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 5bdc932e3..c7f74d965 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -380,3 +380,4 @@ BOOTSTRAP3 = { TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600 DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE DEFAULT_EXPIRED_YEARS = 70 +USER_GUIDE_URL = "" From b2f97a263dbe5731accee6fcae4b304d10d5026c Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 15 Jan 2018 17:41:49 +0800 Subject: [PATCH 022/197] =?UTF-8?q?[Feature]=20=E6=B7=BB=E5=8A=A0=E5=90=8E?= =?UTF-8?q?=E7=AB=AFpaging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api.py | 17 ++- apps/assets/templates/assets/asset_list.html | 24 +++- apps/common/mixins.py | 3 +- apps/jumpserver/settings.py | 10 +- apps/static/js/jumpserver.js | 114 +++++++++++++++++++ apps/users/api.py | 6 +- 6 files changed, 158 insertions(+), 16 deletions(-) diff --git a/apps/assets/api.py b/apps/assets/api.py index e5c5a44ff..f3bfedb5a 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -19,8 +19,9 @@ from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView from django.shortcuts import get_object_or_404 from django.db.models import Q +from rest_framework.pagination import LimitOffsetPagination -from common.mixins import IDInFilterMixin +from common.mixins import CustomFilterMixin from common.utils import get_logger from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \ get_user_granted_assets @@ -34,12 +35,16 @@ from .tasks import update_asset_hardware_info_manual, test_admin_user_connectabi logger = get_logger(__file__) -class AssetViewSet(IDInFilterMixin, BulkModelViewSet): +class AssetViewSet(CustomFilterMixin, BulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ + filter_fields = ("hostname", "ip") + search_fields = filter_fields + ordering_fields = ("hostname", "ip", "port", "cluster", "type", "env", "cpu_cores") queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer + pagination_class = LimitOffsetPagination permission_classes = (IsSuperUserOrAppUser,) def get_queryset(self): @@ -78,7 +83,7 @@ class UserAssetListView(generics.ListAPIView): return queryset -class AssetGroupViewSet(IDInFilterMixin, BulkModelViewSet): +class AssetGroupViewSet(CustomFilterMixin, BulkModelViewSet): """ Asset group api set, for add,delete,update,list,retrieve resource """ @@ -112,7 +117,7 @@ class GroupAddAssetsApi(generics.UpdateAPIView): return Response({'error': serializer.errors}, status=400) -class ClusterViewSet(IDInFilterMixin, BulkModelViewSet): +class ClusterViewSet(CustomFilterMixin, BulkModelViewSet): """ Cluster api set, for add,delete,update,list,retrieve resource """ @@ -153,7 +158,7 @@ class ClusterAddAssetsApi(generics.UpdateAPIView): return Response({'error': serializer.errors}, status=400) -class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet): +class AdminUserViewSet(CustomFilterMixin, BulkModelViewSet): """ Admin user api set, for add,delete,update,list,retrieve resource """ @@ -189,7 +194,7 @@ class SystemUserViewSet(BulkModelViewSet): permission_classes = (IsSuperUserOrAppUser,) -class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): +class AssetListUpdateApi(CustomFilterMixin, ListBulkCreateUpdateDestroyAPIView): """ Asset bulk update api """ diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 2c562ffa5..8fc20057f 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -71,10 +71,21 @@ function initTable() { columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { {% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %} - console.log('{{ the_url }}'); var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, + {targets: 4, createdCell: function (td, cellData, rowData) { + $(td).html(rowData.cluster_name) + }}, + {targets: 5, createdCell: function (td, cellData, rowData) { + $(td).html(rowData.get_type_display) + }}, + {targets: 6, createdCell: function (td, cellData, rowData) { + $(td).html(rowData.get_env_display) + }}, + {targets: 7, createdCell: function (td, cellData, rowData) { + $(td).html(rowData.hardware_info) + }}, {targets: 8, createdCell: function (td, cellData) { if (!cellData) { $(td).html('') @@ -98,12 +109,15 @@ function initTable() { }} ], ajax_url: '{% url "api-assets:asset-list" %}', - columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "cluster_name"}, - {data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"}, - {data: "is_active" }, {data: "is_connective"}, {data: "id" }], + columns: [ + {data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, + {data: "cluster"}, {data: "type" }, {data: "env"}, + {data: "cpu_cores"}, {data: "is_active", orderable: false }, + {data: "is_connective", orderable: false}, {data: "id", orderable: false } + ], op_html: $('#actions').html() }; - return jumpserver.initDataTable(options); + return jumpserver.initServerSideDataTable(options); } $(document).ready(function(){ diff --git a/apps/common/mixins.py b/apps/common/mixins.py index 2832424c6..d1afb081b 100644 --- a/apps/common/mixins.py +++ b/apps/common/mixins.py @@ -47,8 +47,9 @@ class JSONResponseMixin(object): return JsonResponse(context) -class IDInFilterMixin(object): +class CustomFilterMixin(object): def filter_queryset(self, queryset): + queryset = super(CustomFilterMixin, self).filter_queryset(queryset) id_list = self.request.query_params.get('id__in') if id_list: import json diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 5bdc932e3..c687c038c 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -288,9 +288,17 @@ REST_FRAMEWORK = { 'users.authentication.PrivateTokenAuthentication', 'users.authentication.SessionAuthentication', ), - 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',), + 'DEFAULT_FILTER_BACKENDS': ( + 'django_filters.rest_framework.DjangoFilterBackend', + 'rest_framework.filters.SearchFilter', + 'rest_framework.filters.OrderingFilter', + ), + 'ORDERING_PARAM': "order", + 'SEARCH_PARAM': "search", 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z', 'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'], + # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', + 'PAGE_SIZE': 15 } AUTHENTICATION_BACKENDS = [ diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 119151897..8be19b4a0 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -330,6 +330,120 @@ jumpserver.initDataTable = function (options) { 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: 'header button', + // op_html: 'div.btn-group?', + // paging: true + // } + var ele = options.ele || $('.dataTable'); + var columnDefs = [ + { + targets: 0, + orderable: false, + createdCell: function (td, cellData) { + $(td).html(''.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; + var length = data.length; + 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; + } + }, + 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, -1], [15, 25, 50, "All"]] + }); + 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'}).select(); + } else { + $(this).closest('table').find('.ipt_check').prop('checked', false); + jumpserver.checked = false; + table.rows({search:'applied'}).deselect(); + } + }); + + return table; +}; + /** * 替换所有匹配exp的字符串为指定字符串 * @param exp 被替换部分的正则 diff --git a/apps/users/api.py b/apps/users/api.py index 326f2c3ec..8dbaf8b9a 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -13,14 +13,14 @@ from .tasks import write_login_log_async from .models import User, UserGroup from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly from .utils import check_user_valid, generate_token -from common.mixins import IDInFilterMixin +from common.mixins import CustomFilterMixin from common.utils import get_logger logger = get_logger(__name__) -class UserViewSet(IDInFilterMixin, BulkModelViewSet): +class UserViewSet(CustomFilterMixin, BulkModelViewSet): queryset = User.objects.exclude(role="App") # queryset = User.objects.all().exclude(role="App").order_by("date_joined") serializer_class = UserSerializer @@ -72,7 +72,7 @@ class UserUpdatePKApi(generics.UpdateAPIView): user.save() -class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet): +class UserGroupViewSet(CustomFilterMixin, BulkModelViewSet): queryset = UserGroup.objects.all() serializer_class = UserGroupSerializer From 7e2d627d3f1147dad3e3e9cd9bbb8fea4918f154 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 15 Jan 2018 18:14:58 +0800 Subject: [PATCH 023/197] =?UTF-8?q?[Bugfix]=20=E8=B5=84=E4=BA=A7=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E9=80=89=E6=8B=A9=E5=88=AB=E7=9A=84=E9=A1=B5=E4=BC=9A?= =?UTF-8?q?=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/templates/assets/asset_list.html | 16 ++++------------ apps/static/js/jumpserver.js | 9 +++++++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 8fc20057f..625fc030d 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -31,8 +31,6 @@

    GPfBAL>O!lOYie8r< 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 diff --git a/apps/static/fonts/fontawesome-webfont.eot b/apps/static/fonts/fontawesome-webfont.eot old mode 100644 new mode 100755 index 33b2bb80055cc480e797de704925acaba4ba7d7d..e9f60ca953f93e35eab4108bd414bc02ddcf3928 GIT binary patch literal 165742 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 G*yFsbY~cLNXLP73aA74Mq6M9f^&YV`isWW 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

    - - @@ -78,22 +76,16 @@ function initTable() { $(td).html(rowData.cluster_name) }}, {targets: 5, createdCell: function (td, cellData, rowData) { - $(td).html(rowData.get_type_display) - }}, - {targets: 6, createdCell: function (td, cellData, rowData) { - $(td).html(rowData.get_env_display) - }}, - {targets: 7, createdCell: function (td, cellData, rowData) { $(td).html(rowData.hardware_info) }}, - {targets: 8, createdCell: function (td, cellData) { + {targets: 6, createdCell: function (td, cellData) { if (!cellData) { $(td).html('') } else { $(td).html('') } }}, - {targets: 9, createdCell: function (td, cellData) { + {targets: 7, createdCell: function (td, cellData) { if (cellData == 'Unknown'){ $(td).html('') } else if (!cellData) { @@ -102,7 +94,7 @@ function initTable() { $(td).html('') } }}, - {targets: 10, createdCell: function (td, cellData, rowData) { + {targets: 8, createdCell: function (td, cellData, rowData) { var update_btn = '{% trans "Update" %}'.replace("{{ DEFAULT_PK }}", cellData); var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData); $(td).html(update_btn + del_btn) @@ -111,7 +103,7 @@ function initTable() { ajax_url: '{% url "api-assets:asset-list" %}', columns: [ {data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, - {data: "cluster"}, {data: "type" }, {data: "env"}, + {data: "cluster"}, {data: "cpu_cores"}, {data: "is_active", orderable: false }, {data: "is_connective", orderable: false}, {data: "id", orderable: false } ], diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 8be19b4a0..7b52c2b32 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -373,7 +373,6 @@ jumpserver.initServerSideDataTable = function (options) { url: options.ajax_url , data: function (data) { delete data.columns; - var length = data.length; if (data.length !== null ){ data.limit = data.length; delete data.length; @@ -395,6 +394,12 @@ jumpserver.initServerSideDataTable = function (options) { 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 || [], @@ -414,7 +419,7 @@ jumpserver.initServerSideDataTable = function (options) { last: "»" } }, - lengthMenu: [[15, 25, 50, -1], [15, 25, 50, "All"]] + lengthMenu: [[15, 25, 50], [15, 25, 50]] }); table.on('select', function(e, dt, type, indexes) { var $node = table[ type ]( indexes ).nodes().to$(); From dad21cadb3e494acb6887dbf7ca32e8318f7204f Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 15 Jan 2018 18:32:07 +0800 Subject: [PATCH 024/197] =?UTF-8?q?[Update]=20check=20all=20=E5=8F=AA?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E5=BD=93=E5=89=8D=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/static/js/jumpserver.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 7b52c2b32..aca70a5a0 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -319,11 +319,11 @@ jumpserver.initDataTable = function (options) { if (!jumpserver.checked) { $(this).closest('table').find('.ipt_check').prop('checked', true); jumpserver.checked = true; - table.rows({search:'applied'}).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'}).deselect(); + table.rows({search:'applied', page:'current'}).deselect(); } }); @@ -438,11 +438,11 @@ jumpserver.initServerSideDataTable = function (options) { if (!jumpserver.checked) { $(this).closest('table').find('.ipt_check').prop('checked', true); jumpserver.checked = true; - table.rows({search:'applied'}).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'}).deselect(); + table.rows({search:'applied', page:'current'}).deselect(); } }); From 29061aa0886315643c268ab2342eb22f36c9c4b4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jan 2018 09:58:33 +0800 Subject: [PATCH 025/197] [Bugfix] user login ip --- apps/terminal/api.py | 4 +++- apps/users/api.py | 6 +++++- apps/users/utils.py | 5 +++-- apps/users/views/login.py | 7 +++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/terminal/api.py b/apps/terminal/api.py index 810a19496..87311d5b2 100644 --- a/apps/terminal/api.py +++ b/apps/terminal/api.py @@ -141,7 +141,9 @@ class StatusViewSet(viewsets.ModelViewSet): session = serializer.save() return session else: - msg = "session data is not valid {}".format(serializer.errors) + msg = "session data is not valid {}: {}".format( + serializer.errors, str(serializer.data) + ) logger.error(msg) return None diff --git a/apps/users/api.py b/apps/users/api.py index 8dbaf8b9a..05193d03c 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -128,7 +128,11 @@ class UserAuthApi(APIView): user_agent = request.data.get('HTTP_USER_AGENT', '') if not login_ip: - login_ip = request.META.get('HTTP_X_FORWARDED_FOR') or request.META.get("REMOTE_ADDR") + x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR').split() + if x_forwarded_for: + login_ip = x_forwarded_for[0] + else: + login_ip = request.META.get("REMOTE_ADDR") user, msg = check_user_valid( username=username, password=password, diff --git a/apps/users/utils.py b/apps/users/utils.py index 685bf31d6..68840f955 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -180,8 +180,9 @@ def validate_ip(ip): def write_login_log(username, type='', ip='', user_agent=''): if not (ip and validate_ip(ip)): - ip = '0.0.0.0' - city = get_ip_city(ip) + city = "Unknown" + else: + city = get_ip_city(ip) LoginLog.objects.create( username=username, type=type, ip=ip, city=city, user_agent=user_agent diff --git a/apps/users/views/login.py b/apps/users/views/login.py index a5b78cc73..83d0fd891 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -53,8 +53,11 @@ class UserLoginView(FormView): if not self.request.session.test_cookie_worked(): return HttpResponse(_("Please enable cookies and try again.")) auth_login(self.request, form.get_user()) - login_ip = self.request.META.get('HTTP_X_FORWARDED_FOR') or \ - self.request.META.get('REMOTE_ADDR', '') + x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split() + if x_forwarded_for: + login_ip = x_forwarded_for[0] + else: + login_ip = self.request.META.get('REMOTE_ADDR', '') user_agent = self.request.META.get('HTTP_USER_AGENT', '') write_login_log_async.delay( self.request.user.username, type='W', From 681ddb5af25bd64186dfb054bcb797b1b628e6c2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jan 2018 11:08:05 +0800 Subject: [PATCH 026/197] [Bugfix] for login ip --- apps/users/api.py | 2 +- apps/users/utils.py | 1 + apps/users/views/login.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/users/api.py b/apps/users/api.py index 05193d03c..2dfd89fba 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -128,7 +128,7 @@ class UserAuthApi(APIView): user_agent = request.data.get('HTTP_USER_AGENT', '') if not login_ip: - x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR').split() + x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',') if x_forwarded_for: login_ip = x_forwarded_for[0] else: diff --git a/apps/users/utils.py b/apps/users/utils.py index 68840f955..fd03ad97d 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -180,6 +180,7 @@ def validate_ip(ip): def write_login_log(username, type='', ip='', user_agent=''): if not (ip and validate_ip(ip)): + ip = ip[:15] city = "Unknown" else: city = get_ip_city(ip) diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 83d0fd891..614f321db 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -53,7 +53,7 @@ class UserLoginView(FormView): if not self.request.session.test_cookie_worked(): return HttpResponse(_("Please enable cookies and try again.")) auth_login(self.request, form.get_user()) - x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split() + x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split(',') if x_forwarded_for: login_ip = x_forwarded_for[0] else: From d3109a03b2265b8777cc3a53d0765816c62d1750 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jan 2018 11:20:26 +0800 Subject: [PATCH 027/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E5=88=97=E8=A1=A8=E6=98=BE=E7=A4=BAbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/label.py | 27 +++++++++++++++++++ .../templates/assets/admin_user_assets.html | 2 +- .../templates/assets/asset_group_detail.html | 2 +- .../templates/assets/cluster_assets.html | 2 +- .../templates/assets/system_user_asset.html | 2 +- 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 apps/assets/models/label.py diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py new file mode 100644 index 000000000..59a5b6cd2 --- /dev/null +++ b/apps/assets/models/label.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# + +import uuid +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class Label(models.Model): + SYSTEM_CATEGORY = "S" + USER_CATEGORY = "U" + CATEGORY_CHOICES = ( + ("S", _("System")), + ("U", _("User")) + ) + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_("Name")) + alias = models.CharField(max_length=128, verbose_name=_("Alias"), blank=True) + value = models.CharField(max_length=128, verbose_name=_("Value")) + category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, verbose_name=_("Category")) + is_active = models.BooleanField(default=False, verbose_name=_("Is active")) + date_created = models.DateTimeField( + auto_now_add=True, null=True, blank=True, verbose_name=_('Date created') + ) + + class Meta: + db_table = "assets_label" diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index c00a56e60..7ec123fe2 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -121,7 +121,7 @@ function initTable() { {data: "type" }, {data: "is_connective" }], op_html: $('#actions').html() }; - jumpserver.initDataTable(options); + jumpserver.initServerSideDataTable(options); } $(document).ready(function () { diff --git a/apps/assets/templates/assets/asset_group_detail.html b/apps/assets/templates/assets/asset_group_detail.html index 4a9b719b1..dda393e0d 100644 --- a/apps/assets/templates/assets/asset_group_detail.html +++ b/apps/assets/templates/assets/asset_group_detail.html @@ -184,7 +184,7 @@ function initTable() { {data: "get_type_display" }, {data: "is_connective" }, {data: "id"}], op_html: $('#actions').html() }; - jumpserver.initDataTable(options); + jumpserver.initServerSideDataTable(options); } diff --git a/apps/assets/templates/assets/cluster_assets.html b/apps/assets/templates/assets/cluster_assets.html index 88969a000..b2e33e578 100644 --- a/apps/assets/templates/assets/cluster_assets.html +++ b/apps/assets/templates/assets/cluster_assets.html @@ -176,7 +176,7 @@ function initTable() { {data: "get_type_display" }, {data: "is_connective" }, {data: "id"}], op_html: $('#actions').html() }; - jumpserver.initDataTable(options); + jumpserver.initServerSideDataTable(options); } diff --git a/apps/assets/templates/assets/system_user_asset.html b/apps/assets/templates/assets/system_user_asset.html index afff889f6..91c2ffbe0 100644 --- a/apps/assets/templates/assets/system_user_asset.html +++ b/apps/assets/templates/assets/system_user_asset.html @@ -121,7 +121,7 @@ function initAssetsTable() { columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }], op_html: $('#actions').html() }; - jumpserver.initDataTable(options); + jumpserver.initServerSideDataTable(options); } $(document).ready(function () { From f1ec53d1bcc50084a6f4542b60ffd6f9085895e6 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jan 2018 11:26:30 +0800 Subject: [PATCH 028/197] [Remove] labels --- apps/assets/models/label.py | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 apps/assets/models/label.py diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py deleted file mode 100644 index 59a5b6cd2..000000000 --- a/apps/assets/models/label.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# - -import uuid -from django.db import models -from django.utils.translation import ugettext_lazy as _ - - -class Label(models.Model): - SYSTEM_CATEGORY = "S" - USER_CATEGORY = "U" - CATEGORY_CHOICES = ( - ("S", _("System")), - ("U", _("User")) - ) - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, verbose_name=_("Name")) - alias = models.CharField(max_length=128, verbose_name=_("Alias"), blank=True) - value = models.CharField(max_length=128, verbose_name=_("Value")) - category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, verbose_name=_("Category")) - is_active = models.BooleanField(default=False, verbose_name=_("Is active")) - date_created = models.DateTimeField( - auto_now_add=True, null=True, blank=True, verbose_name=_('Date created') - ) - - class Meta: - db_table = "assets_label" From 01895bafc0f6baa70584c80d7ae6911084062134 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jan 2018 11:40:18 +0800 Subject: [PATCH 029/197] =?UTF-8?q?[Bugfix]=20task=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=EF=BC=8C=E5=9B=A0=E4=B8=BAtasks=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models.py | 2 +- apps/ops/utils.py | 2 ++ apps/terminal/signals_handler.py | 4 ---- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/ops/models.py b/apps/ops/models.py index 3c96f5146..471df25d7 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -165,7 +165,7 @@ class AdHoc(models.Model): if item and isinstance(item, list): self._tasks = json.dumps(item) else: - raise SyntaxError('Tasks should be a list') + raise SyntaxError('Tasks should be a list: {}'.format(item)) @property def hosts(self): diff --git a/apps/ops/utils.py b/apps/ops/utils.py index 0a9dce8c9..8ff9c321f 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -16,6 +16,8 @@ def update_or_create_ansible_task( run_as_admin=False, run_as="", become_info=None, created_by=None, ): + if not hosts or not tasks or not task_name: + return defaults = { 'name': task_name, diff --git a/apps/terminal/signals_handler.py b/apps/terminal/signals_handler.py index 757a97bc6..3926a5751 100644 --- a/apps/terminal/signals_handler.py +++ b/apps/terminal/signals_handler.py @@ -13,10 +13,6 @@ RUNNING = False logger = get_logger(__file__) -@shared_task -@register_as_period_task(interval=3600) -@after_app_ready_start -@after_app_shutdown_clean def set_session_info_cache(): logger.debug("") from .utils import get_session_asset_list, get_session_user_list, \ From 5bbad019091abbee5b0681701b102a1b143fa5e7 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jan 2018 16:32:06 +0800 Subject: [PATCH 030/197] =?UTF-8?q?[Feature]=20=E5=A2=9E=E5=8A=A0=E6=A0=87?= =?UTF-8?q?=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api.py | 20 +++++- apps/assets/models/__init__.py | 1 + apps/assets/models/asset.py | 2 + apps/assets/models/label.py | 30 ++++++++ apps/assets/models/user.py | 1 + apps/assets/serializers.py | 43 +++++++++++- apps/assets/templates/assets/label_list.html | 72 ++++++++++++++++++++ apps/assets/urls/api_urls.py | 1 + apps/assets/urls/views_urls.py | 1 + apps/assets/views/__init__.py | 1 + apps/assets/views/label.py | 43 ++++++++++++ apps/templates/_nav.html | 1 + 12 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 apps/assets/models/label.py create mode 100644 apps/assets/templates/assets/label_list.html create mode 100644 apps/assets/views/label.py diff --git a/apps/assets/api.py b/apps/assets/api.py index f3bfedb5a..bd22c7336 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -17,15 +17,15 @@ from rest_framework import generics from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView -from django.shortcuts import get_object_or_404 -from django.db.models import Q from rest_framework.pagination import LimitOffsetPagination +from django.shortcuts import get_object_or_404 +from django.db.models import Q, Count from common.mixins import CustomFilterMixin from common.utils import get_logger from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \ get_user_granted_assets -from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser +from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser, Label from . import serializers from .tasks import update_asset_hardware_info_manual, test_admin_user_connectability_manual, \ test_asset_connectability_manual, push_system_user_to_cluster_assets_manual, \ @@ -295,3 +295,17 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): system_user = self.get_object() test_system_user_connectability_manual.delay(system_user) return Response({"msg": "Task created"}) + + +class LabelViewSet(BulkModelViewSet): + queryset = Label.objects.annotate(asset_count=Count("assets"))\ + .annotate(admin_user_count=Count("adminuser")) \ + .annotate(system_user_count=Count("systemuser")) + permission_classes = (IsSuperUser,) + serializer_class = serializers.LabelSerializer + + def list(self, request, *args, **kwargs): + if request.query_params.get("distinct"): + self.serializer_class = serializers.LabelDistinctSerializer + self.queryset = self.queryset.values("name").distinct() + return super().list(request, *args, **kwargs) diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index 38ed90721..6e0621ba8 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # from .user import AdminUser, SystemUser +from .label import Label from .cluster import * from .group import * from .asset import * diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index c83443077..0b1cdf0ec 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -88,6 +88,8 @@ class Asset(models.Model): os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) + labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) + created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py new file mode 100644 index 000000000..d4be2286e --- /dev/null +++ b/apps/assets/models/label.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# + +import uuid +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class Label(models.Model): + SYSTEM_CATEGORY = "S" + USER_CATEGORY = "U" + CATEGORY_CHOICES = ( + ("S", _("System")), + ("U", _("User")) + ) + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_("Name")) + value = models.CharField(max_length=128, verbose_name=_("Value")) + category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, verbose_name=_("Category")) + is_active = models.BooleanField(default=True, verbose_name=_("Is active")) + comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) + date_created = models.DateTimeField( + auto_now_add=True, null=True, blank=True, verbose_name=_('Date created') + ) + + def __str__(self): + return "{}:{}".format(self.name, self.value) + + class Meta: + db_table = "assets_label" diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index aa44a539c..ee71afab1 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -30,6 +30,7 @@ class AssetUser(models.Model): _password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) _private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ]) _public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key')) + labels = models.ManyToManyField('assets.Label', blank=True, verbose_name=_("Labels")) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True) date_updated = models.DateTimeField(auto_now=True) diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index cb939fcc6..7fe14da1b 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -4,7 +4,7 @@ from rest_framework import serializers from rest_framework_bulk.serializers import BulkListSerializer from common.mixins import BulkSerializerMixin -from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser +from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser, Label from .const import ADMIN_USER_CONN_CACHE_KEY, SYSTEM_USER_CONN_CACHE_KEY @@ -284,3 +284,44 @@ class MyAssetGroupGrantedSerializer(serializers.ModelSerializer): @staticmethod def get_assets_amount(obj): return len(obj.assets_granted) + + +class LabelSerializer(serializers.ModelSerializer): + asset_count = serializers.SerializerMethodField() + admin_user_count = serializers.SerializerMethodField() + system_user_count = serializers.SerializerMethodField() + + class Meta: + model = Label + fields = '__all__' + list_serializer_class = BulkListSerializer + + @staticmethod + def get_asset_count(obj): + return obj.asset_count + + @staticmethod + def get_admin_user_count(obj): + return obj.admin_user_count + + @staticmethod + def get_system_user_count(obj): + return obj.system_user_count + + def get_field_names(self, declared_fields, info): + fields = super().get_field_names(declared_fields, info) + fields.extend(['get_category_display']) + return fields + + +class LabelDistinctSerializer(serializers.ModelSerializer): + value = serializers.SerializerMethodField() + + class Meta: + model = Label + fields = ("name", "value") + + @staticmethod + def get_value(obj): + labels = Label.objects.filter(name=obj["name"]) + return ', '.join([label.value for label in labels]) diff --git a/apps/assets/templates/assets/label_list.html b/apps/assets/templates/assets/label_list.html new file mode 100644 index 000000000..81c5358b2 --- /dev/null +++ b/apps/assets/templates/assets/label_list.html @@ -0,0 +1,72 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block table_search %}{% endblock %} +{% block table_container %} + +
    {% trans 'Hardware' %} {% trans 'Active' %} {% trans 'Connective' %}{% trans 'Action' %}
    {% trans 'IP' %} {% trans 'Port' %} {% trans 'Cluster' %}{% trans 'Type' %}{% trans 'Env' %} {% trans 'Hardware' %} {% trans 'Active' %} {% trans 'Reachable' %}
    + + + + + + + + + + + + + +
    + + {% trans 'Name' %}{% trans 'Value' %}{% trans 'Asset' %}{% trans 'Admin user' %}{% trans 'System user' %}{% trans 'Action' %}
    +{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + +{% endblock %} + + + diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 503464b31..8c7b77dc7 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -12,6 +12,7 @@ router.register(r'v1/assets', api.AssetViewSet, 'asset') router.register(r'v1/clusters', api.ClusterViewSet, 'cluster') router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user') router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user') +router.register(r'v1/labels', api.LabelViewSet, 'label') urlpatterns = [ url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index e24721c09..09ff322ee 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -53,5 +53,6 @@ urlpatterns = [ # url(r'^system-user/(?P[0-9a-zA-Z\-]{36})/asset-group$', views.SystemUserAssetGroupView.as_view(), # name='system-user-asset-group'), + url(r'^label/$', views.LabelListView.as_view(), name='label-list'), ] diff --git a/apps/assets/views/__init__.py b/apps/assets/views/__init__.py index 883120b36..112a46d0d 100644 --- a/apps/assets/views/__init__.py +++ b/apps/assets/views/__init__.py @@ -4,4 +4,5 @@ from .group import * from .cluster import * from .system_user import * from .admin_user import * +from .label import * diff --git a/apps/assets/views/label.py b/apps/assets/views/label.py new file mode 100644 index 000000000..375c500a5 --- /dev/null +++ b/apps/assets/views/label.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# + +from django.views.generic import ListView, TemplateView, CreateView, \ + UpdateView, DeleteView, DetailView +from django.utils.translation import ugettext_lazy as _ + + +from common.mixins import AdminUserRequiredMixin + + +__all__ = ( + "LabelListView", "LabelCreateView", "LabelUpdateView", + "LabelDetailView", "LabelDeleteView", +) + + +class LabelListView(AdminUserRequiredMixin, TemplateView): + template_name = 'assets/label_list.html' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('Label list'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class LabelCreateView(AdminUserRequiredMixin, CreateView): + pass + + +class LabelUpdateView(AdminUserRequiredMixin, UpdateView): + pass + + +class LabelDetailView(AdminUserRequiredMixin, DetailView): + pass + + +class LabelDeleteView(AdminUserRequiredMixin, DeleteView): + pass diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 39b14add8..8bc795c9c 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -24,6 +24,7 @@
  • {% trans 'Cluster' %}
  • {% trans 'Admin user' %}
  • {% trans 'System user' %}
  • +
  • {% trans 'Label' %}
  • From ce9ff67b24c65a12e372371f688ef3c706de10de Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 17 Jan 2018 16:18:33 +0800 Subject: [PATCH 031/197] =?UTF-8?q?[Bugfix]=20=E8=AF=BB=E5=8F=96=E4=B8=8D?= =?UTF-8?q?=E5=88=B0prefix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api.py | 4 ++-- apps/assets/serializers.py | 2 +- apps/assets/templates/assets/asset_group_list.html | 4 ---- apps/jumpserver/settings.py | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/assets/api.py b/apps/assets/api.py index f3bfedb5a..5f1c73bf0 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -18,7 +18,7 @@ from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView from django.shortcuts import get_object_or_404 -from django.db.models import Q +from django.db.models import Q, Count from rest_framework.pagination import LimitOffsetPagination from common.mixins import CustomFilterMixin @@ -87,7 +87,7 @@ class AssetGroupViewSet(CustomFilterMixin, BulkModelViewSet): """ Asset group api set, for add,delete,update,list,retrieve resource """ - queryset = AssetGroup.objects.all() + queryset = AssetGroup.objects.all().annotate(asset_count=Count("assets")) serializer_class = serializers.AssetGroupSerializer permission_classes = (IsSuperUser,) diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index cb939fcc6..024d88e22 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -22,7 +22,7 @@ class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): @staticmethod def get_assets_amount(obj): - return obj.assets.count() + return obj.asset_count class AssetUpdateSystemUserSerializer(serializers.ModelSerializer): diff --git a/apps/assets/templates/assets/asset_group_list.html b/apps/assets/templates/assets/asset_group_list.html index e27d78867..58fbbc51c 100644 --- a/apps/assets/templates/assets/asset_group_list.html +++ b/apps/assets/templates/assets/asset_group_list.html @@ -34,10 +34,6 @@ $(document).ready(function(){ var detail_btn = '' + cellData + ''; $(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('' + innerHtml + ''); - }}, {targets: 4, createdCell: function (td, cellData, rowData) { var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData); diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 06db81d85..7a8aa422e 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -274,7 +274,7 @@ EMAIL_HOST_USER = CONFIG.EMAIL_HOST_USER EMAIL_HOST_PASSWORD = CONFIG.EMAIL_HOST_PASSWORD EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS -EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX +EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX or '' REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, From ba5ab21b371fd05672adcb618d502ee81a92f2d4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 17 Jan 2018 17:17:18 +0800 Subject: [PATCH 032/197] For storage --- .../templates/common/basic_setting.html | 3 + .../templates/common/email_setting.html | 3 + .../common/templates/common/ldap_setting.html | 3 + .../templates/common/storage_setting.html | 102 ++++++++++++++++++ apps/common/urls/view_urls.py | 1 + apps/common/views.py | 28 +++++ apps/locale/zh/LC_MESSAGES/django.po | 2 +- 7 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 apps/common/templates/common/storage_setting.html diff --git a/apps/common/templates/common/basic_setting.html b/apps/common/templates/common/basic_setting.html index fb5039795..9d67d762d 100644 --- a/apps/common/templates/common/basic_setting.html +++ b/apps/common/templates/common/basic_setting.html @@ -20,6 +20,9 @@
  • {% trans 'LDAP setting' %}
  • +
  • + {% trans 'Storage setting' %} +
  • diff --git a/apps/common/templates/common/email_setting.html b/apps/common/templates/common/email_setting.html index 7561849bd..2cd018021 100644 --- a/apps/common/templates/common/email_setting.html +++ b/apps/common/templates/common/email_setting.html @@ -20,6 +20,9 @@
  • {% trans 'LDAP setting' %}
  • +
  • + {% trans 'Storage setting' %} +
  • diff --git a/apps/common/templates/common/ldap_setting.html b/apps/common/templates/common/ldap_setting.html index 26f021569..de4a196c9 100644 --- a/apps/common/templates/common/ldap_setting.html +++ b/apps/common/templates/common/ldap_setting.html @@ -20,6 +20,9 @@
  • {% trans 'LDAP setting' %}
  • +
  • + {% trans 'Storage setting' %} +
  • diff --git a/apps/common/templates/common/storage_setting.html b/apps/common/templates/common/storage_setting.html new file mode 100644 index 000000000..cf2c243f1 --- /dev/null +++ b/apps/common/templates/common/storage_setting.html @@ -0,0 +1,102 @@ +{% extends 'base.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block content %} +
    +
    +
    +
    + +
    +
    +
    +
    + {% if form.non_field_errors %} +
    + {{ form.non_field_errors }} +
    + {% endif %} + {% csrf_token %} +

    {% trans "Command storage" %}

    + + + + + + + + + + +
    {% trans 'Name' %}{% trans 'Engine' %}{% trans 'Action' %}
    + +
    +

    {% trans "Replay storage" %}

    + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py index ff8086bde..57594b043 100644 --- a/apps/common/urls/view_urls.py +++ b/apps/common/urls/view_urls.py @@ -10,4 +10,5 @@ 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'), + url(r'^storage/$', views.StorageSettingView.as_view(), name='storage-setting'), ] diff --git a/apps/common/views.py b/apps/common/views.py index 4135ca82c..6ab46ead4 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -4,6 +4,7 @@ from django.contrib import messages from django.utils.translation import ugettext as _ from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm +from .models import Setting from .mixins import AdminUserRequiredMixin from .signals import ldap_auth_enable @@ -86,3 +87,30 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView): context = self.get_context_data() context.update({"form": form}) return render(request, self.template_name, context) + + +class StorageSettingView(AdminUserRequiredMixin, TemplateView): + form_class = LDAPSettingForm + template_name = "common/storage_setting.html" + + def get_context_data(self, **kwargs): + context = { + 'app': _('Settings'), + 'action': _('Storage setting'), + 'form': self.form_class(), + 'command_storage': Setting.objects.filter(name__endswith="_COMMAND_STORAGE") + } + 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:storage-setting') + else: + context = self.get_context_data() + context.update({"form": form}) + return render(request, self.template_name, context) diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 2c7f39971..55e035bfe 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -1311,7 +1311,7 @@ msgstr "Email主题前缀" #: common/forms.py:76 msgid "Enable LDAP Auth" -msgstr "二次验证" +msgstr "LDAP认证" #: common/forms.py:82 msgid "SMTP host" From a77acb7dfbb47415ec604ce72616a7db7bbd2ecd Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 17 Jan 2018 17:18:13 +0800 Subject: [PATCH 033/197] =?UTF-8?q?[Change]=20=E4=BF=AE=E6=94=B9=E9=83=A8?= =?UTF-8?q?=E5=88=86=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 31610 -> 31608 bytes apps/locale/zh/LC_MESSAGES/django.po | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 5b01e493bc366126e93b5de9115b6256c4691b6c..3d866a742aa33c001d3230ffb61d3ac13449f036 100644 GIT binary patch delta 2863 zcmXZedrX#P7{~Dk6AgP60}+Ojq+|`Hu1OK8L>Qfp9 z!8``B(kQVMb-)T#!8+6d8?X;#PMlV1Lr~~ zezhGqgNaVZg=JBJ$*9Jrp{``U^JS>QtL%E`U$KqOH#u%b?QgN+=L~d(-`Pv3f>-TL z%py)IkIY8RKa9GvB0IzRId~`eMULaP%GTS>NF!megF!wUT2KYgdcn_*|F&JW$Md%x z$CgL8G9A@ej?G71X`$oC?E)K5$n&papoR6QTl^ZP;O@i%8bdX9&>r^uamT0aIaHm? zI10N^^)gmO-iNxAkJvdFO%Tg1i=U# zg*jM^Fk9QIOVy~kL_SoB~_5CWNd^+k; zWaD_u$3#Pw+Y!t&f@d6`$NP!fQSZ{An#hT$^>JHe>v532|IG~aOm?CQ?zJDGHXgLc>>1Br zaompj9B(+Eu_g!}ARdk*aT@9Z%WMOx?oLd6|L-vvOyV=tcY6|5;2NsHkY}R}g;+p5 z)p0fIdk;|uyn$)>F{a~Js0+J@L-87Ff1lduLLaL2_h0D56jWz3aR|i>h6P`?8&qn`10)E#^a2jU)71FfjWj-cwFb^iBS?!P*};RUg^(J$Tz)WXSj7OKEP z&#!QPlk-iE_u5ZU2OmW>_Jh5QYOuq0h8}dI4(vr8G+*an zYTp7IM;%;+ldu6*uMM^TThE`j;a?v7=LIRxMFrDsE~;Pw>Lq*3`Nht!#0>K59dEPm zpbk8MYOoE}$k(2~gjxFjuQSlXz{P|H0qC2 u+%{r4aU1F-99^e+yr%OK1O7Up3RJe9&waJH|Lmz#=5~D0+*%b+$^0LRxp+MQ delta 2865 zcmXZedrX#P7{~DkH57dVq);rxq(WyXTdV`viopzOS*}%1n`?@`DhQ$+N*lfquDL?l z(A;dcGMknISD~#~!)$rT<}9<)JfvlYmNv&EMs4){@%;Y!+}CyA_jO;->4m=Dxc=U_ z56`5=B0&%=Sr`Pe*1m%A#4V0DJKk=0*^h8E>pn&8KY}UvljpD4>zF{k54CQ{qR^y8 zL2Pg$-H9yJ!MQjT^Bq6z`5Bl*{%OYxa3t|^)V|fW5%Y-G;Y9or)!0Q;1OK2J8WxQO z!7~gJqhX>HbwCBGU_I)9wKxP@Y%5M9e#3r+CB!{gfDbJWt;cxcF4VyX93MwDa4P1+ zZ}tinGjZK<-jc9D0jjZL)RoM3z6@1(g$cVTEsl4f_P5*E7YuZT-`jJjf|qSC zrV$Ta8k&xpzYld~h4wM$XW8tb(CJ%8BoaeE3? z=RDqtJ*awP%R?uj?&Je@7DhNfSjymbtV12tstMfUc!%S5OeNpx_*>N5eHOKE0M&T> zve0CEo4pGo%uln^F{X}77^uMWsAsnTRj3>@uofe@3AJt;>frbB9z24o(~G*0gbE%5 z-i_mN21c;dR-x+DSNQv1OG3A1gC};P3hs8iA64M6{Q*Z2|7x$H3ijK$%5eP%R6Z5; zDAMtM%=Ua~CHG$kRgsv2O{fjKQFmak<9+rZs*$7iXVm^b9QWf$;^E6fQ|x#<8IzdL zMb$5gc`(z?w)0Vql%oz@X|-MlHruzI{}_|V?{j?2o&C`0&@IVA z9XJp5C>A20eo*iDk{v*Gp0*-16SXfJ)o89QuruvkyV%ws`(i<3I0)8xq6KfF&{oXG zcbz|m!-&tI4!mHy?cb;ddQo4=(3Ro(aj1qfQ2Qoh3g%#h^MgVLy5gq>6I`|9T1+9o z!EUpAPzN1C6+GeiG)^M!M!ieP)uGc->!Y^XHejN@|Md*?Otzv5w%HEU#!h?4p78ty z$K9yU@rLtbYq(0{49vu0)CHE=CRE+6IQad4z#y5#=cw=YD5}6!RDm(I;f6e%O8ltf z8r1jRh&tdcOu}RNh5270T(Vj;&c*S0idC-SCZ~%2sV8U>B;9Z#;hv)AapcW1xk@SBJM|H0s%=q4Md@XQ2vBwfW8$+b2=$pLP5K>W@>@ ww&GIaF4Rjn`DNAPHGOVyz+Wd+fvS#EceE5GT Date: Wed, 17 Jan 2018 17:22:04 +0800 Subject: [PATCH 034/197] =?UTF-8?q?[Update]=20=E5=90=AF=E7=94=A8ldap?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/forms.py | 6 +++--- apps/common/views.py | 4 ++-- apps/locale/zh/LC_MESSAGES/django.mo | Bin 31608 -> 31614 bytes apps/locale/zh/LC_MESSAGES/django.po | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/common/forms.py b/apps/common/forms.py index 073bb671f..6b83c54cc 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -72,9 +72,6 @@ class BasicSettingForm(BaseForm): max_length=1024, label=_("Email Subject Prefix"), initial="[Jumpserver] " ) - AUTH_LDAP = forms.BooleanField( - label=_("Enable LDAP Auth"), initial=False, required=False - ) class EmailSettingForm(BaseForm): @@ -129,3 +126,6 @@ class LDAPSettingForm(BaseForm): 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 + ) diff --git a/apps/common/views.py b/apps/common/views.py index 4135ca82c..43b249cee 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -25,8 +25,6 @@ class BasicSettingView(AdminUserRequiredMixin, TemplateView): 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:basic-setting') @@ -79,6 +77,8 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView): 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') diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 3d866a742aa33c001d3230ffb61d3ac13449f036..e11f9f5560cde51416848d6495e4036da7014744 100644 GIT binary patch delta 2862 zcmXZedrX#P7{~F)NIdWo^eUzyin`2@n#HEGLR~t{w3fx%ifMDnK~xk)5L-QA5U#mA zu#vgZY-Kj>;9-?Ea}BfQA)B*IOY@L)L`xf*vW{+3X;QVXb`$lZl%guXnu3zHdLon_0ITwf`{2@dwXev>n)s{B_j2u$aW$Jb?_KW!f}ot@ca}^Bmacs66{U97`5+t+kj(|qzzVzpn`|@QMf{rm5+5YKiW6``X=EKH6Stxc-tYJrs)19X z6TjGtIGKqK$N6PZfk~*wrlPK-$oUFX;U#vt^Q&yL^DT}yqxNsJ;pYr=h2Pn8sDc-4 zC-x`qF()z`H9r=0Wd-(O=L@kX`B{$3ZH=wBYmi35;B^K$Y}ke>c+v}ga{QOQYP&q& zy*yeU$6o9kfNE@*9fi8me8-R48MZtj&%cI&7S^L~@vEqUn-U9X4As~kyU+8793Qi% zP<77ZFuaPYmpM0bB?FH-tM!geM$48 z#(Ueoc90!`G1lE<$786Dr!i20r%}(Y1a$`%;2^BU7_LLD+lV^&T^xyrQFS^|7m_+Z z2nORFn1xd?hNZR&Ri|#gzyB2^bZgdnVhgI^cE|fr1rFI0m_q!sy^Jc@WxH2I>tm?= z0Mw(%#?d&+^Q9Hse;rgsA_p5$8@8kFz%Ivo?EzFHN9>QN{eL*_!rsIw3nKg3AvPD& zm>+|xKRNVZx-GKBs74l`4y?8>q7Gbb-*o;XOe4S7@lksQwf=(RU}3Z_6;(IPVxU`+ zgF0{)>QR&-pMFs1_;;IB8FfC;4oB@9g(^74PO{VOOgq;uMfQb3Lo^6hdZG!tQ)mO` z;@i$2#T$vwpbl)a?e=d}1D&X^q{pIYeHQ9g4@K?E#W?0+jPrv62D;+M6Nw<0=eQQ* znfgQ$YX9iPVAiQ7@{Qs2dq<5BC&ZH=wRoAmv!VUUI!PzATzk5L=<*n{@C z=i3~&qdv!L&SzEy!3g4^I25O%F0jHjqUvtI#P|OmgLD#~p}yNAr~;Qz1#VpuZOF$v zi63&j6!pC~pbmHgtKNd)36`w%sqZt^Qy~+3fUN{s+)ydu{*# delta 2855 zcmXZeeN2{B7{~D&QVe;#ag1S7>-bv<=T>Ib4_iJ2)>|zsfBTeYpzrl znVXJQX47iwmPsquFk8N4bCy|YC~2>1X=6-d)MobmJ@@ag&$+I1&ULQ)?ScRLlKT6S z_I787X+aRw#DgG)jrIjhA#QcN+3|M!o_!y0XWd7r{l_p9zxVuQ+k>g(`%vqG1(Aan z1Yu$#&xr!m!6kSLPH_C7=PNLS{Ns-2p)O!4YTs%b;zZ(gcqe{_YU~$O1An6$O0Eur zU?ziDb(ENoI$$xXU=!+qwU~^pwhiwke$9T4(};Vq949S|Y{C@cPSnAN9G^rra3*x( zXL}i^GSTC>v?eN0hH9)5btP5K*Pse7vum7x*|s^q#qkc*{@phGl!31B8{3U4c*$PJ zT;inK$UM~i{irJ|w^N;;fwz*M<2Y{XZL{5gG!h0|85FT$H>%)iFZj{%pSIWbd;X^5 z*rMoGW}zC(w?(KcEp_~eon_+*dH(ebw6Ga7z>iDERgQ{~L z$6zn2UiRY1yHI!X0XqX@oF6P?FdSE)4r}zFHxE^)7Vp4DjNvBKx^1X~-@&nX3{~ek>OuxB4T6z4 z2J^83V>sW|q3Sd(_4mJ)gl^47PwYe$+~fEls=!hEE#5}_lf8;6*l%y5)~D1(`7G3< z$iwkigo%dgxc@q+jzkf*pbG6l-GL6r2kc?g#^d$})c)Tc_hTCIz-5s`>~LF%8O)cU z>Q4?mm~N}=TvQ{qr~{YV=TQf)w{JMV7cRKp`s`wB4=i!sLeK{*3m@neZZ5L7#E#7y!V z?KZm)b)emecaaDW=z-jzkz|CNjs|GF8cv$<9>U@p7Q(! z#}`qb<2C2AR|LU5#3OJtR-!Jj#zIijVitaZy0CLN46mT}C$Eey^uCq;{!5*hjOuI}4#g_Ri#)#y^=>qy z{y%sT^*itq>KSiF-N83;Fz!S((1B{~5UT!Z=YL(v{a5GLydbtJ`o$ZGT3BYMqYBLS z{9@qPDU+VfpD{KJEPydd@2s9=^Ig(_H#ddVJgey;ONFq`}u$D8ci zr~~(*8tgaQLj{V)WwJ7{0{+#bmIU3 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 55e035bfe..50aec9767 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -1311,7 +1311,7 @@ msgstr "Email主题前缀" #: common/forms.py:76 msgid "Enable LDAP Auth" -msgstr "LDAP认证" +msgstr "开启LDAP认证" #: common/forms.py:82 msgid "SMTP host" From 3c3e9a41130f96d3a4a95515ca6dd4cae28efd85 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 17 Jan 2018 17:26:25 +0800 Subject: [PATCH 035/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 31614 -> 31661 bytes apps/locale/zh/LC_MESSAGES/django.po | 149 +++++++++++++-------------- 2 files changed, 73 insertions(+), 76 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index e11f9f5560cde51416848d6495e4036da7014744..7be8a996a99f2451a1f8854e863216f289eaa052 100644 GIT binary patch delta 4807 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\n" "Language-Team: Jumpserver team\n" @@ -129,7 +129,7 @@ msgid "Password or private key password" msgstr "密码或秘钥不合法" #: assets/forms.py:201 assets/forms.py:262 assets/models/user.py:30 -#: common/forms.py:110 users/forms.py:16 users/forms.py:24 +#: 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/user_create.html:11 @@ -661,7 +661,7 @@ msgstr "其它" #: assets/templates/assets/asset_group_create.html:16 #: assets/templates/assets/asset_update.html:55 #: assets/templates/assets/cluster_create_update.html:54 -#: common/templates/common/basic_setting.html:56 +#: 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 @@ -681,10 +681,10 @@ msgstr "重置" #: assets/templates/assets/asset_bulk_update.html:24 #: assets/templates/assets/asset_create.html:41 #: assets/templates/assets/asset_group_create.html:17 -#: assets/templates/assets/asset_list.html:55 +#: assets/templates/assets/asset_list.html:53 #: assets/templates/assets/asset_update.html:56 #: assets/templates/assets/cluster_create_update.html:55 -#: common/templates/common/basic_setting.html:57 +#: 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 @@ -726,8 +726,8 @@ msgstr "资产列表" #: 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:42 -#: assets/templates/assets/asset_list.html:95 +#: 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 @@ -750,8 +750,8 @@ msgstr "更新" #: 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:43 -#: assets/templates/assets/asset_list.html:96 +#: 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 @@ -776,7 +776,6 @@ msgstr "资产列表" #: assets/templates/assets/admin_user_assets.html:62 #: 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/user_asset_list.html:22 #: users/templates/users/login_log_list.html:50 @@ -786,7 +785,7 @@ msgstr "类型" #: assets/templates/assets/admin_user_assets.html:63 #: assets/templates/assets/admin_user_list.html:25 #: 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_list.html:27 msgid "Reachable" @@ -827,8 +826,8 @@ msgstr "使用集群管理用户" #: assets/templates/assets/admin_user_detail.html:101 #: assets/templates/assets/asset_detail.html:230 -#: assets/templates/assets/asset_group_list.html:85 -#: assets/templates/assets/asset_list.html:214 +#: assets/templates/assets/asset_group_list.html:81 +#: assets/templates/assets/asset_list.html:220 #: assets/templates/assets/cluster_assets.html:104 #: assets/templates/assets/cluster_list.html:89 #: assets/templates/assets/system_user_detail.html:164 @@ -859,7 +858,7 @@ msgstr "比例" #: assets/templates/assets/admin_user_list.html:29 #: assets/templates/assets/asset_group_detail.html:55 #: 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_list.html:23 #: assets/templates/assets/system_user_list.html:31 @@ -909,7 +908,7 @@ msgid "Quick modify" msgstr "快速修改" #: 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 #: perms/templates/perms/asset_permission_create_update.html:47 #: perms/templates/perms/asset_permission_detail.html:116 @@ -970,8 +969,8 @@ msgstr "移除" msgid "Create asset group" msgstr "创建资产组" -#: assets/templates/assets/asset_group_list.html:80 -#: assets/templates/assets/asset_list.html:209 +#: assets/templates/assets/asset_group_list.html:76 +#: assets/templates/assets/asset_list.html:215 #: assets/templates/assets/cluster_list.html:84 #: assets/templates/assets/system_user_list.html:129 #: users/templates/users/user_detail.html:333 @@ -981,29 +980,29 @@ msgstr "创建资产组" msgid "Are you sure?" 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 msgid "This will delete the selected groups !!!" msgstr "删除选择组" -#: assets/templates/assets/asset_group_list.html:89 +#: assets/templates/assets/asset_group_list.html:85 msgid "Group deleted" msgstr "组已被删除" -#: assets/templates/assets/asset_group_list.html:90 -#: assets/templates/assets/asset_group_list.html:95 +#: assets/templates/assets/asset_group_list.html:86 +#: assets/templates/assets/asset_group_list.html:91 msgid "Group Delete" msgstr "删除" -#: assets/templates/assets/asset_group_list.html:94 +#: assets/templates/assets/asset_group_list.html:90 msgid "Group deleting failed." 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." msgstr "更新成功" -#: assets/templates/assets/asset_group_list.html:158 +#: assets/templates/assets/asset_group_list.html:154 msgid "AssetGroup Updated" msgstr "资产组更新" @@ -1021,52 +1020,47 @@ msgstr "导出" msgid "Create asset" msgstr "创建资产" -#: assets/templates/assets/asset_list.html:35 -#: assets/templates/assets/user_asset_list.html:23 -msgid "Env" -msgstr "环境" - -#: assets/templates/assets/asset_list.html:36 +#: assets/templates/assets/asset_list.html:34 #: assets/templates/assets/user_asset_list.html:24 msgid "Hardware" msgstr "硬件" -#: assets/templates/assets/asset_list.html:48 +#: assets/templates/assets/asset_list.html:46 #: users/templates/users/user_list.html:37 msgid "Delete selected" msgstr "批量删除" -#: assets/templates/assets/asset_list.html:49 +#: assets/templates/assets/asset_list.html:47 #: users/templates/users/user_list.html:38 msgid "Update selected" msgstr "批量更新" -#: assets/templates/assets/asset_list.html:50 +#: assets/templates/assets/asset_list.html:48 #: users/templates/users/user_list.html:39 msgid "Deactive selected" msgstr "禁用所选" -#: assets/templates/assets/asset_list.html:51 +#: assets/templates/assets/asset_list.html:49 #: users/templates/users/user_list.html:40 msgid "Active selected" msgstr "激活所选" -#: assets/templates/assets/asset_list.html:210 +#: assets/templates/assets/asset_list.html:216 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" # msgid "Deleted!" # msgstr "删除" -#: assets/templates/assets/asset_list.html:218 +#: assets/templates/assets/asset_list.html:224 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:219 -#: assets/templates/assets/asset_list.html:224 +#: assets/templates/assets/asset_list.html:225 +#: assets/templates/assets/asset_list.html:230 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:223 +#: assets/templates/assets/asset_list.html:229 msgid "Asset Deleting failed." msgstr "删除失败" @@ -1202,6 +1196,10 @@ msgstr "删除系统用户" msgid "System Users Deleting failed." msgstr "系统用户删除失败" +#: assets/templates/assets/user_asset_list.html:23 +msgid "Env" +msgstr "环境" + #: assets/templates/assets/user_asset_list.html:26 msgid "Connective" msgstr "连接性" @@ -1309,66 +1307,66 @@ msgstr "用户第一次登录,修改profile后重定向到地址" msgid "Email Subject Prefix" msgstr "Email主题前缀" -#: common/forms.py:76 -msgid "Enable LDAP Auth" -msgstr "开启LDAP认证" - -#: common/forms.py:82 +#: common/forms.py:79 msgid "SMTP host" msgstr "SMTP主机" -#: common/forms.py:84 +#: common/forms.py:81 msgid "SMTP port" msgstr "SMTP端口" -#: common/forms.py:86 +#: common/forms.py:83 msgid "SMTP user" msgstr "SMTP账号" -#: common/forms.py:89 +#: common/forms.py:86 msgid "SMTP password" msgstr "SMTP密码" -#: common/forms.py:90 +#: common/forms.py:87 msgid "Some provider use token except password" msgstr "一些邮件提供商需要输入的是Token" -#: common/forms.py:93 common/forms.py:130 +#: common/forms.py:90 common/forms.py:127 msgid "Use SSL" msgstr "使用SSL" -#: common/forms.py:94 +#: common/forms.py:91 msgid "If SMTP port is 465, may be select" msgstr "如果SMTP端口是465,通常需要启用SSL" -#: common/forms.py:97 +#: common/forms.py:94 msgid "Use TLS" msgstr "使用TLS" -#: common/forms.py:98 +#: common/forms.py:95 msgid "If SMTP port is 587, may be select" msgstr "如果SMTP端口是587,通常需要启用TLS" -#: common/forms.py:104 +#: common/forms.py:101 msgid "LDAP server" msgstr "LDAP地址" -#: common/forms.py:107 +#: common/forms.py:104 msgid "Bind DN" msgstr "绑定DN" -#: common/forms.py:114 +#: common/forms.py:111 msgid "User OU" msgstr "用户OU" -#: common/forms.py:117 +#: common/forms.py:114 msgid "User search filter" msgstr "用户过滤器" -#: common/forms.py:120 +#: common/forms.py:117 msgid "User attr map" msgstr "LDAP属性映射" +#: common/forms.py:130 +msgid "Enable LDAP Auth" +msgstr "开启LDAP认证" + #: common/mixins.py:29 msgid "is discard" msgstr "" @@ -1393,7 +1391,7 @@ 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:45 +#: common/templates/common/ldap_setting.html:18 common/views.py:44 msgid "Email setting" msgstr "邮件设置" @@ -1403,20 +1401,19 @@ msgstr "邮件设置" msgid "LDAP setting" msgstr "LDAP设置" -#: common/templates/common/basic_setting.html:55 #: 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:44 common/views.py:69 +#: common/views.py:17 common/views.py:43 common/views.py:69 #: templates/_nav.html:69 msgid "Settings" msgstr "系统设置" -#: common/views.py:30 common/views.py:55 common/views.py:80 -msgid "Update setting successfully" -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 msgid "Interval" @@ -1912,8 +1909,8 @@ msgid "Close" msgstr "关闭" #: templates/_nav.html:9 users/views/group.py:28 users/views/group.py:44 -#: users/views/group.py:62 users/views/group.py:79 users/views/login.py:194 -#: users/views/login.py:243 users/views/user.py:57 users/views/user.py:72 +#: users/views/group.py:62 users/views/group.py:79 users/views/login.py:197 +#: users/views/login.py:246 users/views/user.py:57 users/views/user.py:72 #: users/views/user.py:91 users/views/user.py:147 users/views/user.py:304 #: users/views/user.py:318 users/views/user.py:355 users/views/user.py:377 msgid "Users" @@ -2740,48 +2737,48 @@ msgstr "编辑用户组" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:84 +#: users/views/login.py:87 msgid "Logout success" msgstr "退出登录成功" -#: users/views/login.py:85 +#: users/views/login.py:88 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: users/views/login.py:101 +#: users/views/login.py:104 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:114 +#: users/views/login.py:117 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:115 +#: users/views/login.py:118 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views/login.py:129 +#: users/views/login.py:132 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:130 +#: users/views/login.py:133 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:147 users/views/login.py:160 +#: users/views/login.py:150 users/views/login.py:163 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:156 +#: users/views/login.py:159 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:194 +#: users/views/login.py:197 msgid "First login" msgstr "首次登陆" -#: users/views/login.py:244 +#: users/views/login.py:247 msgid "Login log list" msgstr "登录日志" From 67001dd99f9798a67a4d6b89d7c0adf20d3ca019 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 18 Jan 2018 09:56:13 +0800 Subject: [PATCH 036/197] =?UTF-8?q?[Feature]=20=E6=94=AF=E6=8C=81es?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings.py | 3 ++- apps/terminal/backends/command/es.py | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 apps/terminal/backends/command/es.py diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 7a8aa422e..89686cada 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -373,7 +373,8 @@ CAPTCHA_FOREGROUND_COLOR = '#001100' CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',) CAPTCHA_TEST_MODE = CONFIG.CAPTCHA_TEST_MODE -COMMAND_STORAGE_BACKEND = 'terminal.backends.command.db' +#COMMAND_STORAGE_BACKEND = 'terminal.backends.command.db' +COMMAND_STORAGE_BACKEND = 'terminal.backends.command.es' # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html BOOTSTRAP3 = { diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py new file mode 100644 index 000000000..39c83f09d --- /dev/null +++ b/apps/terminal/backends/command/es.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# + +from jms_es_storage import ESStore +from .base import CommandBase + + +class CommandStore(CommandBase, ESStore): + def __init__(self): + ESStore.__init__(self, hosts=["http://elastic:changeme@localhost:9200"]) + + def save(self, command): + return ESStore.save(self, command) + + def bulk_save(self, commands): + return ESStore.bulk_save(self, commands) + + def filter(self, date_from=None, date_to=None, + user=None, asset=None, system_user=None, + input=None, session=None): + + data = ESStore.filter( + self, date_from=date_from, date_to=date_to, + user=user, asset=asset, system_user=system_user, + input=input, session=session + ) + return [item["_source"] for item in data["hits"] if item] From 689558b8c7d1ea3b435bda1044491a7e984fb814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Thu, 18 Jan 2018 18:12:39 +0800 Subject: [PATCH 037/197] Update README.md --- README.md | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/README.md b/README.md index 14a2c4143..0177c1abe 100644 --- a/README.md +++ b/README.md @@ -26,36 +26,7 @@ Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互 ### Install 安装 - 1. 安装 Python3 - 略 - - 2. 安装依赖 - - ``` - $ cd requirements && yum -y install $(cat rpm_requirements.txt) && pip install -r requirements.txt - ``` - - 3. 修改配置文件 - - ``` - $ cp config_example.py config.py - ``` - - 4. 修改表结构 - - ``` - $ cd apps && python manage.py makemigrations && python manage.py migrate - ``` - - 5. 运行 - - ``` - $ python run_server.py - ``` - - 6. 其它 - - 整合luna,coco需要nginx来配合, 详见详细安装文档 +    [详细安装](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7) ### Usage 使用 From b936d54a4883782bcd5f2db779c9441dd5c49193 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 20 Jan 2018 22:22:09 +0800 Subject: [PATCH 038/197] =?UTF-8?q?[Feature]=20=E6=B7=BB=E5=8A=A0es?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/views/cluster.py | 5 --- apps/common/forms.py | 43 ++++++++++++++++--- apps/common/models.py | 15 +++++-- .../_add_terminal_command_storage_modal.html | 21 +++++++++ .../templates/common/basic_setting.html | 2 +- .../templates/common/email_setting.html | 2 +- .../common/templates/common/ldap_setting.html | 2 +- ...age_setting.html => terminal_setting.html} | 38 +++++++++++++--- apps/common/urls/view_urls.py | 2 +- apps/common/views.py | 20 +++++---- apps/jumpserver/settings.py | 18 +++++++- apps/terminal/api.py | 11 ++++- apps/terminal/backends/__init__.py | 30 +++++++++++-- apps/terminal/backends/command/base.py | 6 +++ apps/terminal/backends/command/db.py | 34 ++++++++++++--- apps/terminal/backends/command/models.py | 15 +++++++ apps/terminal/forms.py | 2 +- apps/terminal/models.py | 20 +++++++++ apps/terminal/serializers.py | 4 +- .../terminal/terminal_modal_accept.html | 1 + .../templates/terminal/terminal_update.html | 1 + apps/terminal/templatetags/terminal_tags.py | 10 +++-- apps/terminal/views/command.py | 9 ++-- apps/terminal/views/session.py | 4 +- 24 files changed, 259 insertions(+), 56 deletions(-) create mode 100644 apps/common/templates/common/_add_terminal_command_storage_modal.html rename apps/common/templates/common/{storage_setting.html => terminal_setting.html} (63%) diff --git a/apps/assets/views/cluster.py b/apps/assets/views/cluster.py index 835229fc1..5df58953c 100644 --- a/apps/assets/views/cluster.py +++ b/apps/assets/views/cluster.py @@ -60,11 +60,6 @@ class ClusterUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView) success_url = reverse_lazy('assets:cluster-list') success_message = update_success_msg - def form_valid(self, form): - cluster = form.save(commit=False) - cluster.save() - return super().form_valid(form) - def get_context_data(self, **kwargs): context = { 'app': _('assets'), diff --git a/apps/common/forms.py b/apps/common/forms.py index 073bb671f..ab3dadba3 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -4,7 +4,9 @@ import json from django import forms from django.utils.translation import ugettext_lazy as _ +from django.utils.html import escape from django.db import transaction +from django.conf import settings from .models import Setting from .fields import DictField @@ -30,28 +32,32 @@ def to_form_value(value): class BaseForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - settings = Setting.objects.all() + db_settings = Setting.objects.all() for name, field in self.fields.items(): - db_value = getattr(settings, name).value - if db_value: + db_value = getattr(db_settings, name).value + django_value = getattr(settings, name) if hasattr(settings, name) else None + if db_value is not None: field.initial = to_form_value(db_value) + elif django_value is not None: + field.initial = django_value - def save(self): + def save(self, category="default"): if not self.is_bound: raise ValueError("Form is not bound") - settings = Setting.objects.all() + db_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): + if value == to_form_value(getattr(db_settings, name).value): continue defaults = { 'name': name, + 'category': category, 'value': to_model_value(value) } Setting.objects.update_or_create(defaults=defaults, name=name) @@ -129,3 +135,28 @@ class LDAPSettingForm(BaseForm): AUTH_LDAP_START_TLS = forms.BooleanField( label=_("Use SSL"), initial=False, required=False ) + + +class TerminalSettingForm(BaseForm): + SORT_BY_CHOICES = ( + ('hostname', _('Hostname')), + ('ip', _('IP')), + ) + TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField( + choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by") + ) + TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField( + initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds") + ) + TERMINAL_PASSWORD_AUTH = forms.BooleanField( + initial=True, required=False, label=_("Password auth") + ) + TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField( + initial=True, required=False, label=_("Public key auth") + ) + TERMINAL_COMMAND_STORAGE = DictField( + label=_("Command storage"), help_text=_( + "Set terminal storage setting, `default` is the using as default," + "You can set other storage and some terminal using" + ) + ) diff --git a/apps/common/models.py b/apps/common/models.py index 091b8b83a..afee1e13d 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -24,6 +24,7 @@ class SettingManager(models.Manager): class Setting(models.Model): name = models.CharField(max_length=128, unique=True, verbose_name=_("Name")) value = models.TextField(verbose_name=_("Value")) + category = models.CharField(max_length=128, default="default") enabled = models.BooleanField(verbose_name=_("Enabled"), default=True) comment = models.TextField(verbose_name=_("Comment")) @@ -33,12 +34,20 @@ class Setting(models.Model): return self.name @property - def value_(self): + def cleaned_value(self): try: return json.loads(self.value) except json.JSONDecodeError: return None + @cleaned_value.setter + def cleaned_value(self, item): + try: + v = json.dumps(item) + self.value = v + except json.JSONDecodeError as e: + raise ValueError("Json dump error: {}".format(str(e))) + @classmethod def refresh_all_settings(cls): settings_list = cls.objects.all() @@ -53,9 +62,9 @@ class Setting(models.Model): setattr(settings, self.name, value) if self.name == "AUTH_LDAP": - if self.value_ and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS: + if self.cleaned_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: + elif not self.cleaned_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": diff --git a/apps/common/templates/common/_add_terminal_command_storage_modal.html b/apps/common/templates/common/_add_terminal_command_storage_modal.html new file mode 100644 index 000000000..678152981 --- /dev/null +++ b/apps/common/templates/common/_add_terminal_command_storage_modal.html @@ -0,0 +1,21 @@ +{% extends '_modal.html' %} +{% load i18n %} +{% block modal_id %}add_command_storage_model{% endblock %} +{% block modal_title%}{% trans "Add command storage" %}{% endblock %} +{% block modal_body %} +
    + {% csrf_token %} +
    + + {% trans 'Download' %} +
    +
    + + + + {% trans 'If set id, will use this id update asset existed' %} + +
    +
    +{% endblock %} +{% block modal_confirm_id %}btn_asset_import{% endblock %} diff --git a/apps/common/templates/common/basic_setting.html b/apps/common/templates/common/basic_setting.html index 9d67d762d..496eca977 100644 --- a/apps/common/templates/common/basic_setting.html +++ b/apps/common/templates/common/basic_setting.html @@ -21,7 +21,7 @@ {% trans 'LDAP setting' %}
  • - {% trans 'Storage setting' %} + {% trans 'Terminal setting' %}
  • diff --git a/apps/common/templates/common/email_setting.html b/apps/common/templates/common/email_setting.html index 2cd018021..1fd772db1 100644 --- a/apps/common/templates/common/email_setting.html +++ b/apps/common/templates/common/email_setting.html @@ -21,7 +21,7 @@ {% trans 'LDAP setting' %}
  • - {% trans 'Storage setting' %} + {% trans 'Terminal setting' %}
  • diff --git a/apps/common/templates/common/ldap_setting.html b/apps/common/templates/common/ldap_setting.html index de4a196c9..f0569f873 100644 --- a/apps/common/templates/common/ldap_setting.html +++ b/apps/common/templates/common/ldap_setting.html @@ -21,7 +21,7 @@ {% trans 'LDAP setting' %}
  • - {% trans 'Storage setting' %} + {% trans 'Terminal setting' %}
  • diff --git a/apps/common/templates/common/storage_setting.html b/apps/common/templates/common/terminal_setting.html similarity index 63% rename from apps/common/templates/common/storage_setting.html rename to apps/common/templates/common/terminal_setting.html index cf2c243f1..3d0b7eb6f 100644 --- a/apps/common/templates/common/storage_setting.html +++ b/apps/common/templates/common/terminal_setting.html @@ -21,7 +21,7 @@ {% trans 'LDAP setting' %}
  • - {% trans 'Storage setting' %} + {% trans 'Terminal setting' %}
  • @@ -35,26 +35,50 @@ {% endif %} {% csrf_token %} +

    {% trans "Basic setting" %}

    + {% for field in form %} + {% if not field.field|is_bool_field %} + {% bootstrap_field field layout="horizontal" %} + {% else %} +
    + +
    +
    + {{ field }} +
    +
    + {{ field.help_text }} +
    +
    +
    + {% endif %} + {% endfor %} + +

    {% trans "Command storage" %}

    - - + + {% for name, setting in command_storage.items %} + + + + + {% endfor %}
    {% trans 'Name' %}{% trans 'Engine' %}{% trans 'Action' %}{% trans 'Type' %}
    {{ name }}{{ setting.TYPE }}
    - +{# #}

    {% trans "Replay storage" %}

    -
    @@ -68,6 +92,7 @@
    + {% include 'common/_add_terminal_command_storage_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py index 57594b043..466f7c49c 100644 --- a/apps/common/urls/view_urls.py +++ b/apps/common/urls/view_urls.py @@ -10,5 +10,5 @@ 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'), - url(r'^storage/$', views.StorageSettingView.as_view(), name='storage-setting'), + url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'), ] diff --git a/apps/common/views.py b/apps/common/views.py index 6ab46ead4..8e7dc8341 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -1,9 +1,11 @@ -from django.views.generic import View, TemplateView +from django.views.generic import TemplateView from django.shortcuts import render, redirect from django.contrib import messages from django.utils.translation import ugettext as _ +from django.conf import settings -from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm +from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ + TerminalSettingForm from .models import Setting from .mixins import AdminUserRequiredMixin from .signals import ldap_auth_enable @@ -89,27 +91,29 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView): return render(request, self.template_name, context) -class StorageSettingView(AdminUserRequiredMixin, TemplateView): - form_class = LDAPSettingForm - template_name = "common/storage_setting.html" +class TerminalSettingView(AdminUserRequiredMixin, TemplateView): + form_class = TerminalSettingForm + template_name = "common/terminal_setting.html" def get_context_data(self, **kwargs): + command_storage = settings.TERMINAL_COMMAND_STORAGE context = { 'app': _('Settings'), - 'action': _('Storage setting'), + 'action': _('Terminal setting'), 'form': self.form_class(), - 'command_storage': Setting.objects.filter(name__endswith="_COMMAND_STORAGE") + 'command_storage': command_storage, } kwargs.update(context) return super().get_context_data(**kwargs) def post(self, request): + print(request.POST) 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:storage-setting') + return redirect('settings:terminal-setting') else: context = self.get_context_data() context.update({"form": form}) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 7a8aa422e..d023b587a 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -373,7 +373,23 @@ CAPTCHA_FOREGROUND_COLOR = '#001100' CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',) CAPTCHA_TEST_MODE = CONFIG.CAPTCHA_TEST_MODE -COMMAND_STORAGE_BACKEND = 'terminal.backends.command.db' +COMMAND_STORAGE = { + 'ENGINE': 'terminal.backends.command.db', +} + +TERMINAL_COMMAND_STORAGE = { + 'default': { + 'TYPE': 'server', + }, + # 'ali-es': { + # 'TYPE': 'elasticsearch', + # 'HOSTS': ['http://elastic:changeme@localhost:9200'], + # }, + # 'ali-hz-es': { + # 'TYPE': 'elasticsearch', + # 'HOSTS': ['http://elastic:changeme@localhost:9200'], + # } +} # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html BOOTSTRAP3 = { diff --git a/apps/terminal/api.py b/apps/terminal/api.py index 87311d5b2..e587d8928 100644 --- a/apps/terminal/api.py +++ b/apps/terminal/api.py @@ -21,7 +21,7 @@ from .serializers import TerminalSerializer, StatusSerializer, \ SessionSerializer, TaskSerializer, ReplaySerializer from .hands import IsSuperUserOrAppUser, IsAppUser, \ IsSuperUserOrAppUserOrUserReadonly -from .backends import get_command_store, SessionCommandSerializer +from .backends import get_terminal_command_store, SessionCommandSerializer logger = logging.getLogger(__file__) @@ -196,7 +196,7 @@ class CommandViewSet(viewsets.ViewSet): } """ - command_store = get_command_store() + command_store = get_terminal_command_store() serializer_class = SessionCommandSerializer permission_classes = (IsSuperUserOrAppUser,) @@ -260,3 +260,10 @@ class SessionReplayViewSet(viewsets.ViewSet): return redirect(url) else: return HttpResponseNotFound() + + +class LoadConfig(APIView): + permission_classes = (IsAppUser,) + + def get(self, request): + pass diff --git a/apps/terminal/backends/__init__.py b/apps/terminal/backends/__init__.py index 6baaed3c4..8b09f0c55 100644 --- a/apps/terminal/backends/__init__.py +++ b/apps/terminal/backends/__init__.py @@ -2,9 +2,33 @@ from importlib import import_module from django.conf import settings from .command.serializers import SessionCommandSerializer +TYPE_ENGINE_MAPPING = { + 'elasticsearch': 'terminal.backends.command.db', +} + def get_command_store(): - command_engine = import_module(settings.COMMAND_STORAGE_BACKEND) - command_store = command_engine.CommandStore() - return command_store + params = settings.COMMAND_STORAGE + engine_class = import_module(params['ENGINE']) + storage = engine_class.CommandStore(params) + return storage + + +def get_terminal_command_store(): + storage_list = {} + for name, params in settings.TERMINAL_COMMAND_STORAGE.items(): + tp = params['TYPE'] + if tp == 'server': + storage = get_command_store() + else: + if not TYPE_ENGINE_MAPPING.get(tp): + raise AssertionError("Command storage type should in {}".format( + ', '.join(TYPE_ENGINE_MAPPING.keys())) + ) + engine_class = import_module(TYPE_ENGINE_MAPPING[tp]) + storage = engine_class.CommandStore(params) + storage_list[name] = storage + return storage_list + + diff --git a/apps/terminal/backends/command/base.py b/apps/terminal/backends/command/base.py index 0aa689738..585930a5d 100644 --- a/apps/terminal/backends/command/base.py +++ b/apps/terminal/backends/command/base.py @@ -19,3 +19,9 @@ class CommandBase(object): input=None, session=None): pass + @abc.abstractmethod + def count(self, date_from=None, date_to=None, + user=None, asset=None, system_user=None, + input=None, session=None): + pass + diff --git a/apps/terminal/backends/command/db.py b/apps/terminal/backends/command/db.py index 27ee19a14..85b99ce80 100644 --- a/apps/terminal/backends/command/db.py +++ b/apps/terminal/backends/command/db.py @@ -8,7 +8,7 @@ from .base import CommandBase class CommandStore(CommandBase): - def __init__(self): + def __init__(self, params): from terminal.models import Command self.model = Command @@ -37,9 +37,11 @@ class CommandStore(CommandBase): )) return self.model.objects.bulk_create(_commands) - def filter(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, - input=None, session=None): + @staticmethod + def make_filter_kwargs( + date_from=None, date_to=None, + user=None, asset=None, system_user=None, + input=None, session=None): filter_kwargs = {} date_from_default = timezone.now() - datetime.timedelta(days=7) date_to_default = timezone.now() @@ -60,10 +62,28 @@ class CommandStore(CommandBase): if session: filter_kwargs['session'] = session + return filter_kwargs + + def filter(self, date_from=None, date_to=None, + user=None, asset=None, system_user=None, + input=None, session=None): + filter_kwargs = self.make_filter_kwargs( + date_from=date_from, date_to=date_to, user=user, + asset=asset, system_user=system_user, input=input, + session=session, + ) queryset = self.model.objects.filter(**filter_kwargs) return queryset - def all(self): - """返回所有数据""" - return self.model.objects.iterator() + def count(self, date_from=None, date_to=None, + user=None, asset=None, system_user=None, + input=None, session=None): + filter_kwargs = self.make_filter_kwargs( + date_from=date_from, date_to=date_to, user=user, + asset=asset, system_user=system_user, input=input, + session=session, + ) + count = self.model.objects.filter(**filter_kwargs).count() + return count + diff --git a/apps/terminal/backends/command/models.py b/apps/terminal/backends/command/models.py index 2d1387427..cc0d375e6 100644 --- a/apps/terminal/backends/command/models.py +++ b/apps/terminal/backends/command/models.py @@ -18,5 +18,20 @@ class AbstractSessionCommand(models.Model): class Meta: abstract = True + @classmethod + def from_dict(cls, d): + self = cls() + for k, v in d.items(): + setattr(self, k, v) + return self + + @classmethod + def from_multi_dict(cls, l): + commands = [] + for d in l: + command = cls.from_dict(d) + commands.append(command) + return commands + def __str__(self): return self.input diff --git a/apps/terminal/forms.py b/apps/terminal/forms.py index 5b6278c02..4253da70a 100644 --- a/apps/terminal/forms.py +++ b/apps/terminal/forms.py @@ -10,7 +10,7 @@ from .models import Terminal class TerminalForm(forms.ModelForm): class Meta: model = Terminal - fields = ['name', 'remote_addr', 'ssh_port', 'http_port', 'comment'] + fields = ['name', 'remote_addr', 'ssh_port', 'http_port', 'comment', 'command_storage'] help_texts = { 'ssh_port': _("Coco ssh listen port"), 'http_port': _("Coco http/ws listen port"), diff --git a/apps/terminal/models.py b/apps/terminal/models.py index f545baa5f..b8524f727 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -4,17 +4,27 @@ import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.conf import settings from users.models import User from .backends.command.models import AbstractSessionCommand +def get_all_command_storage(): + storage_choices = [] + for k, v in settings.TERMINAL_COMMAND_STORAGE.items(): + storage_choices.append((k, k)) + return storage_choices + + class Terminal(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=32, verbose_name=_('Name')) remote_addr = models.CharField(max_length=128, verbose_name=_('Remote Address')) ssh_port = models.IntegerField(verbose_name=_('SSH Port'), default=2222) http_port = models.IntegerField(verbose_name=_('HTTP Port'), default=5000) + command_storage = models.CharField(max_length=128, verbose_name=_("Command storage"), default='default', choices=get_all_command_storage()) + replay_storage = models.CharField(max_length=128, verbose_name=_("Replay storage"), default='default') user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, on_delete=models.CASCADE) is_accepted = models.BooleanField(default=False, verbose_name='Is Accepted') is_deleted = models.BooleanField(default=False) @@ -33,6 +43,16 @@ class Terminal(models.Model): self.user.is_active = active self.user.save() + def get_common_storage(self): + pass + + def get_replay_storage(self): + pass + + @property + def config(self): + return + def create_app_user(self): random = uuid.uuid4().hex[:6] user, access_key = User.create_app_user(name="{}-{}".format(self.name, random), comment=self.comment) diff --git a/apps/terminal/serializers.py b/apps/terminal/serializers.py index 52b4d2b3c..ecea7edfd 100644 --- a/apps/terminal/serializers.py +++ b/apps/terminal/serializers.py @@ -5,7 +5,7 @@ from django.utils import timezone from rest_framework import serializers from .models import Terminal, Status, Session, Task -from .backends import get_command_store +from .backends import get_terminal_command_store class TerminalSerializer(serializers.ModelSerializer): @@ -43,7 +43,7 @@ class TerminalSerializer(serializers.ModelSerializer): class SessionSerializer(serializers.ModelSerializer): command_amount = serializers.SerializerMethodField() - command_store = get_command_store() + command_store = get_terminal_command_store() class Meta: model = Session diff --git a/apps/terminal/templates/terminal/terminal_modal_accept.html b/apps/terminal/templates/terminal/terminal_modal_accept.html index b4a18b17c..fe70bb342 100644 --- a/apps/terminal/templates/terminal/terminal_modal_accept.html +++ b/apps/terminal/templates/terminal/terminal_modal_accept.html @@ -12,6 +12,7 @@ {% bootstrap_field form.remote_addr layout="horizontal" %} {% bootstrap_field form.ssh_port layout="horizontal" %} {% bootstrap_field form.http_port layout="horizontal" %} + {% bootstrap_field form.command_storage layout="horizontal" %} {% bootstrap_field form.comment layout="horizontal" %} diff --git a/apps/terminal/templates/terminal/terminal_update.html b/apps/terminal/templates/terminal/terminal_update.html index 9ebd9d2d8..cbf745608 100644 --- a/apps/terminal/templates/terminal/terminal_update.html +++ b/apps/terminal/templates/terminal/terminal_update.html @@ -35,6 +35,7 @@ {% bootstrap_field form.remote_addr layout="horizontal" %} {% bootstrap_field form.ssh_port layout="horizontal" %} {% bootstrap_field form.http_port layout="horizontal" %} + {% bootstrap_field form.command_storage layout="horizontal" %}

    {% trans 'Other' %}

    diff --git a/apps/terminal/templatetags/terminal_tags.py b/apps/terminal/templatetags/terminal_tags.py index 22b517880..72822b00a 100644 --- a/apps/terminal/templatetags/terminal_tags.py +++ b/apps/terminal/templatetags/terminal_tags.py @@ -1,13 +1,15 @@ # ~*~ coding: utf-8 ~*~ from django import template -from ..backends import get_command_store +from ..backends import get_terminal_command_store register = template.Library() -command_store = get_command_store() +command_store_dict = get_terminal_command_store() @register.filter def get_session_command_amount(session_id): - return len(command_store.filter(session=str(session_id))) - + amount = 0 + for name, store in command_store_dict.items(): + amount += store.count(session=str(session_id)) + return amount diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index 8b0479d3e..79b2eb2cc 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _ from common.mixins import DatetimeSearchMixin from ..models import Command from .. import utils -from ..backends import get_command_store +from ..backends import get_terminal_command_store __all__ = ['CommandListView'] -command_store = get_command_store() +command_store_list = get_terminal_command_store() class CommandListView(DatetimeSearchMixin, ListView): @@ -39,7 +39,10 @@ class CommandListView(DatetimeSearchMixin, ListView): filter_kwargs['system_user'] = self.system_user if self.command: filter_kwargs['input'] = self.command - queryset = command_store.filter(**filter_kwargs) + queryset = [] + for store in command_store_list: + queryset.extend(store.filter(**filter_kwargs)) + queryset = sorted(queryset, key=lambda c: c.timestamp, reverse=True) return queryset def get_context_data(self, **kwargs): diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py index 22d04fbaf..63fcf5eb7 100644 --- a/apps/terminal/views/session.py +++ b/apps/terminal/views/session.py @@ -10,7 +10,7 @@ from django.conf import settings from users.utils import AdminUserRequiredMixin from common.mixins import DatetimeSearchMixin from ..models import Session, Command, Terminal -from ..backends import get_command_store +from ..backends import get_terminal_command_store from .. import utils @@ -19,7 +19,7 @@ __all__ = [ 'SessionDetailView', ] -command_store = get_command_store() +command_store = get_terminal_command_store() class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): From 57e508f33141ce0eee16cc47bf96953b8f7b74b3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 20 Jan 2018 22:26:21 +0800 Subject: [PATCH 039/197] =?UTF-8?q?[update]=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=88=9B=E5=BB=BA=E6=97=B6=20=E5=A7=93=E5=90=8D?= =?UTF-8?q?=E5=92=8C=E7=94=A8=E6=88=B7=E5=90=8D=E7=9A=84=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/templates/users/_user.html | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/users/templates/users/_user.html b/apps/users/templates/users/_user.html index b33abce51..fe76954f0 100644 --- a/apps/users/templates/users/_user.html +++ b/apps/users/templates/users/_user.html @@ -11,8 +11,8 @@
    {% csrf_token %}

    {% trans 'Account' %}

    - {% bootstrap_field form.username layout="horizontal" %} {% bootstrap_field form.name layout="horizontal" %} + {% bootstrap_field form.username layout="horizontal" %} {% bootstrap_field form.email layout="horizontal" %} {% bootstrap_field form.groups layout="horizontal" %} @@ -32,12 +32,6 @@ {{ form.date_expired.errors }} -{#
    #} -{# #} -{#
    #} -{# {{ form.enable_otp }}#} -{#
    #} -{#
    #}

    {% trans 'Profile' %}

    {% bootstrap_field form.phone layout="horizontal" %} From 8fe3caf2ea4b5d18182fec48d3a6e2c8957f74f3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 20 Jan 2018 22:54:15 +0800 Subject: [PATCH 040/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9install.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/install.md | 130 +----------------------------------------------- 1 file changed, 1 insertion(+), 129 deletions(-) diff --git a/docs/install.md b/docs/install.md index e6b240ab1..1a6892f53 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,129 +1 @@ -## Jumpserver v0.4.0 版本安装详细过程 - -### 环境 - -- 系统: CentOS 6.5 x86\_64 mini -- Python: 版本 3.6 大部分功能兼容 2.7 -- 安装目录 - - /opt/jumpserver - - /opt/coco - -#### 一. 环境准备 - -##### 1.1 安装基本工具和库 - - $ yum -y install sqlite-devel git epel-release - $ yum -y install sshpass python-devel libffi-devel openssl-devel - $ yum -y install gcc gcc-c++ - - -##### 1.2 安装Python 3.6 和 虚拟环境 -略 - - -#### 二. Jumpserver安装 - -##### 2.1 下载仓库代码 - - $ cd /opt - $ git clone https://github.com/jumpserver/jumpserver.git - $ cd jumpserver - $ git checkout dev - -##### 2.2 安装依赖 - - $ cd requirements - $ sudo yum -y install `cat rpm_requirements.txt` - $ pip install -r requirements.txt -i https://pypi.doubanio.com/simple - - // 解决Mac安装ldap提示 Modules/LDAPObject.c:18:10: fatal error: 'sasl.h' file not found - pip install python-ldap \ - --global-option=build_ext \ - --global-option="-I$(xcrun --show-sdk-path)/usr/include/sasl" - -##### 2.3 准备配置文件 - - $ cd .. - $ cp config_example.py config.py - $ vim config.py - - // 默认使用的是 DevelpmentConfig 所以应该去修改这部分 - class DevelopmentConfig(Config): - EMAIL_HOST = 'smtp.exmail.qq.com' - EMAIL_PORT = 465 - EMAIL_HOST_USER = 'ask@jumpserver.org' - EMAIL_HOST_PASSWORD = 'xxx' - EMAIL_USE_SSL = True // 端口是 465 设置 True 否则 False - EMAIL_USE_TLS = False // 端口是 587 设置为 True 否则 False - SITE_URL = 'http://localhost:8080' // 发送邮件会使用这个地址 - -##### 2.4 初始化数据库 - - $ cd utils - $ sh make_migrations.sh - $ sh init_db.sh - -##### 2.5 安装redis server - - $ yum -y install redis - $ service redis start - -**2.6 启动** -``` -$ cd .. -$ python run_server.py -``` -访问 http://ip:8080 -账号密码: admin admin - -**2.7 测试使用** -- 创建用户 - 会发送邮件,测试是否正常修改密码,登录 - -- 创建管理用户 - 创建一个管理用户, 创建资产时需要关联 - -- 创建资产 - 创建一个 资产,关联刚创建的管理用户 - -- 创建系统用户 - 系统用户是用来登录资产的,授权时需要 - -- 创建授权规则 - 关联用户,资产,系统用户 形成授权规则,授权的系统用户会自动推送到资产上 - - -#### 三. 安装 SSH SERVER - COCO -**3.1 下载代码库** -``` -$ cd /opt -$ git clone https://github.com/jumpserver/coco.git -``` - -**3.2 安装依赖** -``` -$ cd coco -$ pip install -r requirements.txt # -i https://pypi.doubanio.com/simple -``` - -**3.3 启动** - -``` -$ python run_server.py -``` - -说明: Coco启动后会向jumpserver注册,请去 jumpserver页面 - 应用程序 - terminal - coco - Accept 允许, 这时 coco就 运行在 2222端口,可以ssh来连接 - -命令行: -```  -ssh admin@YourServerIP -p2222 -``` - -**3.5 测试** -- 测试登录 ssh server -- 测试跳转 -- 测试命令记录回 - -[1]: https://segmentfault.com/a/1190000000654227 -[2]: https://github.com/jumpserver/jumpserver.git -[3]: https://github.com/jumpserver/coco.git +More see [安装文档](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7) From 7f18821990faa4a05625173f962ce2a4c7924d51 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 20 Jan 2018 23:02:17 +0800 Subject: [PATCH 041/197] [Update] remote default PAGE_SIZE stting --- apps/jumpserver/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 7a8aa422e..8b0aa2a64 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -298,7 +298,7 @@ REST_FRAMEWORK = { 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z', 'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'], # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', - 'PAGE_SIZE': 15 + # 'PAGE_SIZE': 15 } AUTHENTICATION_BACKENDS = [ From 41f1c3f7f74f2b5c70f081c2e1cdfa33ae717b98 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 21 Jan 2018 15:12:59 +0800 Subject: [PATCH 042/197] [Feature] terminal config load --- apps/terminal/api.py | 7 +++++-- apps/terminal/models.py | 14 ++++++++++++-- apps/terminal/urls/api_urls.py | 3 ++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/terminal/api.py b/apps/terminal/api.py index e587d8928..25408b6b4 100644 --- a/apps/terminal/api.py +++ b/apps/terminal/api.py @@ -262,8 +262,11 @@ class SessionReplayViewSet(viewsets.ViewSet): return HttpResponseNotFound() -class LoadConfig(APIView): +class TerminalConfig(APIView): permission_classes = (IsAppUser,) def get(self, request): - pass + user = request.user + terminal = user.terminal + configs = terminal.config + return Response(configs, status=200) diff --git a/apps/terminal/models.py b/apps/terminal/models.py index b8524f727..4c5244bcb 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -44,14 +44,24 @@ class Terminal(models.Model): self.user.save() def get_common_storage(self): - pass + storage_all = settings.TERMINAL_COMMAND_STORAGE + if self.command_storage in storage_all: + storage = storage_all.get(self.command_storage) + else: + storage = storage_all.get('default') + return {"TERMINAL_COMMAND_STORAGE": storage} def get_replay_storage(self): pass @property def config(self): - return + configs = {} + for k in dir(settings): + if k.startswith('TERMINAL'): + configs[k] = getattr(settings, k) + configs.update(self.get_common_storage()) + return configs def create_app_user(self): random = uuid.uuid4().hex[:6] diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 55d77ee00..a3ebc2129 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -20,7 +20,8 @@ urlpatterns = [ url(r'^v1/sessions/(?P[0-9a-zA-Z\-]{36})/replay/$', api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}), name='session-replay'), - url(r'^v1/terminal/(?P[a-zA-Z0-9\-]{36})/access-key', api.TerminalTokenApi.as_view(), name='terminal-access-key') + url(r'^v1/terminal/(?P[a-zA-Z0-9\-]{36})/access-key', api.TerminalTokenApi.as_view(), name='terminal-access-key'), + url(r'^v1/terminal/config', api.TerminalConfig.as_view(), name='terminal-config'), ] urlpatterns += router.urls From 0a931bbf776dc39b729922c7c9c92e13b9a33e50 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 22 Jan 2018 11:38:40 +0800 Subject: [PATCH 043/197] [Feature] es support --- apps/common/api.py | 5 - apps/common/fields.py | 1 - apps/common/forms.py | 4 +- apps/common/models.py | 10 +- apps/common/views.py | 1 - apps/jumpserver/settings.py | 4 - apps/locale/zh/LC_MESSAGES/django.mo | Bin 31661 -> 32209 bytes apps/locale/zh/LC_MESSAGES/django.po | 227 ++++++++++++++++----------- apps/users/forms.py | 1 - 9 files changed, 147 insertions(+), 106 deletions(-) diff --git a/apps/common/api.py b/apps/common/api.py index e8680e85e..e75f6147a 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -63,8 +63,6 @@ class LDAPTestingAPI(APIView): 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: @@ -77,9 +75,6 @@ class LDAPTestingAPI(APIView): 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: diff --git a/apps/common/fields.py b/apps/common/fields.py index 36a8bdf9a..f19689830 100644 --- a/apps/common/fields.py +++ b/apps/common/fields.py @@ -18,7 +18,6 @@ class DictField(forms.Field): # 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: diff --git a/apps/common/forms.py b/apps/common/forms.py index bb708c2f7..3eb8c4989 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -36,9 +36,9 @@ class BaseForm(forms.Form): for name, field in self.fields.items(): db_value = getattr(db_settings, name).value django_value = getattr(settings, name) if hasattr(settings, name) else None - if db_value is not None: + if db_value is False or db_value: field.initial = to_form_value(db_value) - elif django_value is not None: + elif django_value is False or django_value: field.initial = django_value def save(self, category="default"): diff --git a/apps/common/models.py b/apps/common/models.py index afee1e13d..1f634bce2 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -2,6 +2,7 @@ import json import ldap from django.db import models +from django.db.utils import ProgrammingError, OperationalError from django.utils.translation import ugettext_lazy as _ from django.conf import settings from django_auth_ldap.config import LDAPSearch @@ -50,9 +51,12 @@ class Setting(models.Model): @classmethod def refresh_all_settings(cls): - settings_list = cls.objects.all() - for setting in settings_list: - setting.refresh_setting() + try: + settings_list = cls.objects.all() + for setting in settings_list: + setting.refresh_setting() + except (ProgrammingError, OperationalError): + pass def refresh_setting(self): try: diff --git a/apps/common/views.py b/apps/common/views.py index 1e8ad503d..dc0d74f92 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -107,7 +107,6 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView): return super().get_context_data(**kwargs) def post(self, request): - print(request.POST) form = self.form_class(request.POST) if form.is_valid(): form.save() diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index a164d3355..6b9242d45 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -385,10 +385,6 @@ TERMINAL_COMMAND_STORAGE = { # 'TYPE': 'elasticsearch', # 'HOSTS': ['http://elastic:changeme@localhost:9200'], # }, - # 'ali-hz-es': { - # 'TYPE': 'elasticsearch', - # 'HOSTS': ['http://elastic:changeme@localhost:9200'], - # } } diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 7be8a996a99f2451a1f8854e863216f289eaa052..f6489066200c442d9a9bbce51198493a10222cac 100644 GIT binary patch delta 11945 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 delta 11406 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

    RW};csY=njA*9v`~|IReD;^$EV zj7L4YNvIu|iKTEM7Q)S_e!EcJen0q7Tu{3!q#$X>TgdTI=Fh2hpXgLK< zv^=MO2f7@;`oc;H$h=L}X zhw-=)wSdE@g&ni}l=%zlx+~@#)b;)&+Q^y<{4VOc zhi1TNH!p}5F`E<({Vg&UK=5F&S>bhT1 z1KzMa4=a!djB($kM6)xh|5S6n)aQQ%jZj>VdM4XZ10FKJMO}Ep{MEc+=O0=gFxLGz z7C~KK4J%?DjKhAY8=PUTMBnFsJBbkEjKWkn?-<_it7W|@XBx->%9&3~{6HpUYMGe>xH9!Z{S8`v}KtoVFkcoO^ zFQcxTXD&xww+`#zHq>}GQP&6XS8AR2gj%DNnS|=l05xC>vy+|gi~7zVZS~i!z8asP zzQyt{%^y+Y{e~LvCTby%-18o%$V=|lmqB%`gW8%j)U$1E^>nL0hZ?B2In3%WnNv{X z%(8qD>X*}U^K%?TeiJJ@o%ze*%kF@)&84UT-a$Vs#t;495wFtzoVYNJMocv5i5!EG#?+gROfh-=t{e-<*Mal1v;J~t`a|U zu8^JQPMqzu?-F0reh>5WY<>T7{0B)0q3iGAG}NJgLm5lxXh{6vYdKCWYISLMzzz5c zHbOlUJ&!-hYZ7sU4n1@oJIN>G8lomqf%$#MbNsBt$rHpWVi4zjuQaY8{|WCCQ;BLsMdGkJb4;WiVe|V6)%lrAF^KqQ8F8F;F`^cs;|y_$dQ+T@d+}%D@$nZm&p3V@rShd!Pnthc|BQB1 zOeb{k2MFJxS2fkzAK-A#|4#U`GlR*0wR0WJS2_0@vBz@JnY@yozm78`tBFapi{hum z<6|K00>qbAJC0R2`G$4)jXdgcXZ)1-o7iprb!v#U-$Z>A1+q~7;fZgn|Lp(!oP34i z3hO+9_BG-ld0%`RU$=9uOv+AO+UZz;LC^oEKArXnb+BU-P9gYGand-ase2Ts(VxZ; z;s@F_@Ec+PAI@T)3@TNL+C(tJ>+q=UHl5P67Z5u1?~Kn7Tiw*j!O`S}EPoe2CVzow zVKs4tT*oY;hPCJ8-Nz3{C!g1k{~Y||nDcy_A zgbw}V={({F@rXQ-SVEj8-{{8nQIGa2!jJYc>_jBdetZn0 zQP7fZ)c;4jFpea`i84eTYJcN*gpMd&jsFlY(f){dPtX4g3cUy&M~FF`&@qwNMtdPq z+gEXaY=IAnjQ`ZsJWHIkYmb=+sO_K~gPrhcyi6RTy^2_`Pl%U7Gm;c+g6)V2#9l&2 z2%n0-@E#GW0>?lXXD+UxUo6%qmJ*4Cj&pW>YuYWXt@=~60~qrh?M?cq>DcGugp!Z9 z+G6Z(?Ok^Mj`^`U-yCTk#OLf9l>&(T_OVtzgV;^PDdEuj=ld@v=hfgCN>pZm5%`M@ z)ZA=9tqScHmRBSHifB*hC~x(v*oAyEUa;CNUk}~~j3$~9-}t%nJJED1LZJz9iAW{y zYZrb`uH$tV=PT+JX{X>i+=DIc+$6i^AM-TzDYP3CTZlzg-+)hX?gM@PlSu;X#D0pG zY0t)fR-@^>NW4YNtf3iQi~9CNhZ}@36%8gS?fhwF~mju3g4I_?fiScCo3MV+VTI z)Qk1=9\n" "Language-Team: Jumpserver team\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" #: 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:246 +#: perms/templates/perms/asset_permission_asset.html:116 users/forms.py:245 msgid "Select asset groups" msgstr "选择资产组" @@ -44,7 +44,7 @@ msgstr "默认使用管理用户" #: assets/forms.py:76 assets/forms.py:81 assets/forms.py:127 #: assets/templates/assets/asset_group_detail.html:75 perms/forms.py:34 -#: perms/templates/perms/asset_permission_asset.html:88 users/forms.py:243 +#: perms/templates/perms/asset_permission_asset.html:88 users/forms.py:242 msgid "Select assets" msgstr "选择资产" @@ -66,7 +66,7 @@ msgstr "端口" #: assets/templates/assets/system_user_list.html:26 perms/models.py:17 #: perms/templates/perms/asset_permission_create_update.html:40 #: perms/templates/perms/asset_permission_list.html:28 templates/_nav.html:22 -#: terminal/backends/command/models.py:11 terminal/models.py:93 +#: terminal/backends/command/models.py:11 terminal/models.py:124 #: terminal/templates/terminal/command_list.html:40 #: terminal/templates/terminal/command_list.html:73 #: terminal/templates/terminal/session_list.html:41 @@ -77,7 +77,7 @@ msgid "Asset" msgstr "资产" #: assets/forms.py:161 perms/forms.py:40 -#: perms/templates/perms/asset_permission_detail.html:144 users/forms.py:249 +#: perms/templates/perms/asset_permission_detail.html:144 users/forms.py:248 msgid "Select system users" msgstr "选择系统用户" @@ -99,14 +99,15 @@ msgstr "选择的系统用户将会在该集群资产上创建" #: assets/templates/assets/cluster_detail.html:57 #: assets/templates/assets/cluster_list.html:19 #: assets/templates/assets/system_user_detail.html:58 -#: assets/templates/assets/system_user_list.html:24 common/models.py:25 -#: ops/models.py:31 ops/templates/ops/task_detail.html:56 -#: ops/templates/ops/task_list.html:34 perms/models.py:14 +#: assets/templates/assets/system_user_list.html:24 common/models.py:26 +#: common/templates/common/terminal_setting.html:62 ops/models.py:31 +#: ops/templates/ops/task_detail.html:56 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_detail.html:62 #: perms/templates/perms/asset_permission_list.html:25 -#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:14 -#: terminal/models.py:118 terminal/templates/terminal/terminal_detail.html:43 +#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:23 +#: terminal/models.py:149 terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 #: users/models/user.py:35 users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_detail.html:62 @@ -126,10 +127,10 @@ msgstr "集群级别管理用户" #: assets/forms.py:200 msgid "Password or private key password" -msgstr "密码或秘钥不合法" +msgstr "密码或秘钥密码" #: assets/forms.py:201 assets/forms.py:262 assets/models/user.py:30 -#: common/forms.py:107 users/forms.py:16 users/forms.py:24 +#: common/forms.py:113 users/forms.py:16 users/forms.py:24 #: users/templates/users/login.html:56 #: users/templates/users/reset_password.html:52 #: users/templates/users/user_create.html:11 @@ -239,7 +240,7 @@ msgstr "测试环境" #: assets/templates/assets/asset_list.html:31 #: assets/templates/assets/cluster_assets.html:52 #: assets/templates/assets/system_user_asset.html:53 -#: assets/templates/assets/user_asset_list.html:20 +#: assets/templates/assets/user_asset_list.html:20 common/forms.py:140 #: perms/templates/perms/asset_permission_asset.html:55 #: users/templates/users/login_log_list.html:52 #: users/templates/users/user_granted_asset.html:49 @@ -253,7 +254,7 @@ msgstr "IP" #: assets/templates/assets/asset_list.html:30 #: assets/templates/assets/cluster_assets.html:51 #: assets/templates/assets/system_user_asset.html:52 -#: assets/templates/assets/user_asset_list.html:19 +#: assets/templates/assets/user_asset_list.html:19 common/forms.py:139 #: perms/templates/perms/asset_permission_asset.html:54 #: users/templates/users/user_granted_asset.html:48 #: users/templates/users/user_group_granted_asset.html:49 @@ -400,9 +401,9 @@ msgstr "创建日期" #: assets/templates/assets/asset_group_list.html:17 #: assets/templates/assets/cluster_detail.html:97 #: assets/templates/assets/system_user_detail.html:100 -#: assets/templates/assets/system_user_list.html:30 common/models.py:28 +#: assets/templates/assets/system_user_list.html:30 common/models.py:30 #: ops/models.py:37 perms/models.py:24 -#: perms/templates/perms/asset_permission_detail.html:98 terminal/models.py:22 +#: perms/templates/perms/asset_permission_detail.html:98 terminal/models.py:33 #: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15 #: users/models/user.py:47 users/templates/users/user_detail.html:110 #: users/templates/users/user_group_detail.html:67 @@ -494,7 +495,7 @@ msgstr "Shell" #: assets/models/user.py:269 perms/models.py:19 #: perms/templates/perms/asset_permission_detail.html:136 #: 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:125 #: terminal/templates/terminal/command_list.html:48 #: terminal/templates/terminal/command_list.html:74 #: terminal/templates/terminal/session_list.html:49 @@ -576,9 +577,9 @@ msgstr "仅修改你需要更新的字段" #: 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:144 #: assets/views/asset.py:161 assets/views/asset.py:185 -#: assets/views/cluster.py:26 assets/views/cluster.py:85 -#: assets/views/cluster.py:102 assets/views/group.py:34 -#: assets/views/group.py:52 assets/views/group.py:69 assets/views/group.py:87 +#: assets/views/cluster.py:26 assets/views/cluster.py:80 +#: assets/views/cluster.py:97 assets/views/group.py:34 assets/views/group.py:52 +#: assets/views/group.py:69 assets/views/group.py:87 #: assets/views/system_user.py:28 assets/views/system_user.py:44 #: assets/views/system_user.py:60 assets/views/system_user.py:75 #: templates/_nav.html:19 @@ -602,20 +603,24 @@ msgid "Import asset" msgstr "导入资产" #: assets/templates/assets/_asset_import_modal.html:9 +#: common/templates/common/_add_terminal_command_storage_modal.html:9 #: users/templates/users/_user_import_modal.html:10 msgid "Template" msgstr "模板" #: assets/templates/assets/_asset_import_modal.html:10 +#: common/templates/common/_add_terminal_command_storage_modal.html:10 #: users/templates/users/_user_import_modal.html:11 msgid "Download" msgstr "下载" #: assets/templates/assets/_asset_import_modal.html:13 +#: common/templates/common/_add_terminal_command_storage_modal.html:13 msgid "Asset csv file" msgstr "资产csv文件" #: assets/templates/assets/_asset_import_modal.html:16 +#: common/templates/common/_add_terminal_command_storage_modal.html:16 msgid "If set id, will use this id update asset existed" msgstr "如果设置了id,则会使用该行信息更新该id的资产" @@ -650,7 +655,7 @@ msgstr "自动生成秘钥" #: assets/templates/assets/asset_update.html:47 #: assets/templates/assets/cluster_create_update.html:46 #: perms/templates/perms/asset_permission_create_update.html:45 -#: terminal/templates/terminal/terminal_update.html:40 +#: terminal/templates/terminal/terminal_update.html:41 msgid "Other" msgstr "其它" @@ -661,11 +666,12 @@ msgstr "其它" #: assets/templates/assets/asset_group_create.html:16 #: assets/templates/assets/asset_update.html:55 #: 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 +#: common/templates/common/basic_setting.html:58 +#: common/templates/common/email_setting.html:59 +#: common/templates/common/ldap_setting.html:59 +#: common/templates/common/terminal_setting.html:82 #: perms/templates/perms/asset_permission_create_update.html:67 -#: terminal/templates/terminal/terminal_update.html:45 +#: terminal/templates/terminal/terminal_update.html:46 #: users/templates/users/_user.html:49 #: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_password_update.html:58 @@ -684,11 +690,12 @@ msgstr "重置" #: assets/templates/assets/asset_list.html:53 #: assets/templates/assets/asset_update.html:56 #: 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 +#: common/templates/common/basic_setting.html:59 +#: common/templates/common/email_setting.html:60 +#: common/templates/common/ldap_setting.html:60 +#: common/templates/common/terminal_setting.html:83 #: perms/templates/perms/asset_permission_create_update.html:68 -#: terminal/templates/terminal/terminal_update.html:46 +#: terminal/templates/terminal/terminal_update.html:47 #: users/templates/users/_user.html:50 #: users/templates/users/first_login.html:62 #: users/templates/users/forgot_password.html:44 @@ -778,6 +785,7 @@ msgstr "资产列表" #: assets/templates/assets/asset_group_detail.html:53 #: assets/templates/assets/cluster_assets.html:54 #: assets/templates/assets/user_asset_list.html:22 +#: common/templates/common/terminal_setting.html:63 #: users/templates/users/login_log_list.html:50 msgid "Type" msgstr "类型" @@ -878,7 +886,7 @@ msgid "Group" msgstr "组" #: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:186 -#: assets/views/cluster.py:103 +#: assets/views/cluster.py:98 msgid "Asset detail" msgstr "资产详情" @@ -1236,16 +1244,16 @@ msgstr "已经存在" msgid "Cluster list" msgstr "集群列表" -#: assets/views/cluster.py:42 assets/views/cluster.py:70 +#: assets/views/cluster.py:42 assets/views/cluster.py:65 #: assets/views/system_user.py:96 msgid "assets" msgstr "资产管理" -#: assets/views/cluster.py:71 +#: assets/views/cluster.py:66 msgid "Update Cluster" msgstr "更新Cluster" -#: assets/views/cluster.py:86 +#: assets/views/cluster.py:81 msgid "Cluster detail" msgstr "集群详情" @@ -1291,85 +1299,108 @@ msgstr "%(name)s 创建成功" msgid "%(name)s was updated successfully" msgstr "%(name)s 更新成功" -#: common/forms.py:64 +#: common/forms.py:70 msgid "Current SITE URL" msgstr "当前站点URL" -#: common/forms.py:68 +#: common/forms.py:74 msgid "User Guide URL" msgstr "用户向导URL" -#: common/forms.py:69 +#: common/forms.py:75 msgid "User first login update profile done redirect to it" msgstr "用户第一次登录,修改profile后重定向到地址" -#: common/forms.py:72 +#: common/forms.py:78 msgid "Email Subject Prefix" msgstr "Email主题前缀" -#: common/forms.py:76 -msgid "Enable LDAP Auth" -msgstr "LDAP认证" - -#: common/forms.py:82 +#: common/forms.py:85 msgid "SMTP host" msgstr "SMTP主机" -#: common/forms.py:81 +#: common/forms.py:87 msgid "SMTP port" msgstr "SMTP端口" -#: common/forms.py:83 +#: common/forms.py:89 msgid "SMTP user" msgstr "SMTP账号" -#: common/forms.py:86 +#: common/forms.py:92 msgid "SMTP password" msgstr "SMTP密码" -#: common/forms.py:87 +#: common/forms.py:93 msgid "Some provider use token except password" msgstr "一些邮件提供商需要输入的是Token" -#: common/forms.py:90 common/forms.py:127 +#: common/forms.py:96 common/forms.py:133 msgid "Use SSL" msgstr "使用SSL" -#: common/forms.py:91 +#: common/forms.py:97 msgid "If SMTP port is 465, may be select" msgstr "如果SMTP端口是465,通常需要启用SSL" -#: common/forms.py:94 +#: common/forms.py:100 msgid "Use TLS" msgstr "使用TLS" -#: common/forms.py:95 +#: common/forms.py:101 msgid "If SMTP port is 587, may be select" msgstr "如果SMTP端口是587,通常需要启用TLS" -#: common/forms.py:101 +#: common/forms.py:107 msgid "LDAP server" msgstr "LDAP地址" -#: common/forms.py:104 +#: common/forms.py:110 msgid "Bind DN" msgstr "绑定DN" -#: common/forms.py:111 +#: common/forms.py:117 msgid "User OU" msgstr "用户OU" -#: common/forms.py:114 +#: common/forms.py:120 msgid "User search filter" msgstr "用户过滤器" -#: common/forms.py:117 +#: common/forms.py:123 msgid "User attr map" msgstr "LDAP属性映射" -#: common/forms.py:130 -msgid "Enable LDAP Auth" -msgstr "开启LDAP认证" +#: common/forms.py:143 +msgid "List sort by" +msgstr "资产列表排序" + +#: common/forms.py:146 +msgid "Heartbeat interval" +msgstr "心跳间隔" + +#: common/forms.py:146 ops/models.py:32 +msgid "Units: seconds" +msgstr "单位: 秒" + +#: common/forms.py:149 +msgid "Password auth" +msgstr "密码认证" + +#: common/forms.py:152 +msgid "Public key auth" +msgstr "秘钥认证" + +#: common/forms.py:155 common/templates/common/terminal_setting.html:58 +#: terminal/models.py:27 +msgid "Command storage" +msgstr "命令存储" + +#: common/forms.py:156 +msgid "" +"Set terminal storage setting, `default` is the using as default,You can set " +"other storage and some terminal using" +msgstr "设置终端命令存储,default是默认用的存储方式" #: common/mixins.py:29 msgid "is discard" @@ -1379,43 +1410,62 @@ msgstr "" msgid "discard time" msgstr "" -#: common/models.py:26 +#: common/models.py:27 msgid "Value" msgstr "值" -#: common/models.py:27 +#: common/models.py:29 msgid "Enabled" msgstr "启用" +#: common/templates/common/_add_terminal_command_storage_modal.html:4 +msgid "Add command storage" +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 +#: common/templates/common/ldap_setting.html:15 +#: common/templates/common/terminal_setting.html:15 +#: common/templates/common/terminal_setting.html:38 common/views.py:21 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 +#: common/templates/common/ldap_setting.html:18 +#: common/templates/common/terminal_setting.html:18 common/views.py:47 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 +#: common/templates/common/ldap_setting.html:21 +#: common/templates/common/terminal_setting.html:21 common/views.py:73 msgid "LDAP setting" msgstr "LDAP设置" -#: common/templates/common/email_setting.html:55 -#: common/templates/common/ldap_setting.html:55 +#: common/templates/common/basic_setting.html:24 +#: common/templates/common/email_setting.html:24 +#: common/templates/common/ldap_setting.html:24 +#: common/templates/common/terminal_setting.html:24 common/views.py:102 +msgid "Terminal setting" +msgstr "终端设置" + +#: common/templates/common/email_setting.html:58 +#: common/templates/common/ldap_setting.html:58 msgid "Test connection" msgstr "测试连接" -#: common/views.py:17 common/views.py:43 common/views.py:69 +#: common/templates/common/terminal_setting.html:77 terminal/models.py:28 +msgid "Replay storage" +msgstr "录像存储" + +#: common/views.py:20 common/views.py:46 common/views.py:72 common/views.py:101 #: templates/_nav.html:69 msgid "Settings" msgstr "系统设置" -#: common/views.py:28 common/views.py:54 common/views.py:82 +#: common/views.py:31 common/views.py:57 common/views.py:85 common/views.py:113 msgid "Update setting successfully, please restart program" msgstr "更新设置成功, 请手动重启程序" @@ -1423,10 +1473,6 @@ msgstr "更新设置成功, 请手动重启程序" msgid "Interval" msgstr "间隔" -#: ops/models.py:32 -msgid "Units: seconds" -msgstr "单位: 秒" - #: ops/models.py:33 msgid "Crontab" msgstr "Crontab" @@ -1570,7 +1616,7 @@ msgstr "执行历史" #: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history_detail.html:58 -#: ops/templates/ops/task_history.html:55 terminal/models.py:101 +#: ops/templates/ops/task_history.html:55 terminal/models.py:132 #: terminal/templates/terminal/session_list.html:77 msgid "Date start" msgstr "开始日期" @@ -1687,18 +1733,18 @@ msgid "Task run history" msgstr "执行历史" #: perms/forms.py:16 users/forms.py:147 users/forms.py:152 users/forms.py:164 -#: users/forms.py:195 +#: users/forms.py:194 msgid "Select users" msgstr "选择用户" #: perms/forms.py:18 perms/models.py:15 #: perms/templates/perms/asset_permission_create_update.html:36 #: perms/templates/perms/asset_permission_list.html:26 templates/_nav.html:12 -#: terminal/backends/command/models.py:10 terminal/models.py:92 +#: terminal/backends/command/models.py:10 terminal/models.py:123 #: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:191 +#: terminal/templates/terminal/session_list.html:71 users/forms.py:190 #: users/models/user.py:30 users/templates/users/user_group_detail.html:78 #: users/views/user.py:337 msgid "User" @@ -1953,7 +1999,7 @@ msgstr "在线会话" msgid "Session offline" msgstr "离线会话" -#: templates/_nav.html:53 terminal/models.py:99 +#: templates/_nav.html:53 terminal/models.py:130 #: terminal/templates/terminal/command_list.html:55 #: terminal/templates/terminal/command_list.html:71 #: terminal/templates/terminal/session_detail.html:48 @@ -2002,56 +2048,56 @@ msgstr "SSH 监听端口" msgid "Coco http/ws listen port" msgstr "Http/Websocket 监听端口" -#: terminal/models.py:15 +#: terminal/models.py:24 msgid "Remote Address" msgstr "远端地址" -#: terminal/models.py:16 +#: terminal/models.py:25 msgid "SSH Port" msgstr "SSH端口" -#: terminal/models.py:17 +#: terminal/models.py:26 msgid "HTTP Port" msgstr "HTTP端口" -#: terminal/models.py:68 +#: terminal/models.py:99 msgid "Session Online" msgstr "在线会话" -#: terminal/models.py:69 +#: terminal/models.py:100 msgid "CPU Usage" msgstr "CPU使用" -#: terminal/models.py:70 +#: terminal/models.py:101 msgid "Memory Used" msgstr "内存使用" -#: terminal/models.py:71 +#: terminal/models.py:102 msgid "Connections" msgstr "连接数" -#: terminal/models.py:72 +#: terminal/models.py:103 msgid "Threads" msgstr "线程数" -#: terminal/models.py:73 +#: terminal/models.py:104 msgid "Boot Time" msgstr "运行时间" -#: terminal/models.py:96 terminal/templates/terminal/session_list.html:74 +#: terminal/models.py:127 terminal/templates/terminal/session_list.html:74 #: terminal/templates/terminal/terminal_detail.html:47 msgid "Remote addr" msgstr "远端地址" -#: terminal/models.py:98 terminal/templates/terminal/session_list.html:100 +#: terminal/models.py:129 terminal/templates/terminal/session_list.html:100 msgid "Replay" msgstr "回放" -#: terminal/models.py:102 +#: terminal/models.py:133 msgid "Date end" msgstr "结束日期" -#: terminal/models.py:119 +#: terminal/models.py:150 msgid "Args" msgstr "参数" @@ -2814,5 +2860,8 @@ msgstr "密码更新" msgid "Public key update" msgstr "秘钥更新" +#~ msgid "Enable LDAP Auth" +#~ msgstr "LDAP认证" + #~ msgid "Connect" #~ msgstr "连接" diff --git a/apps/users/forms.py b/apps/users/forms.py index 018c39a18..b239f58ee 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -172,7 +172,6 @@ class UserBulkUpdateForm(forms.ModelForm): if self.data.get(field) is not None: changed_fields.append(field) - print(changed_fields) cleaned_data = {k: v for k, v in self.cleaned_data.items() if k in changed_fields} users = cleaned_data.pop('users', '') From 8766a936f339a59499f1ab49a477907b5044fdf0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 22 Jan 2018 12:01:32 +0800 Subject: [PATCH 044/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9requiremen?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index d4807ceba..c8fef52bb 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -59,3 +59,4 @@ gunicorn==19.7.1 django_celery_beat==1.1.0 ephem==3.7.6.0 python-gssapi==0.6.4 +jms-es-sdk==0.5.1 From 9d399c475c281f6228a0627917abc93e6c633ec8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 22 Jan 2018 12:49:55 +0800 Subject: [PATCH 045/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9requiremen?= =?UTF-8?q?ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index c8fef52bb..0e80fd04d 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -59,4 +59,4 @@ gunicorn==19.7.1 django_celery_beat==1.1.0 ephem==3.7.6.0 python-gssapi==0.6.4 -jms-es-sdk==0.5.1 +jms-es-sdk From cafb0e02a31f95683d40536c6824e06d0131f968 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 22 Jan 2018 17:21:03 +0800 Subject: [PATCH 046/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9dictfiled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/fields.py | 10 +++++---- apps/common/forms.py | 4 ++-- .../_add_terminal_command_storage_modal.html | 21 ------------------- .../templates/common/terminal_setting.html | 5 ++--- apps/jumpserver/settings.py | 4 ++-- 5 files changed, 12 insertions(+), 32 deletions(-) delete mode 100644 apps/common/templates/common/_add_terminal_command_storage_modal.html diff --git a/apps/common/fields.py b/apps/common/fields.py index f19689830..417e88504 100644 --- a/apps/common/fields.py +++ b/apps/common/fields.py @@ -5,6 +5,7 @@ import json from django import forms from django.utils import six from django.core.exceptions import ValidationError +from django.utils.translation import ugettext as _ class DictField(forms.Field): @@ -21,12 +22,13 @@ class DictField(forms.Field): value = json.loads(value) return value except json.JSONDecodeError: - pass - value = {} - return value + return ValidationError(_("Not a valid json")) + else: + return ValidationError(_("Not a string type")) def validate(self, value): - print(value) + if isinstance(value, ValidationError): + raise value if not value and self.required: raise ValidationError(self.error_messages['required'], code='required') diff --git a/apps/common/forms.py b/apps/common/forms.py index 3eb8c4989..9b88af3b9 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -26,7 +26,7 @@ def to_form_value(value): data = value return data except json.JSONDecodeError: - return '' + return "" class BaseForm(forms.Form): @@ -39,7 +39,7 @@ class BaseForm(forms.Form): if db_value is False or db_value: field.initial = to_form_value(db_value) elif django_value is False or django_value: - field.initial = django_value + field.initial = to_form_value(to_model_value(django_value)) def save(self, category="default"): if not self.is_bound: diff --git a/apps/common/templates/common/_add_terminal_command_storage_modal.html b/apps/common/templates/common/_add_terminal_command_storage_modal.html deleted file mode 100644 index 678152981..000000000 --- a/apps/common/templates/common/_add_terminal_command_storage_modal.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}add_command_storage_model{% endblock %} -{% block modal_title%}{% trans "Add command storage" %}{% endblock %} -{% block modal_body %} - - {% csrf_token %} -

    - - {% trans 'Download' %} -
    -
    - - - - {% trans 'If set id, will use this id update asset existed' %} - -
    - -{% endblock %} -{% block modal_confirm_id %}btn_asset_import{% endblock %} diff --git a/apps/common/templates/common/terminal_setting.html b/apps/common/templates/common/terminal_setting.html index 3d0b7eb6f..c15ff4bc7 100644 --- a/apps/common/templates/common/terminal_setting.html +++ b/apps/common/templates/common/terminal_setting.html @@ -73,8 +73,8 @@ {# #} -
    -

    {% trans "Replay storage" %}

    +{#
    #} +{#

    {% trans "Replay storage" %}

    #}
    @@ -92,7 +92,6 @@
    - {% include 'common/_add_terminal_command_storage_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/label_create_update.html b/apps/assets/templates/assets/label_create_update.html new file mode 100644 index 000000000..358faaab8 --- /dev/null +++ b/apps/assets/templates/assets/label_create_update.html @@ -0,0 +1,31 @@ +{% extends '_base_create_update.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} + +{% block form %} +
    + {% csrf_token %} + {% bootstrap_field form.name layout="horizontal" %} + {% bootstrap_field form.value layout="horizontal" %} + {% bootstrap_field form.assets layout="horizontal" %} + +
    +
    +
    + + +
    +
    +
    +{% endblock %} + +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/label_list.html b/apps/assets/templates/assets/label_list.html index efed4a994..bbe6ae5c1 100644 --- a/apps/assets/templates/assets/label_list.html +++ b/apps/assets/templates/assets/label_list.html @@ -3,7 +3,7 @@ {% block table_search %}{% endblock %} {% block table_container %} diff --git a/apps/assets/templatetags/__init__.py b/apps/assets/templatetags/__init__.py new file mode 100644 index 000000000..ec51c5a2b --- /dev/null +++ b/apps/assets/templatetags/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +# diff --git a/apps/assets/templatetags/asset_tags.py b/apps/assets/templatetags/asset_tags.py index 829ab2a80..15605f835 100644 --- a/apps/assets/templatetags/asset_tags.py +++ b/apps/assets/templatetags/asset_tags.py @@ -1,6 +1,12 @@ +from collections import defaultdict from django import template -from django.utils import timezone -from django.conf import settings register = template.Library() + +@register.filter +def group_labels(queryset): + grouped = defaultdict(list) + for label in queryset: + grouped[label.name].append(label) + return [(name, labels) for name, labels in grouped.items()] diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index 09ff322ee..a63c065cf 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -54,5 +54,6 @@ urlpatterns = [ # name='system-user-asset-group'), url(r'^label/$', views.LabelListView.as_view(), name='label-list'), + url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'), ] diff --git a/apps/assets/views/label.py b/apps/assets/views/label.py index 375c500a5..e0b489323 100644 --- a/apps/assets/views/label.py +++ b/apps/assets/views/label.py @@ -1,12 +1,14 @@ # -*- coding: utf-8 -*- # -from django.views.generic import ListView, TemplateView, CreateView, \ +from django.views.generic import TemplateView, CreateView, \ UpdateView, DeleteView, DetailView from django.utils.translation import ugettext_lazy as _ - +from django.urls import reverse_lazy from common.mixins import AdminUserRequiredMixin +from common.const import create_success_msg +from ..forms import LabelForm __all__ = ( @@ -28,7 +30,18 @@ class LabelListView(AdminUserRequiredMixin, TemplateView): class LabelCreateView(AdminUserRequiredMixin, CreateView): - pass + template_name = 'assets/label_create_update.html' + form_class = LabelForm + success_url = reverse_lazy('assets:label-list') + success_message = create_success_msg + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('Create label'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) class LabelUpdateView(AdminUserRequiredMixin, UpdateView): diff --git a/apps/common/templatetags/common_tags.py b/apps/common/templatetags/common_tags.py index c10c228c8..747868430 100644 --- a/apps/common/templatetags/common_tags.py +++ b/apps/common/templatetags/common_tags.py @@ -92,3 +92,8 @@ def is_bool_field(field): return True else: return False + + +@register.filter +def to_dict(data): + return dict(data) diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 8bc795c9c..256b79a10 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -24,7 +24,7 @@
  • {% trans 'Cluster' %}
  • {% trans 'Admin user' %}
  • {% trans 'System user' %}
  • -
  • {% trans 'Label' %}
  • +
  • {% trans 'Label' %}
  • @@ -54,8 +54,6 @@
  • {% trans 'Command' %}
  • - - {#
  • #} {# #} {# {% trans 'File' %}#} From 070c3d73ccd58bbb17d46e3746c821d229a4be36 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 25 Jan 2018 12:16:26 +0800 Subject: [PATCH 059/197] [Bugfix] git status --- apps/assets/models/group.py | 2 +- apps/assets/serializers.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/assets/models/group.py b/apps/assets/models/group.py index c01a2a529..b9bf16f18 100644 --- a/apps/assets/models/group.py +++ b/apps/assets/models/group.py @@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) class AssetGroup(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=64, unique=True, verbose_name=_('Name')) - created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by')) + created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created')) comment = models.TextField(blank=True, verbose_name=_('Comment')) diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index 024d88e22..bef50c5f0 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -13,7 +13,9 @@ class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): 资产组序列化数据模型 """ assets_amount = serializers.SerializerMethodField() - assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) + assets = serializers.PrimaryKeyRelatedField( + many=True, queryset=Asset.objects.all(), required=False + ) class Meta: model = AssetGroup @@ -22,7 +24,7 @@ class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): @staticmethod def get_assets_amount(obj): - return obj.asset_count + return obj.assets.all().count() class AssetUpdateSystemUserSerializer(serializers.ModelSerializer): From d74c2e84668991ba6884bedde95cbfd11973501e Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 25 Jan 2018 12:17:26 +0800 Subject: [PATCH 060/197] =?UTF-8?q?[Model]=20=E4=BF=AE=E6=94=B9create=5Fby?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/models/group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/users/models/group.py b/apps/users/models/group.py index b4b7aacc1..07238c1a5 100644 --- a/apps/users/models/group.py +++ b/apps/users/models/group.py @@ -15,7 +15,7 @@ class UserGroup(NoDeleteModelMixin): comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created')) - created_by = models.CharField(max_length=100) + created_by = models.CharField(max_length=100, null=True, blank=True) def __str__(self): return self.name From 17f91f7f53604350af0deb03a1267f89aa63bb35 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 25 Jan 2018 15:43:52 +0800 Subject: [PATCH 061/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/templates/_nav.html | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 39b14add8..fbda9b396 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -1,12 +1,12 @@ {% load i18n %}
  • - {% trans 'Dashboard' %} + {% trans 'Dashboard' %}
  • - {% trans 'Users' %} + {% trans 'Users' %}
  • -
  • - - {% trans 'Job Center' %} - - -
  • {% trans 'Terminal' %} @@ -53,8 +45,14 @@
  • {% trans 'Command' %}
  • - - +
  • + + {% trans 'Job Center' %} + + +
  • {#
  • #} {# #} {# {% trans 'File' %}#} From 0a2b6494cc9ac7ccdff1b97aa18f05da41a19620 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 25 Jan 2018 16:38:40 +0800 Subject: [PATCH 062/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=AD=BE?= =?UTF-8?q?=E5=90=8Dmd5=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/common/utils.py b/apps/common/utils.py index f366e6786..0915564ae 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -238,9 +238,10 @@ def content_md5(data): """ if isinstance(data, str): data = hashlib.md5(data.encode('utf-8')) - value = base64.b64encode(data.digest()) + value = base64.b64encode(data.hexdigest().encode('utf-8')) return value.decode('utf-8') + _STRPTIME_LOCK = threading.Lock() _GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT" From cb902362b5448b5b193ffd7618d999268e5205c8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 26 Jan 2018 14:47:10 +0800 Subject: [PATCH 063/197] =?UTF-8?q?[Feature]=20=E8=B5=84=E4=BA=A7=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=A0=87=E7=AD=BE=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api.py | 3 +- apps/assets/forms.py | 48 ++++++++++++++----- .../assets/templates/assets/asset_create.html | 37 +++++++++----- apps/assets/templates/assets/asset_list.html | 22 +++++++++ apps/assets/utils.py | 27 ++++++++++- apps/assets/views/asset.py | 16 ++++--- apps/static/css/jumpserver.css | 4 +- apps/static/js/jumpserver.js | 18 ++++++- 8 files changed, 140 insertions(+), 35 deletions(-) diff --git a/apps/assets/api.py b/apps/assets/api.py index dfea55741..317ed51c2 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -30,12 +30,13 @@ from . import serializers from .tasks import update_asset_hardware_info_manual, test_admin_user_connectability_manual, \ test_asset_connectability_manual, push_system_user_to_cluster_assets_manual, \ test_system_user_connectability_manual +from .utils import LabelFilter logger = get_logger(__file__) -class AssetViewSet(CustomFilterMixin, BulkModelViewSet): +class AssetViewSet(CustomFilterMixin, LabelFilter, BulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 2653ca793..b3ee468c6 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -5,7 +5,6 @@ from django.utils.translation import gettext_lazy as _ from .models import Cluster, Asset, AssetGroup, AdminUser, SystemUser, Label from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, get_logger - logger = get_logger(__file__) @@ -19,10 +18,18 @@ class AssetCreateForm(forms.ModelForm): ] widgets = { - 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}), - 'cluster': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select cluster')}), - 'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select admin user')}), - 'labels': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select labels')}), + 'groups': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Select asset groups') + }), + 'cluster': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Select cluster') + }), + 'admin_user': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Select admin user') + }), + 'labels': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Select labels') + }), 'port': forms.TextInput(), } help_texts = { @@ -40,9 +47,13 @@ class AssetCreateForm(forms.ModelForm): raise forms.ValidationError(_("You need set a admin user if cluster not have")) return self.cleaned_data['admin_user'] - def save(self, commit=True): - print(self.cleaned_data) - return super().save(commit=commit) + def is_valid(self): + print(self.data) + result = super().is_valid() + if not result: + print(self.errors) + print(self.cleaned_data) + return result class AssetUpdateForm(forms.ModelForm): @@ -54,8 +65,19 @@ class AssetUpdateForm(forms.ModelForm): 'cabinet_pos', 'number', 'comment', 'admin_user', 'labels' ] widgets = { - 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}), - 'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _("Default using cluster admin user")}) + 'groups': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Select asset groups') + }), + 'cluster': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Select cluster') + }), + 'admin_user': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Select admin user') + }), + 'labels': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Select labels') + }), + 'port': forms.TextInput(), } help_texts = { 'hostname': '* required', @@ -72,9 +94,9 @@ class AssetUpdateForm(forms.ModelForm): raise forms.ValidationError(_("You need set a admin user if cluster not have")) return self.cleaned_data['admin_user'] - def save(self, commit=True): - print(self.cleaned_data) - return super().save(commit=commit) + def is_valid(self): + print(self.data) + return super().is_valid() class AssetBulkUpdateForm(forms.ModelForm): diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html index 8378d5f14..30cba19cb 100644 --- a/apps/assets/templates/assets/asset_create.html +++ b/apps/assets/templates/assets/asset_create.html @@ -32,25 +32,31 @@

    {% trans 'Labels' %}

    -
  • @@ -114,6 +122,20 @@ function initTable() { $(document).ready(function(){ initTable(); + $(".select2").select2(); +}) +.on('click', '.labels li', function () { + var val = $(this).text(); + {#var origin_val = $("#asset_list_table_filter input").val();#} + {#var new_val;#} + {#if (origin_val === "") {#} + {# new_val = val;#} + {# } else { #} + {# new_val = origin_val + " " + val;#} + {# } #} + $("#asset_list_table_filter input").val(val); + {#$('#asset_list_table').DataTable().search(val).draw();#} + jumpserver.table.search(val).draw(); }) .on('click', '.btn_export', function () { var $data_table = $('#asset_list_table').DataTable(); diff --git a/apps/assets/utils.py b/apps/assets/utils.py index ab63bbafc..67642be2e 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -1,8 +1,13 @@ # ~*~ coding: utf-8 ~*~ # from collections import defaultdict +from functools import reduce +import operator + +from django.db.models import Q + from common.utils import get_object_or_none -from .models import Asset, SystemUser +from .models import Asset, SystemUser, Label def get_assets_by_id_list(id_list): @@ -27,3 +32,23 @@ def check_assets_have_system_user(assets, system_users): if asset.cluster not in clusters: errors[asset].append(system_user) return errors + + +class LabelFilter: + def filter_queryset(self, queryset): + query_keys = self.request.query_params.keys() + all_label_keys = Label.objects.values_list('name', flat=True) + valid_keys = set(all_label_keys) & set(query_keys) + labels_query = {} + for key in valid_keys: + labels_query[key] = self.request.query_params.get(key) + + conditions = [] + for k, v in labels_query.items(): + query = {'labels__name': k, 'labels__value': v} + conditions.append(query) + + if conditions: + for kwargs in conditions: + queryset = queryset.filter(**kwargs) + return queryset diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index b2f57e323..c14b0c54f 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -27,7 +27,7 @@ from common.mixins import JSONResponseMixin from common.utils import get_object_or_none, get_logger, is_uuid from common.const import create_success_msg, update_success_msg from .. import forms -from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser +from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser, Label from ..hands import AdminUserRequiredMixin @@ -48,6 +48,7 @@ class AssetListView(AdminUserRequiredMixin, TemplateView): 'app': _('Assets'), 'action': _('Asset list'), 'system_users': SystemUser.objects.all(), + 'labels': Label.objects.all().order_by('name'), } kwargs.update(context) return super().get_context_data(**kwargs) @@ -72,12 +73,13 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): template_name = 'assets/asset_create.html' success_url = reverse_lazy('assets:asset-list') - def form_valid(self, form): - asset = form.save() - asset.created_by = self.request.user.username or 'Admin' - asset.date_created = timezone.now() - asset.save() - return super().form_valid(form) + # def form_valid(self, form): + # print("form valid") + # asset = form.save() + # asset.created_by = self.request.user.username or 'Admin' + # asset.date_created = timezone.now() + # asset.save() + # return super().form_valid(form) def get_context_data(self, **kwargs): context = { diff --git a/apps/static/css/jumpserver.css b/apps/static/css/jumpserver.css index 999fe5810..2a3226796 100644 --- a/apps/static/css/jumpserver.css +++ b/apps/static/css/jumpserver.css @@ -338,4 +338,6 @@ div.dataTables_wrapper div.dataTables_filter { .nav.nav-tabs li.active a { border: none; -} \ No newline at end of file +} + + diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index aca70a5a0..8a0e97605 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -383,7 +383,22 @@ jumpserver.initServerSideDataTable = function (options) { } if (data.search !== null) { var search_val = data.search.value; - data.search = search_val; + var search_list = search_val.split(" "); + var search_attr = {}; + var search_raw = []; + + search_list.map(function (val, index) { + var kv = val.split(":"); + if (kv.length === 2) { + search_attr[kv[0]] = kv[1] + } else { + search_raw.push(kv) + } + }); + data.search = search_raw.join(""); + $.each(search_attr, function (k, v) { + data[k] = v + }) } if (data.order !== null && data.order.length === 1) { var col = data.order[0].column; @@ -446,6 +461,7 @@ jumpserver.initServerSideDataTable = function (options) { } }); + jumpserver.table = table; return table; }; From b2d6645f4645c7761d22e73d97e36b2b5faa9282 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 26 Jan 2018 15:32:23 +0800 Subject: [PATCH 064/197] =?UTF-8?q?[Feature]=20=E6=B7=BB=E5=8A=A0=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E8=AF=A6=E6=83=85=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms.py | 6 +++++ .../assets/templates/assets/asset_detail.html | 27 +++++++++++++++++++ apps/assets/templates/assets/label_list.html | 2 +- apps/assets/urls/views_urls.py | 1 + apps/assets/views/label.py | 18 +++++++++++-- 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index b3ee468c6..38e1e65ef 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -418,6 +418,12 @@ class LabelForm(forms.ModelForm): model = Label fields = ['name', 'value', 'assets'] + def __init__(self, *args, **kwargs): + if kwargs.get('instance', None): + initial = kwargs.get('initial', {}) + initial['assets'] = kwargs['instance'].assets.all() + super().__init__(*args, **kwargs) + def save(self, commit=True): label = super().save(commit=commit) assets = self.cleaned_data['assets'] diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html index fc2320420..346a52723 100644 --- a/apps/assets/templates/assets/asset_detail.html +++ b/apps/assets/templates/assets/asset_detail.html @@ -244,6 +244,33 @@
    + +
    +
    + {% trans 'Labels' %} +
    +
    +{# #} +{# #} +{# {% for label in asset.labels.all %}#} +{# #} +{# #} +{# #} +{# #} +{# {% endfor %}#} +{# #} +{#
    {{ label.name }}#} +{# #} +{# {{ label.value }}#} +{# #} +{#
    #} + +
    +
    {% endif %} diff --git a/apps/assets/templates/assets/label_list.html b/apps/assets/templates/assets/label_list.html index bbe6ae5c1..b9430bb97 100644 --- a/apps/assets/templates/assets/label_list.html +++ b/apps/assets/templates/assets/label_list.html @@ -35,7 +35,7 @@ function initTable() { }}, {targets: 4, createdCell: function (td, cellData, rowData) { - var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData); + var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData); $(td).html(update_btn + del_btn) }}], diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index a63c065cf..6207ae0c5 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -55,5 +55,6 @@ urlpatterns = [ url(r'^label/$', views.LabelListView.as_view(), name='label-list'), url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'), + url(r'^label/(?P[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'), ] diff --git a/apps/assets/views/label.py b/apps/assets/views/label.py index e0b489323..a6e1d3aa3 100644 --- a/apps/assets/views/label.py +++ b/apps/assets/views/label.py @@ -7,7 +7,8 @@ from django.utils.translation import ugettext_lazy as _ from django.urls import reverse_lazy from common.mixins import AdminUserRequiredMixin -from common.const import create_success_msg +from common.const import create_success_msg, update_success_msg +from ..models import Label from ..forms import LabelForm @@ -30,6 +31,7 @@ class LabelListView(AdminUserRequiredMixin, TemplateView): class LabelCreateView(AdminUserRequiredMixin, CreateView): + model = Label template_name = 'assets/label_create_update.html' form_class = LabelForm success_url = reverse_lazy('assets:label-list') @@ -45,7 +47,19 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView): class LabelUpdateView(AdminUserRequiredMixin, UpdateView): - pass + model = Label + template_name = 'assets/label_create_update.html' + form_class = LabelForm + success_url = reverse_lazy('assets:label-list') + success_message = update_success_msg + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('Update label'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) class LabelDetailView(AdminUserRequiredMixin, DetailView): From f6d21585abe23bc5ca21e0eb1e3fa10012d40dec Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 26 Jan 2018 15:53:43 +0800 Subject: [PATCH 065/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=90=9C=E7=B4=A2bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/assets/utils.py b/apps/assets/utils.py index 67642be2e..79be4f82c 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -36,6 +36,7 @@ def check_assets_have_system_user(assets, system_users): class LabelFilter: def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) query_keys = self.request.query_params.keys() all_label_keys = Label.objects.values_list('name', flat=True) valid_keys = set(all_label_keys) & set(query_keys) From f0b9a11482aeaaa646f7a468ee8f257fe9cb000d Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 26 Jan 2018 16:06:23 +0800 Subject: [PATCH 066/197] [Update] ansible disk bug --- apps/assets/tasks.py | 2 +- apps/users/signals.py | 3 ++- apps/users/utils.py | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index f06f720d9..454dc5d19 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -19,7 +19,7 @@ FORKS = 10 TIMEOUT = 60 logger = get_logger(__file__) CACHE_MAX_TIME = 60*60*60 -disk_pattern = re.compile(r'^hd|sd|xvd') +disk_pattern = re.compile(r'^hd|sd|xvd|vd') @shared_task diff --git a/apps/users/signals.py b/apps/users/signals.py index 39e57f5a5..f942c39b7 100644 --- a/apps/users/signals.py +++ b/apps/users/signals.py @@ -16,5 +16,6 @@ def on_user_created(sender, instance=None, created=False, **kwargs): logger.debug("Receive user `{}` create signal".format(instance.name)) from .utils import send_user_created_mail logger.info(" - Sending welcome mail ...".format(instance.name)) - send_user_created_mail(instance) + if instance.email: + send_user_created_mail(instance) diff --git a/apps/users/utils.py b/apps/users/utils.py index fd03ad97d..abc4b2018 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -59,7 +59,10 @@ def send_user_created_mail(user): 'login_url': reverse('users:login', external=True), } if settings.DEBUG: - print(message) + try: + print(message) + except OSError: + pass send_mail_async.delay(subject, message, recipient_list, html_message=message) From 9e36a2cbb1cd937ebef74860e6e639c5f4a1fc5f Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 26 Jan 2018 16:06:36 +0800 Subject: [PATCH 067/197] [Update] ansible disk bug --- apps/assets/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 454dc5d19..3900de769 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -55,7 +55,7 @@ def set_assets_hardware_info(result, **kwargs): else: ___cpu_model = 'Unknown' ___cpu_count = info['ansible_processor_count'] - ___cpu_cores = info['ansible_processor_cores'] + ___cpu_cores = info.get('ansible_processor_cores', '') ___memory = '%s %s' % capacity_convert('{} MB'.format(info['ansible_memtotal_mb'])) disk_info = {} for dev, dev_info in info['ansible_devices'].items(): From 41756b06e19158c39eed5b2d53736da3e07ad4ee Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 26 Jan 2018 16:21:14 +0800 Subject: [PATCH 068/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96kvmcpu=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 3900de769..dc4a3b2fa 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -36,9 +36,10 @@ def set_assets_hardware_info(result, **kwargs): result_raw = result[0] assets_updated = [] for hostname, info in result_raw.get('ok', {}).items(): - if info: + if info and info.get('setup'): info = info['setup']['ansible_facts'] else: + logger.error("Get asset info failed: {}".format(hostname)) continue asset = get_object_or_none(Asset, hostname=hostname) @@ -50,7 +51,7 @@ def set_assets_hardware_info(result, **kwargs): ___sn = info['ansible_product_serial'] for ___cpu_model in info['ansible_processor']: - if ___cpu_model.endswith('GHz'): + if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"): break else: ___cpu_model = 'Unknown' From 723805d395a904d8171a4fdff1f8d93450b01cf8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 26 Jan 2018 16:28:40 +0800 Subject: [PATCH 069/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8Dbsd?= =?UTF-8?q?=E8=8E=B7=E5=8F=96cpu=E6=95=B0=E9=87=8Fbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index dc4a3b2fa..4ba59298e 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -56,7 +56,7 @@ def set_assets_hardware_info(result, **kwargs): else: ___cpu_model = 'Unknown' ___cpu_count = info['ansible_processor_count'] - ___cpu_cores = info.get('ansible_processor_cores', '') + ___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', [])) ___memory = '%s %s' % capacity_convert('{} MB'.format(info['ansible_memtotal_mb'])) disk_info = {} for dev, dev_info in info['ansible_devices'].items(): From 9433d6177a2a5563307edc870adca74f3e43ab55 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 26 Jan 2018 16:50:59 +0800 Subject: [PATCH 070/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 32504 -> 32504 bytes apps/locale/zh/LC_MESSAGES/django.po | 57 ++++++++++++++------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index c74a4110ce203398ab9746d1a268bc7575be310a..4ba21ca5c955f19c8fa03f3f3fda7058d72bd2aa 100644 GIT binary patch delta 19 bcmezIm+{A6#tmxWEM`{57Mr!g(dUaklt delta 19 bcmezIm+{A6#tmxWET&c_#+$Xm(dUXBPI diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 08191a8ac..5a6b7a478 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-01-26 15:43+0800\n" +"POT-Creation-Date: 2018-01-26 16:38+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -32,6 +32,7 @@ msgid "Select admin user" msgstr "选择管理用户" #: assets/forms.py:31 assets/forms.py:78 +#: assets/templates/assets/asset_create.html:38 msgid "Select labels" msgstr "选择标签" @@ -377,10 +378,8 @@ msgid "Hostname raw" msgstr "主机名原始" #: assets/models/asset.py:91 assets/templates/assets/asset_create.html:34 -#: assets/templates/assets/asset_create.html:36 #: assets/templates/assets/asset_detail.html:250 -#: assets/templates/assets/asset_update.html:39 -#: assets/templates/assets/asset_update.html:41 +#: assets/templates/assets/asset_update.html:39 templates/_nav.html:27 msgid "Labels" msgstr "标签管理" @@ -560,43 +559,43 @@ msgstr "推送系统用户到: {}->{}" msgid "Push system user to assets" msgstr "推送系统用户到资产" -#: assets/tasks.py:91 +#: assets/tasks.py:92 msgid "Update some assets hardware info" msgstr "更新一些资产硬件信息" -#: assets/tasks.py:107 +#: assets/tasks.py:108 msgid "Update asset hardware info" msgstr "更新资产硬件信息" -#: assets/tasks.py:121 +#: assets/tasks.py:122 msgid "Update assets hardware info period" msgstr "定期更新资产硬件信息" -#: assets/tasks.py:189 +#: assets/tasks.py:190 msgid "Test admin user connectability period: {}" msgstr "定期测试管理用户可连接性: {}" -#: assets/tasks.py:203 +#: assets/tasks.py:204 msgid "Test admin user connectability: {}" msgstr "测试管理用户可连接性: {}" -#: assets/tasks.py:212 +#: assets/tasks.py:213 msgid "Test asset connectability" msgstr "测试资产可连接性" -#: assets/tasks.py:283 +#: assets/tasks.py:284 msgid "Test system user connectability: {}" msgstr "测试系统用户可连接性: {}" -#: assets/tasks.py:295 +#: assets/tasks.py:296 msgid "Test system user connectability period: {}" msgstr "定期测试系统用户可连接性: {}" -#: assets/tasks.py:376 +#: assets/tasks.py:377 msgid "Push system user to cluster assets: {}" msgstr "推送系统用户到资产: {}" -#: assets/tasks.py:397 +#: assets/tasks.py:398 msgid "Push cluster system users to assets period: {}" msgstr "定期推送集群系统用户到资产: {}" @@ -686,7 +685,7 @@ msgid "Auto generate key" msgstr "自动生成秘钥" #: assets/templates/assets/_system_user.html:65 -#: assets/templates/assets/asset_create.html:61 +#: assets/templates/assets/asset_create.html:60 #: assets/templates/assets/asset_update.html:70 #: assets/templates/assets/cluster_create_update.html:46 #: perms/templates/perms/asset_permission_create_update.html:45 @@ -697,7 +696,7 @@ msgstr "其它" #: assets/templates/assets/_system_user.html:71 #: assets/templates/assets/admin_user_create_update.html:45 #: assets/templates/assets/asset_bulk_update.html:23 -#: assets/templates/assets/asset_create.html:68 +#: assets/templates/assets/asset_create.html:67 #: assets/templates/assets/asset_group_create.html:16 #: assets/templates/assets/asset_update.html:78 #: assets/templates/assets/cluster_create_update.html:54 @@ -721,7 +720,7 @@ msgstr "重置" #: assets/templates/assets/_system_user.html:72 #: assets/templates/assets/admin_user_create_update.html:46 #: assets/templates/assets/asset_bulk_update.html:24 -#: assets/templates/assets/asset_create.html:69 +#: assets/templates/assets/asset_create.html:68 #: assets/templates/assets/asset_group_create.html:17 #: assets/templates/assets/asset_list.html:61 #: assets/templates/assets/asset_update.html:79 @@ -925,6 +924,12 @@ msgstr "动作" msgid "Group" msgstr "组" +#: assets/templates/assets/asset_create.html:36 +#: assets/templates/assets/asset_list.html:27 +#: assets/templates/assets/asset_update.html:41 +msgid "Label" +msgstr "标签" + #: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:188 #: assets/views/cluster.py:98 msgid "Asset detail" @@ -1068,10 +1073,6 @@ msgstr "导出" msgid "Create asset" msgstr "创建资产" -#: assets/templates/assets/asset_list.html:27 templates/_nav.html:27 -msgid "Label" -msgstr "标签" - #: assets/templates/assets/asset_list.html:42 #: assets/templates/assets/user_asset_list.html:24 msgid "Hardware" @@ -2509,7 +2510,7 @@ msgstr "城市" #: users/templates/users/reset_password.html:45 #: users/templates/users/user_detail.html:325 -#: users/templates/users/user_profile.html:136 users/utils.py:68 +#: users/templates/users/user_profile.html:136 users/utils.py:71 msgid "Reset password" msgstr "重置密码" @@ -2730,7 +2731,7 @@ msgstr "" "
    \n" " " -#: users/utils.py:70 +#: users/utils.py:73 #, python-format msgid "" "\n" @@ -2774,11 +2775,11 @@ msgstr "" "
    \n" " " -#: users/utils.py:101 +#: users/utils.py:104 msgid "SSH Key Reset" msgstr "重置ssh密钥" -#: users/utils.py:103 +#: users/utils.py:106 #, python-format msgid "" "\n" @@ -2803,15 +2804,15 @@ msgstr "" "
    \n" " " -#: users/utils.py:136 +#: users/utils.py:139 msgid "User not exist" msgstr "用户不存在" -#: users/utils.py:138 +#: users/utils.py:141 msgid "Disabled or expired" msgstr "禁用或失效" -#: users/utils.py:151 +#: users/utils.py:154 msgid "Password or SSH public key invalid" msgstr "密码或秘钥不合法" From 6d4e9c7d30e5b9c687245200e0f01c86f9f05947 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 26 Jan 2018 17:17:37 +0800 Subject: [PATCH 071/197] =?UTF-8?q?[Bugfix]=20=E8=B5=84=E4=BA=A7model=20?= =?UTF-8?q?=E5=A4=AA=E9=95=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 4ba59298e..1e17656e1 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -55,6 +55,7 @@ def set_assets_hardware_info(result, **kwargs): break else: ___cpu_model = 'Unknown' + ___cpu_model = ___cpu_model[:64] ___cpu_count = info['ansible_processor_count'] ___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', [])) ___memory = '%s %s' % capacity_convert('{} MB'.format(info['ansible_memtotal_mb'])) From 1f792a9e3c7c31da1bce2fdb82987cab5f6b2a00 Mon Sep 17 00:00:00 2001 From: chenchen1750 Date: Mon, 29 Jan 2018 11:07:01 +0800 Subject: [PATCH 072/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E6=94=B9=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BB=93=E6=9E=84=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正"项目多语言目录" --- docs/project_structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/project_structure.md b/docs/project_structure.md index d66f043f9..826fd1860 100644 --- a/docs/project_structure.md +++ b/docs/project_structure.md @@ -45,6 +45,6 @@ │ │ └── wsgi.py │ ├── manage.py │ ├── static // 项目静态资源目录 -│ ├── static // 项目多语言目录 +│ ├── locale // 项目多语言目录 │ └── templates // 项目模板目录 ``` From 04c344e2a0a4ce29443f4c35a8229073bf025ba7 Mon Sep 17 00:00:00 2001 From: liuzheng Date: Mon, 29 Jan 2018 11:50:41 +0800 Subject: [PATCH 073/197] Update project_structure.md --- docs/project_structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/project_structure.md b/docs/project_structure.md index 826fd1860..3cc9b8f94 100644 --- a/docs/project_structure.md +++ b/docs/project_structure.md @@ -45,6 +45,6 @@ │ │ └── wsgi.py │ ├── manage.py │ ├── static // 项目静态资源目录 -│ ├── locale // 项目多语言目录 +│   ├── i18n                     // 项目多语言目录 │ └── templates // 项目模板目录 ``` From cc4eca2563cf666967f8a2f0cd79f31ac26a206d Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 29 Jan 2018 11:53:49 +0800 Subject: [PATCH 074/197] [Update] add debug log --- apps/terminal/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/terminal/api.py b/apps/terminal/api.py index 279ec0121..cb45ef847 100644 --- a/apps/terminal/api.py +++ b/apps/terminal/api.py @@ -56,6 +56,7 @@ class TerminalViewSet(viewsets.ModelViewSet): return Response(data, status=201) else: data = serializer.errors + logger.error("Register terminal error: {}".format(data)) return Response(data, status=400) def get_permissions(self): From e821d2995d2569671ee95ed84c231526873789eb Mon Sep 17 00:00:00 2001 From: liuzheng Date: Mon, 29 Jan 2018 11:53:50 +0800 Subject: [PATCH 075/197] refactor: rename folder i18n --- apps/{locale => i18n}/zh/LC_MESSAGES/django.mo | Bin apps/{locale => i18n}/zh/LC_MESSAGES/django.po | 0 apps/jumpserver/settings.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename apps/{locale => i18n}/zh/LC_MESSAGES/django.mo (100%) rename apps/{locale => i18n}/zh/LC_MESSAGES/django.po (100%) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo similarity index 100% rename from apps/locale/zh/LC_MESSAGES/django.mo rename to apps/i18n/zh/LC_MESSAGES/django.mo diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po similarity index 100% rename from apps/locale/zh/LC_MESSAGES/django.po rename to apps/i18n/zh/LC_MESSAGES/django.po diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 4926c4538..77dd8e114 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -241,7 +241,7 @@ USE_L10N = True USE_TZ = True # I18N translation -LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale'), ] +LOCALE_PATHS = [os.path.join(BASE_DIR, 'i18n'), ] # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ From 6d552f4680e444e79225d2378718c4658110db5e Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 29 Jan 2018 16:18:30 +0800 Subject: [PATCH 076/197] =?UTF-8?q?[Feature]=20=E6=B7=BB=E5=8A=A0=E9=93=BE?= =?UTF-8?q?=E6=8E=A5token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/api.py | 33 ++++++++++++++++++++++++++++++++- apps/users/urls/api_urls.py | 1 + apps/users/views/login.py | 3 ++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/apps/users/api.py b/apps/users/api.py index d7c95e5f5..98d174124 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -1,4 +1,7 @@ # ~*~ coding: utf-8 ~*~ +import uuid + +from django.core.cache import cache from rest_framework import generics from rest_framework.permissions import AllowAny, IsAuthenticated @@ -11,7 +14,8 @@ from .serializers import UserSerializer, UserGroupSerializer, \ UserUpdateGroupSerializer, ChangeUserPasswordSerializer from .tasks import write_login_log_async from .models import User, UserGroup -from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly +from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \ + IsSuperUserOrAppUser from .utils import check_user_valid, generate_token from common.mixins import CustomFilterMixin from common.utils import get_logger @@ -160,3 +164,30 @@ class UserAuthApi(APIView): return Response({'token': token, 'user': user.to_json()}) else: return Response({'msg': msg}, status=401) + + +class UserConnectionTokenApi(APIView): + permission_classes = (IsSuperUserOrAppUser,) + + def post(self, request): + user_id = request.data.get('user', '') + asset_id = request.data.get('asset', '') + system_user_id = request.data.get('system_user', '') + token = str(uuid.uuid4()) + value = { + 'user': user_id, + 'asset': asset_id, + 'system_user': system_user_id + } + cache.set(token, value, timeout=3600) + return Response({"token": token}, status=201) + + def get(self, request): + token = request.query_params.get('token') + value = cache.get(token, None) + if value: + cache.delete(token) + return Response(value) + + + diff --git a/apps/users/urls/api_urls.py b/apps/users/urls/api_urls.py index fcde1e28f..ce681c146 100644 --- a/apps/users/urls/api_urls.py +++ b/apps/users/urls/api_urls.py @@ -17,6 +17,7 @@ router.register(r'v1/groups', api.UserGroupViewSet, 'user-group') urlpatterns = [ # url(r'', api.UserListView.as_view()), url(r'^v1/token/$', api.UserToken.as_view(), name='user-token'), + url(r'^v1/connection-token/$', api.UserConnectionTokenApi.as_view(), name='connection-token'), url(r'^v1/profile/$', api.UserProfile.as_view(), name='user-profile'), url(r'^v1/auth/$', api.UserAuthApi.as_view(), name='user-auth'), url(r'^v1/users/(?P[0-9a-zA-Z\-]{36})/password/$', diff --git a/apps/users/views/login.py b/apps/users/views/login.py index dd9654805..44abd0a81 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -80,7 +80,8 @@ class UserLogoutView(TemplateView): def get(self, request, *args, **kwargs): auth_logout(request) - return super().get(request, *args, **kwargs) + response = super().get(request, *args, **kwargs) + return response def get_context_data(self, **kwargs): context = { From 58d22b72ec0e88474474fdd839ae07ec1595c9b0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 29 Jan 2018 16:25:30 +0800 Subject: [PATCH 077/197] =?UTF-8?q?[Feature]=20Label=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/templates/assets/label_list.html | 8 ++++---- apps/assets/urls/views_urls.py | 1 + apps/assets/views/label.py | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/assets/templates/assets/label_list.html b/apps/assets/templates/assets/label_list.html index b9430bb97..ebefb243a 100644 --- a/apps/assets/templates/assets/label_list.html +++ b/apps/assets/templates/assets/label_list.html @@ -36,7 +36,7 @@ function initTable() { {targets: 4, createdCell: function (td, cellData, rowData) { var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData); - var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData); + var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData); $(td).html(update_btn + del_btn) }}], ajax_url: '{% url "api-assets:label-list" %}?sort=name', @@ -51,12 +51,12 @@ function initTable() { $(document).ready(function(){ initTable(); }) -.on('click', '.btn_cluster_delete', function () { +.on('click', '.btn-delete', function () { var $this = $(this); - var $data_table = $('#cluster_list_table').DataTable(); + var $data_table = $('#label_list_table').DataTable(); var name = $(this).closest("tr").find(":nth-child(2)").children('a').html(); var uid = $this.data('uid'); - var the_url = '{% url "api-assets:cluster-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid); + var the_url = '{% url "api-assets:label-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid); objectDelete($this, name, the_url); setTimeout( function () { $data_table.ajax.reload(); diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index 6207ae0c5..48d01dafd 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -56,5 +56,6 @@ urlpatterns = [ url(r'^label/$', views.LabelListView.as_view(), name='label-list'), url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'), url(r'^label/(?P[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'), + url(r'^label/(?P[0-9a-zA-Z\-]{36})/delete/$', views.LabelDeleteView.as_view(), name='label-delete'), ] diff --git a/apps/assets/views/label.py b/apps/assets/views/label.py index a6e1d3aa3..0b2d0d6ad 100644 --- a/apps/assets/views/label.py +++ b/apps/assets/views/label.py @@ -67,4 +67,6 @@ class LabelDetailView(AdminUserRequiredMixin, DetailView): class LabelDeleteView(AdminUserRequiredMixin, DeleteView): - pass + model = Label + template_name = 'delete_confirm.html' + success_url = reverse_lazy('assets:label-list') From 8b626ea9d40bf5e357d1538fcd1614ac9cfd3c09 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 29 Jan 2018 16:27:21 +0800 Subject: [PATCH 078/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E9=83=A8?= =?UTF-8?q?=E5=88=86=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 32504 -> 32510 bytes apps/locale/zh/LC_MESSAGES/django.po | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 4ba21ca5c955f19c8fa03f3f3fda7058d72bd2aa..4848a6f135717c82505d96f4d303668a008e68c6 100644 GIT binary patch delta 1228 zcmXZbO-Phc6vpv8)C^5IO^rH8qJsu$HMP-NL#&IGH;2{u7j?ntfOB#55)3*R?8YNXpx)6W>IoE4 z8%<*kezmw_^TEshH&TVGn2+H)jHCKAqxvK$2-(68Hzx_PI?C(Rzz0n!*|f0tPqIG078G-r;P_fZ=^Mm2ni zYEZ;ASVG-&4)wSELcO{QYF+q>ZydF5Gw#7QjIqD#WuOMbRxo1TG9TFd3sl2N^MlQo zP#@b*%Wu5uZ`6pj*NrRxe=h^wY|siuQLo?*s_;4L$rMoU_>JYKEI*Ap z*cbD=<^Pz=s6N3VzfTmm6UT?#7T3ezAPIfN4^fRinPt?%h5Xy5Gbci`qnX+36F1sZ R?V<9cv2r$(KbOj`{SPg*wM_s3 delta 1221 zcmXZaPe@cz6vy!+mNxmP!JHZ?i*aFA4z-Yo$`(l!1uj~&Xd#pcN+3~=WmCR3QnZSi zB4|+rDM9uJsYSDxY9S4^kSt;rK}l#q2`PaUX5U}$tUmXgd+xdSzWe^p7tPHVJ#4HE zO3ds?k6ANzq2eJd$NRV#v)G8wJwJ_$i2tDKEfreoR-)F&Fot!g`mGqjX4JYpDGRKM zK`RL@IE-=Z_V~QV8C2b{`^+6jt(!zOob~uOt|Kn(HQRuj+(W4P0rzrm5GHPtsAS?E zF2gLU;VbtIYGJ{hbZ1>V8P->z>Z&n;3D37+1Mxnr!xN~BUJHEi0JZQX>Zb27ieGRo z{z5gVITbe8g{tqsBp&zp7Oo;5#yI9t{U&fZ{zP3c(r2~;gBk{V8EnSg*oS(f9O?_? zQ5%h6HGc4T#^)DM|47McvtlgAGK`}7tVZ>z#|liM4zL|LSYZ1ZXoEJNIEp%XIxMgZ zt|1;m)s4F2?nhit{yVB}Wjd_eg1UK&#|PbZ)B%oRg#GQ@|AE;>)JZe$4fi%`#q8hz*KcN;*kK_{_2TG;}vr|uVSDJd7 KM!I`0E&T@;0kS6m diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 5a6b7a478..1074337e4 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -2012,7 +2012,7 @@ msgstr "资产授权" #: templates/_nav.html:40 msgid "Sessions" -msgstr "会话" +msgstr "会话管理" #: templates/_nav.html:43 msgid "Session online" @@ -2020,7 +2020,7 @@ msgstr "在线会话" #: templates/_nav.html:44 msgid "Session offline" -msgstr "离线会话" +msgstr "历史会话" #: templates/_nav.html:45 msgid "Commands" From 012614562f99d79cae8bdfa4a4f8a228494fabf4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 29 Jan 2018 16:57:50 +0800 Subject: [PATCH 079/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=B0=8Fb?= =?UTF-8?q?ug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/views/login.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 44abd0a81..10ad697ba 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -29,10 +29,12 @@ from ..tasks import write_login_log_async from .. import forms -__all__ = ['UserLoginView', 'UserLogoutView', - 'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView', - 'UserResetPasswordView', 'UserResetPasswordSuccessView', - 'UserFirstLoginView', 'LoginLogListView'] +__all__ = [ + 'UserLoginView', 'UserLogoutView', + 'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView', + 'UserResetPasswordView', 'UserResetPasswordSuccessView', + 'UserFirstLoginView', 'LoginLogListView' +] @method_decorator(sensitive_post_parameters(), name='dispatch') From bad397e2e661e2b7a8adf17fc542e1d50b7922ce Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Jan 2018 10:38:40 +0800 Subject: [PATCH 080/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E8=B5=84=E4=BA=A7=E4=BF=A1=E6=81=AF=E5=BC=82=E5=B8=B8?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 1e17656e1..9920adcd5 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -36,9 +36,8 @@ def set_assets_hardware_info(result, **kwargs): result_raw = result[0] assets_updated = [] for hostname, info in result_raw.get('ok', {}).items(): - if info and info.get('setup'): - info = info['setup']['ansible_facts'] - else: + info = info.get('setup', {}).get('ansible_facts', {}) + if not info: logger.error("Get asset info failed: {}".format(hostname)) continue @@ -46,31 +45,31 @@ def set_assets_hardware_info(result, **kwargs): if not asset: continue - ___vendor = info['ansible_system_vendor'] - ___model = info['ansible_product_version'] - ___sn = info['ansible_product_serial'] + ___vendor = info.get('ansible_system_vendor', 'Unknown') + ___model = info.get('ansible_product_version', 'Unknown') + ___sn = info.get('ansible_product_serial', 'Unknown') - for ___cpu_model in info['ansible_processor']: + for ___cpu_model in info.get('ansible_processor', []): if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"): break else: ___cpu_model = 'Unknown' ___cpu_model = ___cpu_model[:64] - ___cpu_count = info['ansible_processor_count'] + ___cpu_count = info.get('ansible_processor_count', 'Unknown') ___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', [])) - ___memory = '%s %s' % capacity_convert('{} MB'.format(info['ansible_memtotal_mb'])) + ___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb'))) disk_info = {} - for dev, dev_info in info['ansible_devices'].items(): + for dev, dev_info in info.get('ansible_devices', {}).items(): if disk_pattern.match(dev) and dev_info['removable'] == '0': disk_info[dev] = dev_info['size'] ___disk_total = '%s %s' % sum_capacity(disk_info.values()) ___disk_info = json.dumps(disk_info) - ___platform = info['ansible_system'] - ___os = info['ansible_distribution'] - ___os_version = info['ansible_distribution_version'] - ___os_arch = info['ansible_architecture'] - ___hostname_raw = info['ansible_hostname'] + ___platform = info.get('ansible_system', 'Unknown') + ___os = info.get('ansible_distribution', 'Unknown') + ___os_version = info.get('ansible_distribution_version', 'Unknown') + ___os_arch = info.get('ansible_architecture', 'Unknown') + ___hostname_raw = info.get('ansible_hostname', 'Unknown') for k, v in locals().items(): if k.startswith('___'): From 1192e8cdad0c5fa2a8de0c74ae518928ec6af528 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Jan 2018 11:56:41 +0800 Subject: [PATCH 081/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7=E4=B8=8A=E4=BC=A0=E7=A7=98=E9=92=A5?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 38e1e65ef..7c069d9ee 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -284,11 +284,11 @@ class AdminUserForm(forms.ModelForm): class SystemUserForm(forms.ModelForm): # Admin user assets define, let user select, save it in form not in view auto_generate_key = forms.BooleanField(initial=True, required=False) + # Need use upload private key file except paste private key content + private_key_file = forms.FileField(required=False, label=_("Private key")) # Form field name can not start with `_`, so redefine it, password = forms.CharField(widget=forms.PasswordInput, required=False, max_length=128, strip=True, label=_("Password")) - # Need use upload private key file except paste private key content - private_key_file = forms.FileField(required=False, label=_("Private key")) def save(self, commit=True): # Because we define custom field, so we need rewrite :method: `save` @@ -328,7 +328,7 @@ class SystemUserForm(forms.ModelForm): model = SystemUser fields = [ 'name', 'username', 'protocol', 'auto_generate_key', - 'password', 'private_key_file', 'auto_push', 'sudo', + 'private_key_file', 'password', 'auto_push', 'sudo', 'comment', 'shell', 'cluster', 'priority', ] widgets = { From da4c5f48a193e34de8aa2c0b14aa9b368a9b3514 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Jan 2018 12:09:33 +0800 Subject: [PATCH 082/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E8=B5=84=E4=BA=A7=E4=BF=A1=E6=81=AF=E4=BA=A7=E7=94=9F?= =?UTF-8?q?=E7=9A=84=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 9920adcd5..ca0a32e4e 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -55,7 +55,7 @@ def set_assets_hardware_info(result, **kwargs): else: ___cpu_model = 'Unknown' ___cpu_model = ___cpu_model[:64] - ___cpu_count = info.get('ansible_processor_count', 'Unknown') + ___cpu_count = info.get('ansible_processor_count', 0) ___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', [])) ___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb'))) disk_info = {} From 1fbf4ac08c25191b2a28b580343eb155df33980e Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Jan 2018 14:46:02 +0800 Subject: [PATCH 083/197] =?UTF-8?q?[Update]=20=E5=88=A0=E9=99=A4=E9=83=A8?= =?UTF-8?q?=E5=88=86=E8=B5=84=E4=BA=A7=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms.py | 15 ++------ apps/assets/models/asset.py | 26 ------------- apps/assets/serializers.py | 1 - .../assets/templates/assets/asset_create.html | 2 - .../assets/templates/assets/asset_detail.html | 38 ------------------- .../assets/templates/assets/asset_update.html | 9 ----- 6 files changed, 4 insertions(+), 87 deletions(-) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 7c069d9ee..96106d89f 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -12,9 +12,8 @@ class AssetCreateForm(forms.ModelForm): class Meta: model = Asset fields = [ - 'hostname', 'ip', 'public_ip', 'port', 'type', 'comment', - 'cluster', 'groups', 'status', 'env', 'is_active', - 'admin_user', 'labels' + 'hostname', 'ip', 'public_ip', 'port', 'comment', 'cluster', + 'groups', 'is_active', 'admin_user', 'labels' ] widgets = { @@ -61,8 +60,7 @@ class AssetUpdateForm(forms.ModelForm): model = Asset fields = [ 'hostname', 'ip', 'port', 'groups', "cluster", 'is_active', - 'type', 'env', 'status', 'public_ip', 'remote_card_ip', 'cabinet_no', - 'cabinet_pos', 'number', 'comment', 'admin_user', 'labels' + 'public_ip', 'number', 'comment', 'admin_user', 'labels' ] widgets = { 'groups': forms.SelectMultiple(attrs={ @@ -94,10 +92,6 @@ class AssetUpdateForm(forms.ModelForm): raise forms.ValidationError(_("You need set a admin user if cluster not have")) return self.cleaned_data['admin_user'] - def is_valid(self): - print(self.data) - return super().is_valid() - class AssetBulkUpdateForm(forms.ModelForm): assets = forms.ModelMultipleChoiceField( @@ -117,8 +111,7 @@ class AssetBulkUpdateForm(forms.ModelForm): class Meta: model = Asset fields = [ - 'assets', 'port', 'groups', "cluster", - 'type', 'env', + 'assets', 'port', 'groups', "cluster", 'labels' ] widgets = { 'groups': forms.SelectMultiple( diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 0b1cdf0ec..3677b04b5 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -29,25 +29,6 @@ def default_cluster(): class Asset(models.Model): - # Todo: Move them to settings - STATUS_CHOICES = ( - ('In use', _('In use')), - ('Out of use', _('Out of use')), - ) - TYPE_CHOICES = ( - ('Server', _('Server')), - ('VM', _('VM')), - ('Switch', _('Switch')), - ('Router', _('Router')), - ('Firewall', _('Firewall')), - ('Storage', _("Storage")), - ) - ENV_CHOICES = ( - ('Prod', _('Production')), - ('Dev', _('Development')), - ('Test', _('Testing')), - ) - # Important id = models.UUIDField(default=uuid.uuid4, primary_key=True) ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) @@ -56,18 +37,12 @@ class Asset(models.Model): groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups')) cluster = models.ForeignKey(Cluster, related_name='assets', default=default_cluster, on_delete=models.SET_DEFAULT, verbose_name=_('Cluster')) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) - type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),) - env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),) - status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True, default='In use', verbose_name=_('Asset status')) # Auth admin_user = models.ForeignKey('assets.AdminUser', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("Admin user")) # Some information public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP')) - remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote control card IP')) - cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number')) - cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position')) number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number')) # Collect @@ -89,7 +64,6 @@ class Asset(models.Model): hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) - created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index 1fd6e1d9a..1b40ef371 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -173,7 +173,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): def get_field_names(self, declared_fields, info): fields = super().get_field_names(declared_fields, info) fields.extend([ - 'get_type_display', 'get_env_display', 'hardware_info', 'is_connective', ]) return fields diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html index 0fb2f6002..4c5f12fdd 100644 --- a/apps/assets/templates/assets/asset_create.html +++ b/apps/assets/templates/assets/asset_create.html @@ -19,8 +19,6 @@ {% bootstrap_field form.port layout="horizontal" %} {% bootstrap_field form.cluster layout="horizontal" %} {% bootstrap_field form.public_ip layout="horizontal" %} - {% bootstrap_field form.type layout="horizontal" %} - {% bootstrap_field form.env layout="horizontal" %}

    {% trans 'Auth' %}

    diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html index 346a52723..e7b136555 100644 --- a/apps/assets/templates/assets/asset_detail.html +++ b/apps/assets/templates/assets/asset_detail.html @@ -77,22 +77,10 @@ {% endif %} - - {% trans 'Remote card IP' %}: - {{ asset.remote_card_ip|default:"" }} - {% trans 'Cluster' %}: {{ asset.cluster.name }} - - {% trans 'Cabinet number' %}: - {{ asset.cabinet_no|default:"" }} - - - {% trans 'Cabinet position' %}: - {{ asset.cabinet_pos|default:"" }} - {% trans 'Vendor' %}: {{ asset.vendor|default:"" }} @@ -121,22 +109,10 @@ {% trans 'OS' %}: {{ asset.os|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:"" }} - - {% trans 'Asset status' %}: - {{ asset.status }} - {% trans 'Is active' %}: {{ asset.is_active|yesno:"Yes,No" }} - - {% trans 'Asset type' %}: - {{ asset.type }} - - - {% trans 'Asset environment' %}: - {{ asset.env }} - {% trans 'Serial number' %}: {{ asset.sn|default:"" }} @@ -250,20 +226,6 @@ {% trans 'Labels' %}
    -{# #} -{# #} -{# {% for label in asset.labels.all %}#} -{# #} -{# #} -{# #} -{# #} -{# {% endfor %}#} -{# #} -{#
    {{ label.name }}#} -{# #} -{# {{ label.value }}#} -{# #} -{#
    #}
      {% for label in asset.labels.all %}
    • {{ label.name }}:{{ label.value }}
    • diff --git a/apps/assets/templates/assets/asset_update.html b/apps/assets/templates/assets/asset_update.html index bcf9aecf1..818f89519 100644 --- a/apps/assets/templates/assets/asset_update.html +++ b/apps/assets/templates/assets/asset_update.html @@ -24,8 +24,6 @@ {% bootstrap_field form.port layout="horizontal" %} {% bootstrap_field form.cluster layout="horizontal" %} {% bootstrap_field form.public_ip layout="horizontal" %} - {% bootstrap_field form.type layout="horizontal" %} - {% bootstrap_field form.env layout="horizontal" %}

      {% trans 'Auth' %}

      @@ -59,16 +57,9 @@

      {% trans 'Configuration' %}

      {% bootstrap_field form.number layout="horizontal" %} - {% bootstrap_field form.remote_card_ip layout="horizontal" %} - -
      -

      {% trans 'Location' %}

      - {% bootstrap_field form.cabinet_no layout="horizontal" %} - {% bootstrap_field form.cabinet_pos layout="horizontal" %}

      {% trans 'Other' %}

      - {% bootstrap_field form.status layout="horizontal" %} {% bootstrap_field form.comment layout="horizontal" %} {% bootstrap_field form.is_active layout="horizontal" %} From b4f833740e3ba69c52416769765ba4a382737ac3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Jan 2018 14:54:27 +0800 Subject: [PATCH 084/197] =?UTF-8?q?[Bugfix]=20=E8=B5=84=E4=BA=A7=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E4=BE=BF=E6=8D=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 96106d89f..08347fc65 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -107,6 +107,15 @@ class AssetBulkUpdateForm(forms.ModelForm): port = forms.IntegerField( label=_('Port'), required=False, min_value=1, max_value=65535, ) + cluster = forms.ModelChoiceField( + required=False, label=_("Cluster"), queryset=Cluster.objects.all(), + widget=forms.Select( + attrs={ + 'class': 'select2', + 'data-placeholder': _('Select cluster') + } + ) + ) class Meta: model = Asset @@ -117,6 +126,9 @@ class AssetBulkUpdateForm(forms.ModelForm): 'groups': forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')} ), + 'labels': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('Select lables')} + ), } def save(self, commit=True): @@ -129,11 +141,15 @@ class AssetBulkUpdateForm(forms.ModelForm): if k in changed_fields} assets = cleaned_data.pop('assets') groups = cleaned_data.pop('groups', []) + labels = cleaned_data.pop('labels', []) assets = Asset.objects.filter(id__in=[asset.id for asset in assets]) assets.update(**cleaned_data) if groups: for asset in assets: asset.groups.set(groups) + if labels: + for asset in assets: + asset.labels.set(labels) return assets From 3e62a7f5b7a8fade275b9b29cbea4ed0e98aa7b9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Jan 2018 16:12:33 +0800 Subject: [PATCH 085/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E8=AE=A4?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms.py | 152 +++++------------- .../assets/templates/assets/_system_user.html | 2 +- .../templates/assets/system_user_update.html | 2 +- apps/assets/views/system_user.py | 4 +- 4 files changed, 48 insertions(+), 112 deletions(-) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 08347fc65..c93f75626 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -228,46 +228,29 @@ class ClusterForm(forms.ModelForm): return instance -class AdminUserForm(forms.ModelForm): +class PasswordAndKeyAuthForm(forms.ModelForm): # Form field name can not start with `_`, so redefine it, password = forms.CharField( widget=forms.PasswordInput, max_length=128, strip=True, required=False, - help_text=_('Password or private key password'), + help_text=_('Password or private key passphrase'), label=_("Password"), ) # Need use upload private key file except paste private key content private_key_file = forms.FileField(required=False, label=_("Private key")) - def save(self, commit=True): - # Because we define custom field, so we need rewrite :method: `save` - admin_user = super().save(commit=commit) - password = self.cleaned_data['password'] - private_key = self.cleaned_data['private_key_file'] - public_key = None - - if not password: - password = None - - if private_key: - public_key = ssh_pubkey_gen(private_key, password=password) - - admin_user.set_auth(password=password, public_key=public_key, private_key=private_key) - return admin_user - def clean_private_key_file(self): private_key_file = self.cleaned_data['private_key_file'] password = self.cleaned_data['password'] if private_key_file: - private_key = private_key_file.read() - if not validate_ssh_private_key(private_key, password): + key_string = private_key_file.read() + private_key_file.seek(0) + if not validate_ssh_private_key(key_string, password): raise forms.ValidationError(_('Invalid private key')) - return private_key return private_key_file - def clean(self): - super().clean() + def validate_password_key(self): password = self.cleaned_data['password'] private_key_file = self.cleaned_data.get('private_key_file', '') @@ -276,10 +259,34 @@ class AdminUserForm(forms.ModelForm): 'Password and private key file must be input one' )) + def gen_keys(self): + password = self.cleaned_data.get('password', '') or None + private_key_file = self.cleaned_data['private_key_file'] + public_key = private_key = None + + if private_key_file: + private_key = private_key_file.read().strip().decode('utf-8') + public_key = ssh_pubkey_gen(private_key=private_key, password=password) + return private_key, public_key + + +class AdminUserForm(PasswordAndKeyAuthForm): + def save(self, commit=True): + # Because we define custom field, so we need rewrite :method: `save` + admin_user = super().save(commit=commit) + password = self.cleaned_data.get('password', '') or None + private_key, public_key = super().gen_keys() + admin_user.set_auth(password=password, public_key=public_key, private_key=private_key) + return admin_user + + def clean(self): + super().clean() + if not self.instance: + super().validate_password_key() + class Meta: model = AdminUser - fields = ['name', 'username', 'password', - 'private_key_file', 'comment'] + fields = ['name', 'username', 'password', 'private_key_file', 'comment'] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), @@ -290,54 +297,35 @@ class AdminUserForm(forms.ModelForm): } -class SystemUserForm(forms.ModelForm): +class SystemUserForm(PasswordAndKeyAuthForm): # Admin user assets define, let user select, save it in form not in view auto_generate_key = forms.BooleanField(initial=True, required=False) - # Need use upload private key file except paste private key content - private_key_file = forms.FileField(required=False, label=_("Private key")) - # Form field name can not start with `_`, so redefine it, - password = forms.CharField(widget=forms.PasswordInput, required=False, - max_length=128, strip=True, label=_("Password")) def save(self, commit=True): # Because we define custom field, so we need rewrite :method: `save` system_user = super().save() - password = self.cleaned_data.get('password', None) - private_key_file = self.cleaned_data.get('private_key_file') - auto_generate_key = self.cleaned_data.get('auto_generate_key') - private_key = None - public_key = None + password = self.cleaned_data.get('password', '') or None + auto_generate_key = self.cleaned_data.get('auto_generate_key', False) + private_key, public_key = super().gen_keys() - if auto_generate_key: - logger.info('Auto set system user auth') + if not self.instance and auto_generate_key: + logger.info('Auto generate key and set system user auth') system_user.auto_gen_auth() else: - if private_key_file: - private_key = private_key_file.read().strip().decode('utf-8') - public_key = ssh_pubkey_gen(private_key=private_key) system_user.set_auth(password=password, private_key=private_key, public_key=public_key) return system_user - def clean_private_key_file(self): - if self.cleaned_data.get('private_key_file'): - key_string = self.cleaned_data['private_key_file'].read() - self.cleaned_data['private_key_file'].seek(0) - if not validate_ssh_private_key(key_string): - raise forms.ValidationError(_('Invalid private key')) - return self.cleaned_data['private_key_file'] - - def clean_password(self): - if not self.cleaned_data.get('password') and \ - not self.cleaned_data.get('private_key_file') and \ - not self.cleaned_data.get('auto_generate_key'): - raise forms.ValidationError(_('Auth info required, private_key or password')) - return self.cleaned_data['password'] + def clean(self): + super().clean() + auto_generate = self.cleaned_data.get('auto_generate_key') + if not self.instance and not auto_generate: + super().validate_password_key() class Meta: model = SystemUser fields = [ 'name', 'username', 'protocol', 'auto_generate_key', - 'private_key_file', 'password', 'auto_push', 'sudo', + 'password', 'private_key_file', 'auto_push', 'sudo', 'comment', 'shell', 'cluster', 'priority', ] widgets = { @@ -359,58 +347,6 @@ class SystemUserForm(forms.ModelForm): } -class SystemUserUpdateForm(SystemUserForm): - def save(self, commit=True): - # Because we define custom field, so we need rewrite :method: `save` - password = self.cleaned_data.get('password', None) - private_key_file = self.cleaned_data.get('private_key_file') - system_user = super(forms.ModelForm, self).save() - - if private_key_file: - private_key = private_key_file.read().strip().decode('utf-8') - public_key = ssh_pubkey_gen(private_key=private_key) - else: - private_key = public_key = None - system_user.set_auth(password=password, private_key=private_key, public_key=public_key) - return system_user - - def clean_password(self): - return self.cleaned_data['password'] - - -class SystemUserAuthForm(forms.Form): - password = forms.CharField(widget=forms.PasswordInput, required=False, max_length=128, strip=True) - private_key_file = forms.FileField(required=False) - - def clean_private_key_file(self): - if self.cleaned_data.get('private_key_file'): - key_string = self.cleaned_data['private_key_file'].read() - self.cleaned_data['private_key_file'].seek(0) - if not validate_ssh_private_key(key_string): - raise forms.ValidationError(_('Invalid private key')) - return self.cleaned_data['private_key_file'] - - def clean_password(self): - if not self.cleaned_data.get('password') and \ - not self.cleaned_data.get('private_key_file'): - msg = _('Auth info required, private_key or password') - raise forms.ValidationError(msg) - return self.cleaned_data['password'] - - def update(self, system_user): - password = self.cleaned_data.get('password') - private_key_file = self.cleaned_data.get('private_key_file') - - if private_key_file: - private_key = private_key_file.read().strip() - public_key = ssh_pubkey_gen(private_key=private_key) - else: - private_key = None - public_key = None - system_user.set_auth(password=password, private_key=private_key, public_key=public_key) - return system_user - - class FileForm(forms.Form): file = forms.FileField() diff --git a/apps/assets/templates/assets/_system_user.html b/apps/assets/templates/assets/_system_user.html index 34501ef49..4ebb64279 100644 --- a/apps/assets/templates/assets/_system_user.html +++ b/apps/assets/templates/assets/_system_user.html @@ -52,8 +52,8 @@
    - {% bootstrap_field form.private_key_file layout="horizontal" %} {% bootstrap_field form.password layout="horizontal" %} + {% bootstrap_field form.private_key_file layout="horizontal" %}
    diff --git a/apps/assets/templates/assets/system_user_update.html b/apps/assets/templates/assets/system_user_update.html index 0d2029ee8..46ef8d6a3 100644 --- a/apps/assets/templates/assets/system_user_update.html +++ b/apps/assets/templates/assets/system_user_update.html @@ -5,8 +5,8 @@ {% block auth %}

    {% trans 'Auth' %}

    - {% bootstrap_field form.private_key_file layout="horizontal" %} {% bootstrap_field form.password layout="horizontal" %} + {% bootstrap_field form.private_key_file layout="horizontal" %}
    diff --git a/apps/assets/views/system_user.py b/apps/assets/views/system_user.py index f7e7eaa35..b7744735a 100644 --- a/apps/assets/views/system_user.py +++ b/apps/assets/views/system_user.py @@ -8,7 +8,7 @@ from django.contrib.messages.views import SuccessMessageMixin from django.views.generic.detail import DetailView from common.const import create_success_msg, update_success_msg -from ..forms import SystemUserForm, SystemUserUpdateForm +from ..forms import SystemUserForm from ..models import SystemUser, Cluster from ..hands import AdminUserRequiredMixin @@ -50,7 +50,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = SystemUser - form_class = SystemUserUpdateForm + form_class = SystemUserForm template_name = 'assets/system_user_update.html' success_url = reverse_lazy('assets:system-user-list') success_message = update_success_msg From 9e09a962af79df7aa4b08e1790f700f37b5026f5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Jan 2018 18:07:51 +0800 Subject: [PATCH 086/197] =?UTF-8?q?[Feature]=20=E6=94=AF=E6=8C=81popover?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api.py | 2 +- apps/assets/serializers.py | 8 +++---- .../templates/assets/asset_group_list.html | 8 +++++-- .../templates/assets/asset_modal_list.html | 1 - .../perms/asset_permission_list.html | 23 +++++++++++++------ apps/static/css/jumpserver.css | 5 ++++ apps/static/js/jumpserver.js | 23 +++++++++++++++++-- apps/users/serializers.py | 6 ++--- .../templates/users/user_group_list.html | 8 +++++-- 9 files changed, 62 insertions(+), 22 deletions(-) diff --git a/apps/assets/api.py b/apps/assets/api.py index 317ed51c2..937670092 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -42,7 +42,7 @@ class AssetViewSet(CustomFilterMixin, LabelFilter, BulkModelViewSet): """ filter_fields = ("hostname", "ip") search_fields = filter_fields - ordering_fields = ("hostname", "ip", "port", "cluster", "type", "env", "cpu_cores") + ordering_fields = ("hostname", "ip", "port", "cluster", "cpu_cores") queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer pagination_class = LimitOffsetPagination diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index 1b40ef371..08a27b358 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -12,7 +12,7 @@ class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): """ 资产组序列化数据模型 """ - assets_amount = serializers.SerializerMethodField() + assets_display = serializers.SerializerMethodField() assets = serializers.PrimaryKeyRelatedField( many=True, queryset=Asset.objects.all(), required=False ) @@ -20,11 +20,11 @@ class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): class Meta: model = AssetGroup list_serializer_class = BulkListSerializer - fields = ['id', 'name', 'comment', 'assets_amount', 'assets'] + fields = ['id', 'name', 'comment', 'assets_display', 'assets'] @staticmethod - def get_assets_amount(obj): - return obj.assets.all().count() + def get_assets_display(obj): + return [asset.hostname for asset in obj.assets.all()] class AssetUpdateSystemUserSerializer(serializers.ModelSerializer): diff --git a/apps/assets/templates/assets/asset_group_list.html b/apps/assets/templates/assets/asset_group_list.html index 58fbbc51c..449486e58 100644 --- a/apps/assets/templates/assets/asset_group_list.html +++ b/apps/assets/templates/assets/asset_group_list.html @@ -33,14 +33,18 @@ $(document).ready(function(){ {targets: 1, createdCell: function (td, cellData, rowData) { var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); - }}, + }}, + {targets: 2, createdCell: function (td, cellData, rowData) { + var html = createPopover(cellData); + $(td).html(html); + }}, {targets: 4, createdCell: function (td, cellData, rowData) { var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData); $(td).html(update_btn + del_btn) }}], ajax_url: '{% url "api-assets:asset-group-list" %}', - columns: [{data: "id"}, {data: "name" }, {data: "assets_amount" }, {data: "comment" }, {data: "id"}], + columns: [{data: "id"}, {data: "name" }, {data: "assets_display" }, {data: "comment" }, {data: "id"}], op_html: $('#actions').html() }; jumpserver.initDataTable(options); diff --git a/apps/assets/templates/assets/asset_modal_list.html b/apps/assets/templates/assets/asset_modal_list.html index ee82d530e..ee418c435 100644 --- a/apps/assets/templates/assets/asset_modal_list.html +++ b/apps/assets/templates/assets/asset_modal_list.html @@ -29,7 +29,6 @@ {{ asset.id }} {{ asset.hostname }} {{ asset.ip }} - {{ asset.env }}-{{ asset.type }} {% endfor %} diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html index a8bdab5a1..1844d61f1 100644 --- a/apps/perms/templates/perms/asset_permission_list.html +++ b/apps/perms/templates/perms/asset_permission_list.html @@ -48,19 +48,24 @@ function initTable() { $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, {targets: 2, createdCell: function (td, cellData) { - $(td).html(cellData.length); + var html = createPopover(cellData); + $(td).html(html); }}, {targets: 3, createdCell: function (td, cellData) { - $(td).html(cellData.length); + var html = createPopover(cellData); + $(td).html(html); }}, {targets: 4, createdCell: function (td, cellData) { - $(td).html(cellData.length); + var html = createPopover(cellData); + $(td).html(html); }}, {targets: 5, createdCell: function (td, cellData) { - $(td).html(cellData.length); + var html = createPopover(cellData); + $(td).html(html); }}, {targets: 6, createdCell: function (td, cellData) { - $(td).html(cellData.length); + var html = createPopover(cellData); + $(td).html(html); }}, {targets: 7, createdCell: function (td, cellData) { if (!cellData) { @@ -85,11 +90,15 @@ function initTable() { ], op_html: $('#actions').html() }; - jumpserver.initDataTable(options); + jumpserver.initDataTable(options).on('daw', function () { + $('[data-toggle="popover"]').popover({ + html: true + }); + }); } $(document).ready(function(){ - initTable(); + initTable() }) .on('click', '.btn-del', function () { diff --git a/apps/static/css/jumpserver.css b/apps/static/css/jumpserver.css index 2a3226796..1b82ed794 100644 --- a/apps/static/css/jumpserver.css +++ b/apps/static/css/jumpserver.css @@ -340,4 +340,9 @@ div.dataTables_wrapper div.dataTables_filter { border: none; } +.popover{ + max-width: 100%; /* Max Width of the popover (depending on the container!) */ + padding-left: 20px; + padding-right: 20px; +} diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 8a0e97605..7fac3d1d3 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -310,10 +310,15 @@ jumpserver.initDataTable = function (options) { 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(){ + }).on('draw', function(){ $('#op').html(options.op_html || ''); $('#uc').html(options.uc_html || ''); + $('[data-toggle="popover"]').popover({ + html: true, + placement: 'bottom', + trigger: 'hover', + container: 'body' + }); }); $('.ipt_check_all').on('click', function() { if (!jumpserver.checked) { @@ -512,3 +517,17 @@ function delCookie(key) { document.cookie = key + '=' + val + ";expires" + expires.toUTCString() + ';path=/'; } } + +function createPopover(dataset, title, callback) { + if (callback !== undefined){ + var new_dataset = []; + $.each(dataset, function (index, value) { + new_dataset.push(callback(value)) + }); + dataset = new_dataset; + } + var data_content = dataset.join("
    "); + + var html = "" + dataset.length + ""; + return html; +} \ No newline at end of file diff --git a/apps/users/serializers.py b/apps/users/serializers.py index ba88438bf..e72f97407 100644 --- a/apps/users/serializers.py +++ b/apps/users/serializers.py @@ -52,7 +52,7 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer): class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): - user_amount = serializers.SerializerMethodField() + users = serializers.SerializerMethodField() class Meta: model = UserGroup @@ -60,8 +60,8 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): fields = '__all__' @staticmethod - def get_user_amount(obj): - return obj.users.count() + def get_users(obj): + return [user.name for user in obj.users.all()] class UserGroupUpdateMemeberSerializer(serializers.ModelSerializer): diff --git a/apps/users/templates/users/user_group_list.html b/apps/users/templates/users/user_group_list.html index bc909ceb4..48d4a2711 100644 --- a/apps/users/templates/users/user_group_list.html +++ b/apps/users/templates/users/user_group_list.html @@ -10,7 +10,7 @@ {% trans 'Name' %} - {% trans 'User count' %} + {% trans 'User' %} {% trans 'Comment' %} {% trans 'Action' %} @@ -31,6 +31,10 @@ $(document).ready(function() { var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, + {targets: 2, createdCell: function (td, cellData, rowData) { + var html = createPopover(cellData); + $(td).html(html); + }}, {targets: 3, createdCell: function (td, cellData) { var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData; $(td).html('' + innerHtml + ''); @@ -49,7 +53,7 @@ $(document).ready(function() { }} ], ajax_url: '{% url "api-users:user-group-list" %}', - columns: [{data: function(){return ""}}, {data: "name" }, {data: "user_amount"}, + columns: [{data: function(){return ""}}, {data: "name" }, {data: "users"}, {data: "comment"}, {data: "id" }], order: [], op_html: $('#actions').html() From 460fa8e8a9dc710ff09cd4a561902069681ca423 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Jan 2018 19:57:47 +0800 Subject: [PATCH 087/197] [Feature] tree --- apps/assets/models/__init__.py | 1 + apps/assets/models/asset.py | 1 + apps/assets/models/tree.py | 58 ++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 apps/assets/models/tree.py diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index 6e0621ba8..b4b0b8707 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -5,5 +5,6 @@ from .user import AdminUser, SystemUser from .label import Label from .cluster import * from .group import * +from .tree import * from .asset import * from .utils import * diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 3677b04b5..a9b371637 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -36,6 +36,7 @@ class Asset(models.Model): port = models.IntegerField(default=22, verbose_name=_('Port')) groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups')) cluster = models.ForeignKey(Cluster, related_name='assets', default=default_cluster, on_delete=models.SET_DEFAULT, verbose_name=_('Cluster')) + nodes = models.ManyToManyField('assets.Node', blank=True, related_name='assets', verbose_name=_("Nodes")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) # Auth diff --git a/apps/assets/models/tree.py b/apps/assets/models/tree.py new file mode 100644 index 000000000..74268524c --- /dev/null +++ b/apps/assets/models/tree.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +__all__ = ['Node'] + + +class Node(models.Model): + id = models.CharField(primary_key=True, max_length=64) # '1:1:1:1' + name = models.CharField(max_length=128, verbose_name=_("Name")) + child_mark = models.IntegerField(default=0) + date_create = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.name + + @property + def level(self): + return len(self.id.split(':')) + + def get_next_child_id(self): + mark = self.child_mark + self.child_mark += 1 + self.save() + return "{}:{}".format(self.id, mark) + + def create_child(self, name): + child_id = self.get_next_child_id() + child = self.__class__.objects.create(id=child_id, name=name) + return child + + def get_children(self): + return self.__class__.objects.filter(id__regex=r'{}:[0-9]+$'.format(self.id)) + + def get_all_children(self): + return self.__class__.objects.filter(id__startswith='{}:'.format(self.id)) + + def get_assets(self): + from .asset import Asset + children = self.get_children() + assets = Asset.objects.filter(nodes__in=children) + return assets + + def get_all_assets(self): + from .asset import Asset + children = self.get_all_children() + assets = Asset.objects.filter(nodes__in=children) + return assets + + @classmethod + def root(cls): + obj, created = cls.objects.get_or_create( + id='0', defaults={"id": '0', 'name': "ROOT"} + ) + return obj From 3603b33a42be7e0806b28e6745ff872bbc23089f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 31 Jan 2018 10:46:26 +0800 Subject: [PATCH 088/197] =?UTF-8?q?[Feature]=20=E6=B7=BB=E5=8A=A0=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=A0=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api.py | 23 ++- apps/assets/models/tree.py | 2 +- apps/assets/templates/assets/tree.html | 166 ++++++++++++++++++ apps/assets/urls/api_urls.py | 1 + apps/assets/urls/views_urls.py | 2 + apps/assets/views/__init__.py | 1 + apps/assets/views/tree.py | 23 +++ apps/static/css/jumpserver.css | 40 ++++- apps/static/js/jumpserver.js | 2 +- apps/static/js/plugins/echarts/chart/tree.js | 2 +- .../js/plugins/echarts/chart/treemap.js | 2 +- apps/static/js/plugins/echarts/echarts-all.js | 4 +- .../plugins/highcharts/modules/heatmap.src.js | 2 +- 13 files changed, 258 insertions(+), 12 deletions(-) create mode 100644 apps/assets/templates/assets/tree.html create mode 100644 apps/assets/views/tree.py diff --git a/apps/assets/api.py b/apps/assets/api.py index 937670092..c61bf1e83 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -14,6 +14,7 @@ # limitations under the License. from rest_framework import generics +from rest_framework.views import APIView from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView @@ -25,7 +26,7 @@ from common.mixins import CustomFilterMixin from common.utils import get_logger from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \ get_user_granted_assets -from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser, Label +from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser, Label, Node from . import serializers from .tasks import update_asset_hardware_info_manual, test_admin_user_connectability_manual, \ test_asset_connectability_manual, push_system_user_to_cluster_assets_manual, \ @@ -308,3 +309,23 @@ class LabelViewSet(BulkModelViewSet): self.serializer_class = serializers.LabelDistinctSerializer self.queryset = self.queryset.values("name").distinct() return super().list(request, *args, **kwargs) + + +class TreeViewApi(APIView): + + def get_queryset(self): + return Node.objects.all() + + def get(self, request): + data = [] + for node in self.get_queryset(): + if node.id == "0": + parent = "#" + else: + parent = ":".join(node.id.split(":")[:-1]) + data.append({ + "id": node.id, + "parent": parent, + "text": node.name + }) + return Response(data) \ No newline at end of file diff --git a/apps/assets/models/tree.py b/apps/assets/models/tree.py index 74268524c..2b070eb7a 100644 --- a/apps/assets/models/tree.py +++ b/apps/assets/models/tree.py @@ -51,7 +51,7 @@ class Node(models.Model): return assets @classmethod - def root(cls): + def get_root_node(cls): obj, created = cls.objects.get_or_create( id='0', defaults={"id": '0', 'name': "ROOT"} ) diff --git a/apps/assets/templates/assets/tree.html b/apps/assets/templates/assets/tree.html new file mode 100644 index 000000000..acca9462c --- /dev/null +++ b/apps/assets/templates/assets/tree.html @@ -0,0 +1,166 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + +{% endblock %} + +{% block content %} +
    +
    +
    +
    +
    +
    +
    Tree View
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + + +
    + + + + + + + + + + + + + + + + +
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Cluster' %}{% trans 'Hardware' %}{% trans 'Active' %}{% trans 'Reachable' %}{% trans 'Action' %}
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +{% endblock %} + +{% block custom_foot_js %} + + + + +{% endblock %} \ No newline at end of file diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 8c7b77dc7..03db4ebcf 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -42,6 +42,7 @@ urlpatterns = [ api.SystemUserPushApi.as_view(), name='system-user-push'), url(r'^v1/system-user/(?P[0-9a-zA-Z\-]{36})/connective/$', api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'), + url(r'^v1/tree/$', api.TreeViewApi.as_view(), name='tree-view') ] urlpatterns += router.urls diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index 48d01dafd..43d25300e 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -57,5 +57,7 @@ urlpatterns = [ url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'), url(r'^label/(?P[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'), url(r'^label/(?P[0-9a-zA-Z\-]{36})/delete/$', views.LabelDeleteView.as_view(), name='label-delete'), + + url(r'^tree/$', views.TreeView.as_view(), name='tree-view'), ] diff --git a/apps/assets/views/__init__.py b/apps/assets/views/__init__.py index 112a46d0d..597de79e9 100644 --- a/apps/assets/views/__init__.py +++ b/apps/assets/views/__init__.py @@ -5,4 +5,5 @@ from .cluster import * from .system_user import * from .admin_user import * from .label import * +from .tree import * diff --git a/apps/assets/views/tree.py b/apps/assets/views/tree.py new file mode 100644 index 000000000..60213e85a --- /dev/null +++ b/apps/assets/views/tree.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# + +from django.views.generic import TemplateView +from django.utils.translation import ugettext_lazy as _ + +from common.mixins import AdminUserRequiredMixin + + +__all__ = ['TreeView'] + + +class TreeView(AdminUserRequiredMixin, TemplateView): + template_name = 'assets/tree.html' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('TreeView view'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + diff --git a/apps/static/css/jumpserver.css b/apps/static/css/jumpserver.css index 1b82ed794..a29dfb257 100644 --- a/apps/static/css/jumpserver.css +++ b/apps/static/css/jumpserver.css @@ -340,9 +340,41 @@ div.dataTables_wrapper div.dataTables_filter { border: none; } -.popover{ - max-width: 100%; /* Max Width of the popover (depending on the container!) */ - padding-left: 20px; - padding-right: 20px; +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} + +.popover{ + padding: 9px 14px; + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 1.42857143; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ccc; + border-radius: 6px; + box-shadow: 0 5px 10px rgba(0,0,0,.2); + line-break: auto; } diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 7fac3d1d3..5f0b148a0 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -316,7 +316,7 @@ jumpserver.initDataTable = function (options) { $('[data-toggle="popover"]').popover({ html: true, placement: 'bottom', - trigger: 'hover', + // trigger: 'hover', container: 'body' }); }); diff --git a/apps/static/js/plugins/echarts/chart/tree.js b/apps/static/js/plugins/echarts/chart/tree.js index b2560fc74..ee4fb662c 100644 --- a/apps/static/js/plugins/echarts/chart/tree.js +++ b/apps/static/js/plugins/echarts/chart/tree.js @@ -1 +1 @@ -define("echarts/chart/tree",["require","./base","../util/shape/Icon","zrender/shape/Image","zrender/shape/Line","zrender/shape/BezierCurve","../layout/Tree","../data/Tree","../config","../util/ecData","zrender/config","zrender/tool/event","zrender/tool/util","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o),this.refresh(a)}var i=e("./base"),n=.618,a=e("../util/shape/Icon"),o=e("zrender/shape/Image"),r=e("zrender/shape/Line"),s=e("zrender/shape/BezierCurve"),l=e("../layout/Tree"),h=e("../data/Tree"),m=e("../config");m.tree={zlevel:1,z:2,calculable:!1,clickable:!0,rootLocation:{},orient:"vertical",symbol:"circle",symbolSize:20,nodePadding:30,layerPadding:100,itemStyle:{normal:{label:{show:!0},lineStyle:{width:1,color:"#777",type:"curve"}},emphasis:{}}};var V=e("../util/ecData"),U=(e("zrender/config"),e("zrender/tool/event"),e("zrender/tool/util"));return t.prototype={type:m.CHART_TYPE_TREE,_buildShape:function(e,t){var i=e.data[0];this.tree=h.fromOptionData(i.name,i.children),this.tree.root.data=i,this._setTreeShape(e),this.tree.traverse(function(i){this._buildItem(i,e,t),i.children.length>0&&this._buildLink(i,e)},this);var n=e.roam===!0||"move"===e.roam,a=e.roam===!0||"scale"===e.roam;this.zr.modLayer(this.getZlevelBase(),{panable:n,zoomable:a}),(this.query("markPoint.effect.show")||this.query("markLine.effect.show"))&&this.zr.modLayer(m.EFFECT_ZLEVEL,{panable:n,zoomable:a}),this.addShapeList()},_buildItem:function(e,t,i){var n=[e.data,t],r=this.deepQuery(n,"symbol"),s=this.deepMerge(n,"itemStyle.normal")||{},l=this.deepMerge(n,"itemStyle.emphasis")||{},h=s.color||this.zr.getColor(),m=l.color||this.zr.getColor(),U=-e.layout.angle||0;e.id===this.tree.root.id&&(U=0);var d="right";Math.abs(U)>=Math.PI/2&&Math.abs(U)<3*Math.PI/2&&(U+=Math.PI,d="left");var p=[U,e.layout.position[0],e.layout.position[1]],c=new a({zlevel:this.getZlevelBase(),z:this.getZBase()+1,rotation:p,clickable:this.deepQuery(n,"clickable"),style:{x:e.layout.position[0]-.5*e.layout.width,y:e.layout.position[1]-.5*e.layout.height,width:e.layout.width,height:e.layout.height,iconType:r,color:h,brushType:"both",lineWidth:s.borderWidth,strokeColor:s.borderColor},highlightStyle:{color:m,lineWidth:l.borderWidth,strokeColor:l.borderColor}});c.style.iconType.match("image")&&(c.style.image=c.style.iconType.replace(new RegExp("^image:\\/\\/"),""),c=new o({rotation:p,style:c.style,highlightStyle:c.highlightStyle,clickable:c.clickable,zlevel:this.getZlevelBase(),z:this.getZBase()})),this.deepQuery(n,"itemStyle.normal.label.show")&&(c.style.text=null==e.data.label?e.id:e.data.label,c.style.textPosition=this.deepQuery(n,"itemStyle.normal.label.position"),"radial"===t.orient&&"inside"!==c.style.textPosition&&(c.style.textPosition=d),c.style.textColor=this.deepQuery(n,"itemStyle.normal.label.textStyle.color"),c.style.textFont=this.getFont(this.deepQuery(n,"itemStyle.normal.label.textStyle")||{})),this.deepQuery(n,"itemStyle.emphasis.label.show")&&(c.highlightStyle.textPosition=this.deepQuery(n,"itemStyle.emphasis.label.position"),c.highlightStyle.textColor=this.deepQuery(n,"itemStyle.emphasis.label.textStyle.color"),c.highlightStyle.textFont=this.getFont(this.deepQuery(n,"itemStyle.emphasis.label.textStyle")||{})),V.pack(c,t,i,e.data,0,e.id),this.shapeList.push(c)},_buildLink:function(e,t){var i=t.itemStyle.normal.lineStyle;if("broken"===i.type)return void this._buildBrokenLine(e,i,t);for(var n=0;nr&&(t=r),r>n&&(n=r)}e.layout.position[0]=e.children.length>0?(t+n)/2:0;var s=this._layerOffsets[e.depth]||0;if(s>e.layout.position[0]){var l=s-e.layout.position[0];this._shiftSubtree(e,l);for(var a=e.depth+1;a=0&&(t.splice(i,1),e.parent=null)},t.prototype.traverse=function(e,t){e.call(t,this);for(var i=0;it&&(t=n.height)}this.height=t+1},t.prototype.getNodeById=function(e){if(this.id===e)return this;for(var t=0;t0&&this._buildLink(i,e)},this);var n=e.roam===!0||"move"===e.roam,a=e.roam===!0||"scale"===e.roam;this.zr.modLayer(this.getZlevelBase(),{panable:n,zoomable:a}),(this.query("markPoint.effect.show")||this.query("markLine.effect.show"))&&this.zr.modLayer(m.EFFECT_ZLEVEL,{panable:n,zoomable:a}),this.addShapeList()},_buildItem:function(e,t,i){var n=[e.data,t],r=this.deepQuery(n,"symbol"),s=this.deepMerge(n,"itemStyle.normal")||{},l=this.deepMerge(n,"itemStyle.emphasis")||{},h=s.color||this.zr.getColor(),m=l.color||this.zr.getColor(),U=-e.layout.angle||0;e.id===this.tree.root.id&&(U=0);var d="right";Math.abs(U)>=Math.PI/2&&Math.abs(U)<3*Math.PI/2&&(U+=Math.PI,d="left");var p=[U,e.layout.position[0],e.layout.position[1]],c=new a({zlevel:this.getZlevelBase(),z:this.getZBase()+1,rotation:p,clickable:this.deepQuery(n,"clickable"),style:{x:e.layout.position[0]-.5*e.layout.width,y:e.layout.position[1]-.5*e.layout.height,width:e.layout.width,height:e.layout.height,iconType:r,color:h,brushType:"both",lineWidth:s.borderWidth,strokeColor:s.borderColor},highlightStyle:{color:m,lineWidth:l.borderWidth,strokeColor:l.borderColor}});c.style.iconType.match("image")&&(c.style.image=c.style.iconType.replace(new RegExp("^image:\\/\\/"),""),c=new o({rotation:p,style:c.style,highlightStyle:c.highlightStyle,clickable:c.clickable,zlevel:this.getZlevelBase(),z:this.getZBase()})),this.deepQuery(n,"itemStyle.normal.label.show")&&(c.style.text=null==e.data.label?e.id:e.data.label,c.style.textPosition=this.deepQuery(n,"itemStyle.normal.label.position"),"radial"===t.orient&&"inside"!==c.style.textPosition&&(c.style.textPosition=d),c.style.textColor=this.deepQuery(n,"itemStyle.normal.label.textStyle.color"),c.style.textFont=this.getFont(this.deepQuery(n,"itemStyle.normal.label.textStyle")||{})),this.deepQuery(n,"itemStyle.emphasis.label.show")&&(c.highlightStyle.textPosition=this.deepQuery(n,"itemStyle.emphasis.label.position"),c.highlightStyle.textColor=this.deepQuery(n,"itemStyle.emphasis.label.textStyle.color"),c.highlightStyle.textFont=this.getFont(this.deepQuery(n,"itemStyle.emphasis.label.textStyle")||{})),V.pack(c,t,i,e.data,0,e.id),this.shapeList.push(c)},_buildLink:function(e,t){var i=t.itemStyle.normal.lineStyle;if("broken"===i.type)return void this._buildBrokenLine(e,i,t);for(var n=0;nr&&(t=r),r>n&&(n=r)}e.layout.position[0]=e.children.length>0?(t+n)/2:0;var s=this._layerOffsets[e.depth]||0;if(s>e.layout.position[0]){var l=s-e.layout.position[0];this._shiftSubtree(e,l);for(var a=e.depth+1;a=0&&(t.splice(i,1),e.parent=null)},t.prototype.traverse=function(e,t){e.call(t,this);for(var i=0;it&&(t=n.height)}this.height=t+1},t.prototype.getNodeById=function(e){if(this.id===e)return this;for(var t=0;tt.width||e.normal.label.y+U>t.height)&&(h=""):h="",e.emphasis.label.show?(s.x+u>t.width||s.y+y>t.height)&&(p=""):p="";var g={style:{textX:t.x+e.normal.label.x,textY:t.y+e.normal.label.y,text:h,textPosition:"specific",textColor:o.color,textFont:m},highlightStyle:{textX:t.x+e.emphasis.label.x,textY:t.y+e.emphasis.label.y,text:p,textColor:s.color,textPosition:"specific"}};return g},getLabelText:function(e,t,i){return i?"function"==typeof i?i.call(this.myChart,e,t):"string"==typeof i?(i=i.replace("{b}","{b0}").replace("{c}","{c0}"),i=i.replace("{b0}",e).replace("{c0}",t)):void 0:e},_buildChildrenTreemap:function(e,t,i,n){for(var a=i.width*i.height,o=0,r=[],l=0;l ":"")},V),clickable:!0,highlightStyle:p});m.set(u,"seriesIndex",t),m.set(u,"name",a[c]),i+=u.getRect(u.style).width,this.shapeList.push(u)}},__onclick:function(e){var t=e.target;if(t){var i=m.get(t,"seriesIndex"),n=m.get(t,"name"),a=this._treesMap[i],o=a.getNodeById(n);o&&o.children.length&&this._buildTreemap(o,i)}}},U.inherits(t,i),e("../chart").define("treemap",t),t}),define("echarts/layout/TreeMap",["require"],function(){function e(e){({x:e.x,y:e.y,width:e.width,height:e.height});this.x=e.x,this.y=e.y,this.width=e.width,this.height=e.height}return e.prototype.run=function(e){var t=[];return this._squarify(e,{x:this.x,y:this.y,width:this.width,height:this.height},t),t},e.prototype._squarify=function(e,t,i){var n="VERTICAL",a=t.width,o=t.height;t.widthl;l++)r[s].y+=r[l].height}var h={};if("VERTICAL"==n){for(var m=0;ml;l++){var h=i*e[l]/o;a.push({width:s,height:h})}return a},e.prototype._isFirstBetter=function(e,t){var i=e[0].height/e[0].width;i=i>1?1/i:i;var n=t[0].height/t[0].width;return n=n>1?1/n:n,Math.abs(i-1)<=Math.abs(n-1)?!0:!1},e}),define("echarts/data/Tree",["require","zrender/tool/util"],function(e){function t(e,t){this.id=e,this.depth=0,this.height=0,this.children=[],this.parent=null,this.data=t||null}function i(e){this.root=new t(e)}var n=e("zrender/tool/util");return t.prototype.add=function(e){var t=this.children;e.parent!==this&&(t.push(e),e.parent=this)},t.prototype.remove=function(e){var t=this.children,i=n.indexOf(t,e);i>=0&&(t.splice(i,1),e.parent=null)},t.prototype.traverse=function(e,t){e.call(t,this);for(var i=0;it&&(t=n.height)}this.height=t+1},t.prototype.getNodeById=function(e){if(this.id===e)return this;for(var t=0;tt.width||e.normal.label.y+U>t.height)&&(h=""):h="",e.emphasis.label.show?(s.x+u>t.width||s.y+y>t.height)&&(p=""):p="";var g={style:{textX:t.x+e.normal.label.x,textY:t.y+e.normal.label.y,text:h,textPosition:"specific",textColor:o.color,textFont:m},highlightStyle:{textX:t.x+e.emphasis.label.x,textY:t.y+e.emphasis.label.y,text:p,textColor:s.color,textPosition:"specific"}};return g},getLabelText:function(e,t,i){return i?"function"==typeof i?i.call(this.myChart,e,t):"string"==typeof i?(i=i.replace("{b}","{b0}").replace("{c}","{c0}"),i=i.replace("{b0}",e).replace("{c0}",t)):void 0:e},_buildChildrenTreemap:function(e,t,i,n){for(var a=i.width*i.height,o=0,r=[],l=0;l ":"")},V),clickable:!0,highlightStyle:p});m.set(u,"seriesIndex",t),m.set(u,"name",a[c]),i+=u.getRect(u.style).width,this.shapeList.push(u)}},__onclick:function(e){var t=e.target;if(t){var i=m.get(t,"seriesIndex"),n=m.get(t,"name"),a=this._treesMap[i],o=a.getNodeById(n);o&&o.children.length&&this._buildTreemap(o,i)}}},U.inherits(t,i),e("../chart").define("treemap",t),t}),define("echarts/layout/TreeMap",["require"],function(){function e(e){({x:e.x,y:e.y,width:e.width,height:e.height});this.x=e.x,this.y=e.y,this.width=e.width,this.height=e.height}return e.prototype.run=function(e){var t=[];return this._squarify(e,{x:this.x,y:this.y,width:this.width,height:this.height},t),t},e.prototype._squarify=function(e,t,i){var n="VERTICAL",a=t.width,o=t.height;t.widthl;l++)r[s].y+=r[l].height}var h={};if("VERTICAL"==n){for(var m=0;ml;l++){var h=i*e[l]/o;a.push({width:s,height:h})}return a},e.prototype._isFirstBetter=function(e,t){var i=e[0].height/e[0].width;i=i>1?1/i:i;var n=t[0].height/t[0].width;return n=n>1?1/n:n,Math.abs(i-1)<=Math.abs(n-1)?!0:!1},e}),define("echarts/data/Tree",["require","zrender/tool/util"],function(e){function t(e,t){this.id=e,this.depth=0,this.height=0,this.children=[],this.parent=null,this.data=t||null}function i(e){this.root=new t(e)}var n=e("zrender/tool/util");return t.prototype.add=function(e){var t=this.children;e.parent!==this&&(t.push(e),e.parent=this)},t.prototype.remove=function(e){var t=this.children,i=n.indexOf(t,e);i>=0&&(t.splice(i,1),e.parent=null)},t.prototype.traverse=function(e,t){e.call(t,this);for(var i=0;it&&(t=n.height)}this.height=t+1},t.prototype.getNodeById=function(e){if(this.id===e)return this;for(var t=0;tt;t++)e[t].type===h.CHART_TYPE_GAUGE&&(this.selectedMap[e[t].name]=!0,e[t]=this.reformOption(e[t]),this.legendHoverLink=e[t].legendHoverLink||this.legendHoverLink,this._buildSingleGauge(t),this.buildMark(t));this.addShapeList()},_buildSingleGauge:function(e){var t=this.series[e];this._paramsMap[e]={center:this.parseCenter(this.zr,t.center),radius:this.parseRadius(this.zr,t.radius),startAngle:t.startAngle.toFixed(2)-0,endAngle:t.endAngle.toFixed(2)-0},this._paramsMap[e].totalAngle=this._paramsMap[e].startAngle-this._paramsMap[e].endAngle,this._colorMap(e),this._buildAxisLine(e),this._buildSplitLine(e),this._buildAxisTick(e),this._buildAxisLabel(e),this._buildPointer(e),this._buildTitle(e),this._buildDetail(e)},_buildAxisLine:function(e){var t=this.series[e];if(t.axisLine.show)for(var i,n,a=t.min,o=t.max-a,r=this._paramsMap[e],s=r.center,l=r.startAngle,h=r.totalAngle,V=r.colorArray,U=t.axisLine.lineStyle,d=this.parsePercent(U.width,r.radius[1]),p=r.radius[1],c=p-d,u=l,y=0,g=V.length;g>y;y++)n=l-h*(V[y][0]-a)/o,i=this._getSector(s,c,p,n,u,V[y][1],U,t.zlevel,t.z),u=n,i._animationAdd="r",m.set(i,"seriesIndex",e),m.set(i,"dataIndex",y),this.shapeList.push(i)},_buildSplitLine:function(e){var t=this.series[e];if(t.splitLine.show)for(var i,n,a,r=this._paramsMap[e],s=t.splitNumber,l=t.min,h=t.max-l,m=t.splitLine,V=this.parsePercent(m.length,r.radius[1]),U=m.lineStyle,d=U.color,p=r.center,c=r.startAngle*Math.PI/180,u=r.totalAngle*Math.PI/180,y=r.radius[1],g=y-V,b=0;s>=b;b++)i=c-u/s*b,n=Math.sin(i),a=Math.cos(i),this.shapeList.push(new o({zlevel:t.zlevel,z:t.z+1,hoverable:!1,style:{xStart:p[0]+a*y,yStart:p[1]-n*y,xEnd:p[0]+a*g,yEnd:p[1]-n*g,strokeColor:"auto"===d?this._getColor(e,l+h/s*b):d,lineType:U.type,lineWidth:U.width,shadowColor:U.shadowColor,shadowBlur:U.shadowBlur,shadowOffsetX:U.shadowOffsetX,shadowOffsetY:U.shadowOffsetY}}))},_buildAxisTick:function(e){var t=this.series[e];if(t.axisTick.show)for(var i,n,a,r=this._paramsMap[e],s=t.splitNumber,l=t.min,h=t.max-l,m=t.axisTick,V=m.splitNumber,U=this.parsePercent(m.length,r.radius[1]),d=m.lineStyle,p=d.color,c=r.center,u=r.startAngle*Math.PI/180,y=r.totalAngle*Math.PI/180,g=r.radius[1],b=g-U,f=0,k=s*V;k>=f;f++)f%V!==0&&(i=u-y/k*f,n=Math.sin(i),a=Math.cos(i),this.shapeList.push(new o({zlevel:t.zlevel,z:t.z+1,hoverable:!1,style:{xStart:c[0]+a*g,yStart:c[1]-n*g,xEnd:c[0]+a*b,yEnd:c[1]-n*b,strokeColor:"auto"===p?this._getColor(e,l+h/k*f):p,lineType:d.type,lineWidth:d.width,shadowColor:d.shadowColor,shadowBlur:d.shadowBlur,shadowOffsetX:d.shadowOffsetX,shadowOffsetY:d.shadowOffsetY}})))},_buildAxisLabel:function(e){var t=this.series[e];if(t.axisLabel.show)for(var i,n,o,r,s=t.splitNumber,l=t.min,h=t.max-l,m=t.axisLabel.textStyle,U=this.getFont(m),d=m.color,p=this._paramsMap[e],c=p.center,u=p.startAngle,y=p.totalAngle,g=p.radius[1]-this.parsePercent(t.splitLine.length,p.radius[1])-5,b=0;s>=b;b++)r=V.accAdd(l,V.accMul(V.accDiv(h,s),b)),i=u-y/s*b,n=Math.sin(i*Math.PI/180),o=Math.cos(i*Math.PI/180),i=(i+360)%360,this.shapeList.push(new a({zlevel:t.zlevel,z:t.z+1,hoverable:!1,style:{x:c[0]+o*g,y:c[1]-n*g,color:"auto"===d?this._getColor(e,r):d,text:this._getLabelText(t.axisLabel.formatter,r),textAlign:i>=110&&250>=i?"left":70>=i||i>=290?"right":"center",textBaseline:i>=10&&170>=i?"top":i>=190&&350>=i?"bottom":"middle",textFont:U,shadowColor:m.shadowColor,shadowBlur:m.shadowBlur,shadowOffsetX:m.shadowOffsetX,shadowOffsetY:m.shadowOffsetY}}))},_buildPointer:function(e){var t=this.series[e];if(t.pointer.show){var i=t.max-t.min,a=t.pointer,o=this._paramsMap[e],r=this.parsePercent(a.length,o.radius[1]),l=this.parsePercent(a.width,o.radius[1]),h=o.center,V=this._getValue(e);V=V2?2:l/2,color:"#fff"}});m.pack(p,this.series[e],e,this.series[e].data[0],0,this.series[e].data[0].name,V),this.shapeList.push(p),this.shapeList.push(new s({zlevel:t.zlevel,z:t.z+2,hoverable:!1,style:{x:h[0],y:h[1],r:a.width/2.5,color:"#fff"}}))}},_buildTitle:function(e){var t=this.series[e];if(t.title.show){var i=t.data[0],n=null!=i.name?i.name:"";if(""!==n){var o=t.title,r=o.offsetCenter,s=o.textStyle,l=s.color,h=this._paramsMap[e],m=h.center[0]+this.parsePercent(r[0],h.radius[1]),V=h.center[1]+this.parsePercent(r[1],h.radius[1]);this.shapeList.push(new a({zlevel:t.zlevel,z:t.z+(Math.abs(m-h.center[0])+Math.abs(V-h.center[1])<2*s.fontSize?2:1),hoverable:!1,style:{x:m,y:V,color:"auto"===l?this._getColor(e):l,text:n,textAlign:"center",textFont:this.getFont(s),shadowColor:s.shadowColor,shadowBlur:s.shadowBlur,shadowOffsetX:s.shadowOffsetX,shadowOffsetY:s.shadowOffsetY}}))}}},_buildDetail:function(e){var t=this.series[e];if(t.detail.show){var i=t.detail,n=i.offsetCenter,a=i.backgroundColor,o=i.textStyle,s=o.color,l=this._paramsMap[e],h=this._getValue(e),m=l.center[0]-i.width/2+this.parsePercent(n[0],l.radius[1]),V=l.center[1]+this.parsePercent(n[1],l.radius[1]);this.shapeList.push(new r({zlevel:t.zlevel,z:t.z+(Math.abs(m+i.width/2-l.center[0])+Math.abs(V+i.height/2-l.center[1])r;r++)o.push([a[r][0]*n+i,a[r][1]]);this._paramsMap[e].colorArray=o},_getColor:function(e,t){null==t&&(t=this._getValue(e));for(var i=this._paramsMap[e].colorArray,n=0,a=i.length;a>n;n++)if(i[n][0]>=t)return i[n][1];return i[i.length-1][1]},_getSector:function(e,t,i,n,a,o,r,s,h){return new l({zlevel:s,z:h,hoverable:!1,style:{x:e[0],y:e[1],r0:t,r:i,startAngle:n,endAngle:a,brushType:"fill",color:o,shadowColor:r.shadowColor,shadowBlur:r.shadowBlur,shadowOffsetX:r.shadowOffsetX,shadowOffsetY:r.shadowOffsetY}})},_getLabelText:function(e,t){if(e){if("function"==typeof e)return e.call(this.myChart,t);if("string"==typeof e)return e.replace("{value}",t)}return t},refresh:function(e){e&&(this.option=e,this.series=e.series),this.backupShapeList(),this._buildShape()}},U.inherits(t,i),e("../chart").define("gauge",t),t}),i("echarts/util/shape/GaugePointer",["require","zrender/shape/Base","zrender/tool/util","./normalIsCover"],function(e){function t(e){i.call(this,e)}var i=e("zrender/shape/Base"),n=e("zrender/tool/util");return t.prototype={type:"gauge-pointer",buildPath:function(e,t){var i=t.r,n=t.width,a=t.angle,o=t.x-Math.cos(a)*n*(n>=i/3?1:2),r=t.y+Math.sin(a)*n*(n>=i/3?1:2);a=t.angle-Math.PI/2,e.moveTo(o,r),e.lineTo(t.x+Math.cos(a)*n,t.y-Math.sin(a)*n),e.lineTo(t.x+Math.cos(t.angle)*i,t.y-Math.sin(t.angle)*i),e.lineTo(t.x-Math.cos(a)*n,t.y+Math.sin(a)*n),e.lineTo(o,r)},getRect:function(e){if(e.__rect)return e.__rect;var t=2*e.width,i=e.x,n=e.y,a=i+Math.cos(e.angle)*e.r,o=n-Math.sin(e.angle)*e.r;return e.__rect={x:Math.min(i,a)-t,y:Math.min(n,o)-t,width:Math.abs(i-a)+t,height:Math.abs(n-o)+t},e.__rect},isCover:e("./normalIsCover")},n.inherits(t,i),t}),i("echarts/chart/funnel",["require","./base","zrender/shape/Text","zrender/shape/Line","zrender/shape/Polygon","../config","../util/ecData","../util/number","zrender/tool/util","zrender/tool/color","zrender/tool/area","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o),this.refresh(a)}var i=e("./base"),n=e("zrender/shape/Text"),a=e("zrender/shape/Line"),o=e("zrender/shape/Polygon"),r=e("../config");r.funnel={zlevel:0,z:2,clickable:!0,legendHoverLink:!0,x:80,y:60,x2:80,y2:60,min:0,max:100,minSize:"0%",maxSize:"100%",sort:"descending",gap:0,funnelAlign:"center",itemStyle:{normal:{borderColor:"#fff",borderWidth:1,label:{show:!0,position:"outer"},labelLine:{show:!0,length:10,lineStyle:{width:1,type:"solid"}}},emphasis:{borderColor:"rgba(0,0,0,0)",borderWidth:1,label:{show:!0},labelLine:{show:!0}}}};var s=e("../util/ecData"),l=e("../util/number"),h=e("zrender/tool/util"),m=e("zrender/tool/color"),V=e("zrender/tool/area");return t.prototype={type:r.CHART_TYPE_FUNNEL,_buildShape:function(){var e=this.series,t=this.component.legend;this._paramsMap={},this._selected={},this.selectedMap={};for(var i,n=0,a=e.length;a>n;n++)if(e[n].type===r.CHART_TYPE_FUNNEL){if(e[n]=this.reformOption(e[n]),this.legendHoverLink=e[n].legendHoverLink||this.legendHoverLink,i=e[n].name||"",this.selectedMap[i]=t?t.isSelected(i):!0,!this.selectedMap[i])continue;this._buildSingleFunnel(n),this.buildMark(n)}this.addShapeList()},_buildSingleFunnel:function(e){var t=this.component.legend,i=this.series[e],n=this._mapData(e),a=this._getLocation(e);this._paramsMap[e]={location:a,data:n};for(var o,r=0,s=[],h=0,m=n.length;m>h;h++)o=n[h].name,this.selectedMap[o]=t?t.isSelected(o):!0,this.selectedMap[o]&&!isNaN(n[h].value)&&(s.push(n[h]),r++);if(0!==r){for(var V,U,d,p,c=this._buildFunnelCase(e),u=i.funnelAlign,y=i.gap,g=r>1?(a.height-(r-1)*y)/r:a.height,b=a.y,f="descending"===i.sort?this._getItemWidth(e,s[0].value):l.parsePercent(i.minSize,a.width),k="descending"===i.sort?1:0,x=a.centerX,_=[],h=0,m=s.length;m>h;h++)if(o=s[h].name,this.selectedMap[o]&&!isNaN(s[h].value)){switch(V=m-2>=h?this._getItemWidth(e,s[h+k].value):"descending"===i.sort?l.parsePercent(i.minSize,a.width):l.parsePercent(i.maxSize,a.width),u){case"left":U=a.x;break;case"right":U=a.x+a.width-f;break;default:U=x-f/2}d=this._buildItem(e,s[h]._index,t?t.getColor(o):this.zr.getColor(s[h]._index),U,b,f,V,g,u),b+=g+y,p=d.style.pointList,_.unshift([p[0][0]-10,p[0][1]]),_.push([p[1][0]+10,p[1][1]]),0===h&&(0===f?(p=_.pop(),"center"==u&&(_[0][0]+=10),"right"==u&&(_[0][0]=p[0]),_[0][1]-="center"==u?10:15,1==m&&(p=d.style.pointList)):(_[_.length-1][1]-=5,_[0][1]-=5)),f=V}c&&(_.unshift([p[3][0]-10,p[3][1]]),_.push([p[2][0]+10,p[2][1]]),0===f?(p=_.pop(),"center"==u&&(_[0][0]+=10),"right"==u&&(_[0][0]=p[0]),_[0][1]+="center"==u?10:15):(_[_.length-1][1]+=5,_[0][1]+=5),c.style.pointList=_)}},_buildFunnelCase:function(e){var t=this.series[e];if(this.deepQuery([t,this.option],"calculable")){var i=this._paramsMap[e].location,n=10,a={hoverable:!1,style:{pointListd:[[i.x-n,i.y-n],[i.x+i.width+n,i.y-n],[i.x+i.width+n,i.y+i.height+n],[i.x-n,i.y+i.height+n]],brushType:"stroke",lineWidth:1,strokeColor:t.calculableHolderColor||this.ecTheme.calculableHolderColor||r.calculableHolderColor}};return s.pack(a,t,e,void 0,-1),this.setCalculable(a),a=new o(a),this.shapeList.push(a),a}},_getLocation:function(e){var t=this.series[e],i=this.zr.getWidth(),n=this.zr.getHeight(),a=this.parsePercent(t.x,i),o=this.parsePercent(t.y,n),r=null==t.width?i-a-this.parsePercent(t.x2,i):this.parsePercent(t.width,i);return{x:a,y:o,width:r,height:null==t.height?n-o-this.parsePercent(t.y2,n):this.parsePercent(t.height,n),centerX:a+r/2}},_mapData:function(e){function t(e,t){return"-"===e.value?1:"-"===t.value?-1:t.value-e.value}function i(e,i){return-t(e,i)}for(var n=this.series[e],a=h.clone(n.data),o=0,r=a.length;r>o;o++)a[o]._index=o;return"none"!=n.sort&&a.sort("descending"===n.sort?t:i),a},_buildItem:function(e,t,i,n,a,o,r,l,h){var m=this.series,V=m[e],U=V.data[t],d=this.getPolygon(e,t,i,n,a,o,r,l,h);s.pack(d,m[e],e,m[e].data[t],t,m[e].data[t].name),this.shapeList.push(d);var p=this.getLabel(e,t,i,n,a,o,r,l,h);s.pack(p,m[e],e,m[e].data[t],t,m[e].data[t].name),this.shapeList.push(p),this._needLabel(V,U,!1)||(p.invisible=!0);var c=this.getLabelLine(e,t,i,n,a,o,r,l,h);this.shapeList.push(c),this._needLabelLine(V,U,!1)||(c.invisible=!0);var u=[],y=[];return this._needLabelLine(V,U,!0)&&(u.push(c.id),y.push(c.id)),this._needLabel(V,U,!0)&&(u.push(p.id),y.push(d.id)),d.hoverConnect=u,p.hoverConnect=y,d},_getItemWidth:function(e,t){var i=this.series[e],n=this._paramsMap[e].location,a=i.min,o=i.max,r=l.parsePercent(i.minSize,n.width),s=l.parsePercent(i.maxSize,n.width);return(t-a)*(s-r)/(o-a)+r},getPolygon:function(e,t,i,n,a,r,s,l,h){var V,U=this.series[e],d=U.data[t],p=[d,U],c=this.deepMerge(p,"itemStyle.normal")||{},u=this.deepMerge(p,"itemStyle.emphasis")||{},y=this.getItemStyleColor(c.color,e,t,d)||i,g=this.getItemStyleColor(u.color,e,t,d)||("string"==typeof y?m.lift(y,-.2):y);switch(h){case"left":V=n;break;case"right":V=n+(r-s);break;default:V=n+(r-s)/2}var b={zlevel:U.zlevel,z:U.z,clickable:this.deepQuery(p,"clickable"),style:{pointList:[[n,a],[n+r,a],[V+s,a+l],[V,a+l]],brushType:"both",color:y,lineWidth:c.borderWidth,strokeColor:c.borderColor},highlightStyle:{color:g,lineWidth:u.borderWidth,strokeColor:u.borderColor}};return this.deepQuery([d,U,this.option],"calculable")&&(this.setCalculable(b),b.draggable=!0),new o(b)},getLabel:function(e,t,i,a,o,r,s,l,U){var d,p=this.series[e],c=p.data[t],u=this._paramsMap[e].location,y=h.merge(h.clone(c.itemStyle)||{},p.itemStyle),g="normal",b=y[g].label,f=b.textStyle||{},k=y[g].labelLine.length,x=this.getLabelText(e,t,g),_=this.getFont(f),L=i;b.position=b.position||y.normal.label.position,"inner"===b.position||"inside"===b.position||"center"===b.position?(d=U,L=Math.max(r,s)/2>V.getTextWidth(x,_)?"#fff":m.reverse(i)):d="left"===b.position?"right":"left";var W={zlevel:p.zlevel,z:p.z+1,style:{x:this._getLabelPoint(b.position,a,u,r,s,k,U),y:o+l/2,color:f.color||L,text:x,textAlign:f.align||d,textBaseline:f.baseline||"middle",textFont:_}};return g="emphasis",b=y[g].label||b,f=b.textStyle||f,k=y[g].labelLine.length||k,b.position=b.position||y.normal.label.position,x=this.getLabelText(e,t,g),_=this.getFont(f),L=i,"inner"===b.position||"inside"===b.position||"center"===b.position?(d=U,L=Math.max(r,s)/2>V.getTextWidth(x,_)?"#fff":m.reverse(i)):d="left"===b.position?"right":"left",W.highlightStyle={x:this._getLabelPoint(b.position,a,u,r,s,k,U),color:f.color||L,text:x,textAlign:f.align||d,textFont:_,brushType:"fill"},new n(W)},getLabelText:function(e,t,i){var n=this.series,a=n[e],o=a.data[t],r=this.deepQuery([o,a],"itemStyle."+i+".label.formatter");return r?"function"==typeof r?r.call(this.myChart,{seriesIndex:e,seriesName:a.name||"",series:a,dataIndex:t,data:o,name:o.name,value:o.value}):"string"==typeof r?r=r.replace("{a}","{a0}").replace("{b}","{b0}").replace("{c}","{c0}").replace("{a0}",a.name).replace("{b0}",o.name).replace("{c0}",o.value):void 0:o.name},getLabelLine:function(e,t,i,n,o,r,s,l,m){var V=this.series[e],U=V.data[t],d=this._paramsMap[e].location,p=h.merge(h.clone(U.itemStyle)||{},V.itemStyle),c="normal",u=p[c].labelLine,y=p[c].labelLine.length,g=u.lineStyle||{},b=p[c].label;b.position=b.position||p.normal.label.position;var f={zlevel:V.zlevel,z:V.z+1,hoverable:!1,style:{xStart:this._getLabelLineStartPoint(n,d,r,s,m),yStart:o+l/2,xEnd:this._getLabelPoint(b.position,n,d,r,s,y,m),yEnd:o+l/2,strokeColor:g.color||i,lineType:g.type,lineWidth:g.width}};return c="emphasis",u=p[c].labelLine||u,y=p[c].labelLine.length||y,g=u.lineStyle||g,b=p[c].label||b,b.position=b.position,f.highlightStyle={xEnd:this._getLabelPoint(b.position,n,d,r,s,y,m),strokeColor:g.color||i,lineType:g.type,lineWidth:g.width},new a(f)},_getLabelPoint:function(e,t,i,n,a,o,r){switch(e="inner"===e||"inside"===e?"center":e){case"center":return"center"==r?t+n/2:"left"==r?t+10:t+n-10;case"left":return"auto"===o?i.x-10:"center"==r?i.centerX-Math.max(n,a)/2-o:"right"==r?t-(a>n?a-n:0)-o:i.x-o;default:return"auto"===o?i.x+i.width+10:"center"==r?i.centerX+Math.max(n,a)/2+o:"right"==r?i.x+i.width+o:t+Math.max(n,a)+o}},_getLabelLineStartPoint:function(e,t,i,n,a){return"center"==a?t.centerX:n>i?e+Math.min(i,n)/2:e+Math.max(i,n)/2},_needLabel:function(e,t,i){return this.deepQuery([t,e],"itemStyle."+(i?"emphasis":"normal")+".label.show")},_needLabelLine:function(e,t,i){return this.deepQuery([t,e],"itemStyle."+(i?"emphasis":"normal")+".labelLine.show")},refresh:function(e){e&&(this.option=e,this.series=e.series),this.backupShapeList(),this._buildShape()}},h.inherits(t,i),e("../chart").define("funnel",t),t}),i("echarts/chart/eventRiver",["require","./base","../layout/eventRiver","zrender/shape/Polygon","../component/axis","../component/grid","../component/dataZoom","../config","../util/ecData","../util/date","zrender/tool/util","zrender/tool/color","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o);var r=this;r._ondragend=function(){r.isDragend=!0},this.refresh(a)}var i=e("./base"),n=e("../layout/eventRiver"),a=e("zrender/shape/Polygon");e("../component/axis"),e("../component/grid"),e("../component/dataZoom");var o=e("../config");o.eventRiver={zlevel:0,z:2,clickable:!0,legendHoverLink:!0,itemStyle:{normal:{borderColor:"rgba(0,0,0,0)",borderWidth:1,label:{show:!0,position:"inside",formatter:"{b}"}},emphasis:{borderColor:"rgba(0,0,0,0)",borderWidth:1,label:{show:!0}}}};var r=e("../util/ecData"),s=e("../util/date"),l=e("zrender/tool/util"),h=e("zrender/tool/color");return t.prototype={type:o.CHART_TYPE_EVENTRIVER,_buildShape:function(){var e=this.series;this.selectedMap={},this._dataPreprocessing();for(var t=this.component.legend,i=[],a=0;an;n++)if(i[n].type===this.type){e=this.component.xAxis.getAxis(i[n].xAxisIndex||0);for(var o=0,r=i[n].data.length;r>o;o++){t=i[n].data[o].evolution;for(var l=0,h=t.length;h>l;l++)t[l].timeScale=e.getCoord(s.getNewDate(t[l].time)-0),t[l].valueScale=Math.pow(t[l].value,.8)}}this._intervalX=Math.round(this.component.grid.getWidth()/40)},_drawEventRiver:function(){for(var e=this.series,t=0;ta)){for(var o=[],r=[],s=0;a>s;s++)o.push(n[s].timeScale),r.push(n[s].valueScale);var l=[];l.push([o[0],i]);var s=0;for(s=0;a-1>s;s++)l.push([(o[s]+o[s+1])/2,r[s]/-2+i]);for(l.push([(o[s]+(o[s]+t))/2,r[s]/-2+i]),l.push([o[s]+t,i]),l.push([(o[s]+(o[s]+t))/2,r[s]/2+i]),s=a-1;s>0;s--)l.push([(o[s]+o[s-1])/2,r[s-1]/2+i]);return l}},ondragend:function(e,t){this.isDragend&&e.target&&(t.dragOut=!0,t.dragIn=!0,t.needRefresh=!1,this.isDragend=!1)},refresh:function(e){e&&(this.option=e,this.series=e.series),this.backupShapeList(),this._buildShape()}},l.inherits(t,i),e("../chart").define("eventRiver",t),t}),i("echarts/layout/eventRiver",["require"],function(){function e(e,i,o){function r(e,t){var i=e.importance,n=t.importance;return i>n?-1:n>i?1:0}for(var s=4,l=0;l=e)return[0];for(var t=[];e--;)t.push(0);return t}(),u=c.slice(0),y=[],g=0,b=0,l=0;l.5?.5:1,r=t.y,s=(t.height-n)/i,l=0,h=e.length;h>l;l++){var m=e[l];m.y=r+s*m.y+m._offset*o,delete m.time,delete m.value,delete m.xpx,delete m.ypx,delete m._offset;for(var V=m.evolution,U=0,d=V.length;d>U;U++)V[U].valueScale*=s}}function i(e,t,i,n){if(e===i)throw new Error("x0 is equal with x1!!!");if(t===n)return function(){return t};var a=(t-n)/(e-i),o=(n*e-t*i)/(e-i);return function(e){return a*e+o}}function n(e,t,n){var a=~~t,o=e.time.length;e.xpx=[],e.ypx=[];for(var r,s=0,l=0,h=0,m=0,V=0;o>s;s++){l=~~e.time[s],m=e.value[s]/2,s===o-1?(h=l+a,V=0):(h=~~e.time[s+1],V=e.value[s+1]/2),r=i(l,m,h,V);for(var U=l;h>U;U++)e.xpx.push(U-n),e.ypx.push(r(U))}e.xpx.push(h-n),e.ypx.push(V)}function a(e,t,i){for(var n,a=0,o=t.xpx.length,r=0;o>r;r++)n=i(t,r),a=Math.max(a,n+e[t.xpx[r]]);for(r=0;o>r;r++)n=i(t,r),e[t.xpx[r]]=a+n;return a}return e}),i("echarts/chart/venn",["require","./base","zrender/shape/Text","zrender/shape/Circle","zrender/shape/Path","../config","../util/ecData","zrender/tool/util","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o),this.refresh(a)}var i=e("./base"),n=e("zrender/shape/Text"),a=e("zrender/shape/Circle"),o=e("zrender/shape/Path"),r=e("../config");r.venn={zlevel:0,z:1,calculable:!1};var s=e("../util/ecData"),l=e("zrender/tool/util");return t.prototype={type:r.CHART_TYPE_VENN,_buildShape:function(){this.selectedMap={},this._symbol=this.option.symbolList,this._queryTarget,this._dropBoxList=[],this._vennDataCounter=0;for(var e=this.series,t=this.component.legend,i=0;ia[1].value?(t=this.zr.getHeight()/3,i=t*Math.sqrt(a[1].value)/Math.sqrt(a[0].value)):(i=this.zr.getHeight()/3,t=i*Math.sqrt(a[0].value)/Math.sqrt(a[1].value));var o=this.zr.getWidth()/2-t,r=(t+i)/2*Math.sqrt(a[2].value)/Math.sqrt((a[0].value+a[1].value)/2),s=t+i;0!==a[2].value&&(s=this._getCoincideLength(a[0].value,a[1].value,a[2].value,t,i,r,Math.abs(t-i),t+i));var l=o+s,h=this.zr.getHeight()/2;if(this._buildItem(e,0,a[0],o,h,t),this._buildItem(e,1,a[1],l,h,i),0!==a[2].value&&a[2].value!==a[0].value&&a[2].value!==a[1].value){var m=(t*t-i*i)/(2*s)+s/2,V=s/2-(t*t-i*i)/(2*s),U=Math.sqrt(t*t-m*m),d=0,p=0;a[0].value>a[1].value&&o+m>l&&(p=1),a[0].valuel&&(d=1),this._buildCoincideItem(e,2,a[2],o+m,h-U,h+U,t,i,d,p)}},_getCoincideLength:function(e,t,i,n,a,o,r,s){var l=(n*n-a*a)/(2*o)+o/2,h=o/2-(n*n-a*a)/(2*o),m=Math.acos(l/n),V=Math.acos(h/a),U=n*n*Math.PI,d=m*n*n-l*n*Math.sin(m)+V*a*a-h*a*Math.sin(V),p=d/U,c=i/e,u=Math.abs(p/c);return u>.999&&1.001>u?o:.999>=u?(s=o,o=(o+r)/2,this._getCoincideLength(e,t,i,n,a,o,r,s)):(r=o,o=(o+s)/2,this._getCoincideLength(e,t,i,n,a,o,r,s))},_buildItem:function(e,t,i,n,a,o){var r=this.series,l=r[e],h=this.getCircle(e,t,i,n,a,o);if(s.pack(h,l,e,i,t,i.name),this.shapeList.push(h),l.itemStyle.normal.label.show){var m=this.getLabel(e,t,i,n,a,o);s.pack(m,l,e,l.data[t],t,l.data[t].name),this.shapeList.push(m)}},_buildCoincideItem:function(e,t,i,n,a,r,l,h,m,V){var U=this.series,d=U[e],p=[i,d],c=this.deepMerge(p,"itemStyle.normal")||{},u=this.deepMerge(p,"itemStyle.emphasis")||{},y=c.color||this.zr.getColor(t),g=u.color||this.zr.getColor(t),b="M"+n+","+a+"A"+l+","+l+",0,"+m+",1,"+n+","+r+"A"+h+","+h+",0,"+V+",1,"+n+","+a,f={color:y,path:b},k={zlevel:d.zlevel,z:d.z,style:f,highlightStyle:{color:g,lineWidth:u.borderWidth,strokeColor:u.borderColor}};k=new o(k),k.buildPathArray&&(k.style.pathArray=k.buildPathArray(f.path)),s.pack(k,U[e],0,i,t,i.name),this.shapeList.push(k)},getCircle:function(e,t,i,n,o,r){var s=this.series[e],l=[i,s],h=this.deepMerge(l,"itemStyle.normal")||{},m=this.deepMerge(l,"itemStyle.emphasis")||{},V=h.color||this.zr.getColor(t),U=m.color||this.zr.getColor(t),d={zlevel:s.zlevel,z:s.z,clickable:!0,style:{x:n,y:o,r:r,brushType:"fill",opacity:1,color:V},highlightStyle:{color:U,lineWidth:m.borderWidth,strokeColor:m.borderColor}};return this.deepQuery([i,s,this.option],"calculable")&&(this.setCalculable(d),d.draggable=!0),new a(d)},getLabel:function(e,t,i,a,o,r){var s=this.series[e],l=s.itemStyle,h=[i,s],m=this.deepMerge(h,"itemStyle.normal")||{},V="normal",U=l[V].label,d=U.textStyle||{},p=this.getLabelText(t,i,V),c=this.getFont(d),u=m.color||this.zr.getColor(t),y=d.fontSize||12,g={zlevel:s.zlevel,z:s.z,style:{x:a,y:o-r-y,color:d.color||u,text:p,textFont:c,textAlign:"center"}};return new n(g)},getLabelText:function(e,t,i){var n=this.series,a=n[0],o=this.deepQuery([t,a],"itemStyle."+i+".label.formatter");return o?"function"==typeof o?o(a.name,t.name,t.value):"string"==typeof o?(o=o.replace("{a}","{a0}").replace("{b}","{b0}").replace("{c}","{c0}"),o=o.replace("{a0}",a.name).replace("{b0}",t.name).replace("{c0}",t.value)):void 0:t.name},refresh:function(e){e&&(this.option=e,this.series=e.series),this._buildShape()}},l.inherits(t,i),e("../chart").define("venn",t),t}),i("echarts/chart/treemap",["require","./base","zrender/tool/area","zrender/shape/Rectangle","zrender/shape/Text","zrender/shape/Line","../layout/TreeMap","../data/Tree","../config","../util/ecData","zrender/config","zrender/tool/event","zrender/tool/util","zrender/tool/color","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o),this.refresh(a);var r=this;r._onclick=function(e){return r.__onclick(e)},r.zr.on(V.EVENT.CLICK,r._onclick)}var i=e("./base"),n=e("zrender/tool/area"),a=e("zrender/shape/Rectangle"),o=e("zrender/shape/Text"),r=e("zrender/shape/Line"),s=e("../layout/TreeMap"),l=e("../data/Tree"),h=e("../config");h.treemap={zlevel:0,z:1,calculable:!1,clickable:!0,center:["50%","50%"],size:["80%","80%"],root:"",itemStyle:{normal:{label:{ -show:!0,x:5,y:12,textStyle:{align:"left",color:"#000",fontFamily:"Arial",fontSize:13,fontStyle:"normal",fontWeight:"normal"}},breadcrumb:{show:!0,textStyle:{}},borderWidth:1,borderColor:"#ccc",childBorderWidth:1,childBorderColor:"#ccc"},emphasis:{}}};var m=e("../util/ecData"),V=e("zrender/config"),U=(e("zrender/tool/event"),e("zrender/tool/util")),d=e("zrender/tool/color");return t.prototype={type:h.CHART_TYPE_TREEMAP,refresh:function(e){this.clear(),e&&(this.option=e,this.series=this.option.series),this._treesMap={};for(var t=this.series,i=this.component.legend,n=0;nt.width||e.normal.label.y+U>t.height)&&(h=""):h="",e.emphasis.label.show?(s.x+u>t.width||s.y+y>t.height)&&(p=""):p="";var g={style:{textX:t.x+e.normal.label.x,textY:t.y+e.normal.label.y,text:h,textPosition:"specific",textColor:o.color,textFont:m},highlightStyle:{textX:t.x+e.emphasis.label.x,textY:t.y+e.emphasis.label.y,text:p,textColor:s.color,textPosition:"specific"}};return g},getLabelText:function(e,t,i){return i?"function"==typeof i?i.call(this.myChart,e,t):"string"==typeof i?(i=i.replace("{b}","{b0}").replace("{c}","{c0}"),i=i.replace("{b0}",e).replace("{c0}",t)):void 0:e},_buildChildrenTreemap:function(e,t,i,n){for(var a=i.width*i.height,o=0,r=[],l=0;l ":"")},V),clickable:!0,highlightStyle:p});m.set(u,"seriesIndex",t),m.set(u,"name",a[c]),i+=u.getRect(u.style).width,this.shapeList.push(u)}},__onclick:function(e){var t=e.target;if(t){var i=m.get(t,"seriesIndex"),n=m.get(t,"name"),a=this._treesMap[i],o=a.getNodeById(n);o&&o.children.length&&this._buildTreemap(o,i)}}},U.inherits(t,i),e("../chart").define("treemap",t),t}),i("echarts/layout/TreeMap",["require"],function(){function e(e){({x:e.x,y:e.y,width:e.width,height:e.height});this.x=e.x,this.y=e.y,this.width=e.width,this.height=e.height}return e.prototype.run=function(e){var t=[];return this._squarify(e,{x:this.x,y:this.y,width:this.width,height:this.height},t),t},e.prototype._squarify=function(e,t,i){var n="VERTICAL",a=t.width,o=t.height;t.widthl;l++)r[s].y+=r[l].height}var h={};if("VERTICAL"==n){for(var m=0;ml;l++){var h=i*e[l]/o;a.push({width:s,height:h})}return a},e.prototype._isFirstBetter=function(e,t){var i=e[0].height/e[0].width;i=i>1?1/i:i;var n=t[0].height/t[0].width;return n=n>1?1/n:n,Math.abs(i-1)<=Math.abs(n-1)?!0:!1},e}),i("echarts/data/Tree",["require","zrender/tool/util"],function(e){function t(e,t){this.id=e,this.depth=0,this.height=0,this.children=[],this.parent=null,this.data=t||null}function i(e){this.root=new t(e)}var n=e("zrender/tool/util");return t.prototype.add=function(e){var t=this.children;e.parent!==this&&(t.push(e),e.parent=this)},t.prototype.remove=function(e){var t=this.children,i=n.indexOf(t,e);i>=0&&(t.splice(i,1),e.parent=null)},t.prototype.traverse=function(e,t){e.call(t,this);for(var i=0;it&&(t=n.height)}this.height=t+1},t.prototype.getNodeById=function(e){if(this.id===e)return this;for(var t=0;t0&&this._buildLink(i,e)},this);var n=e.roam===!0||"move"===e.roam,a=e.roam===!0||"scale"===e.roam;this.zr.modLayer(this.getZlevelBase(),{panable:n,zoomable:a}),(this.query("markPoint.effect.show")||this.query("markLine.effect.show"))&&this.zr.modLayer(m.EFFECT_ZLEVEL,{panable:n,zoomable:a}),this.addShapeList()},_buildItem:function(e,t,i){var n=[e.data,t],r=this.deepQuery(n,"symbol"),s=this.deepMerge(n,"itemStyle.normal")||{},l=this.deepMerge(n,"itemStyle.emphasis")||{},h=s.color||this.zr.getColor(),m=l.color||this.zr.getColor(),U=-e.layout.angle||0;e.id===this.tree.root.id&&(U=0);var d="right";Math.abs(U)>=Math.PI/2&&Math.abs(U)<3*Math.PI/2&&(U+=Math.PI,d="left");var p=[U,e.layout.position[0],e.layout.position[1]],c=new a({zlevel:this.getZlevelBase(),z:this.getZBase()+1,rotation:p,clickable:this.deepQuery(n,"clickable"),style:{x:e.layout.position[0]-.5*e.layout.width,y:e.layout.position[1]-.5*e.layout.height,width:e.layout.width,height:e.layout.height,iconType:r,color:h,brushType:"both",lineWidth:s.borderWidth,strokeColor:s.borderColor},highlightStyle:{color:m,lineWidth:l.borderWidth,strokeColor:l.borderColor}});c.style.iconType.match("image")&&(c.style.image=c.style.iconType.replace(new RegExp("^image:\\/\\/"),""),c=new o({rotation:p,style:c.style,highlightStyle:c.highlightStyle,clickable:c.clickable,zlevel:this.getZlevelBase(),z:this.getZBase()})),this.deepQuery(n,"itemStyle.normal.label.show")&&(c.style.text=null==e.data.label?e.id:e.data.label,c.style.textPosition=this.deepQuery(n,"itemStyle.normal.label.position"),"radial"===t.orient&&"inside"!==c.style.textPosition&&(c.style.textPosition=d),c.style.textColor=this.deepQuery(n,"itemStyle.normal.label.textStyle.color"),c.style.textFont=this.getFont(this.deepQuery(n,"itemStyle.normal.label.textStyle")||{})),this.deepQuery(n,"itemStyle.emphasis.label.show")&&(c.highlightStyle.textPosition=this.deepQuery(n,"itemStyle.emphasis.label.position"),c.highlightStyle.textColor=this.deepQuery(n,"itemStyle.emphasis.label.textStyle.color"),c.highlightStyle.textFont=this.getFont(this.deepQuery(n,"itemStyle.emphasis.label.textStyle")||{})),V.pack(c,t,i,e.data,0,e.id),this.shapeList.push(c)},_buildLink:function(e,t){var i=t.itemStyle.normal.lineStyle;if("broken"===i.type)return void this._buildBrokenLine(e,i,t);for(var n=0;nr&&(t=r),r>n&&(n=r)}e.layout.position[0]=e.children.length>0?(t+n)/2:0;var s=this._layerOffsets[e.depth]||0;if(s>e.layout.position[0]){var l=s-e.layout.position[0];this._shiftSubtree(e,l);for(var a=e.depth+1;ai;i++)this._buildTextShape(e[i],0,i);this.addShapeList()},_buildTextShape:function(e,t,i){var a=this.series,o=a[t],s=o.name||"",h=o.data[i],m=[h,o],V=this.component.legend,U=V?V.getColor(s):this.zr.getColor(t),d=this.deepMerge(m,"itemStyle.normal")||{},p=this.deepMerge(m,"itemStyle.emphasis")||{},c=this.getItemStyleColor(d.color,t,i,h)||U,u=this.getItemStyleColor(p.color,t,i,h)||("string"==typeof c?l.lift(c,-.2):c),y=new n({zlevel:o.zlevel,z:o.z,hoverable:!0,clickable:this.deepQuery(m,"clickable"),style:{x:0,y:0,text:e.text,color:c,textFont:[e.style,e.weight,e.size+"px",e.font].join(" "),textBaseline:"alphabetic",textAlign:"center"},highlightStyle:{brushType:p.borderWidth?"both":"fill",color:u,lineWidth:p.borderWidth||0,strokeColor:p.borderColor},position:[e.x,e.y],rotation:[-e.rotate/180*Math.PI,0,0]});r.pack(y,o,t,h,i,h.name),this.shapeList.push(y)}},s.inherits(t,i),e("../chart").define("wordCloud",t),t}),i("echarts/layout/WordCloud",["require","../layout/WordCloudRectZero","zrender/tool/util"],function(e){function t(e){this._init(e)}var i=e("../layout/WordCloudRectZero"),n=e("zrender/tool/util");return t.prototype={start:function(){function e(){p.totalArea=r,U.autoSizeCal.enable&&p._autoCalTextSize(m,r,a,o,U.autoSizeCal.minSize),V.timer&&clearInterval(V.timer),V.timer=setInterval(t,0),t()}function t(){for(var e,t=+new Date,i=m.length;+new Date-t>1,e.y=d[1]>>1,p._cloudSprite(e,m,s),e.hasText&&p._place(n,e,h)&&(l.push(e),e.x-=d[0]>>1,e.y-=d[1]>>1);s>=i&&(p.stop(),p._fixTagPosition(l),V.endcallback(l))}var n=null,a=0,o=0,r=0,s=-1,l=[],h=null,m=this.wordsdata,V=this.defaultOption,U=V.wordletype,d=V.size,p=this,c=new i({type:U.type,width:d[0],height:d[1]});return c.calculate(function(t){n=t.initarr,a=t.maxWit,o=t.maxHit,r=t.area,h=t.imgboard,e()},this),this},_fixTagPosition:function(e){for(var t=this.defaultOption.center,i=0,n=e.length;n>i;i++)e[i].x+=t[0],e[i].y+=t[1]},stop:function(){return this.defaultOption.timer&&(clearInterval(this.defaultOption.timer),this.defaultOption.timer=null),this},end:function(e){return e&&(this.defaultOption.endcallback=e),this},_init:function(e){this.defaultOption={},this._initProperty(e),this._initMethod(e),this._initCanvas(),this._initData(e.data)},_initData:function(e){var t=this,i=t.defaultOption;this.wordsdata=e.map(function(e,n){return e.text=i.text.call(t,e,n),e.font=i.font.call(t,e,n),e.style=i.fontStyle.call(t,e,n),e.weight=i.fontWeight.call(t,e,n),e.rotate=i.rotate.call(t,e,n),e.size=~~i.fontSize.call(t,e,n),e.padding=i.padding.call(t,e,n),e}).sort(function(e,t){return t.value-e.value})},_initMethod:function(e){function t(e){return e.name}function i(){return"sans-serif"}function n(){return"normal"}function a(e){return e.value}function o(){return 0}function r(e){return function(){return e[Math.round(Math.random()*(e.length-1))]}}function s(){return 0}function l(e){var t=e[0]/e[1];return function(e){return[t*(e*=.1)*Math.cos(e),e*Math.sin(e)]}}function h(e){var t=4,i=t*e[0]/e[1],n=0,a=0;return function(e){var o=0>e?-1:1;switch(Math.sqrt(1+4*o*e)-o&3){case 0:n+=i;break;case 1:a+=t;break;case 2:n-=i;break;default:a-=t}return[n,a]}}function m(e){return"function"==typeof e?e:function(){return e}}var V=this.defaultOption;V.text=e.text?m(e.text):t,V.font=e.font?m(e.font):i,V.fontSize=e.fontSize?m(e.fontSize):a,V.fontStyle=e.fontStyle?m(e.fontStyle):n,V.fontWeight=e.fontWeight?m(e.fontWeight):n,V.rotate=e.rotate?r(e.rotate):o,V.padding=e.padding?m(e.padding):s,V.center=e.center,V.spiral=l,V.endcallback=function(){},V.rectangularSpiral=h,V.archimedeanSpiral=l},_initProperty:function(e){var t=this.defaultOption;t.size=e.size||[256,256],t.wordletype=e.wordletype,t.words=e.words||[],t.timeInterval=1/0,t.timer=null,t.spirals={archimedean:t.archimedeanSpiral,rectangular:t.rectangularSpiral},n.merge(t,{size:[256,256],wordletype:{type:"RECT",areaPresent:.058,autoSizeCal:{enable:!0,minSize:12}}})},_initCanvas:function(){var e,t=Math.PI/180,i=64,n=2048,a=1;"undefined"!=typeof document?(e=document.createElement("canvas"),e.width=1,e.height=1,a=Math.sqrt(e.getContext("2d").getImageData(0,0,1,1).data.length>>2),e.width=(i<<5)/a,e.height=n/a):e=new Canvas(i<<5,n);var o=e.getContext("2d");o.fillStyle=o.strokeStyle="red",o.textAlign="center",this.defaultOption.c=o,this.defaultOption.cw=i,this.defaultOption.ch=n,this.defaultOption.ratio=a,this.defaultOption.cloudRadians=t},_cloudSprite:function(e,t,i){if(!e.sprite){var n=this.defaultOption.cw,a=this.defaultOption.ch,o=this.defaultOption.c,r=this.defaultOption.ratio,s=this.defaultOption.cloudRadians;o.clearRect(0,0,(n<<5)/r,a/r);var l=0,h=0,m=0,V=t.length;for(--i;++i>5<<5,d=~~Math.max(Math.abs(y+g),Math.abs(y-g))}else U=U+31>>5<<5;if(d>m&&(m=d),l+U>=n<<5&&(l=0,h+=m,m=0),h+d>=a)break;o.translate((l+(U>>1))/r,(h+(d>>1))/r),e.rotate&&o.rotate(e.rotate*s),o.fillText(e.text,0,0),e.padding&&(o.lineWidth=2*e.padding,o.strokeText(e.text,0,0)),o.restore(),e.width=U,e.height=d,e.xoff=l,e.yoff=h,e.x1=U>>1,e.y1=d>>1,e.x0=-e.x1,e.y0=-e.y1,e.hasText=!0,l+=U}for(var f=o.getImageData(0,0,(n<<5)/r,a/r).data,k=[];--i>=0;)if(e=t[i],e.hasText){for(var U=e.width,x=U>>5,d=e.y1-e.y0,_=0;d*x>_;_++)k[_]=0;if(l=e.xoff,null==l)return;h=e.yoff;for(var L=0,W=-1,X=0;d>X;X++){for(var _=0;U>_;_++){var v=x*X+(_>>5),w=f[(h+X)*(n<<5)+(l+_)<<2]?1<<31-_%32:0;k[v]|=w,L|=w}L?W=X:(e.y0++,d--,X--,h++)}e.y1=e.y0+W,e.sprite=k.slice(0,(e.y1-e.y0)*x)}}},_place:function(e,t,i){function n(e,t,i){i>>=5;for(var n,a=e.sprite,o=e.width>>5,r=e.x-(o<<4),s=127&r,l=32-s,h=e.y1-e.y0,m=(e.y+e.y0)*i+(r>>5),V=0;h>V;V++){n=0;for(var U=0;o>=U;U++)if((n<U?(n=a[V*o+U])>>>s:0))&t[m+U])return!0;m+=i}return!1}function a(e,t){return t.row[e.y]&&t.cloumn[e.x]&&e.x>=t.row[e.y].start&&e.x<=t.row[e.y].end&&e.y>=t.cloumn[e.x].start&&e.y<=t.cloumn[e.x].end}for(var o,r,s,l=this.defaultOption.size,h=([{x:0,y:0},{x:l[0],y:l[1]}],t.x),m=t.y,V=Math.sqrt(l[0]*l[0]+l[1]*l[1]),U=this.defaultOption.spiral(l),d=Math.random()<.5?1:-1,p=-d;(o=U(p+=d))&&(r=~~o[0],s=~~o[1],!(Math.min(r,s)>V));)if(t.x=h+r,t.y=m+s,!(t.x+t.x0<0||t.y+t.y0<0||t.x+t.x1>l[0]||t.y+t.y1>l[1])&&!n(t,e,l[0])&&a(t,i)){for(var c,u=t.sprite,y=t.width>>5,g=l[0]>>5,b=t.x-(y<<4),f=127&b,k=32-f,x=t.y1-t.y0,_=(t.y+t.y0)*g+(b>>5),L=0;x>L;L++){c=0;for(var W=0;y>=W;W++)e[_+W]|=c<W?(c=u[L*y+W])>>>f:0);_+=g}return delete t.sprite,!0}return!1},_autoCalTextSize:function(e,t,i,n,a){function o(e){c.clearRect(0,0,(d<<5)/u,p/u),c.save(),c.font=e.style+" "+e.weight+" "+~~((e.size+1)/u)+"px "+e.font;var t=c.measureText(e.text+"m").width*u,r=e.size<<1;t=t+31>>5<<5,c.restore(),e.aw=t,e.ah=r;var s,l,h;if(e.rotate){var m=Math.sin(e.rotate*y),V=Math.cos(e.rotate*y),g=t*V,b=t*m,f=r*V,k=r*m;l=Math.max(Math.abs(g+k),Math.abs(g-k))+31>>5<<5,h=~~Math.max(Math.abs(b+f),Math.abs(b-f))}return e.size<=U||e.rotate&&t*r<=e.area&&i>=l&&n>=h||t*r<=e.area&&i>=t&&n>=r?void(e.area=t*r):(s=e.rotate&&l>i&&h>n?Math.min(i/l,n/h):t>i||r>n?Math.min(i/t,n/r):Math.sqrt(e.area/(e.aw*e.ah)),e.size=~~(s*e.size),e.sizel?l:V:l,s.area=t*s.areapre,s.totalarea=t,o(s)}},t}),i("echarts/layout/WordCloudRectZero",["require"],function(){function e(e){this.defaultOption={type:"RECT"},this._init(e)}return e.prototype={RECT:"_calculateRect",_init:function(e){this._initOption(e),this._initCanvas()},_initOption:function(e){for(k in e)this.defaultOption[k]=e[k]},_initCanvas:function(){var e=document.createElement("canvas");e.width=1,e.height=1;var t=Math.sqrt(e.getContext("2d").getImageData(0,0,1,1).data.length>>2);if(e.width=this.defaultOption.width,e.height=this.defaultOption.height,e.getContext)var i=e.getContext("2d");this.canvas=e,this.ctx=i,this.ratio=t},calculate:function(e,t){var i=this.defaultOption.type,n=this[i];this[n].call(this,e,t)},_calculateReturn:function(e,t,i){t.call(i,e)},_calculateRect:function(e,t){var i={},n=this.defaultOption.width>>5<<5,a=this.defaultOption.height;i.initarr=this._rectZeroArray(n*a),i.area=n*a,i.maxHit=a,i.maxWit=n,i.imgboard=this._rectBoard(n,a),this._calculateReturn(i,e,t)},_rectBoard:function(e,t){for(var i=[],n=0;t>n;n++)i.push({y:n,start:0,end:e});for(var a=[],n=0;e>n;n++)a.push({x:n,start:0,end:t});return{row:i,cloumn:a}},_rectZeroArray:function(e){for(var t=[],i=e,n=-1;++ni;++i)if(e[i].type===a.CHART_TYPE_HEATMAP){e[i]=this.reformOption(e[i]);var o=new n(e[i]),s=o.getCanvas(e[i].data,this.zr.getWidth(),this.zr.getHeight()),l=new r({position:[0,0],scale:[1,1],hoverable:this.option.hoverable,style:{x:0,y:0,image:s,width:s.width,height:s.height}});this.shapeList.push(l)}this.addShapeList()}},o.inherits(t,i),e("../chart").define("heatmap",t),t});var n=t("zrender");n.tool={color:t("zrender/tool/color"),math:t("zrender/tool/math"),util:t("zrender/tool/util"),vector:t("zrender/tool/vector"),area:t("zrender/tool/area"),event:t("zrender/tool/event")},n.animation={Animation:t("zrender/animation/Animation"),Cip:t("zrender/animation/Clip"),easing:t("zrender/animation/easing")};var a=t("echarts");a.config=t("echarts/config"),a.util={mapData:{params:t("echarts/util/mapData/params")}},t("echarts/chart/line"),t("echarts/chart/bar"),t("echarts/chart/scatter"),t("echarts/chart/k"),t("echarts/chart/pie"),t("echarts/chart/radar"),t("echarts/chart/chord"),t("echarts/chart/force"),t("echarts/chart/map"),t("echarts/chart/gauge"),t("echarts/chart/funnel"),t("echarts/chart/eventRiver"),t("echarts/chart/venn"),t("echarts/chart/treemap"),t("echarts/chart/tree"),t("echarts/chart/wordCloud"),t("echarts/chart/heatmap"),e.echarts=a,e.zrender=n}(window); \ No newline at end of file +encodeOffsets:[[124437,30983]]}},{type:"Feature",id:"3310",properties:{name:"台州市",cp:[121.1353,28.6688],childNum:7},geometry:{type:"Polygon",coordinates:["@@lV„IVWVz@bXJl@Xal@°„nLll@nVxnV„K@UJVbƒ¦°„k`UIWJXnƚ@bUJ„Xl@lb„Wn@UzVV@bVVšmVnnJVXna‚bšKUKnUVVUnVLlKVLXa„Jm£@mU@WanaU_°@VWnV@UVWnIVVVKlXœÒlK@wVK„L°m„@„„l@ô„Kšw„ĉƾůUƒl£@»UƒVk„m@ƅUƒƒaÛIŏmUk@m„w@a™£ƒWk@ţšƒIm±@ankôUlaU™Uw¯ƒōaƒbÇbţm™ÞšÞVĖ„b„l@š@n‚VXxƒbUl@XmbƒŽ¯lUUU™W@ÛI±xU@mƒb@bmJ@bUzƒV@b¯bƒKUa¯KV_@Kk@@mWIƒ@lUU›b@bkVm@kwUÇU_WKU@Ux™@ƒVUnllX@Vn‚J@UXV@bWL@lUbbVLUJ@z‚V@lnbWbnnnJVŽ@L"],encodeOffsets:[[123312,29526]]}},{type:"Feature",id:"3307",properties:{name:"金华市",cp:[120.0037,29.1028],childNum:8},geometry:{type:"Polygon",coordinates:["@@nbVb„@VbUVlb@VUnVxk`lXnJlbnƒlL@bX@Vƒ@klƒV@nLnx@JlI„V‚U@VUVn„VV„I@WVLVbVKXbWnXl@VlXUx„b@ŽlVUbl„œlVUšIÜVnalKX@@bV@@aUUlUƒwUw„@naWW„UVaUUšaVb„LlxXJVk°ƒUƒlkU¥@k„a@LVlXLVlšVWznVn@lxšJl_@WX_@mVa„a@alU@kVVna„KVLlK„b@UUaVašbnUWmXU@k@yVI@ařWmXIVJl_¯ƒ„¥UaVI@ƒLmUUw@mkkmK¯ƒk@Wbk@WI@aUyUXƒJkU@bU@WLUyƒXUbkbW`UVVkKmbUaVUƒUK™£@KVUUUm@UWkXWaUKƒV@b¯ƒ¯mU™V@UkƒmW@kkKƒwUƒmkkVUI@WlkUamL@Wk_Wƒ@UVm@Ua¯KWXk@Uxm@UK@xV„mV@Xk@UVV¼@‚VLUb™Uƒ„U@ƒyULUbVlU@@XlVUVVbƒU@lXXVW@XUVl@@VUVƒÈn@VVU„@lVa@„U„mL@`X@`WL@VUX@lUL@xlx"],encodeOffsets:[[122119,29948]]}},{type:"Feature",id:"3308",properties:{name:"衢州市",cp:[118.6853,28.8666],childNum:5},geometry:{type:"Polygon",coordinates:["@@XkVKnwl@@aVK@UšwnL‚K@aÞaš¹@Kb@UVaUaVaVK@k°V„UllnL@„V@šxV@œšV@VV„m„_Wa„m@wlaÞbn@lL@WnLšk@V@VlK@nkVVb@blKXklakw@wVK@kVW@UXK@_‚W@_nKVƒ@ƒUb@kVƒUUm@„ÇVU@Uk@VU@WUXWW@k„VUaVUkU@WWXUKk@Ukmm¯LmmƒUJUIWJkImmƒ_—±WLkKm£@aVUmKUnƒLmWUkVmw@¥U„LVWm@WUka@UmmLmm@@bUX™@@WUIm@UVUK@UVUUU™VVJmb@b„Xn‚mVƒ¼nnn¦mJUVƒL„V@VW@UzUlVnUbl`UnVl@XU@kl@bmÈUx™Vk@@J@„ƒ¼W@ÅaVVnzmVƒ„@WJk@kWJ@ƒlXbWbXxmVnšlLXb@°lKVXnWšbWV„„X„mbV@Xl‚bšI@Kn@@x@šVLlm"],encodeOffsets:[[121185,30184]]}},{type:"Feature",id:"3306",properties:{name:"绍兴市",cp:[120.564,29.7565],childNum:6},geometry:{type:"Polygon",coordinates:["@@„x@„˜VnnVJnIVJV_VKXblUXJlŽlLUŽUnU@UVVX@ŽmVUUUJl„XUlbV@@V„LVmX@@XlaVJVXXJ@b‚@XU„@lUšJ„È‚bœ¤Ō„JšçV™UUnml@@kna@wšWVU@LVKV@namwkIUwmƒnmlaVL„kUmVUkmmIUak@VmUUVUƒWV_kK@U„K‚bnkWy„U@ƒ@UXwl@VUÞUVak±VUUU@mlI@™™wXWƒIWbUKkLUKVmUUmVVL™LambUWmIUm™nUU@aUUVym@ƒXkak@ƒW@z@lWVXnmV™aUbVb@VƒakLUKƒLmbUU@lkV@bƒbUb@nW`@Xk`™Ikwm@mUXy™UUkWKUk@Kƒb@lV¦klV„¯„UlWIkwƒKUa™bVVUbƒVXXmbƒ@Vx„xkVVV@bU@@aW@kLmb@lVUIVKmL@bUV@bUV@L„a˜lnUV@nbVbUlVXšJVUnx"],encodeOffsets:[[122997,30561]]}},{type:"Feature",id:"3304",properties:{name:"嘉兴市",cp:[120.9155,30.6354],childNum:6},geometry:{type:"Polygon",coordinates:["@@@blIX@@VÜVUnn@l‚k„lKnI°Þl`²LVKVbnbVaVLUVn@W¦@VkVVb„@VI„`@blLnL‚aX@„VVb@U‚@XlVa„@@kVaUKV»U_lWXUƒƒ@alb„k@VllnLVKn@@UVIUw@y°IVVXU@VV@lw„m@wVkƾaœJ‚LkΡƧƒ™l™LÝUmW¯ķÿĉ¥ƒIŋŽWn™èkVƧU¯ÅmlVx@V¯aƒz„Ž@„@JU@U¦m@@šnVmn@V„LV‚"],encodeOffsets:[[123233,31382]]}},{type:"Feature",id:"3305",properties:{name:"湖州市",cp:[119.8608,30.7782],childNum:4},geometry:{type:"Polygon",coordinates:["@@kLlƒkm@VmÛU@UW@kJ@aUƒK@UnmmU@™maÛL@JWUUKUwUIUJ@XƒKWV@Vk@UIUmVk@mm@ÅnmaUVkL@VƒKmLVbU@klU@ÝbV™@mVUKV™@wUkVƒ—ƒmIUJ@nVV@L™akJWbUIka@UmKmLKmmƒUUVk@@nmLX`WXUV@Ž@nUl™kmlU@Ub„„ƒxVVšIlV„Žšnn„@@n˜„UҚ@„°n@@xmb@„VbnV@šš„@b@`@L@L@x@blVklVbnnV@‚aXb°VlU@W„b°U„LXWVUV™„™VwÈwÜ»ĸaĠnUVw²X@V@lVU@wlaUUVm@knUV›"],encodeOffsets:[[123379,31500]]}}],UTF8Encoding:!0}}),i("echarts/chart/gauge",["require","./base","../util/shape/GaugePointer","zrender/shape/Text","zrender/shape/Line","zrender/shape/Rectangle","zrender/shape/Circle","zrender/shape/Sector","../config","../util/ecData","../util/accMath","zrender/tool/util","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o),this.refresh(a)}var i=e("./base"),n=e("../util/shape/GaugePointer"),a=e("zrender/shape/Text"),o=e("zrender/shape/Line"),r=e("zrender/shape/Rectangle"),s=e("zrender/shape/Circle"),l=e("zrender/shape/Sector"),h=e("../config");h.gauge={zlevel:0,z:2,center:["50%","50%"],clickable:!0,legendHoverLink:!0,radius:"75%",startAngle:225,endAngle:-45,min:0,max:100,splitNumber:10,axisLine:{show:!0,lineStyle:{color:[[.2,"#228b22"],[.8,"#48b"],[1,"#ff4500"]],width:30}},axisTick:{show:!0,splitNumber:5,length:8,lineStyle:{color:"#eee",width:1,type:"solid"}},axisLabel:{show:!0,textStyle:{color:"auto"}},splitLine:{show:!0,length:30,lineStyle:{color:"#eee",width:2,type:"solid"}},pointer:{show:!0,length:"80%",width:8,color:"auto"},title:{show:!0,offsetCenter:[0,"-40%"],textStyle:{color:"#333",fontSize:15}},detail:{show:!0,backgroundColor:"rgba(0,0,0,0)",borderWidth:0,borderColor:"#ccc",width:100,height:40,offsetCenter:[0,"40%"],textStyle:{color:"auto",fontSize:30}}};var m=e("../util/ecData"),V=e("../util/accMath"),U=e("zrender/tool/util");return t.prototype={type:h.CHART_TYPE_GAUGE,_buildShape:function(){var e=this.series;this._paramsMap={},this.selectedMap={};for(var t=0,i=e.length;i>t;t++)e[t].type===h.CHART_TYPE_GAUGE&&(this.selectedMap[e[t].name]=!0,e[t]=this.reformOption(e[t]),this.legendHoverLink=e[t].legendHoverLink||this.legendHoverLink,this._buildSingleGauge(t),this.buildMark(t));this.addShapeList()},_buildSingleGauge:function(e){var t=this.series[e];this._paramsMap[e]={center:this.parseCenter(this.zr,t.center),radius:this.parseRadius(this.zr,t.radius),startAngle:t.startAngle.toFixed(2)-0,endAngle:t.endAngle.toFixed(2)-0},this._paramsMap[e].totalAngle=this._paramsMap[e].startAngle-this._paramsMap[e].endAngle,this._colorMap(e),this._buildAxisLine(e),this._buildSplitLine(e),this._buildAxisTick(e),this._buildAxisLabel(e),this._buildPointer(e),this._buildTitle(e),this._buildDetail(e)},_buildAxisLine:function(e){var t=this.series[e];if(t.axisLine.show)for(var i,n,a=t.min,o=t.max-a,r=this._paramsMap[e],s=r.center,l=r.startAngle,h=r.totalAngle,V=r.colorArray,U=t.axisLine.lineStyle,d=this.parsePercent(U.width,r.radius[1]),p=r.radius[1],c=p-d,u=l,y=0,g=V.length;g>y;y++)n=l-h*(V[y][0]-a)/o,i=this._getSector(s,c,p,n,u,V[y][1],U,t.zlevel,t.z),u=n,i._animationAdd="r",m.set(i,"seriesIndex",e),m.set(i,"dataIndex",y),this.shapeList.push(i)},_buildSplitLine:function(e){var t=this.series[e];if(t.splitLine.show)for(var i,n,a,r=this._paramsMap[e],s=t.splitNumber,l=t.min,h=t.max-l,m=t.splitLine,V=this.parsePercent(m.length,r.radius[1]),U=m.lineStyle,d=U.color,p=r.center,c=r.startAngle*Math.PI/180,u=r.totalAngle*Math.PI/180,y=r.radius[1],g=y-V,b=0;s>=b;b++)i=c-u/s*b,n=Math.sin(i),a=Math.cos(i),this.shapeList.push(new o({zlevel:t.zlevel,z:t.z+1,hoverable:!1,style:{xStart:p[0]+a*y,yStart:p[1]-n*y,xEnd:p[0]+a*g,yEnd:p[1]-n*g,strokeColor:"auto"===d?this._getColor(e,l+h/s*b):d,lineType:U.type,lineWidth:U.width,shadowColor:U.shadowColor,shadowBlur:U.shadowBlur,shadowOffsetX:U.shadowOffsetX,shadowOffsetY:U.shadowOffsetY}}))},_buildAxisTick:function(e){var t=this.series[e];if(t.axisTick.show)for(var i,n,a,r=this._paramsMap[e],s=t.splitNumber,l=t.min,h=t.max-l,m=t.axisTick,V=m.splitNumber,U=this.parsePercent(m.length,r.radius[1]),d=m.lineStyle,p=d.color,c=r.center,u=r.startAngle*Math.PI/180,y=r.totalAngle*Math.PI/180,g=r.radius[1],b=g-U,f=0,k=s*V;k>=f;f++)f%V!==0&&(i=u-y/k*f,n=Math.sin(i),a=Math.cos(i),this.shapeList.push(new o({zlevel:t.zlevel,z:t.z+1,hoverable:!1,style:{xStart:c[0]+a*g,yStart:c[1]-n*g,xEnd:c[0]+a*b,yEnd:c[1]-n*b,strokeColor:"auto"===p?this._getColor(e,l+h/k*f):p,lineType:d.type,lineWidth:d.width,shadowColor:d.shadowColor,shadowBlur:d.shadowBlur,shadowOffsetX:d.shadowOffsetX,shadowOffsetY:d.shadowOffsetY}})))},_buildAxisLabel:function(e){var t=this.series[e];if(t.axisLabel.show)for(var i,n,o,r,s=t.splitNumber,l=t.min,h=t.max-l,m=t.axisLabel.textStyle,U=this.getFont(m),d=m.color,p=this._paramsMap[e],c=p.center,u=p.startAngle,y=p.totalAngle,g=p.radius[1]-this.parsePercent(t.splitLine.length,p.radius[1])-5,b=0;s>=b;b++)r=V.accAdd(l,V.accMul(V.accDiv(h,s),b)),i=u-y/s*b,n=Math.sin(i*Math.PI/180),o=Math.cos(i*Math.PI/180),i=(i+360)%360,this.shapeList.push(new a({zlevel:t.zlevel,z:t.z+1,hoverable:!1,style:{x:c[0]+o*g,y:c[1]-n*g,color:"auto"===d?this._getColor(e,r):d,text:this._getLabelText(t.axisLabel.formatter,r),textAlign:i>=110&&250>=i?"left":70>=i||i>=290?"right":"center",textBaseline:i>=10&&170>=i?"top":i>=190&&350>=i?"bottom":"middle",textFont:U,shadowColor:m.shadowColor,shadowBlur:m.shadowBlur,shadowOffsetX:m.shadowOffsetX,shadowOffsetY:m.shadowOffsetY}}))},_buildPointer:function(e){var t=this.series[e];if(t.pointer.show){var i=t.max-t.min,a=t.pointer,o=this._paramsMap[e],r=this.parsePercent(a.length,o.radius[1]),l=this.parsePercent(a.width,o.radius[1]),h=o.center,V=this._getValue(e);V=V2?2:l/2,color:"#fff"}});m.pack(p,this.series[e],e,this.series[e].data[0],0,this.series[e].data[0].name,V),this.shapeList.push(p),this.shapeList.push(new s({zlevel:t.zlevel,z:t.z+2,hoverable:!1,style:{x:h[0],y:h[1],r:a.width/2.5,color:"#fff"}}))}},_buildTitle:function(e){var t=this.series[e];if(t.title.show){var i=t.data[0],n=null!=i.name?i.name:"";if(""!==n){var o=t.title,r=o.offsetCenter,s=o.textStyle,l=s.color,h=this._paramsMap[e],m=h.center[0]+this.parsePercent(r[0],h.radius[1]),V=h.center[1]+this.parsePercent(r[1],h.radius[1]);this.shapeList.push(new a({zlevel:t.zlevel,z:t.z+(Math.abs(m-h.center[0])+Math.abs(V-h.center[1])<2*s.fontSize?2:1),hoverable:!1,style:{x:m,y:V,color:"auto"===l?this._getColor(e):l,text:n,textAlign:"center",textFont:this.getFont(s),shadowColor:s.shadowColor,shadowBlur:s.shadowBlur,shadowOffsetX:s.shadowOffsetX,shadowOffsetY:s.shadowOffsetY}}))}}},_buildDetail:function(e){var t=this.series[e];if(t.detail.show){var i=t.detail,n=i.offsetCenter,a=i.backgroundColor,o=i.textStyle,s=o.color,l=this._paramsMap[e],h=this._getValue(e),m=l.center[0]-i.width/2+this.parsePercent(n[0],l.radius[1]),V=l.center[1]+this.parsePercent(n[1],l.radius[1]);this.shapeList.push(new r({zlevel:t.zlevel,z:t.z+(Math.abs(m+i.width/2-l.center[0])+Math.abs(V+i.height/2-l.center[1])r;r++)o.push([a[r][0]*n+i,a[r][1]]);this._paramsMap[e].colorArray=o},_getColor:function(e,t){null==t&&(t=this._getValue(e));for(var i=this._paramsMap[e].colorArray,n=0,a=i.length;a>n;n++)if(i[n][0]>=t)return i[n][1];return i[i.length-1][1]},_getSector:function(e,t,i,n,a,o,r,s,h){return new l({zlevel:s,z:h,hoverable:!1,style:{x:e[0],y:e[1],r0:t,r:i,startAngle:n,endAngle:a,brushType:"fill",color:o,shadowColor:r.shadowColor,shadowBlur:r.shadowBlur,shadowOffsetX:r.shadowOffsetX,shadowOffsetY:r.shadowOffsetY}})},_getLabelText:function(e,t){if(e){if("function"==typeof e)return e.call(this.myChart,t);if("string"==typeof e)return e.replace("{value}",t)}return t},refresh:function(e){e&&(this.option=e,this.series=e.series),this.backupShapeList(),this._buildShape()}},U.inherits(t,i),e("../chart").define("gauge",t),t}),i("echarts/util/shape/GaugePointer",["require","zrender/shape/Base","zrender/tool/util","./normalIsCover"],function(e){function t(e){i.call(this,e)}var i=e("zrender/shape/Base"),n=e("zrender/tool/util");return t.prototype={type:"gauge-pointer",buildPath:function(e,t){var i=t.r,n=t.width,a=t.angle,o=t.x-Math.cos(a)*n*(n>=i/3?1:2),r=t.y+Math.sin(a)*n*(n>=i/3?1:2);a=t.angle-Math.PI/2,e.moveTo(o,r),e.lineTo(t.x+Math.cos(a)*n,t.y-Math.sin(a)*n),e.lineTo(t.x+Math.cos(t.angle)*i,t.y-Math.sin(t.angle)*i),e.lineTo(t.x-Math.cos(a)*n,t.y+Math.sin(a)*n),e.lineTo(o,r)},getRect:function(e){if(e.__rect)return e.__rect;var t=2*e.width,i=e.x,n=e.y,a=i+Math.cos(e.angle)*e.r,o=n-Math.sin(e.angle)*e.r;return e.__rect={x:Math.min(i,a)-t,y:Math.min(n,o)-t,width:Math.abs(i-a)+t,height:Math.abs(n-o)+t},e.__rect},isCover:e("./normalIsCover")},n.inherits(t,i),t}),i("echarts/chart/funnel",["require","./base","zrender/shape/Text","zrender/shape/Line","zrender/shape/Polygon","../config","../util/ecData","../util/number","zrender/tool/util","zrender/tool/color","zrender/tool/area","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o),this.refresh(a)}var i=e("./base"),n=e("zrender/shape/Text"),a=e("zrender/shape/Line"),o=e("zrender/shape/Polygon"),r=e("../config");r.funnel={zlevel:0,z:2,clickable:!0,legendHoverLink:!0,x:80,y:60,x2:80,y2:60,min:0,max:100,minSize:"0%",maxSize:"100%",sort:"descending",gap:0,funnelAlign:"center",itemStyle:{normal:{borderColor:"#fff",borderWidth:1,label:{show:!0,position:"outer"},labelLine:{show:!0,length:10,lineStyle:{width:1,type:"solid"}}},emphasis:{borderColor:"rgba(0,0,0,0)",borderWidth:1,label:{show:!0},labelLine:{show:!0}}}};var s=e("../util/ecData"),l=e("../util/number"),h=e("zrender/tool/util"),m=e("zrender/tool/color"),V=e("zrender/tool/area");return t.prototype={type:r.CHART_TYPE_FUNNEL,_buildShape:function(){var e=this.series,t=this.component.legend;this._paramsMap={},this._selected={},this.selectedMap={};for(var i,n=0,a=e.length;a>n;n++)if(e[n].type===r.CHART_TYPE_FUNNEL){if(e[n]=this.reformOption(e[n]),this.legendHoverLink=e[n].legendHoverLink||this.legendHoverLink,i=e[n].name||"",this.selectedMap[i]=t?t.isSelected(i):!0,!this.selectedMap[i])continue;this._buildSingleFunnel(n),this.buildMark(n)}this.addShapeList()},_buildSingleFunnel:function(e){var t=this.component.legend,i=this.series[e],n=this._mapData(e),a=this._getLocation(e);this._paramsMap[e]={location:a,data:n};for(var o,r=0,s=[],h=0,m=n.length;m>h;h++)o=n[h].name,this.selectedMap[o]=t?t.isSelected(o):!0,this.selectedMap[o]&&!isNaN(n[h].value)&&(s.push(n[h]),r++);if(0!==r){for(var V,U,d,p,c=this._buildFunnelCase(e),u=i.funnelAlign,y=i.gap,g=r>1?(a.height-(r-1)*y)/r:a.height,b=a.y,f="descending"===i.sort?this._getItemWidth(e,s[0].value):l.parsePercent(i.minSize,a.width),k="descending"===i.sort?1:0,x=a.centerX,_=[],h=0,m=s.length;m>h;h++)if(o=s[h].name,this.selectedMap[o]&&!isNaN(s[h].value)){switch(V=m-2>=h?this._getItemWidth(e,s[h+k].value):"descending"===i.sort?l.parsePercent(i.minSize,a.width):l.parsePercent(i.maxSize,a.width),u){case"left":U=a.x;break;case"right":U=a.x+a.width-f;break;default:U=x-f/2}d=this._buildItem(e,s[h]._index,t?t.getColor(o):this.zr.getColor(s[h]._index),U,b,f,V,g,u),b+=g+y,p=d.style.pointList,_.unshift([p[0][0]-10,p[0][1]]),_.push([p[1][0]+10,p[1][1]]),0===h&&(0===f?(p=_.pop(),"center"==u&&(_[0][0]+=10),"right"==u&&(_[0][0]=p[0]),_[0][1]-="center"==u?10:15,1==m&&(p=d.style.pointList)):(_[_.length-1][1]-=5,_[0][1]-=5)),f=V}c&&(_.unshift([p[3][0]-10,p[3][1]]),_.push([p[2][0]+10,p[2][1]]),0===f?(p=_.pop(),"center"==u&&(_[0][0]+=10),"right"==u&&(_[0][0]=p[0]),_[0][1]+="center"==u?10:15):(_[_.length-1][1]+=5,_[0][1]+=5),c.style.pointList=_)}},_buildFunnelCase:function(e){var t=this.series[e];if(this.deepQuery([t,this.option],"calculable")){var i=this._paramsMap[e].location,n=10,a={hoverable:!1,style:{pointListd:[[i.x-n,i.y-n],[i.x+i.width+n,i.y-n],[i.x+i.width+n,i.y+i.height+n],[i.x-n,i.y+i.height+n]],brushType:"stroke",lineWidth:1,strokeColor:t.calculableHolderColor||this.ecTheme.calculableHolderColor||r.calculableHolderColor}};return s.pack(a,t,e,void 0,-1),this.setCalculable(a),a=new o(a),this.shapeList.push(a),a}},_getLocation:function(e){var t=this.series[e],i=this.zr.getWidth(),n=this.zr.getHeight(),a=this.parsePercent(t.x,i),o=this.parsePercent(t.y,n),r=null==t.width?i-a-this.parsePercent(t.x2,i):this.parsePercent(t.width,i);return{x:a,y:o,width:r,height:null==t.height?n-o-this.parsePercent(t.y2,n):this.parsePercent(t.height,n),centerX:a+r/2}},_mapData:function(e){function t(e,t){return"-"===e.value?1:"-"===t.value?-1:t.value-e.value}function i(e,i){return-t(e,i)}for(var n=this.series[e],a=h.clone(n.data),o=0,r=a.length;r>o;o++)a[o]._index=o;return"none"!=n.sort&&a.sort("descending"===n.sort?t:i),a},_buildItem:function(e,t,i,n,a,o,r,l,h){var m=this.series,V=m[e],U=V.data[t],d=this.getPolygon(e,t,i,n,a,o,r,l,h);s.pack(d,m[e],e,m[e].data[t],t,m[e].data[t].name),this.shapeList.push(d);var p=this.getLabel(e,t,i,n,a,o,r,l,h);s.pack(p,m[e],e,m[e].data[t],t,m[e].data[t].name),this.shapeList.push(p),this._needLabel(V,U,!1)||(p.invisible=!0);var c=this.getLabelLine(e,t,i,n,a,o,r,l,h);this.shapeList.push(c),this._needLabelLine(V,U,!1)||(c.invisible=!0);var u=[],y=[];return this._needLabelLine(V,U,!0)&&(u.push(c.id),y.push(c.id)),this._needLabel(V,U,!0)&&(u.push(p.id),y.push(d.id)),d.hoverConnect=u,p.hoverConnect=y,d},_getItemWidth:function(e,t){var i=this.series[e],n=this._paramsMap[e].location,a=i.min,o=i.max,r=l.parsePercent(i.minSize,n.width),s=l.parsePercent(i.maxSize,n.width);return(t-a)*(s-r)/(o-a)+r},getPolygon:function(e,t,i,n,a,r,s,l,h){var V,U=this.series[e],d=U.data[t],p=[d,U],c=this.deepMerge(p,"itemStyle.normal")||{},u=this.deepMerge(p,"itemStyle.emphasis")||{},y=this.getItemStyleColor(c.color,e,t,d)||i,g=this.getItemStyleColor(u.color,e,t,d)||("string"==typeof y?m.lift(y,-.2):y);switch(h){case"left":V=n;break;case"right":V=n+(r-s);break;default:V=n+(r-s)/2}var b={zlevel:U.zlevel,z:U.z,clickable:this.deepQuery(p,"clickable"),style:{pointList:[[n,a],[n+r,a],[V+s,a+l],[V,a+l]],brushType:"both",color:y,lineWidth:c.borderWidth,strokeColor:c.borderColor},highlightStyle:{color:g,lineWidth:u.borderWidth,strokeColor:u.borderColor}};return this.deepQuery([d,U,this.option],"calculable")&&(this.setCalculable(b),b.draggable=!0),new o(b)},getLabel:function(e,t,i,a,o,r,s,l,U){var d,p=this.series[e],c=p.data[t],u=this._paramsMap[e].location,y=h.merge(h.clone(c.itemStyle)||{},p.itemStyle),g="normal",b=y[g].label,f=b.textStyle||{},k=y[g].labelLine.length,x=this.getLabelText(e,t,g),_=this.getFont(f),L=i;b.position=b.position||y.normal.label.position,"inner"===b.position||"inside"===b.position||"center"===b.position?(d=U,L=Math.max(r,s)/2>V.getTextWidth(x,_)?"#fff":m.reverse(i)):d="left"===b.position?"right":"left";var W={zlevel:p.zlevel,z:p.z+1,style:{x:this._getLabelPoint(b.position,a,u,r,s,k,U),y:o+l/2,color:f.color||L,text:x,textAlign:f.align||d,textBaseline:f.baseline||"middle",textFont:_}};return g="emphasis",b=y[g].label||b,f=b.textStyle||f,k=y[g].labelLine.length||k,b.position=b.position||y.normal.label.position,x=this.getLabelText(e,t,g),_=this.getFont(f),L=i,"inner"===b.position||"inside"===b.position||"center"===b.position?(d=U,L=Math.max(r,s)/2>V.getTextWidth(x,_)?"#fff":m.reverse(i)):d="left"===b.position?"right":"left",W.highlightStyle={x:this._getLabelPoint(b.position,a,u,r,s,k,U),color:f.color||L,text:x,textAlign:f.align||d,textFont:_,brushType:"fill"},new n(W)},getLabelText:function(e,t,i){var n=this.series,a=n[e],o=a.data[t],r=this.deepQuery([o,a],"itemStyle."+i+".label.formatter");return r?"function"==typeof r?r.call(this.myChart,{seriesIndex:e,seriesName:a.name||"",series:a,dataIndex:t,data:o,name:o.name,value:o.value}):"string"==typeof r?r=r.replace("{a}","{a0}").replace("{b}","{b0}").replace("{c}","{c0}").replace("{a0}",a.name).replace("{b0}",o.name).replace("{c0}",o.value):void 0:o.name},getLabelLine:function(e,t,i,n,o,r,s,l,m){var V=this.series[e],U=V.data[t],d=this._paramsMap[e].location,p=h.merge(h.clone(U.itemStyle)||{},V.itemStyle),c="normal",u=p[c].labelLine,y=p[c].labelLine.length,g=u.lineStyle||{},b=p[c].label;b.position=b.position||p.normal.label.position;var f={zlevel:V.zlevel,z:V.z+1,hoverable:!1,style:{xStart:this._getLabelLineStartPoint(n,d,r,s,m),yStart:o+l/2,xEnd:this._getLabelPoint(b.position,n,d,r,s,y,m),yEnd:o+l/2,strokeColor:g.color||i,lineType:g.type,lineWidth:g.width}};return c="emphasis",u=p[c].labelLine||u,y=p[c].labelLine.length||y,g=u.lineStyle||g,b=p[c].label||b,b.position=b.position,f.highlightStyle={xEnd:this._getLabelPoint(b.position,n,d,r,s,y,m),strokeColor:g.color||i,lineType:g.type,lineWidth:g.width},new a(f)},_getLabelPoint:function(e,t,i,n,a,o,r){switch(e="inner"===e||"inside"===e?"center":e){case"center":return"center"==r?t+n/2:"left"==r?t+10:t+n-10;case"left":return"auto"===o?i.x-10:"center"==r?i.centerX-Math.max(n,a)/2-o:"right"==r?t-(a>n?a-n:0)-o:i.x-o;default:return"auto"===o?i.x+i.width+10:"center"==r?i.centerX+Math.max(n,a)/2+o:"right"==r?i.x+i.width+o:t+Math.max(n,a)+o}},_getLabelLineStartPoint:function(e,t,i,n,a){return"center"==a?t.centerX:n>i?e+Math.min(i,n)/2:e+Math.max(i,n)/2},_needLabel:function(e,t,i){return this.deepQuery([t,e],"itemStyle."+(i?"emphasis":"normal")+".label.show")},_needLabelLine:function(e,t,i){return this.deepQuery([t,e],"itemStyle."+(i?"emphasis":"normal")+".labelLine.show")},refresh:function(e){e&&(this.option=e,this.series=e.series),this.backupShapeList(),this._buildShape()}},h.inherits(t,i),e("../chart").define("funnel",t),t}),i("echarts/chart/eventRiver",["require","./base","../layout/eventRiver","zrender/shape/Polygon","../component/axis","../component/grid","../component/dataZoom","../config","../util/ecData","../util/date","zrender/tool/util","zrender/tool/color","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o);var r=this;r._ondragend=function(){r.isDragend=!0},this.refresh(a)}var i=e("./base"),n=e("../layout/eventRiver"),a=e("zrender/shape/Polygon");e("../component/axis"),e("../component/grid"),e("../component/dataZoom");var o=e("../config");o.eventRiver={zlevel:0,z:2,clickable:!0,legendHoverLink:!0,itemStyle:{normal:{borderColor:"rgba(0,0,0,0)",borderWidth:1,label:{show:!0,position:"inside",formatter:"{b}"}},emphasis:{borderColor:"rgba(0,0,0,0)",borderWidth:1,label:{show:!0}}}};var r=e("../util/ecData"),s=e("../util/date"),l=e("zrender/tool/util"),h=e("zrender/tool/color");return t.prototype={type:o.CHART_TYPE_EVENTRIVER,_buildShape:function(){var e=this.series;this.selectedMap={},this._dataPreprocessing();for(var t=this.component.legend,i=[],a=0;an;n++)if(i[n].type===this.type){e=this.component.xAxis.getAxis(i[n].xAxisIndex||0);for(var o=0,r=i[n].data.length;r>o;o++){t=i[n].data[o].evolution;for(var l=0,h=t.length;h>l;l++)t[l].timeScale=e.getCoord(s.getNewDate(t[l].time)-0),t[l].valueScale=Math.pow(t[l].value,.8)}}this._intervalX=Math.round(this.component.grid.getWidth()/40)},_drawEventRiver:function(){for(var e=this.series,t=0;ta)){for(var o=[],r=[],s=0;a>s;s++)o.push(n[s].timeScale),r.push(n[s].valueScale);var l=[];l.push([o[0],i]);var s=0;for(s=0;a-1>s;s++)l.push([(o[s]+o[s+1])/2,r[s]/-2+i]);for(l.push([(o[s]+(o[s]+t))/2,r[s]/-2+i]),l.push([o[s]+t,i]),l.push([(o[s]+(o[s]+t))/2,r[s]/2+i]),s=a-1;s>0;s--)l.push([(o[s]+o[s-1])/2,r[s-1]/2+i]);return l}},ondragend:function(e,t){this.isDragend&&e.target&&(t.dragOut=!0,t.dragIn=!0,t.needRefresh=!1,this.isDragend=!1)},refresh:function(e){e&&(this.option=e,this.series=e.series),this.backupShapeList(),this._buildShape()}},l.inherits(t,i),e("../chart").define("eventRiver",t),t}),i("echarts/layout/eventRiver",["require"],function(){function e(e,i,o){function r(e,t){var i=e.importance,n=t.importance;return i>n?-1:n>i?1:0}for(var s=4,l=0;l=e)return[0];for(var t=[];e--;)t.push(0);return t}(),u=c.slice(0),y=[],g=0,b=0,l=0;l.5?.5:1,r=t.y,s=(t.height-n)/i,l=0,h=e.length;h>l;l++){var m=e[l];m.y=r+s*m.y+m._offset*o,delete m.time,delete m.value,delete m.xpx,delete m.ypx,delete m._offset;for(var V=m.evolution,U=0,d=V.length;d>U;U++)V[U].valueScale*=s}}function i(e,t,i,n){if(e===i)throw new Error("x0 is equal with x1!!!");if(t===n)return function(){return t};var a=(t-n)/(e-i),o=(n*e-t*i)/(e-i);return function(e){return a*e+o}}function n(e,t,n){var a=~~t,o=e.time.length;e.xpx=[],e.ypx=[];for(var r,s=0,l=0,h=0,m=0,V=0;o>s;s++){l=~~e.time[s],m=e.value[s]/2,s===o-1?(h=l+a,V=0):(h=~~e.time[s+1],V=e.value[s+1]/2),r=i(l,m,h,V);for(var U=l;h>U;U++)e.xpx.push(U-n),e.ypx.push(r(U))}e.xpx.push(h-n),e.ypx.push(V)}function a(e,t,i){for(var n,a=0,o=t.xpx.length,r=0;o>r;r++)n=i(t,r),a=Math.max(a,n+e[t.xpx[r]]);for(r=0;o>r;r++)n=i(t,r),e[t.xpx[r]]=a+n;return a}return e}),i("echarts/chart/venn",["require","./base","zrender/shape/Text","zrender/shape/Circle","zrender/shape/Path","../config","../util/ecData","zrender/tool/util","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o),this.refresh(a)}var i=e("./base"),n=e("zrender/shape/Text"),a=e("zrender/shape/Circle"),o=e("zrender/shape/Path"),r=e("../config");r.venn={zlevel:0,z:1,calculable:!1};var s=e("../util/ecData"),l=e("zrender/tool/util");return t.prototype={type:r.CHART_TYPE_VENN,_buildShape:function(){this.selectedMap={},this._symbol=this.option.symbolList,this._queryTarget,this._dropBoxList=[],this._vennDataCounter=0;for(var e=this.series,t=this.component.legend,i=0;ia[1].value?(t=this.zr.getHeight()/3,i=t*Math.sqrt(a[1].value)/Math.sqrt(a[0].value)):(i=this.zr.getHeight()/3,t=i*Math.sqrt(a[0].value)/Math.sqrt(a[1].value));var o=this.zr.getWidth()/2-t,r=(t+i)/2*Math.sqrt(a[2].value)/Math.sqrt((a[0].value+a[1].value)/2),s=t+i;0!==a[2].value&&(s=this._getCoincideLength(a[0].value,a[1].value,a[2].value,t,i,r,Math.abs(t-i),t+i));var l=o+s,h=this.zr.getHeight()/2;if(this._buildItem(e,0,a[0],o,h,t),this._buildItem(e,1,a[1],l,h,i),0!==a[2].value&&a[2].value!==a[0].value&&a[2].value!==a[1].value){var m=(t*t-i*i)/(2*s)+s/2,V=s/2-(t*t-i*i)/(2*s),U=Math.sqrt(t*t-m*m),d=0,p=0;a[0].value>a[1].value&&o+m>l&&(p=1),a[0].valuel&&(d=1),this._buildCoincideItem(e,2,a[2],o+m,h-U,h+U,t,i,d,p)}},_getCoincideLength:function(e,t,i,n,a,o,r,s){var l=(n*n-a*a)/(2*o)+o/2,h=o/2-(n*n-a*a)/(2*o),m=Math.acos(l/n),V=Math.acos(h/a),U=n*n*Math.PI,d=m*n*n-l*n*Math.sin(m)+V*a*a-h*a*Math.sin(V),p=d/U,c=i/e,u=Math.abs(p/c);return u>.999&&1.001>u?o:.999>=u?(s=o,o=(o+r)/2,this._getCoincideLength(e,t,i,n,a,o,r,s)):(r=o,o=(o+s)/2,this._getCoincideLength(e,t,i,n,a,o,r,s))},_buildItem:function(e,t,i,n,a,o){var r=this.series,l=r[e],h=this.getCircle(e,t,i,n,a,o);if(s.pack(h,l,e,i,t,i.name),this.shapeList.push(h),l.itemStyle.normal.label.show){var m=this.getLabel(e,t,i,n,a,o);s.pack(m,l,e,l.data[t],t,l.data[t].name),this.shapeList.push(m)}},_buildCoincideItem:function(e,t,i,n,a,r,l,h,m,V){var U=this.series,d=U[e],p=[i,d],c=this.deepMerge(p,"itemStyle.normal")||{},u=this.deepMerge(p,"itemStyle.emphasis")||{},y=c.color||this.zr.getColor(t),g=u.color||this.zr.getColor(t),b="M"+n+","+a+"A"+l+","+l+",0,"+m+",1,"+n+","+r+"A"+h+","+h+",0,"+V+",1,"+n+","+a,f={color:y,path:b},k={zlevel:d.zlevel,z:d.z,style:f,highlightStyle:{color:g,lineWidth:u.borderWidth,strokeColor:u.borderColor}};k=new o(k),k.buildPathArray&&(k.style.pathArray=k.buildPathArray(f.path)),s.pack(k,U[e],0,i,t,i.name),this.shapeList.push(k)},getCircle:function(e,t,i,n,o,r){var s=this.series[e],l=[i,s],h=this.deepMerge(l,"itemStyle.normal")||{},m=this.deepMerge(l,"itemStyle.emphasis")||{},V=h.color||this.zr.getColor(t),U=m.color||this.zr.getColor(t),d={zlevel:s.zlevel,z:s.z,clickable:!0,style:{x:n,y:o,r:r,brushType:"fill",opacity:1,color:V},highlightStyle:{color:U,lineWidth:m.borderWidth,strokeColor:m.borderColor}};return this.deepQuery([i,s,this.option],"calculable")&&(this.setCalculable(d),d.draggable=!0),new a(d)},getLabel:function(e,t,i,a,o,r){var s=this.series[e],l=s.itemStyle,h=[i,s],m=this.deepMerge(h,"itemStyle.normal")||{},V="normal",U=l[V].label,d=U.textStyle||{},p=this.getLabelText(t,i,V),c=this.getFont(d),u=m.color||this.zr.getColor(t),y=d.fontSize||12,g={zlevel:s.zlevel,z:s.z,style:{x:a,y:o-r-y,color:d.color||u,text:p,textFont:c,textAlign:"center"}};return new n(g)},getLabelText:function(e,t,i){var n=this.series,a=n[0],o=this.deepQuery([t,a],"itemStyle."+i+".label.formatter");return o?"function"==typeof o?o(a.name,t.name,t.value):"string"==typeof o?(o=o.replace("{a}","{a0}").replace("{b}","{b0}").replace("{c}","{c0}"),o=o.replace("{a0}",a.name).replace("{b0}",t.name).replace("{c0}",t.value)):void 0:t.name},refresh:function(e){e&&(this.option=e,this.series=e.series),this._buildShape()}},l.inherits(t,i),e("../chart").define("venn",t),t}),i("echarts/chart/treemap",["require","./base","zrender/tool/area","zrender/shape/Rectangle","zrender/shape/Text","zrender/shape/Line","../layout/TreeMap","../data/TreeView","../config","../util/ecData","zrender/config","zrender/tool/event","zrender/tool/util","zrender/tool/color","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o),this.refresh(a);var r=this;r._onclick=function(e){return r.__onclick(e)},r.zr.on(V.EVENT.CLICK,r._onclick)}var i=e("./base"),n=e("zrender/tool/area"),a=e("zrender/shape/Rectangle"),o=e("zrender/shape/Text"),r=e("zrender/shape/Line"),s=e("../layout/TreeMap"),l=e("../data/TreeView"),h=e("../config");h.treemap={zlevel:0,z:1,calculable:!1,clickable:!0,center:["50%","50%"],size:["80%","80%"],root:"",itemStyle:{normal:{label:{ +show:!0,x:5,y:12,textStyle:{align:"left",color:"#000",fontFamily:"Arial",fontSize:13,fontStyle:"normal",fontWeight:"normal"}},breadcrumb:{show:!0,textStyle:{}},borderWidth:1,borderColor:"#ccc",childBorderWidth:1,childBorderColor:"#ccc"},emphasis:{}}};var m=e("../util/ecData"),V=e("zrender/config"),U=(e("zrender/tool/event"),e("zrender/tool/util")),d=e("zrender/tool/color");return t.prototype={type:h.CHART_TYPE_TREEMAP,refresh:function(e){this.clear(),e&&(this.option=e,this.series=this.option.series),this._treesMap={};for(var t=this.series,i=this.component.legend,n=0;nt.width||e.normal.label.y+U>t.height)&&(h=""):h="",e.emphasis.label.show?(s.x+u>t.width||s.y+y>t.height)&&(p=""):p="";var g={style:{textX:t.x+e.normal.label.x,textY:t.y+e.normal.label.y,text:h,textPosition:"specific",textColor:o.color,textFont:m},highlightStyle:{textX:t.x+e.emphasis.label.x,textY:t.y+e.emphasis.label.y,text:p,textColor:s.color,textPosition:"specific"}};return g},getLabelText:function(e,t,i){return i?"function"==typeof i?i.call(this.myChart,e,t):"string"==typeof i?(i=i.replace("{b}","{b0}").replace("{c}","{c0}"),i=i.replace("{b0}",e).replace("{c0}",t)):void 0:e},_buildChildrenTreemap:function(e,t,i,n){for(var a=i.width*i.height,o=0,r=[],l=0;l ":"")},V),clickable:!0,highlightStyle:p});m.set(u,"seriesIndex",t),m.set(u,"name",a[c]),i+=u.getRect(u.style).width,this.shapeList.push(u)}},__onclick:function(e){var t=e.target;if(t){var i=m.get(t,"seriesIndex"),n=m.get(t,"name"),a=this._treesMap[i],o=a.getNodeById(n);o&&o.children.length&&this._buildTreemap(o,i)}}},U.inherits(t,i),e("../chart").define("treemap",t),t}),i("echarts/layout/TreeMap",["require"],function(){function e(e){({x:e.x,y:e.y,width:e.width,height:e.height});this.x=e.x,this.y=e.y,this.width=e.width,this.height=e.height}return e.prototype.run=function(e){var t=[];return this._squarify(e,{x:this.x,y:this.y,width:this.width,height:this.height},t),t},e.prototype._squarify=function(e,t,i){var n="VERTICAL",a=t.width,o=t.height;t.widthl;l++)r[s].y+=r[l].height}var h={};if("VERTICAL"==n){for(var m=0;ml;l++){var h=i*e[l]/o;a.push({width:s,height:h})}return a},e.prototype._isFirstBetter=function(e,t){var i=e[0].height/e[0].width;i=i>1?1/i:i;var n=t[0].height/t[0].width;return n=n>1?1/n:n,Math.abs(i-1)<=Math.abs(n-1)?!0:!1},e}),i("echarts/data/TreeView",["require","zrender/tool/util"],function(e){function t(e,t){this.id=e,this.depth=0,this.height=0,this.children=[],this.parent=null,this.data=t||null}function i(e){this.root=new t(e)}var n=e("zrender/tool/util");return t.prototype.add=function(e){var t=this.children;e.parent!==this&&(t.push(e),e.parent=this)},t.prototype.remove=function(e){var t=this.children,i=n.indexOf(t,e);i>=0&&(t.splice(i,1),e.parent=null)},t.prototype.traverse=function(e,t){e.call(t,this);for(var i=0;it&&(t=n.height)}this.height=t+1},t.prototype.getNodeById=function(e){if(this.id===e)return this;for(var t=0;t0&&this._buildLink(i,e)},this);var n=e.roam===!0||"move"===e.roam,a=e.roam===!0||"scale"===e.roam;this.zr.modLayer(this.getZlevelBase(),{panable:n,zoomable:a}),(this.query("markPoint.effect.show")||this.query("markLine.effect.show"))&&this.zr.modLayer(m.EFFECT_ZLEVEL,{panable:n,zoomable:a}),this.addShapeList()},_buildItem:function(e,t,i){var n=[e.data,t],r=this.deepQuery(n,"symbol"),s=this.deepMerge(n,"itemStyle.normal")||{},l=this.deepMerge(n,"itemStyle.emphasis")||{},h=s.color||this.zr.getColor(),m=l.color||this.zr.getColor(),U=-e.layout.angle||0;e.id===this.tree.root.id&&(U=0);var d="right";Math.abs(U)>=Math.PI/2&&Math.abs(U)<3*Math.PI/2&&(U+=Math.PI,d="left");var p=[U,e.layout.position[0],e.layout.position[1]],c=new a({zlevel:this.getZlevelBase(),z:this.getZBase()+1,rotation:p,clickable:this.deepQuery(n,"clickable"),style:{x:e.layout.position[0]-.5*e.layout.width,y:e.layout.position[1]-.5*e.layout.height,width:e.layout.width,height:e.layout.height,iconType:r,color:h,brushType:"both",lineWidth:s.borderWidth,strokeColor:s.borderColor},highlightStyle:{color:m,lineWidth:l.borderWidth,strokeColor:l.borderColor}});c.style.iconType.match("image")&&(c.style.image=c.style.iconType.replace(new RegExp("^image:\\/\\/"),""),c=new o({rotation:p,style:c.style,highlightStyle:c.highlightStyle,clickable:c.clickable,zlevel:this.getZlevelBase(),z:this.getZBase()})),this.deepQuery(n,"itemStyle.normal.label.show")&&(c.style.text=null==e.data.label?e.id:e.data.label,c.style.textPosition=this.deepQuery(n,"itemStyle.normal.label.position"),"radial"===t.orient&&"inside"!==c.style.textPosition&&(c.style.textPosition=d),c.style.textColor=this.deepQuery(n,"itemStyle.normal.label.textStyle.color"),c.style.textFont=this.getFont(this.deepQuery(n,"itemStyle.normal.label.textStyle")||{})),this.deepQuery(n,"itemStyle.emphasis.label.show")&&(c.highlightStyle.textPosition=this.deepQuery(n,"itemStyle.emphasis.label.position"),c.highlightStyle.textColor=this.deepQuery(n,"itemStyle.emphasis.label.textStyle.color"),c.highlightStyle.textFont=this.getFont(this.deepQuery(n,"itemStyle.emphasis.label.textStyle")||{})),V.pack(c,t,i,e.data,0,e.id),this.shapeList.push(c)},_buildLink:function(e,t){var i=t.itemStyle.normal.lineStyle;if("broken"===i.type)return void this._buildBrokenLine(e,i,t);for(var n=0;nr&&(t=r),r>n&&(n=r)}e.layout.position[0]=e.children.length>0?(t+n)/2:0;var s=this._layerOffsets[e.depth]||0;if(s>e.layout.position[0]){var l=s-e.layout.position[0];this._shiftSubtree(e,l);for(var a=e.depth+1;ai;i++)this._buildTextShape(e[i],0,i);this.addShapeList()},_buildTextShape:function(e,t,i){var a=this.series,o=a[t],s=o.name||"",h=o.data[i],m=[h,o],V=this.component.legend,U=V?V.getColor(s):this.zr.getColor(t),d=this.deepMerge(m,"itemStyle.normal")||{},p=this.deepMerge(m,"itemStyle.emphasis")||{},c=this.getItemStyleColor(d.color,t,i,h)||U,u=this.getItemStyleColor(p.color,t,i,h)||("string"==typeof c?l.lift(c,-.2):c),y=new n({zlevel:o.zlevel,z:o.z,hoverable:!0,clickable:this.deepQuery(m,"clickable"),style:{x:0,y:0,text:e.text,color:c,textFont:[e.style,e.weight,e.size+"px",e.font].join(" "),textBaseline:"alphabetic",textAlign:"center"},highlightStyle:{brushType:p.borderWidth?"both":"fill",color:u,lineWidth:p.borderWidth||0,strokeColor:p.borderColor},position:[e.x,e.y],rotation:[-e.rotate/180*Math.PI,0,0]});r.pack(y,o,t,h,i,h.name),this.shapeList.push(y)}},s.inherits(t,i),e("../chart").define("wordCloud",t),t}),i("echarts/layout/WordCloud",["require","../layout/WordCloudRectZero","zrender/tool/util"],function(e){function t(e){this._init(e)}var i=e("../layout/WordCloudRectZero"),n=e("zrender/tool/util");return t.prototype={start:function(){function e(){p.totalArea=r,U.autoSizeCal.enable&&p._autoCalTextSize(m,r,a,o,U.autoSizeCal.minSize),V.timer&&clearInterval(V.timer),V.timer=setInterval(t,0),t()}function t(){for(var e,t=+new Date,i=m.length;+new Date-t>1,e.y=d[1]>>1,p._cloudSprite(e,m,s),e.hasText&&p._place(n,e,h)&&(l.push(e),e.x-=d[0]>>1,e.y-=d[1]>>1);s>=i&&(p.stop(),p._fixTagPosition(l),V.endcallback(l))}var n=null,a=0,o=0,r=0,s=-1,l=[],h=null,m=this.wordsdata,V=this.defaultOption,U=V.wordletype,d=V.size,p=this,c=new i({type:U.type,width:d[0],height:d[1]});return c.calculate(function(t){n=t.initarr,a=t.maxWit,o=t.maxHit,r=t.area,h=t.imgboard,e()},this),this},_fixTagPosition:function(e){for(var t=this.defaultOption.center,i=0,n=e.length;n>i;i++)e[i].x+=t[0],e[i].y+=t[1]},stop:function(){return this.defaultOption.timer&&(clearInterval(this.defaultOption.timer),this.defaultOption.timer=null),this},end:function(e){return e&&(this.defaultOption.endcallback=e),this},_init:function(e){this.defaultOption={},this._initProperty(e),this._initMethod(e),this._initCanvas(),this._initData(e.data)},_initData:function(e){var t=this,i=t.defaultOption;this.wordsdata=e.map(function(e,n){return e.text=i.text.call(t,e,n),e.font=i.font.call(t,e,n),e.style=i.fontStyle.call(t,e,n),e.weight=i.fontWeight.call(t,e,n),e.rotate=i.rotate.call(t,e,n),e.size=~~i.fontSize.call(t,e,n),e.padding=i.padding.call(t,e,n),e}).sort(function(e,t){return t.value-e.value})},_initMethod:function(e){function t(e){return e.name}function i(){return"sans-serif"}function n(){return"normal"}function a(e){return e.value}function o(){return 0}function r(e){return function(){return e[Math.round(Math.random()*(e.length-1))]}}function s(){return 0}function l(e){var t=e[0]/e[1];return function(e){return[t*(e*=.1)*Math.cos(e),e*Math.sin(e)]}}function h(e){var t=4,i=t*e[0]/e[1],n=0,a=0;return function(e){var o=0>e?-1:1;switch(Math.sqrt(1+4*o*e)-o&3){case 0:n+=i;break;case 1:a+=t;break;case 2:n-=i;break;default:a-=t}return[n,a]}}function m(e){return"function"==typeof e?e:function(){return e}}var V=this.defaultOption;V.text=e.text?m(e.text):t,V.font=e.font?m(e.font):i,V.fontSize=e.fontSize?m(e.fontSize):a,V.fontStyle=e.fontStyle?m(e.fontStyle):n,V.fontWeight=e.fontWeight?m(e.fontWeight):n,V.rotate=e.rotate?r(e.rotate):o,V.padding=e.padding?m(e.padding):s,V.center=e.center,V.spiral=l,V.endcallback=function(){},V.rectangularSpiral=h,V.archimedeanSpiral=l},_initProperty:function(e){var t=this.defaultOption;t.size=e.size||[256,256],t.wordletype=e.wordletype,t.words=e.words||[],t.timeInterval=1/0,t.timer=null,t.spirals={archimedean:t.archimedeanSpiral,rectangular:t.rectangularSpiral},n.merge(t,{size:[256,256],wordletype:{type:"RECT",areaPresent:.058,autoSizeCal:{enable:!0,minSize:12}}})},_initCanvas:function(){var e,t=Math.PI/180,i=64,n=2048,a=1;"undefined"!=typeof document?(e=document.createElement("canvas"),e.width=1,e.height=1,a=Math.sqrt(e.getContext("2d").getImageData(0,0,1,1).data.length>>2),e.width=(i<<5)/a,e.height=n/a):e=new Canvas(i<<5,n);var o=e.getContext("2d");o.fillStyle=o.strokeStyle="red",o.textAlign="center",this.defaultOption.c=o,this.defaultOption.cw=i,this.defaultOption.ch=n,this.defaultOption.ratio=a,this.defaultOption.cloudRadians=t},_cloudSprite:function(e,t,i){if(!e.sprite){var n=this.defaultOption.cw,a=this.defaultOption.ch,o=this.defaultOption.c,r=this.defaultOption.ratio,s=this.defaultOption.cloudRadians;o.clearRect(0,0,(n<<5)/r,a/r);var l=0,h=0,m=0,V=t.length;for(--i;++i>5<<5,d=~~Math.max(Math.abs(y+g),Math.abs(y-g))}else U=U+31>>5<<5;if(d>m&&(m=d),l+U>=n<<5&&(l=0,h+=m,m=0),h+d>=a)break;o.translate((l+(U>>1))/r,(h+(d>>1))/r),e.rotate&&o.rotate(e.rotate*s),o.fillText(e.text,0,0),e.padding&&(o.lineWidth=2*e.padding,o.strokeText(e.text,0,0)),o.restore(),e.width=U,e.height=d,e.xoff=l,e.yoff=h,e.x1=U>>1,e.y1=d>>1,e.x0=-e.x1,e.y0=-e.y1,e.hasText=!0,l+=U}for(var f=o.getImageData(0,0,(n<<5)/r,a/r).data,k=[];--i>=0;)if(e=t[i],e.hasText){for(var U=e.width,x=U>>5,d=e.y1-e.y0,_=0;d*x>_;_++)k[_]=0;if(l=e.xoff,null==l)return;h=e.yoff;for(var L=0,W=-1,X=0;d>X;X++){for(var _=0;U>_;_++){var v=x*X+(_>>5),w=f[(h+X)*(n<<5)+(l+_)<<2]?1<<31-_%32:0;k[v]|=w,L|=w}L?W=X:(e.y0++,d--,X--,h++)}e.y1=e.y0+W,e.sprite=k.slice(0,(e.y1-e.y0)*x)}}},_place:function(e,t,i){function n(e,t,i){i>>=5;for(var n,a=e.sprite,o=e.width>>5,r=e.x-(o<<4),s=127&r,l=32-s,h=e.y1-e.y0,m=(e.y+e.y0)*i+(r>>5),V=0;h>V;V++){n=0;for(var U=0;o>=U;U++)if((n<U?(n=a[V*o+U])>>>s:0))&t[m+U])return!0;m+=i}return!1}function a(e,t){return t.row[e.y]&&t.cloumn[e.x]&&e.x>=t.row[e.y].start&&e.x<=t.row[e.y].end&&e.y>=t.cloumn[e.x].start&&e.y<=t.cloumn[e.x].end}for(var o,r,s,l=this.defaultOption.size,h=([{x:0,y:0},{x:l[0],y:l[1]}],t.x),m=t.y,V=Math.sqrt(l[0]*l[0]+l[1]*l[1]),U=this.defaultOption.spiral(l),d=Math.random()<.5?1:-1,p=-d;(o=U(p+=d))&&(r=~~o[0],s=~~o[1],!(Math.min(r,s)>V));)if(t.x=h+r,t.y=m+s,!(t.x+t.x0<0||t.y+t.y0<0||t.x+t.x1>l[0]||t.y+t.y1>l[1])&&!n(t,e,l[0])&&a(t,i)){for(var c,u=t.sprite,y=t.width>>5,g=l[0]>>5,b=t.x-(y<<4),f=127&b,k=32-f,x=t.y1-t.y0,_=(t.y+t.y0)*g+(b>>5),L=0;x>L;L++){c=0;for(var W=0;y>=W;W++)e[_+W]|=c<W?(c=u[L*y+W])>>>f:0);_+=g}return delete t.sprite,!0}return!1},_autoCalTextSize:function(e,t,i,n,a){function o(e){c.clearRect(0,0,(d<<5)/u,p/u),c.save(),c.font=e.style+" "+e.weight+" "+~~((e.size+1)/u)+"px "+e.font;var t=c.measureText(e.text+"m").width*u,r=e.size<<1;t=t+31>>5<<5,c.restore(),e.aw=t,e.ah=r;var s,l,h;if(e.rotate){var m=Math.sin(e.rotate*y),V=Math.cos(e.rotate*y),g=t*V,b=t*m,f=r*V,k=r*m;l=Math.max(Math.abs(g+k),Math.abs(g-k))+31>>5<<5,h=~~Math.max(Math.abs(b+f),Math.abs(b-f))}return e.size<=U||e.rotate&&t*r<=e.area&&i>=l&&n>=h||t*r<=e.area&&i>=t&&n>=r?void(e.area=t*r):(s=e.rotate&&l>i&&h>n?Math.min(i/l,n/h):t>i||r>n?Math.min(i/t,n/r):Math.sqrt(e.area/(e.aw*e.ah)),e.size=~~(s*e.size),e.sizel?l:V:l,s.area=t*s.areapre,s.totalarea=t,o(s)}},t}),i("echarts/layout/WordCloudRectZero",["require"],function(){function e(e){this.defaultOption={type:"RECT"},this._init(e)}return e.prototype={RECT:"_calculateRect",_init:function(e){this._initOption(e),this._initCanvas()},_initOption:function(e){for(k in e)this.defaultOption[k]=e[k]},_initCanvas:function(){var e=document.createElement("canvas");e.width=1,e.height=1;var t=Math.sqrt(e.getContext("2d").getImageData(0,0,1,1).data.length>>2);if(e.width=this.defaultOption.width,e.height=this.defaultOption.height,e.getContext)var i=e.getContext("2d");this.canvas=e,this.ctx=i,this.ratio=t},calculate:function(e,t){var i=this.defaultOption.type,n=this[i];this[n].call(this,e,t)},_calculateReturn:function(e,t,i){t.call(i,e)},_calculateRect:function(e,t){var i={},n=this.defaultOption.width>>5<<5,a=this.defaultOption.height;i.initarr=this._rectZeroArray(n*a),i.area=n*a,i.maxHit=a,i.maxWit=n,i.imgboard=this._rectBoard(n,a),this._calculateReturn(i,e,t)},_rectBoard:function(e,t){for(var i=[],n=0;t>n;n++)i.push({y:n,start:0,end:e});for(var a=[],n=0;e>n;n++)a.push({x:n,start:0,end:t});return{row:i,cloumn:a}},_rectZeroArray:function(e){for(var t=[],i=e,n=-1;++ni;++i)if(e[i].type===a.CHART_TYPE_HEATMAP){e[i]=this.reformOption(e[i]);var o=new n(e[i]),s=o.getCanvas(e[i].data,this.zr.getWidth(),this.zr.getHeight()),l=new r({position:[0,0],scale:[1,1],hoverable:this.option.hoverable,style:{x:0,y:0,image:s,width:s.width,height:s.height}});this.shapeList.push(l)}this.addShapeList()}},o.inherits(t,i),e("../chart").define("heatmap",t),t});var n=t("zrender");n.tool={color:t("zrender/tool/color"),math:t("zrender/tool/math"),util:t("zrender/tool/util"),vector:t("zrender/tool/vector"),area:t("zrender/tool/area"),event:t("zrender/tool/event")},n.animation={Animation:t("zrender/animation/Animation"),Cip:t("zrender/animation/Clip"),easing:t("zrender/animation/easing")};var a=t("echarts");a.config=t("echarts/config"),a.util={mapData:{params:t("echarts/util/mapData/params")}},t("echarts/chart/line"),t("echarts/chart/bar"),t("echarts/chart/scatter"),t("echarts/chart/k"),t("echarts/chart/pie"),t("echarts/chart/radar"),t("echarts/chart/chord"),t("echarts/chart/force"),t("echarts/chart/map"),t("echarts/chart/gauge"),t("echarts/chart/funnel"),t("echarts/chart/eventRiver"),t("echarts/chart/venn"),t("echarts/chart/treemap"),t("echarts/chart/tree"),t("echarts/chart/wordCloud"),t("echarts/chart/heatmap"),e.echarts=a,e.zrender=n}(window); \ No newline at end of file diff --git a/apps/static/js/plugins/highcharts/modules/heatmap.src.js b/apps/static/js/plugins/highcharts/modules/heatmap.src.js index 52946cc0d..0057e25e4 100644 --- a/apps/static/js/plugins/highcharts/modules/heatmap.src.js +++ b/apps/static/js/plugins/highcharts/modules/heatmap.src.js @@ -570,7 +570,7 @@ seriesTypes.heatmap = extendClass(seriesTypes.scatter, merge(colorSeriesMixin, { y1 = Math.round(yAxis.translate(point.y - yPad, 0, 1, 0, 1)), y2 = Math.round(yAxis.translate(point.y + yPad, 0, 1, 0, 1)); - // Set plotX and plotY for use in K-D-Tree and more + // Set plotX and plotY for use in K-D-TreeView and more point.plotX = (x1 + x2) / 2; point.plotY = (y1 + y2) / 2; From 1ac30ed09cb27a6397438ae9395a87ef0b628a47 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 31 Jan 2018 12:46:33 +0800 Subject: [PATCH 089/197] =?UTF-8?q?[Feature]=20=E4=BD=BF=E7=94=A8ztree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api.py | 18 +- apps/assets/templates/assets/tree.html | 279 ++++++++++++++++++------- apps/assets/views/tree.py | 2 +- 3 files changed, 213 insertions(+), 86 deletions(-) diff --git a/apps/assets/api.py b/apps/assets/api.py index c61bf1e83..31577ec18 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -319,13 +319,13 @@ class TreeViewApi(APIView): def get(self, request): data = [] for node in self.get_queryset(): - if node.id == "0": - parent = "#" - else: - parent = ":".join(node.id.split(":")[:-1]) - data.append({ + parent = ":".join(node.id.split(":")[:-1]) + d = { "id": node.id, - "parent": parent, - "text": node.name - }) - return Response(data) \ No newline at end of file + "pId": parent, + "name": node.name + } + if node.id == "0": + d["open"] = True + data.append(d) + return Response(data) diff --git a/apps/assets/templates/assets/tree.html b/apps/assets/templates/assets/tree.html index acca9462c..d9796b592 100644 --- a/apps/assets/templates/assets/tree.html +++ b/apps/assets/templates/assets/tree.html @@ -4,6 +4,20 @@ {% block custom_head_css_js %} + +{# #} + + + + {% endblock %} {% block content %} @@ -14,7 +28,7 @@
    Tree View
    -
    +
    @@ -22,49 +36,54 @@
    -
    - -
    - - -
    - - - - - - - - - - - - - - - - -
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Cluster' %}{% trans 'Hardware' %}{% trans 'Active' %}{% trans 'Reachable' %}{% trans 'Action' %}
    -
    -
    - -
    - +
    +
    + +
    + + +
    + + + + + + + + + + + + + + + + +
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Cluster' %}{% trans 'Hardware' %}{% trans 'Active' %}{% trans 'Reachable' %}{% trans 'Action' %}
    +
    +
    + +
    + +
    +
    +
    +
    @@ -121,46 +140,154 @@ op_html: $('#actions').html() }; return jumpserver.initServerSideDataTable(options); + } + function beforeDrag(treeId, treeNodes) { + showLog("Before drag"); + return false; + } + + function beforeEditName(treeId, treeNode) { + showLog("Before edit name"); + className = (className === "dark" ? "" : "dark"); + showLog("[ " + getTime() + " beforeEditName ]     " + treeNode.name); + var zTree = $.fn.zTree.getZTreeObj("treeDemo"); + zTree.selectNode(treeNode); + setTimeout(function () { + if (confirm("进入节点 -- " + treeNode.name + " 的编辑状态吗?")) { + setTimeout(function () { + zTree.editName(treeNode); + }, 0); + } + }, 0); + return false; + } + + function beforeRemove(treeId, treeNode) { + showLog("[ " + getTime() + " beforeRemove ]     " + treeNode.name); + className = (className === "dark" ? "" : "dark"); + var zTree = $.fn.zTree.getZTreeObj("treeDemo"); + zTree.selectNode(treeNode); + return confirm("确认删除 节点 -- " + treeNode.name + " 吗?"); + } + + function onRemove(e, treeId, treeNode) { + showLog("Remove node") + } + + function beforeRename(treeId, treeNode, newName, isCancel) { + showLog((isCancel ? "" : "") + "[ " + getTime() + " beforeRename ]     " + treeNode.name + (isCancel ? "" : "")); + className = (className === "dark" ? "" : "dark"); + if (newName.length == 0) { + setTimeout(function () { + var zTree = $.fn.zTree.getZTreeObj("treeDemo"); + zTree.cancelEditName(); + alert("节点名称不能为空."); + }, 0); + return false; + } + return true; + } + + function onRename(e, treeId, treeNode, isCancel) { + console.log("On remname"); + {#showLog((isCancel ? "" : "") + "[ " + getTime() + " onRename ]     " + treeNode.name + (isCancel ? "" : ""));#} + } + + function showRemoveBtn(treeId, treeNode) { + showLog("Show remove btn"); + return !treeNode.isFirstNode; + } + + function showRenameBtn(treeId, treeNode) { + showLog("Show rename btn"); + return !treeNode.isLastNode; + } + + function showLog(str) { + console.log(str) + } + + function getTime() { + var now = new Date(), + h = now.getHours(), + m = now.getMinutes(), + s = now.getSeconds(), + ms = now.getMilliseconds(); + return (h + ":" + m + ":" + s + " " + ms); + } + + var newCount = 1; + + function addHoverDom(treeId, treeNode) { + var sObj = $("#" + treeNode.tId + "_span"); + if (treeNode.editNameFlag || $("#addBtn_" + treeNode.tId).length > 0) return; + var addStr = ""; + sObj.after(addStr); + var btn = $("#addBtn_" + treeNode.tId); + if (btn) btn.bind("click", function () { + var zTree = $.fn.zTree.getZTreeObj("treeDemo"); + zTree.addNodes(treeNode, { + id: (100 + newCount), + pId: treeNode.id, + name: "new node" + (newCount++) + }); + return false; + }); + }; + + function removeHoverDom(treeId, treeNode) { + showLog("Remove hove dom"); + $("#addBtn_" + treeNode.tId).unbind().remove(); + }; + + function selectAll() { + var zTree = $.fn.zTree.getZTreeObj("treeDemo"); + zTree.setting.edit.editNameSelectAll = $("#selectAll").attr("checked"); + } + + function initTree() { + var setting = { + view: { + addHoverDom: addHoverDom, + removeHoverDom: removeHoverDom, + selectedMulti: false + }, + edit: { + enable: true, + editNameSelectAll: true, + showRemoveBtn: showRemoveBtn, + showRenameBtn: showRenameBtn + }, + data: { + simpleData: { + enable: true + } + }, + callback: { + beforeDrag: beforeDrag, + beforeEditName: beforeEditName, + beforeRemove: beforeRemove, + beforeRename: beforeRename, + onRemove: onRemove, + onRename: onRename + } + }; + + var zNodes = []; + $.get("{% url 'api-assets:tree-view' %}", function(data, status){ + zNodes = data; + console.log(data); + console.log(status); + $.fn.zTree.init($("#treeDemo"), setting, zNodes); + }); + $("#selectAll").bind("click", selectAll); } $(document).ready(function(){ initTable(); - $('#jstree').jstree({ - 'core' : { - 'check_callback' : true, - 'data': { - 'url': '{% url "api-assets:tree-view" %}', - 'data': function (node) { - return {'id': node.id} - } - } - }, - 'plugins' : [ 'types', 'dnd'], - "checkbox" : { - "keep_selected_style" : true - }, - 'types' : { - 'default' : { - 'icon' : 'fa fa-folder' - }, - 'html' : { - 'icon' : 'fa fa-file-code-o' - }, - 'svg' : { - 'icon' : 'fa fa-file-picture-o' - }, - 'css' : { - 'icon' : 'fa fa-file-code-o' - }, - 'img' : { - 'icon' : 'fa fa-file-image-o' - }, - 'js' : { - 'icon' : 'fa fa-file-text-o' - } - } - }); - }) + initTree(); + }); {% endblock %} \ No newline at end of file diff --git a/apps/assets/views/tree.py b/apps/assets/views/tree.py index 60213e85a..707662851 100644 --- a/apps/assets/views/tree.py +++ b/apps/assets/views/tree.py @@ -16,7 +16,7 @@ class TreeView(AdminUserRequiredMixin, TemplateView): def get_context_data(self, **kwargs): context = { 'app': _('Assets'), - 'action': _('TreeView view'), + 'action': _('Tree view'), } kwargs.update(context) return super().get_context_data(**kwargs) From 2f06a2b1d3027db914290aa553d2fd9dbd210fc1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 31 Jan 2018 17:01:21 +0800 Subject: [PATCH 090/197] =?UTF-8?q?[Feature]=20tree=E5=A2=9E=E5=88=A0?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api.py | 55 ++++-- apps/assets/serializers.py | 27 ++- apps/assets/templates/assets/tree.html | 242 +++++++++++++------------ apps/assets/urls/api_urls.py | 3 +- 4 files changed, 192 insertions(+), 135 deletions(-) diff --git a/apps/assets/api.py b/apps/assets/api.py index 31577ec18..e70a4e97b 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from rest_framework import generics +from rest_framework import generics, mixins from rest_framework.views import APIView from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet @@ -21,6 +21,7 @@ from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView from rest_framework.pagination import LimitOffsetPagination from django.shortcuts import get_object_or_404 from django.db.models import Q, Count +from django.utils.translation import ugettext_lazy as _ from common.mixins import CustomFilterMixin from common.utils import get_logger @@ -311,21 +312,41 @@ class LabelViewSet(BulkModelViewSet): return super().list(request, *args, **kwargs) -class TreeViewApi(APIView): +class NodeViewSet(BulkModelViewSet): + queryset = Node.objects.all() + permission_classes = (IsSuperUser,) + serializer_class = serializers.NodeSerializer - def get_queryset(self): - return Node.objects.all() + def perform_create(self, serializer): + child_id = Node.get_root_node().get_next_child_id() + serializer.validated_data["id"] = child_id + serializer.save() - def get(self, request): - data = [] - for node in self.get_queryset(): - parent = ":".join(node.id.split(":")[:-1]) - d = { - "id": node.id, - "pId": parent, - "name": node.name - } - if node.id == "0": - d["open"] = True - data.append(d) - return Response(data) + +class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): + queryset = Node.objects.all() + permission_classes = (IsSuperUser,) + serializer_class = serializers.NodeSerializer + instance = None + + def post(self, request, *args, **kwargs): + if not request.data.get("name"): + request.data["name"] = _("New node {}").format( + Node.get_root_node().get_next_child_id().split(":")[-1] + ) + return super().post(request, *args, **kwargs) + + def create(self, request, *args, **kwargs): + instance = self.get_object() + name = request.data.get("name") + node = instance.create_child(name=name) + return Response({"id": node.id, "name": node.name}, status=201) + + def get(self, request, *args, **kwargs): + instance = self.get_object() + if self.request.query_params.get("all"): + children = instance.get_all_children() + else: + children = instance.get_children() + response = [{"id": node.id, "name": node.name} for node in children] + return Response(response, status=200) diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index 08a27b358..fcd3a0a13 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -4,8 +4,8 @@ from rest_framework import serializers from rest_framework_bulk.serializers import BulkListSerializer from common.mixins import BulkSerializerMixin -from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser, Label -from .const import ADMIN_USER_CONN_CACHE_KEY, SYSTEM_USER_CONN_CACHE_KEY +from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser, Label, Node +from .const import ADMIN_USER_CONN_CACHE_KEY class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): @@ -316,3 +316,26 @@ class LabelDistinctSerializer(serializers.ModelSerializer): def get_value(obj): labels = Label.objects.filter(name=obj["name"]) return ', '.join([label.value for label in labels]) + + +class NodeSerializer(serializers.ModelSerializer): + parent = serializers.SerializerMethodField() + + class Meta: + model = Node + fields = ['id', 'name', 'parent'] + list_serializer_class = BulkListSerializer + + @staticmethod + def get_parent(obj): + if obj.id == "0": + return "#" + if not obj.id.startswith("0"): + return "0" + return ":".join(obj.id.split(":")[:-1]) + + def get_fields(self): + fields = super().get_fields() + field = fields["id"] + field.required = False + return fields diff --git a/apps/assets/templates/assets/tree.html b/apps/assets/templates/assets/tree.html index d9796b592..8fed0cf35 100644 --- a/apps/assets/templates/assets/tree.html +++ b/apps/assets/templates/assets/tree.html @@ -13,8 +13,26 @@ }); @@ -28,8 +46,9 @@
    Tree View
    -
    +
    +
    @@ -83,16 +102,25 @@
    -
    + + {% endblock %} {% block custom_foot_js %} - + {% endblock %} -{% block help_message %} -
    -
    -{% endblock %} - -{% block table_container %} -
    - - {% trans "Create permission" %} - +{% block content %} +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + +
    + + {% trans 'Node' %}{% trans 'User group' %}{% trans 'System user' %}{% trans 'Is active' %}{% trans 'Date expired' %}{% trans 'Action' %}
    +
    +
    +
    - - - - - - - - - - - - - - -
    - - {% trans 'Node' %}{% trans 'User group' %}{% trans 'System user' %}{% trans 'Is active' %}{% trans 'Date expired' %}{% trans 'Action' %}
    {% endblock %} {% block custom_foot_js %} + {% endblock %} diff --git a/apps/perms/views.py b/apps/perms/views.py index b5163946c..120fb07e5 100644 --- a/apps/perms/views.py +++ b/apps/perms/views.py @@ -10,9 +10,9 @@ from django.urls import reverse_lazy from django.contrib.messages.views import SuccessMessageMixin from django.views.generic.detail import DetailView, SingleObjectMixin -from common.const import create_success_msg, update_success_msg +from common.utils import get_object_or_none from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \ - Asset, AssetGroup + Asset, AssetGroup, Node from .models import AssetPermission, NodePermission from .forms import AssetPermissionForm @@ -37,6 +37,15 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView): template_name = 'perms/asset_permission_create_update.html' success_url = reverse_lazy('perms:asset-permission-list') + def get_form(self, form_class=None): + form = super().get_form(form_class=form_class) + node_id = self.request.GET.get("node_id") + node = get_object_or_none(Node, id=node_id) + if not node: + node = Node.root() + form['node'].initial = node + return form + def get_context_data(self, **kwargs): context = { 'app': _('Perms'), diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 5f0b148a0..00cf1f55c 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -530,4 +530,44 @@ function createPopover(dataset, title, callback) { var html = "" + dataset.length + ""; return html; -} \ No newline at end of file +} + + + $(function () { + (function ($) { + $.getUrlParam = function (name) { + var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); + var r = window.location.search.substr(1).match(reg); + if (r != null) return unescape(r[2]); return null; + } + })(jQuery); +}); + +function getUrlParam(name) { + var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); + var r = window.location.search.substr(1).match(reg); + if (r != null) return unescape(r[2]); return null; +} + +function setUrlParam(url, name, value) { + var urlArray = url.split("?"); + if (urlArray.length===1){ + url += "?" + name + "=" + value; + } else { + var oriParam = urlArray[1].split("&"); + var oriParamMap = {}; + $.each(oriParam, function (index, value) { + var v = value.split("="); + oriParamMap[v[0]] = v[1]; + }); + oriParamMap[name] = value; + url = urlArray[0] + "?"; + var newParam = []; + $.each(oriParamMap, function (index, value) { + console.log(index, value); + newParam.push(index + "=" + value); + }); + url += newParam.join("&") + } + return url +} diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 1edadb8e5..c7dafcc95 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -20,8 +20,6 @@
    + {% if form.non_field_errors %} +
    + {{ form.non_field_errors }} +
    + {% endif %}
    {% csrf_token %}

    {% trans 'Basic' %}

    - {% bootstrap_field form.node layout="horizontal" %} +
    + +
    + +
    +
    + {{ form.node }} {% bootstrap_field form.user_group layout="horizontal" %} {% bootstrap_field form.system_user layout="horizontal" %}
    diff --git a/apps/perms/urls/views_urls.py b/apps/perms/urls/views_urls.py index 5c3fa3b48..a4a6e49a5 100644 --- a/apps/perms/urls/views_urls.py +++ b/apps/perms/urls/views_urls.py @@ -9,10 +9,10 @@ urlpatterns = [ url(r'^asset-permission$', views.AssetPermissionListView.as_view(), name='asset-permission-list'), url(r'^asset-permission/create$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'), url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/update$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'), - url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'), + # url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'), url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/delete$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'), - url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/user$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'), - url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/asset$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'), + # url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/user$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'), + # url(r'^asset-permission/(?P[0-9a-zA-Z\-]{36})/asset$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'), ] diff --git a/apps/perms/utils.py b/apps/perms/utils.py index 8f494f2f8..e09186d86 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils.py @@ -2,14 +2,21 @@ from __future__ import absolute_import, unicode_literals import collections +from django.utils import timezone from common.utils import setattr_bulk, get_logger from .tasks import push_users -from .hands import User, UserGroup, Asset, AssetGroup, SystemUser +from .hands import AssetGroup logger = get_logger(__file__) +def get_user_group_permissions(user_group): + return user_group.nodepermission_set.all() \ + .filter(is_active=True) \ + .filter(date_expired=timezone.now()) + + def get_user_group_granted_asset_groups(user_group): """Return asset groups granted of the user group diff --git a/apps/perms/views.py b/apps/perms/views.py index 120fb07e5..2b691459b 100644 --- a/apps/perms/views.py +++ b/apps/perms/views.py @@ -7,7 +7,6 @@ from django.conf import settings from django.views.generic import ListView, CreateView, UpdateView from django.views.generic.edit import DeleteView from django.urls import reverse_lazy -from django.contrib.messages.views import SuccessMessageMixin from django.views.generic.detail import DetailView, SingleObjectMixin from common.utils import get_object_or_none @@ -61,6 +60,11 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView): template_name = 'perms/asset_permission_create_update.html' success_url = reverse_lazy("perms:asset-permission-list") + def get_form(self, form_class=None): + form = super().get_form(form_class=form_class) + form['node'].initial = form.instance.node + return form + def get_context_data(self, **kwargs): context = { 'app': _('Perms'), @@ -70,22 +74,22 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView): return super().get_context_data(**kwargs) -class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView): - template_name = 'perms/asset_permission_detail.html' - context_object_name = 'asset_permission' - model = AssetPermission - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Asset permission detail'), - 'system_users_remain': [ - system_user for system_user in SystemUser.objects.all() - if system_user not in self.object.system_users.all()], - 'system_users': self.object.system_users.all(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) +# class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView): +# template_name = 'perms/asset_permission_detail.html' +# context_object_name = 'asset_permission' +# model = AssetPermission +# +# def get_context_data(self, **kwargs): +# context = { +# 'app': _('Perms'), +# 'action': _('Asset permission detail'), +# 'system_users_remain': [ +# system_user for system_user in SystemUser.objects.all() +# if system_user not in self.object.system_users.all()], +# 'system_users': self.object.system_users.all(), +# } +# kwargs.update(context) +# return super().get_context_data(**kwargs) class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView): @@ -94,61 +98,61 @@ class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView): success_url = reverse_lazy('perms:asset-permission-list') -class AssetPermissionUserView(AdminUserRequiredMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/asset_permission_user.html' - context_object_name = 'asset_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=AssetPermission.objects.all()) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - queryset = self.object.get_granted_users() - return queryset - - def get_context_data(self, **kwargs): - users_granted = self.get_queryset() - groups_granted = self.object.user_groups.all() - context = { - 'app': _('Perms'), - 'action': _('Asset permission user list'), - 'users_remain': User.objects.exclude(id__in=[user.id for user in users_granted]), - 'user_groups': self.object.user_groups.all(), - 'user_groups_remain': UserGroup.objects.exclude(id__in=[group.id for group in groups_granted]) - } - kwargs.update(context) - return super().get_context_data(**kwargs) +# class AssetPermissionUserView(AdminUserRequiredMixin, +# SingleObjectMixin, +# ListView): +# template_name = 'perms/asset_permission_user.html' +# context_object_name = 'asset_permission' +# paginate_by = settings.DISPLAY_PER_PAGE +# object = None +# +# def get(self, request, *args, **kwargs): +# self.object = self.get_object(queryset=AssetPermission.objects.all()) +# return super().get(request, *args, **kwargs) +# +# def get_queryset(self): +# queryset = self.object.get_granted_users() +# return queryset +# +# def get_context_data(self, **kwargs): +# users_granted = self.get_queryset() +# groups_granted = self.object.user_groups.all() +# context = { +# 'app': _('Perms'), +# 'action': _('Asset permission user list'), +# 'users_remain': User.objects.exclude(id__in=[user.id for user in users_granted]), +# 'user_groups': self.object.user_groups.all(), +# 'user_groups_remain': UserGroup.objects.exclude(id__in=[group.id for group in groups_granted]) +# } +# kwargs.update(context) +# return super().get_context_data(**kwargs) -class AssetPermissionAssetView(AdminUserRequiredMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/asset_permission_asset.html' - context_object_name = 'asset_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=AssetPermission.objects.all()) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - queryset = self.object.get_granted_assets() - return queryset - - def get_context_data(self, **kwargs): - assets_granted = self.get_queryset() - groups_granted = self.object.asset_groups.all() - context = { - 'app': _('Perms'), - 'action': _('Asset permission asset list'), - 'assets_remain': Asset.objects.exclude(id__in=[asset.id for asset in assets_granted]), - 'asset_groups': self.object.asset_groups.all(), - 'asset_groups_remain': AssetGroup.objects.exclude(id__in=[group.id for group in groups_granted]) - } - kwargs.update(context) - return super().get_context_data(**kwargs) +# class AssetPermissionAssetView(AdminUserRequiredMixin, +# SingleObjectMixin, +# ListView): +# template_name = 'perms/asset_permission_asset.html' +# context_object_name = 'asset_permission' +# paginate_by = settings.DISPLAY_PER_PAGE +# object = None +# +# def get(self, request, *args, **kwargs): +# self.object = self.get_object(queryset=AssetPermission.objects.all()) +# return super().get(request, *args, **kwargs) +# +# def get_queryset(self): +# queryset = self.object.get_granted_assets() +# return queryset +# +# def get_context_data(self, **kwargs): +# assets_granted = self.get_queryset() +# groups_granted = self.object.asset_groups.all() +# context = { +# 'app': _('Perms'), +# 'action': _('Asset permission asset list'), +# 'assets_remain': Asset.objects.exclude(id__in=[asset.id for asset in assets_granted]), +# 'asset_groups': self.object.asset_groups.all(), +# 'asset_groups_remain': AssetGroup.objects.exclude(id__in=[group.id for group in groups_granted]) +# } +# kwargs.update(context) +# return super().get_context_data(**kwargs) From 114289edafccd18803e32078675c09ab4f675a71 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 2 Feb 2018 19:04:09 +0800 Subject: [PATCH 108/197] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E6=94=B9=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E7=B3=BB=E7=BB=9F=E7=94=A8=E6=88=B7=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms.py | 2 +- apps/assets/tasks.py | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index d8cd0b352..a7ba67364 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -308,7 +308,7 @@ class SystemUserForm(PasswordAndKeyAuthForm): auto_generate_key = self.cleaned_data.get('auto_generate_key', False) private_key, public_key = super().gen_keys() - if not self.instance and auto_generate_key: + if auto_generate_key: logger.info('Auto generate key and set system user auth') system_user.auto_gen_auth() else: diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index ca0a32e4e..4fc5b023b 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -314,8 +314,10 @@ def get_push_system_user_tasks(system_user): if system_user.username == "root": return [] - tasks = [ - { + tasks = [] + + if system_user.password: + tasks.append({ 'name': 'Add user {}'.format(system_user.username), 'action': { 'module': 'user', @@ -324,8 +326,9 @@ def get_push_system_user_tasks(system_user): encrypt_password(system_user.password, salt="K3mIlKK"), ), } - }, - { + }) + if system_user.public_key: + tasks.append({ 'name': 'Set {} authorized key'.format(system_user.username), 'action': { 'module': 'authorized_key', @@ -333,8 +336,9 @@ def get_push_system_user_tasks(system_user): system_user.username, system_user.public_key ) } - }, - { + }) + if system_user.sudo: + tasks.append({ 'name': 'Set {} sudo setting'.format(system_user.username), 'action': { 'module': 'lineinfile', @@ -345,8 +349,7 @@ def get_push_system_user_tasks(system_user): system_user.sudo, ) } - } - ] + }) return tasks From 6edd3f6cf8e56df2c75a68786f46bc4e49f399a8 Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Mon, 5 Feb 2018 21:54:57 +0800 Subject: [PATCH 109/197] feat: rdp support --- apps/assets/models/user.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index aa44a539c..e536f7902 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -212,8 +212,10 @@ class AdminUser(AssetUser): class SystemUser(AssetUser): SSH_PROTOCOL = 'ssh' + RDP_PROTOCOL = 'rdp' PROTOCOL_CHOICES = ( (SSH_PROTOCOL, 'ssh'), + (RDP_PROTOCOL, 'rdp'), ) cluster = models.ManyToManyField('assets.Cluster', blank=True, verbose_name=_("Cluster")) From bd4768c14795db59df3a868371b43228ad84f9eb Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 6 Feb 2018 18:32:02 +0800 Subject: [PATCH 110/197] =?UTF-8?q?[Update]=20=E6=8B=86=E5=88=86asset=20ap?= =?UTF-8?q?i=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api.py | 359 ------------------ apps/assets/api/__init__.py | 7 + apps/assets/api/admin_user.py | 71 ++++ apps/assets/api/asset.py | 124 ++++++ apps/assets/api/cluster.py | 73 ++++ apps/assets/api/group.py | 68 ++++ apps/assets/api/label.py | 38 ++ apps/assets/api/system_user.py | 85 +++++ apps/assets/api/tree.py | 72 ++++ .../_asset_group_bulk_update_modal.html | 1 - .../templates/assets/_asset_list_modal.html | 119 ++++++ .../templates/assets/asset_modal_list.html | 123 ------ apps/assets/templates/assets/tree.html | 22 +- apps/perms/utils.py | 35 +- apps/static/js/jumpserver.js | 20 +- apps/users/models/user.py | 3 + 16 files changed, 713 insertions(+), 507 deletions(-) delete mode 100644 apps/assets/api.py create mode 100644 apps/assets/api/__init__.py create mode 100644 apps/assets/api/admin_user.py create mode 100644 apps/assets/api/asset.py create mode 100644 apps/assets/api/cluster.py create mode 100644 apps/assets/api/group.py create mode 100644 apps/assets/api/label.py create mode 100644 apps/assets/api/system_user.py create mode 100644 apps/assets/api/tree.py create mode 100644 apps/assets/templates/assets/_asset_list_modal.html delete mode 100644 apps/assets/templates/assets/asset_modal_list.html diff --git a/apps/assets/api.py b/apps/assets/api.py deleted file mode 100644 index 71399c78e..000000000 --- a/apps/assets/api.py +++ /dev/null @@ -1,359 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved. -# -# Licensed under the GNU General Public License v2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.gnu.org/licenses/gpl-2.0.html -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from rest_framework import generics, mixins -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework_bulk import BulkModelViewSet -from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView -from rest_framework.pagination import LimitOffsetPagination -from django.shortcuts import get_object_or_404 -from django.db.models import Q, Count -from django.utils.translation import ugettext_lazy as _ - -from common.mixins import IDInFilterMixin -from common.utils import get_logger -from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \ - get_user_granted_assets -from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser, Label, Node -from . import serializers -from .tasks import update_asset_hardware_info_manual, test_admin_user_connectability_manual, \ - test_asset_connectability_manual, push_system_user_to_cluster_assets_manual, \ - test_system_user_connectability_manual -from .utils import LabelFilter - - -logger = get_logger(__file__) - - -class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): - """ - API endpoint that allows Asset to be viewed or edited. - """ - filter_fields = ("hostname", "ip") - search_fields = filter_fields - ordering_fields = ("hostname", "ip", "port", "cluster", "cpu_cores") - queryset = Asset.objects.all() - serializer_class = serializers.AssetSerializer - pagination_class = LimitOffsetPagination - permission_classes = (IsSuperUserOrAppUser,) - - def get_queryset(self): - queryset = super().get_queryset() - cluster_id = self.request.query_params.get('cluster_id') - asset_group_id = self.request.query_params.get('asset_group_id') - admin_user_id = self.request.query_params.get('admin_user_id') - system_user_id = self.request.query_params.get('system_user_id') - node_id = self.request.query_params.get("node_id") - - if cluster_id: - queryset = queryset.filter(cluster__id=cluster_id) - if asset_group_id: - queryset = queryset.filter(groups__id=asset_group_id) - if admin_user_id: - admin_user = get_object_or_404(AdminUser, id=admin_user_id) - assets_direct = [asset.id for asset in admin_user.asset_set.all()] - clusters = [cluster.id for cluster in admin_user.cluster_set.all()] - queryset = queryset.filter(Q(cluster__id__in=clusters)|Q(id__in=assets_direct)) - if system_user_id: - system_user = get_object_or_404(SystemUser, id=system_user_id) - clusters = system_user.get_clusters() - queryset = queryset.filter(cluster__in=clusters) - if node_id: - node = get_object_or_404(Node, id=node_id) - queryset = queryset.filter(nodes__key__startswith=node.key) - return queryset - - -class UserAssetListView(generics.ListAPIView): - queryset = Asset.objects.all() - serializer_class = serializers.AssetSerializer - permission_classes = (IsValidUser,) - - def get_queryset(self): - assets_granted = get_user_granted_assets(self.request.user) - queryset = self.queryset.filter( - id__in=[asset.id for asset in assets_granted] - ) - return queryset - - -class AssetGroupViewSet(IDInFilterMixin, BulkModelViewSet): - """ - Asset group api set, for add,delete,update,list,retrieve resource - """ - queryset = AssetGroup.objects.all().annotate(asset_count=Count("assets")) - serializer_class = serializers.AssetGroupSerializer - permission_classes = (IsSuperUser,) - - -class GroupUpdateAssetsApi(generics.RetrieveUpdateAPIView): - """ - Asset group, update it's asset member - """ - queryset = AssetGroup.objects.all() - serializer_class = serializers.GroupUpdateAssetsSerializer - permission_classes = (IsSuperUser,) - - -class GroupAddAssetsApi(generics.UpdateAPIView): - queryset = AssetGroup.objects.all() - serializer_class = serializers.GroupUpdateAssetsSerializer - permission_classes = (IsSuperUser,) - - def update(self, request, *args, **kwargs): - group = self.get_object() - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - assets = serializer.validated_data['assets'] - group.assets.add(*tuple(assets)) - return Response({"msg": "ok"}) - else: - return Response({'error': serializer.errors}, status=400) - - -class ClusterViewSet(IDInFilterMixin, BulkModelViewSet): - """ - Cluster api set, for add,delete,update,list,retrieve resource - """ - queryset = Cluster.objects.all() - serializer_class = serializers.ClusterSerializer - permission_classes = (IsSuperUser,) - - -class ClusterTestAssetsAliveApi(generics.RetrieveAPIView): - """ - Test cluster asset can connect using admin user or not - """ - queryset = Cluster.objects.all() - permission_classes = (IsSuperUser,) - - def retrieve(self, request, *args, **kwargs): - cluster = self.get_object() - admin_user = cluster.admin_user - test_admin_user_connectability_manual.delay(admin_user) - return Response("Task has been send, seen left assets status") - - -class ClusterAddAssetsApi(generics.UpdateAPIView): - queryset = Cluster.objects.all() - serializer_class = serializers.ClusterUpdateAssetsSerializer - permission_classes = (IsSuperUser,) - - def update(self, request, *args, **kwargs): - cluster = self.get_object() - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - assets = serializer.validated_data['assets'] - for asset in assets: - asset.cluster = cluster - asset.save() - return Response({"msg": "ok"}) - else: - return Response({'error': serializer.errors}, status=400) - - -class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet): - """ - Admin user api set, for add,delete,update,list,retrieve resource - """ - queryset = AdminUser.objects.all() - serializer_class = serializers.AdminUserSerializer - permission_classes = (IsSuperUser,) - - -class AdminUserAddClustersApi(generics.UpdateAPIView): - queryset = AdminUser.objects.all() - serializer_class = serializers.AdminUserUpdateClusterSerializer - permission_classes = (IsSuperUser,) - - def update(self, request, *args, **kwargs): - admin_user = self.get_object() - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - clusters = serializer.validated_data['clusters'] - for cluster in clusters: - cluster.admin_user = admin_user - cluster.save() - return Response({"msg": "ok"}) - else: - return Response({'error': serializer.errors}, status=400) - - -class SystemUserViewSet(BulkModelViewSet): - """ - System user api set, for add,delete,update,list,retrieve resource - """ - queryset = SystemUser.objects.all() - serializer_class = serializers.SystemUserSerializer - permission_classes = (IsSuperUserOrAppUser,) - - -class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): - """ - Asset bulk update api - """ - queryset = Asset.objects.all() - serializer_class = serializers.AssetSerializer - permission_classes = (IsSuperUser,) - - -class SystemUserAuthInfoApi(generics.RetrieveAPIView): - """ - Get system user auth info - """ - queryset = SystemUser.objects.all() - permission_classes = (IsSuperUserOrAppUser,) - - def retrieve(self, request, *args, **kwargs): - system_user = self.get_object() - data = { - 'id': system_user.id, - 'name': system_user.name, - 'username': system_user.username, - 'password': system_user.password, - 'private_key': system_user.private_key, - } - return Response(data) - - -class AssetRefreshHardwareApi(generics.RetrieveAPIView): - """ - Refresh asset hardware info - """ - queryset = Asset.objects.all() - serializer_class = serializers.AssetSerializer - permission_classes = (IsSuperUser,) - - def retrieve(self, request, *args, **kwargs): - asset_id = kwargs.get('pk') - asset = get_object_or_404(Asset, pk=asset_id) - summary = update_asset_hardware_info_manual(asset)[1] - logger.debug("Refresh summary: {}".format(summary)) - if summary.get('dark'): - return Response(summary['dark'].values(), status=501) - else: - return Response({"msg": "ok"}) - - -class AssetAdminUserTestApi(generics.RetrieveAPIView): - """ - Test asset admin user connectivity - """ - queryset = Asset.objects.all() - permission_classes = (IsSuperUser,) - - def retrieve(self, request, *args, **kwargs): - asset_id = kwargs.get('pk') - asset = get_object_or_404(Asset, pk=asset_id) - ok, msg = test_asset_connectability_manual(asset) - if ok: - return Response({"msg": "pong"}) - else: - return Response({"error": msg}, status=502) - - -class AdminUserTestConnectiveApi(generics.RetrieveAPIView): - """ - Test asset admin user connectivity - """ - queryset = AdminUser.objects.all() - permission_classes = (IsSuperUser,) - - def retrieve(self, request, *args, **kwargs): - admin_user = self.get_object() - test_admin_user_connectability_manual.delay(admin_user) - return Response({"msg": "Task created"}) - - -class SystemUserPushApi(generics.RetrieveAPIView): - """ - Push system user to cluster assets api - """ - queryset = SystemUser.objects.all() - permission_classes = (IsSuperUser,) - - def retrieve(self, request, *args, **kwargs): - system_user = self.get_object() - push_system_user_to_cluster_assets_manual.delay(system_user) - return Response({"msg": "Task created"}) - - -class SystemUserTestConnectiveApi(generics.RetrieveAPIView): - """ - Push system user to cluster assets api - """ - queryset = SystemUser.objects.all() - permission_classes = (IsSuperUser,) - - def retrieve(self, request, *args, **kwargs): - system_user = self.get_object() - test_system_user_connectability_manual.delay(system_user) - return Response({"msg": "Task created"}) - - -class LabelViewSet(BulkModelViewSet): - queryset = Label.objects.annotate(asset_count=Count("assets")) - permission_classes = (IsSuperUser,) - serializer_class = serializers.LabelSerializer - - def list(self, request, *args, **kwargs): - if request.query_params.get("distinct"): - self.serializer_class = serializers.LabelDistinctSerializer - self.queryset = self.queryset.values("name").distinct() - return super().list(request, *args, **kwargs) - - -class NodeViewSet(BulkModelViewSet): - queryset = Node.objects.all() - permission_classes = (IsSuperUser,) - serializer_class = serializers.NodeSerializer - - def perform_create(self, serializer): - child_key = Node.root().get_next_child_key() - serializer.validated_data["key"] = child_key - serializer.save() - - -class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): - queryset = Node.objects.all() - permission_classes = (IsSuperUser,) - serializer_class = serializers.NodeSerializer - instance = None - - def post(self, request, *args, **kwargs): - if not request.data.get("value"): - request.data["value"] = _("New node {}").format( - Node.root().get_next_child_key().split(":")[-1] - ) - return super().post(request, *args, **kwargs) - - def create(self, request, *args, **kwargs): - instance = self.get_object() - value = request.data.get("value") - node = instance.create_child(value=value) - return Response( - {"id": node.id, "key": node.key, "value": node.value}, - status=201, - ) - - def get(self, request, *args, **kwargs): - instance = self.get_object() - if self.request.query_params.get("all"): - children = instance.get_all_children() - else: - children = instance.get_children() - response = [{"id": node.id, "key": node.key, "value": node.value} for node in children] - return Response(response, status=200) diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py new file mode 100644 index 000000000..4aa9966b6 --- /dev/null +++ b/apps/assets/api/__init__.py @@ -0,0 +1,7 @@ +from .admin_user import * +from .asset import * +from .cluster import * +from .group import * +from .label import * +from .system_user import * +from .tree import * \ No newline at end of file diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py new file mode 100644 index 000000000..3960ab50c --- /dev/null +++ b/apps/assets/api/admin_user.py @@ -0,0 +1,71 @@ +# ~*~ coding: utf-8 ~*~ +# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved. +# +# Licensed under the GNU General Public License v2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.gnu.org/licenses/gpl-2.0.html +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from rest_framework import generics +from rest_framework.response import Response +from rest_framework_bulk import BulkModelViewSet + +from common.mixins import IDInFilterMixin +from common.utils import get_logger +from ..hands import IsSuperUser +from ..models import AdminUser +from .. import serializers +from ..tasks import test_admin_user_connectability_manual + + +logger = get_logger(__file__) +__all__ = [ + 'AdminUserViewSet', 'AdminUserAddClustersApi', 'AdminUserTestConnectiveApi' +] + + +class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet): + """ + Admin user api set, for add,delete,update,list,retrieve resource + """ + queryset = AdminUser.objects.all() + serializer_class = serializers.AdminUserSerializer + permission_classes = (IsSuperUser,) + + +class AdminUserAddClustersApi(generics.UpdateAPIView): + queryset = AdminUser.objects.all() + serializer_class = serializers.AdminUserUpdateClusterSerializer + permission_classes = (IsSuperUser,) + + def update(self, request, *args, **kwargs): + admin_user = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + clusters = serializer.validated_data['clusters'] + for cluster in clusters: + cluster.admin_user = admin_user + cluster.save() + return Response({"msg": "ok"}) + else: + return Response({'error': serializer.errors}, status=400) + + +class AdminUserTestConnectiveApi(generics.RetrieveAPIView): + """ + Test asset admin user connectivity + """ + queryset = AdminUser.objects.all() + permission_classes = (IsSuperUser,) + + def retrieve(self, request, *args, **kwargs): + admin_user = self.get_object() + test_admin_user_connectability_manual.delay(admin_user) + return Response({"msg": "Task created"}) \ No newline at end of file diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py new file mode 100644 index 000000000..140981263 --- /dev/null +++ b/apps/assets/api/asset.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import generics +from rest_framework.response import Response +from rest_framework_bulk import BulkModelViewSet +from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView +from rest_framework.pagination import LimitOffsetPagination +from django.shortcuts import get_object_or_404 +from django.db.models import Q + +from common.mixins import IDInFilterMixin +from common.utils import get_logger +from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \ + get_user_granted_assets +from ..models import Asset, SystemUser, AdminUser, Node +from .. import serializers +from ..tasks import update_asset_hardware_info_manual, \ + test_asset_connectability_manual +from ..utils import LabelFilter + + +logger = get_logger(__file__) +__all__ = [ + 'AssetViewSet', 'UserAssetListView', 'AssetListUpdateApi', + 'AssetRefreshHardwareApi', 'AssetAdminUserTestApi' +] + + +class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): + """ + API endpoint that allows Asset to be viewed or edited. + """ + filter_fields = ("hostname", "ip") + search_fields = filter_fields + ordering_fields = ("hostname", "ip", "port", "cluster", "cpu_cores") + queryset = Asset.objects.all() + serializer_class = serializers.AssetSerializer + pagination_class = LimitOffsetPagination + permission_classes = (IsSuperUserOrAppUser,) + + def get_queryset(self): + queryset = super().get_queryset() + cluster_id = self.request.query_params.get('cluster_id') + asset_group_id = self.request.query_params.get('asset_group_id') + admin_user_id = self.request.query_params.get('admin_user_id') + system_user_id = self.request.query_params.get('system_user_id') + node_id = self.request.query_params.get("node_id") + + if cluster_id: + queryset = queryset.filter(cluster__id=cluster_id) + if asset_group_id: + queryset = queryset.filter(groups__id=asset_group_id) + if admin_user_id: + admin_user = get_object_or_404(AdminUser, id=admin_user_id) + assets_direct = [asset.id for asset in admin_user.asset_set.all()] + clusters = [cluster.id for cluster in admin_user.cluster_set.all()] + queryset = queryset.filter(Q(cluster__id__in=clusters)|Q(id__in=assets_direct)) + if system_user_id: + system_user = get_object_or_404(SystemUser, id=system_user_id) + clusters = system_user.get_clusters() + queryset = queryset.filter(cluster__in=clusters) + if node_id: + node = get_object_or_404(Node, id=node_id) + queryset = queryset.filter(nodes__key__startswith=node.key) + return queryset + + +class UserAssetListView(generics.ListAPIView): + queryset = Asset.objects.all() + serializer_class = serializers.AssetSerializer + permission_classes = (IsValidUser,) + + def get_queryset(self): + assets_granted = get_user_granted_assets(self.request.user) + queryset = self.queryset.filter( + id__in=[asset.id for asset in assets_granted] + ) + return queryset + + +class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): + """ + Asset bulk update api + """ + queryset = Asset.objects.all() + serializer_class = serializers.AssetSerializer + permission_classes = (IsSuperUser,) + + +class AssetRefreshHardwareApi(generics.RetrieveAPIView): + """ + Refresh asset hardware info + """ + queryset = Asset.objects.all() + serializer_class = serializers.AssetSerializer + permission_classes = (IsSuperUser,) + + def retrieve(self, request, *args, **kwargs): + asset_id = kwargs.get('pk') + asset = get_object_or_404(Asset, pk=asset_id) + summary = update_asset_hardware_info_manual(asset)[1] + logger.debug("Refresh summary: {}".format(summary)) + if summary.get('dark'): + return Response(summary['dark'].values(), status=501) + else: + return Response({"msg": "ok"}) + + +class AssetAdminUserTestApi(generics.RetrieveAPIView): + """ + Test asset admin user connectivity + """ + queryset = Asset.objects.all() + permission_classes = (IsSuperUser,) + + def retrieve(self, request, *args, **kwargs): + asset_id = kwargs.get('pk') + asset = get_object_or_404(Asset, pk=asset_id) + ok, msg = test_asset_connectability_manual(asset) + if ok: + return Response({"msg": "pong"}) + else: + return Response({"error": msg}, status=502) \ No newline at end of file diff --git a/apps/assets/api/cluster.py b/apps/assets/api/cluster.py new file mode 100644 index 000000000..3f0cd1585 --- /dev/null +++ b/apps/assets/api/cluster.py @@ -0,0 +1,73 @@ +# ~*~ coding: utf-8 ~*~ +# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved. +# +# Licensed under the GNU General Public License v2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.gnu.org/licenses/gpl-2.0.html +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from rest_framework import generics +from rest_framework.response import Response +from rest_framework_bulk import BulkModelViewSet + + +from common.mixins import IDInFilterMixin +from common.utils import get_logger +from ..hands import IsSuperUser +from ..models import Cluster +from .. import serializers +from ..tasks import test_admin_user_connectability_manual + + +logger = get_logger(__file__) +__all__ = [ + 'ClusterViewSet', 'ClusterTestAssetsAliveApi', 'ClusterAddAssetsApi', +] + + +class ClusterViewSet(IDInFilterMixin, BulkModelViewSet): + """ + Cluster api set, for add,delete,update,list,retrieve resource + """ + queryset = Cluster.objects.all() + serializer_class = serializers.ClusterSerializer + permission_classes = (IsSuperUser,) + + +class ClusterTestAssetsAliveApi(generics.RetrieveAPIView): + """ + Test cluster asset can connect using admin user or not + """ + queryset = Cluster.objects.all() + permission_classes = (IsSuperUser,) + + def retrieve(self, request, *args, **kwargs): + cluster = self.get_object() + admin_user = cluster.admin_user + test_admin_user_connectability_manual.delay(admin_user) + return Response("Task has been send, seen left assets status") + + +class ClusterAddAssetsApi(generics.UpdateAPIView): + queryset = Cluster.objects.all() + serializer_class = serializers.ClusterUpdateAssetsSerializer + permission_classes = (IsSuperUser,) + + def update(self, request, *args, **kwargs): + cluster = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + assets = serializer.validated_data['assets'] + for asset in assets: + asset.cluster = cluster + asset.save() + return Response({"msg": "ok"}) + else: + return Response({'error': serializer.errors}, status=400) \ No newline at end of file diff --git a/apps/assets/api/group.py b/apps/assets/api/group.py new file mode 100644 index 000000000..eae848466 --- /dev/null +++ b/apps/assets/api/group.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# +# ~*~ coding: utf-8 ~*~ +# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved. +# +# Licensed under the GNU General Public License v2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.gnu.org/licenses/gpl-2.0.html +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from rest_framework import generics +from rest_framework.response import Response +from rest_framework_bulk import BulkModelViewSet +from django.db.models import Count + +from common.mixins import IDInFilterMixin +from common.utils import get_logger +from ..hands import IsSuperUser +from ..models import AssetGroup +from .. import serializers + + +logger = get_logger(__file__) + +__all__ = [ + 'AssetGroupViewSet', 'GroupUpdateAssetsApi', 'GroupAddAssetsApi' +] + + +class AssetGroupViewSet(IDInFilterMixin, BulkModelViewSet): + """ + Asset group api set, for add,delete,update,list,retrieve resource + """ + queryset = AssetGroup.objects.all().annotate(asset_count=Count("assets")) + serializer_class = serializers.AssetGroupSerializer + permission_classes = (IsSuperUser,) + + +class GroupUpdateAssetsApi(generics.RetrieveUpdateAPIView): + """ + Asset group, update it's asset member + """ + queryset = AssetGroup.objects.all() + serializer_class = serializers.GroupUpdateAssetsSerializer + permission_classes = (IsSuperUser,) + + +class GroupAddAssetsApi(generics.UpdateAPIView): + queryset = AssetGroup.objects.all() + serializer_class = serializers.GroupUpdateAssetsSerializer + permission_classes = (IsSuperUser,) + + def update(self, request, *args, **kwargs): + group = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + assets = serializer.validated_data['assets'] + group.assets.add(*tuple(assets)) + return Response({"msg": "ok"}) + else: + return Response({'error': serializer.errors}, status=400) \ No newline at end of file diff --git a/apps/assets/api/label.py b/apps/assets/api/label.py new file mode 100644 index 000000000..858834d0a --- /dev/null +++ b/apps/assets/api/label.py @@ -0,0 +1,38 @@ +# ~*~ coding: utf-8 ~*~ +# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved. +# +# Licensed under the GNU General Public License v2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.gnu.org/licenses/gpl-2.0.html +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from rest_framework_bulk import BulkModelViewSet +from django.db.models import Count + +from common.utils import get_logger +from ..hands import IsSuperUser +from ..models import Label +from .. import serializers + + +logger = get_logger(__file__) +__all__ = ['LabelViewSet'] + + +class LabelViewSet(BulkModelViewSet): + queryset = Label.objects.annotate(asset_count=Count("assets")) + permission_classes = (IsSuperUser,) + serializer_class = serializers.LabelSerializer + + def list(self, request, *args, **kwargs): + if request.query_params.get("distinct"): + self.serializer_class = serializers.LabelDistinctSerializer + self.queryset = self.queryset.values("name").distinct() + return super().list(request, *args, **kwargs) diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py new file mode 100644 index 000000000..d19cf152c --- /dev/null +++ b/apps/assets/api/system_user.py @@ -0,0 +1,85 @@ +# ~*~ coding: utf-8 ~*~ +# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved. +# +# Licensed under the GNU General Public License v2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.gnu.org/licenses/gpl-2.0.html +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from rest_framework import generics +from rest_framework.response import Response +from rest_framework_bulk import BulkModelViewSet +from common.utils import get_logger +from ..hands import IsSuperUser, IsSuperUserOrAppUser +from ..models import SystemUser +from .. import serializers +from ..tasks import push_system_user_to_cluster_assets_manual, \ + test_system_user_connectability_manual + + +logger = get_logger(__file__) +__all__ = [ + 'SystemUserViewSet', 'SystemUserAuthInfoApi', + 'SystemUserPushApi', 'SystemUserTestConnectiveApi' +] + + +class SystemUserViewSet(BulkModelViewSet): + """ + System user api set, for add,delete,update,list,retrieve resource + """ + queryset = SystemUser.objects.all() + serializer_class = serializers.SystemUserSerializer + permission_classes = (IsSuperUserOrAppUser,) + + +class SystemUserAuthInfoApi(generics.RetrieveAPIView): + """ + Get system user auth info + """ + queryset = SystemUser.objects.all() + permission_classes = (IsSuperUserOrAppUser,) + + def retrieve(self, request, *args, **kwargs): + system_user = self.get_object() + data = { + 'id': system_user.id, + 'name': system_user.name, + 'username': system_user.username, + 'password': system_user.password, + 'private_key': system_user.private_key, + } + return Response(data) + + +class SystemUserPushApi(generics.RetrieveAPIView): + """ + Push system user to cluster assets api + """ + queryset = SystemUser.objects.all() + permission_classes = (IsSuperUser,) + + def retrieve(self, request, *args, **kwargs): + system_user = self.get_object() + push_system_user_to_cluster_assets_manual.delay(system_user) + return Response({"msg": "Task created"}) + + +class SystemUserTestConnectiveApi(generics.RetrieveAPIView): + """ + Push system user to cluster assets api + """ + queryset = SystemUser.objects.all() + permission_classes = (IsSuperUser,) + + def retrieve(self, request, *args, **kwargs): + system_user = self.get_object() + test_system_user_connectability_manual.delay(system_user) + return Response({"msg": "Task created"}) \ No newline at end of file diff --git a/apps/assets/api/tree.py b/apps/assets/api/tree.py new file mode 100644 index 000000000..02bb555f3 --- /dev/null +++ b/apps/assets/api/tree.py @@ -0,0 +1,72 @@ +# ~*~ coding: utf-8 ~*~ +# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved. +# +# Licensed under the GNU General Public License v2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.gnu.org/licenses/gpl-2.0.html +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from rest_framework import generics, mixins +from rest_framework.response import Response +from rest_framework_bulk import BulkModelViewSet +from django.utils.translation import ugettext_lazy as _ + +from common.utils import get_logger +from ..hands import IsSuperUser +from ..models import Node +from .. import serializers + + +logger = get_logger(__file__) +__all__ = ['NodeViewSet', 'NodeChildrenApi'] + + +class NodeViewSet(BulkModelViewSet): + queryset = Node.objects.all() + permission_classes = (IsSuperUser,) + serializer_class = serializers.NodeSerializer + + def perform_create(self, serializer): + child_key = Node.root().get_next_child_key() + serializer.validated_data["key"] = child_key + serializer.save() + + +class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): + queryset = Node.objects.all() + permission_classes = (IsSuperUser,) + serializer_class = serializers.NodeSerializer + instance = None + + def post(self, request, *args, **kwargs): + if not request.data.get("value"): + request.data["value"] = _("New node {}").format( + Node.root().get_next_child_key().split(":")[-1] + ) + return super().post(request, *args, **kwargs) + + def create(self, request, *args, **kwargs): + instance = self.get_object() + value = request.data.get("value") + node = instance.create_child(value=value) + return Response( + {"id": node.id, "key": node.key, "value": node.value}, + status=201, + ) + + def get(self, request, *args, **kwargs): + instance = self.get_object() + if self.request.query_params.get("all"): + children = instance.get_all_children() + else: + children = instance.get_children() + response = [{"id": node.id, "key": node.key, "value": node.value} for node in children] + return Response(response, status=200) + diff --git a/apps/assets/templates/assets/_asset_group_bulk_update_modal.html b/apps/assets/templates/assets/_asset_group_bulk_update_modal.html index c253c4d65..0e76ff923 100644 --- a/apps/assets/templates/assets/_asset_group_bulk_update_modal.html +++ b/apps/assets/templates/assets/_asset_group_bulk_update_modal.html @@ -7,7 +7,6 @@ {% load bootstrap3 %}

    {% trans "Hint: only change the field you want to update." %}

    -
    diff --git a/apps/assets/templates/assets/_asset_list_modal.html b/apps/assets/templates/assets/_asset_list_modal.html new file mode 100644 index 000000000..3b9f8d136 --- /dev/null +++ b/apps/assets/templates/assets/_asset_list_modal.html @@ -0,0 +1,119 @@ +{% extends '_modal.html' %} +{% load i18n %} + +{% block modal_class %}modal-lg{% endblock %} +{% block modal_id %}asset_list_modal{% endblock %} +{#{% block modal_title%}{% trans "Please select assets" %}{% endblock %}#} +{% block modal_body %} +{#
    #} +{# #} +{# #} +{#
    #} + + + + + + + + + + + + + + +
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Hardware' %}{% trans 'Active' %}{% trans 'Reachable' %}{% trans 'Action' %}
    +
    +
    + +
    + +
    +
    +
    + + +{% endblock %} +{% block modal_confirm_id %}btn_select_assets{% endblock %} + + diff --git a/apps/assets/templates/assets/asset_modal_list.html b/apps/assets/templates/assets/asset_modal_list.html deleted file mode 100644 index ee418c435..000000000 --- a/apps/assets/templates/assets/asset_modal_list.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - diff --git a/apps/assets/templates/assets/tree.html b/apps/assets/templates/assets/tree.html index 1bf970ba3..dcbb6bec9 100644 --- a/apps/assets/templates/assets/tree.html +++ b/apps/assets/templates/assets/tree.html @@ -105,9 +105,10 @@ {% include 'assets/_asset_import_modal.html' %} +{% include 'assets/_asset_list_modal.html' %} {% endblock %} {% block custom_foot_js %} @@ -167,6 +169,8 @@ function initTable() { } + + function addTreeNode() { hideRMenu(); var parentNode = zTree.getSelectedNodes()[0]; @@ -232,13 +236,13 @@ function OnRightClick(event, treeId, treeNode) { function showRMenu(type, x, y) { $("#rMenu ul").show(); - if (type === "root") { - return - } else { - $("#m_del").show(); - $("#m_check").show(); - $("#m_unCheck").show(); - } + {#if (type === "root") {#} + {# return#} + {# } else {#} + {# $("#m_del").show();#} + {# $("#m_check").show();#} + {# $("#m_unCheck").show();#} + {# }#} x -= 220; rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"}); diff --git a/apps/perms/utils.py b/apps/perms/utils.py index e09186d86..0cd27dfd9 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils.py @@ -5,16 +5,43 @@ import collections from django.utils import timezone from common.utils import setattr_bulk, get_logger +import copy from .tasks import push_users from .hands import AssetGroup logger = get_logger(__file__) -def get_user_group_permissions(user_group): - return user_group.nodepermission_set.all() \ - .filter(is_active=True) \ - .filter(date_expired=timezone.now()) +class NodePermissionUtil: + + @staticmethod + def get_user_group_permissions(user_group): + return user_group.nodepermission_set.all() \ + .filter(is_active=True) \ + .filter(date_expired__gt=timezone.now()) + + @classmethod + def get_user_group_nodes(cls, user_group): + """ + 获取用户组授权的node和系统用户 + :param user_group: + :return: {"node": set(systemuser1, systemuser2), ..} + """ + permissions = cls.get_user_group_permissions(user_group) + nodes_directed = collections.defaultdict(set) + + for perm in permissions: + nodes_directed[perm.node].add(perm.system_user) + + nodes = copy.deepcopy(nodes_directed) + for node, system_users in nodes_directed.items(): + for child in node.get_all_children(): + nodes[child].update(system_users) + return nodes + + @classmethod + def get_user_group(cls): + pass def get_user_group_granted_asset_groups(user_group): diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 00cf1f55c..eee1720c0 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -321,7 +321,7 @@ jumpserver.initDataTable = function (options) { }); }); $('.ipt_check_all').on('click', function() { - if (!jumpserver.checked) { + if ($(this).prop("checked")) { $(this).closest('table').find('.ipt_check').prop('checked', true); jumpserver.checked = true; table.rows({search:'applied', page:'current'}).select(); @@ -455,18 +455,16 @@ jumpserver.initServerSideDataTable = function (options) { $('#uc').html(options.uc_html || ''); }); $('.ipt_check_all').on('click', function() { - if (!jumpserver.checked) { - $(this).closest('table').find('.ipt_check').prop('checked', true); - jumpserver.checked = true; - table.rows({search:'applied', page:'current'}).select(); - } else { - $(this).closest('table').find('.ipt_check').prop('checked', false); - jumpserver.checked = false; - table.rows({search:'applied', page:'current'}).deselect(); - } + if ($(this).prop("checked")) { + $(this).closest('table').find('.ipt_check').prop('checked', true); + table.rows({search:'applied', page:'current'}).select(); + } else { + $(this).closest('table').find('.ipt_check').prop('checked', false); + table.rows({search:'applied', page:'current'}).deselect(); + } }); - jumpserver.table = table; + // jumpserver.table = table; return table; }; diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 4ae0e62c4..8ef432108 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -49,6 +49,9 @@ class User(AbstractUser): date_expired = models.DateTimeField(default=date_expired_default, blank=True, null=True, verbose_name=_('Date expired')) created_by = models.CharField(max_length=30, default='', verbose_name=_('Created by')) + def __str__(self): + return self.username + @property def password_raw(self): raise AttributeError('Password raw is not a readable attribute') From 6104acae8fe9221c49c232fce9ea30c2a9bae858 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 7 Feb 2018 13:39:45 +0800 Subject: [PATCH 111/197] =?UTF-8?q?[Update]=20=E8=B5=84=E4=BA=A7=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E9=80=89=E4=B8=AD=E5=92=8C=E7=A7=BB=E9=99=A4=E8=B5=84?= =?UTF-8?q?=E4=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 2 +- apps/assets/api/tree.py | 29 ++++++++++- apps/assets/serializers.py | 8 +++ .../templates/assets/_asset_list_modal.html | 13 +++++ apps/assets/templates/assets/tree.html | 52 ++++++++++++++++--- apps/assets/urls/api_urls.py | 2 + .../perms/asset_permission_list.html | 21 +++++--- apps/static/js/jumpserver.js | 1 - 8 files changed, 111 insertions(+), 17 deletions(-) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 140981263..bc7aa0409 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -62,7 +62,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): queryset = queryset.filter(cluster__in=clusters) if node_id: node = get_object_or_404(Node, id=node_id) - queryset = queryset.filter(nodes__key__startswith=node.key) + queryset = queryset.filter(nodes__key__startswith=node.key).distinct() return queryset diff --git a/apps/assets/api/tree.py b/apps/assets/api/tree.py index 02bb555f3..a5f9fff8a 100644 --- a/apps/assets/api/tree.py +++ b/apps/assets/api/tree.py @@ -14,6 +14,7 @@ # limitations under the License. from rest_framework import generics, mixins +from rest_framework.views import APIView from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from django.utils.translation import ugettext_lazy as _ @@ -25,7 +26,10 @@ from .. import serializers logger = get_logger(__file__) -__all__ = ['NodeViewSet', 'NodeChildrenApi'] +__all__ = [ + 'NodeViewSet', 'NodeChildrenApi', + 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', +] class NodeViewSet(BulkModelViewSet): @@ -70,3 +74,26 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): response = [{"id": node.id, "key": node.key, "value": node.value} for node in children] return Response(response, status=200) + +class NodeAddAssetsApi(generics.UpdateAPIView): + serializer_class = serializers.NodeAssetsSerializer + queryset = Node.objects.all() + permission_classes = (IsSuperUser,) + instance = None + + def perform_update(self, serializer): + assets = serializer.validated_data.get('assets') + instance = self.get_object() + instance.assets.add(*tuple(assets)) + + +class NodeRemoveAssetsApi(generics.UpdateAPIView): + serializer_class = serializers.NodeAssetsSerializer + queryset = Node.objects.all() + permission_classes = (IsSuperUser,) + instance = None + + def perform_update(self, serializer): + assets = serializer.validated_data.get('assets') + instance = self.get_object() + instance.assets.remove(*tuple(assets)) diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index 4eb05b187..b7cc0ee9a 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -335,3 +335,11 @@ class NodeSerializer(serializers.ModelSerializer): field = fields["key"] field.required = False return fields + + +class NodeAssetsSerializer(serializers.ModelSerializer): + assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) + + class Meta: + model = Node + fields = ['assets'] diff --git a/apps/assets/templates/assets/_asset_list_modal.html b/apps/assets/templates/assets/_asset_list_modal.html index 3b9f8d136..b68ab0378 100644 --- a/apps/assets/templates/assets/_asset_list_modal.html +++ b/apps/assets/templates/assets/_asset_list_modal.html @@ -109,7 +109,20 @@ $(document).ready(function(){ return } + var data = { + 'assets': id_list + }; + var success = function () { + modal_table.ajax.reload() + }; + + APIUpdateAttr({ + 'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/add/', + 'method': 'PUT', + 'body': JSON.stringify(data), + 'success': success + }) }) diff --git a/apps/assets/templates/assets/tree.html b/apps/assets/templates/assets/tree.html index dcbb6bec9..b6c33965a 100644 --- a/apps/assets/templates/assets/tree.html +++ b/apps/assets/templates/assets/tree.html @@ -88,6 +88,7 @@ @@ -169,8 +170,6 @@ function initTable() { } - - function addTreeNode() { hideRMenu(); var parentNode = zTree.getSelectedNodes()[0]; @@ -281,17 +280,26 @@ function onRename(event, treeId, treeNode, isCancel){ function onSelected(event, treeNode) { var url = asset_table.ajax.url(); url = setUrlParam(url, "node_id", treeNode.id); + setCookie('node_selected', treeNode.id); asset_table.ajax.url(url); asset_table.ajax.reload(); } function selectQueryNode() { - var node_id = $.getUrlParam("node"); - if (node_id !== null) { - var node = zTree.getNodesByParam("id", node_id, null); - if (node){ - zTree.selectNode(node[0]); - } + var query_node_id = $.getUrlParam("node"); + var cookie_node_id = getCookie('node_selected'); + var node; + var node_id; + + if (query_node_id !== null) { + node_id = query_node_id + } else if (cookie_node_id !== null) { + node_id = cookie_node_id; + } + + node = zTree.getNodesByParam("id", node_id, null); + if (node){ + zTree.selectNode(node[0]); } } @@ -479,6 +487,31 @@ $(document).ready(function(){ var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string; location.href = url } + + function doRemove() { + var current_node; + var nodes = zTree.getSelectedNodes(); + if (nodes && nodes.length === 1) { + current_node = nodes[0] + } else { + return + } + + var data = { + 'assets': id_list + }; + + var success = function () { + asset_table.ajax.reload() + }; + + APIUpdateAttr({ + 'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/remove/', + 'method': 'PUT', + 'body': JSON.stringify(data), + 'success': success + }) + } switch(action) { case 'deactive': doDeactive(); @@ -492,6 +525,9 @@ $(document).ready(function(){ case 'active': doActive(); break; + case 'remove': + doRemove(); + break; default: break; } diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 43e8aa711..0e14cda21 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -44,6 +44,8 @@ urlpatterns = [ url(r'^v1/system-user/(?P[0-9a-zA-Z\-]{36})/connective/$', api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'), url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/children/$', api.NodeChildrenApi.as_view(), name='node-children'), + url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'), + url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'), ] urlpatterns += router.urls diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html index 257dcaf20..9f8e26a43 100644 --- a/apps/perms/templates/perms/asset_permission_list.html +++ b/apps/perms/templates/perms/asset_permission_list.html @@ -135,17 +135,26 @@ function initTable() { function onSelected(event, treeNode) { var url = table.ajax.url(); url = setUrlParam(url, "node_id", treeNode.id); + setCookie('node_selected', treeNode.id); table.ajax.url(url); table.ajax.reload(); } function selectQueryNode() { - var node_id = $.getUrlParam("node"); - if (node_id !== null) { - var node = zTree.getNodesByParam("id", node_id, null); - if (node) { - zTree.selectNode(node[0]); - } + var query_node_id = $.getUrlParam("node"); + var cookie_node_id = getCookie('node_selected'); + var node; + var node_id; + + if (query_node_id !== null) { + node_id = query_node_id + } else if (cookie_node_id !== null) { + node_id = cookie_node_id; + } + + node = zTree.getNodesByParam("id", node_id, null); + if (node){ + zTree.selectNode(node[0]); } } diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index eee1720c0..178836058 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -562,7 +562,6 @@ function setUrlParam(url, name, value) { url = urlArray[0] + "?"; var newParam = []; $.each(oriParamMap, function (index, value) { - console.log(index, value); newParam.push(index + "=" + value); }); url += newParam.join("&") From 3bb6e089859430774cf6b6b98c258a5d03c21561 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 7 Feb 2018 23:25:15 +0800 Subject: [PATCH 112/197] =?UTF-8?q?[Feature]=20=E6=9B=B4=E6=94=B9perms=20a?= =?UTF-8?q?pi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/{cluster.py => __cluster.py} | 0 apps/assets/api/{group.py => __group.py} | 0 apps/assets/api/__init__.py | 4 +- apps/assets/api/asset.py | 10 +- apps/assets/api/{tree.py => node.py} | 0 apps/assets/hands.py | 2 +- apps/assets/models/tree.py | 12 +- apps/assets/serializers.py | 345 ----------------- apps/assets/serializers/__init__.py | 8 + apps/assets/serializers/admin_user.py | 52 +++ apps/assets/serializers/asset.py | 67 ++++ apps/assets/serializers/cluster.py | 46 +++ apps/assets/serializers/label.py | 37 ++ apps/assets/serializers/node.py | 58 +++ apps/assets/serializers/system_user.py | 57 +++ apps/assets/urls/api_urls.py | 20 +- apps/assets/urls/views_urls.py | 24 +- apps/assets/views/__init__.py | 5 +- apps/perms/api.py | 301 +++------------ apps/perms/hands.py | 2 +- apps/perms/urls/api_urls.py | 19 +- apps/perms/utils.py | 386 +++++++++++-------- 22 files changed, 649 insertions(+), 806 deletions(-) rename apps/assets/api/{cluster.py => __cluster.py} (100%) rename apps/assets/api/{group.py => __group.py} (100%) rename apps/assets/api/{tree.py => node.py} (100%) delete mode 100644 apps/assets/serializers.py create mode 100644 apps/assets/serializers/__init__.py create mode 100644 apps/assets/serializers/admin_user.py create mode 100644 apps/assets/serializers/asset.py create mode 100644 apps/assets/serializers/cluster.py create mode 100644 apps/assets/serializers/label.py create mode 100644 apps/assets/serializers/node.py create mode 100644 apps/assets/serializers/system_user.py diff --git a/apps/assets/api/cluster.py b/apps/assets/api/__cluster.py similarity index 100% rename from apps/assets/api/cluster.py rename to apps/assets/api/__cluster.py diff --git a/apps/assets/api/group.py b/apps/assets/api/__group.py similarity index 100% rename from apps/assets/api/group.py rename to apps/assets/api/__group.py diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index 4aa9966b6..4a41c0706 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -1,7 +1,5 @@ from .admin_user import * from .asset import * -from .cluster import * -from .group import * from .label import * from .system_user import * -from .tree import * \ No newline at end of file +from .node import * diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index bc7aa0409..1f75b49fc 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -12,7 +12,7 @@ from django.db.models import Q from common.mixins import IDInFilterMixin from common.utils import get_logger from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \ - get_user_granted_assets + NodePermissionUtil from ..models import Asset, SystemUser, AdminUser, Node from .. import serializers from ..tasks import update_asset_hardware_info_manual, \ @@ -41,16 +41,10 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): def get_queryset(self): queryset = super().get_queryset() - cluster_id = self.request.query_params.get('cluster_id') - asset_group_id = self.request.query_params.get('asset_group_id') admin_user_id = self.request.query_params.get('admin_user_id') system_user_id = self.request.query_params.get('system_user_id') node_id = self.request.query_params.get("node_id") - if cluster_id: - queryset = queryset.filter(cluster__id=cluster_id) - if asset_group_id: - queryset = queryset.filter(groups__id=asset_group_id) if admin_user_id: admin_user = get_object_or_404(AdminUser, id=admin_user_id) assets_direct = [asset.id for asset in admin_user.asset_set.all()] @@ -72,7 +66,7 @@ class UserAssetListView(generics.ListAPIView): permission_classes = (IsValidUser,) def get_queryset(self): - assets_granted = get_user_granted_assets(self.request.user) + assets_granted = NodePermissionUtil.get_user_assets(self.request.user).keys() queryset = self.queryset.filter( id__in=[asset.id for asset in assets_granted] ) diff --git a/apps/assets/api/tree.py b/apps/assets/api/node.py similarity index 100% rename from apps/assets/api/tree.py rename to apps/assets/api/node.py diff --git a/apps/assets/hands.py b/apps/assets/hands.py index c160051bb..403a08633 100644 --- a/apps/assets/hands.py +++ b/apps/assets/hands.py @@ -14,5 +14,5 @@ from users.utils import AdminUserRequiredMixin from users.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser from users.models import User, UserGroup -from perms.utils import get_user_granted_assets +from perms.utils import NodePermissionUtil from perms.tasks import push_users \ No newline at end of file diff --git a/apps/assets/models/tree.py b/apps/assets/models/tree.py index 1a4e51588..a62b3c1bd 100644 --- a/apps/assets/models/tree.py +++ b/apps/assets/models/tree.py @@ -47,16 +47,20 @@ class Node(models.Model): def get_all_children(self): return self.__class__.objects.filter(key__startswith='{}:'.format(self.key)) + def get_family(self): + children = list(self.get_all_children()) + children.append(self) + return children + def get_assets(self): from .asset import Asset - children = self.get_children() - assets = Asset.objects.filter(nodes__in=children) + assets = Asset.objects.filter(nodes__id=self.id) return assets def get_all_assets(self): from .asset import Asset - children = self.get_all_children() - assets = Asset.objects.filter(nodes__in=children) + nodes = self.get_family() + assets = Asset.objects.filter(nodes__in=nodes) return assets @property diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py deleted file mode 100644 index b7cc0ee9a..000000000 --- a/apps/assets/serializers.py +++ /dev/null @@ -1,345 +0,0 @@ -# -*- coding: utf-8 -*- -from django.core.cache import cache -from rest_framework import serializers -from rest_framework_bulk.serializers import BulkListSerializer - -from common.mixins import BulkSerializerMixin -from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser, Label, Node -from .const import ADMIN_USER_CONN_CACHE_KEY - - -class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): - """ - 资产组序列化数据模型 - """ - assets_display = serializers.SerializerMethodField() - assets = serializers.PrimaryKeyRelatedField( - many=True, queryset=Asset.objects.all(), required=False - ) - - class Meta: - model = AssetGroup - list_serializer_class = BulkListSerializer - fields = ['id', 'name', 'comment', 'assets_display', 'assets'] - - @staticmethod - def get_assets_display(obj): - return [asset.hostname for asset in obj.assets.all()] - - -class AssetUpdateSystemUserSerializer(serializers.ModelSerializer): - """ - 资产更新其系统用户请求的数据结构定义 - """ - system_users = serializers.PrimaryKeyRelatedField(many=True, queryset=SystemUser.objects.all()) - - class Meta: - model = Asset - fields = ['id', 'system_users'] - - -class GroupUpdateAssetsSerializer(serializers.ModelSerializer): - """ - 资产组更新需要的数据结构 - """ - assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) - - class Meta: - model = AssetGroup - fields = ['id', 'assets'] - - -class ClusterUpdateAssetsSerializer(serializers.ModelSerializer): - """ - 集群更新资产数据结构 - """ - assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) - - class Meta: - model = Cluster - fields = ['id', 'assets'] - - -class AdminUserSerializer(serializers.ModelSerializer): - """ - 管理用户 - """ - assets_amount = serializers.SerializerMethodField() - unreachable_amount = serializers.SerializerMethodField() - reachable_amount = serializers.SerializerMethodField() - - class Meta: - model = AdminUser - fields = '__all__' - - @staticmethod - def get_unreachable_amount(obj): - data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name)) - if data: - return len(data.get('dark')) - else: - return 0 - - @staticmethod - def get_reachable_amount(obj): - data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name)) - if data: - return len(data.get('contacted')) - else: - return 0 - - @staticmethod - def get_assets_amount(obj): - return obj.assets_amount - - -class SystemUserSerializer(serializers.ModelSerializer): - """ - 系统用户 - """ - unreachable_amount = serializers.SerializerMethodField() - reachable_amount = serializers.SerializerMethodField() - unreachable_assets = serializers.SerializerMethodField() - reachable_assets = serializers.SerializerMethodField() - assets_amount = serializers.SerializerMethodField() - - class Meta: - model = SystemUser - exclude = ('_password', '_private_key', '_public_key') - - @staticmethod - def get_unreachable_assets(obj): - return obj.unreachable_assets - - @staticmethod - def get_reachable_assets(obj): - return obj.reachable_assets - - def get_unreachable_amount(self, obj): - return len(self.get_unreachable_assets(obj)) - - def get_reachable_amount(self, obj): - return len(self.get_reachable_assets(obj)) - - @staticmethod - def get_assets_amount(obj): - amount = 0 - for cluster in obj.cluster.all(): - amount += cluster.assets.all().count() - return amount - - -class AdminUserUpdateClusterSerializer(serializers.ModelSerializer): - """ - 管理用户更新关联到的集群 - """ - clusters = serializers.PrimaryKeyRelatedField(many=True, queryset=Cluster.objects.all()) - - class Meta: - model = AdminUser - fields = ['id', 'clusters'] - - -class AssetSystemUserSerializer(serializers.ModelSerializer): - """ - 查看授权的资产系统用户的数据结构,这个和AssetSerializer不同,字段少 - """ - class Meta: - model = SystemUser - fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',) - - -class SystemUserSimpleSerializer(serializers.ModelSerializer): - """ - 系统用户最基本信息的数据结构 - """ - class Meta: - model = SystemUser - fields = ('id', 'name', 'username') - - -class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): - """ - 资产的数据结构 - """ - cluster_name = serializers.SerializerMethodField() - - class Meta(object): - model = Asset - list_serializer_class = BulkListSerializer - fields = '__all__' - validators = [] # If not set to [], partial bulk update will be error - - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields.extend([ - 'hardware_info', 'is_connective', - ]) - return fields - - @staticmethod - def get_cluster_name(obj): - return obj.cluster.name - - -class AssetGrantedSerializer(serializers.ModelSerializer): - """ - 被授权资产的数据结构 - """ - system_users_granted = AssetSystemUserSerializer(many=True, read_only=True) - is_inherited = serializers.SerializerMethodField() - system_users_join = serializers.SerializerMethodField() - - class Meta(object): - model = Asset - fields = ( - "id", "hostname", "ip", "port", "system_users_granted", - "is_inherited", "is_active", "system_users_join", "os", - "platform", "comment" - ) - - @staticmethod - def get_is_inherited(obj): - if getattr(obj, 'inherited', ''): - return True - else: - return False - - @staticmethod - def get_system_users_join(obj): - return ', '.join([system_user.username for system_user in obj.system_users_granted]) - - -class MyAssetGrantedSerializer(AssetGrantedSerializer): - """ - 普通用户获取授权的资产定义的数据结构 - """ - - class Meta(object): - model = Asset - fields = ( - "id", "hostname", "system_users_granted", - "is_inherited", "is_active", "system_users_join", - "os", "platform", "comment", - ) - - -class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer): - """ - cluster - """ - assets_amount = serializers.SerializerMethodField() - admin_user_name = serializers.SerializerMethodField() - assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) - system_users = serializers.SerializerMethodField() - - class Meta: - model = Cluster - fields = '__all__' - - @staticmethod - def get_assets_amount(obj): - return obj.assets.count() - - @staticmethod - def get_admin_user_name(obj): - try: - return obj.admin_user.name - except AttributeError: - return '' - - @staticmethod - def get_system_users(obj): - return ', '.join(obj.name for obj in obj.systemuser_set.all()) - - -class AssetGroupGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): - """ - 授权资产组 - """ - assets_granted = AssetGrantedSerializer(many=True, read_only=True) - assets_amount = serializers.SerializerMethodField() - - class Meta: - model = AssetGroup - list_serializer_class = BulkListSerializer - fields = '__all__' - - @staticmethod - def get_assets_amount(obj): - return len(obj.assets_granted) - - -class MyAssetGroupGrantedSerializer(serializers.ModelSerializer): - """ - 普通用户授权资产组结构 - """ - assets_granted = MyAssetGrantedSerializer(many=True, read_only=True) - assets_amount = serializers.SerializerMethodField() - - class Meta: - model = AssetGroup - list_serializer_class = BulkListSerializer - fields = '__all__' - - @staticmethod - def get_assets_amount(obj): - return len(obj.assets_granted) - - -class LabelSerializer(serializers.ModelSerializer): - asset_count = serializers.SerializerMethodField() - - class Meta: - model = Label - fields = '__all__' - list_serializer_class = BulkListSerializer - - @staticmethod - def get_asset_count(obj): - return obj.assets.count() - - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields.extend(['get_category_display']) - return fields - - -class LabelDistinctSerializer(serializers.ModelSerializer): - value = serializers.SerializerMethodField() - - class Meta: - model = Label - fields = ("name", "value") - - @staticmethod - def get_value(obj): - labels = Label.objects.filter(name=obj["name"]) - return ', '.join([label.value for label in labels]) - - -class NodeSerializer(serializers.ModelSerializer): - parent = serializers.SerializerMethodField() - - class Meta: - model = Node - fields = ['id', 'key', 'value', 'parent'] - list_serializer_class = BulkListSerializer - - @staticmethod - def get_parent(obj): - return obj.parent.id - - def get_fields(self): - fields = super().get_fields() - field = fields["key"] - field.required = False - return fields - - -class NodeAssetsSerializer(serializers.ModelSerializer): - assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) - - class Meta: - model = Node - fields = ['assets'] diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py new file mode 100644 index 000000000..c39d34767 --- /dev/null +++ b/apps/assets/serializers/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# + +from .asset import * +from .admin_user import * +from .label import * +from .system_user import * +from .node import * diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py new file mode 100644 index 000000000..d38105106 --- /dev/null +++ b/apps/assets/serializers/admin_user.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# +from django.core.cache import cache +from rest_framework import serializers +from ..models import Cluster, AdminUser +from ..const import ADMIN_USER_CONN_CACHE_KEY + + +class AdminUserSerializer(serializers.ModelSerializer): + """ + 管理用户 + """ + assets_amount = serializers.SerializerMethodField() + unreachable_amount = serializers.SerializerMethodField() + reachable_amount = serializers.SerializerMethodField() + + class Meta: + model = AdminUser + fields = '__all__' + + @staticmethod + def get_unreachable_amount(obj): + data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name)) + if data: + return len(data.get('dark')) + else: + return 0 + + @staticmethod + def get_reachable_amount(obj): + data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name)) + if data: + return len(data.get('contacted')) + else: + return 0 + + @staticmethod + def get_assets_amount(obj): + return obj.assets_amount + + +class AdminUserUpdateClusterSerializer(serializers.ModelSerializer): + """ + 管理用户更新关联到的集群 + """ + clusters = serializers.PrimaryKeyRelatedField( + many=True, queryset=Cluster.objects.all() + ) + + class Meta: + model = AdminUser + fields = ['id', 'clusters'] \ No newline at end of file diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py new file mode 100644 index 000000000..5b5588757 --- /dev/null +++ b/apps/assets/serializers/asset.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import serializers +from rest_framework_bulk.serializers import BulkListSerializer + +from common.mixins import BulkSerializerMixin +from ..models import Asset +from .system_user import AssetSystemUserSerializer + + +class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): + """ + 资产的数据结构 + """ + cluster_name = serializers.SerializerMethodField() + + class Meta: + model = Asset + list_serializer_class = BulkListSerializer + fields = '__all__' + validators = [] # If not set to [], partial bulk update will be error + + def get_field_names(self, declared_fields, info): + fields = super().get_field_names(declared_fields, info) + fields.extend([ + 'hardware_info', 'is_connective', + ]) + return fields + + @staticmethod + def get_cluster_name(obj): + return obj.cluster.name + + +class AssetGrantedSerializer(serializers.ModelSerializer): + """ + 被授权资产的数据结构 + """ + system_users_granted = AssetSystemUserSerializer(many=True, read_only=True) + system_users_join = serializers.SerializerMethodField() + + class Meta: + model = Asset + fields = ( + "id", "hostname", "ip", "port", "system_users_granted", + "is_active", "system_users_join", "os", + "platform", "comment" + ) + + @staticmethod + def get_system_users_join(obj): + system_users = [s.username for s in obj.system_users_granted] + return ', '.join(system_users) + + +class MyAssetGrantedSerializer(AssetGrantedSerializer): + """ + 普通用户获取授权的资产定义的数据结构 + """ + + class Meta: + model = Asset + fields = ( + "id", "hostname", "system_users_granted", + "is_active", "system_users_join", + "os", "platform", "comment", + ) diff --git a/apps/assets/serializers/cluster.py b/apps/assets/serializers/cluster.py new file mode 100644 index 000000000..43724a4a2 --- /dev/null +++ b/apps/assets/serializers/cluster.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import serializers +from common.mixins import BulkSerializerMixin +from ..models import Asset, Cluster + + +class ClusterUpdateAssetsSerializer(serializers.ModelSerializer): + """ + 集群更新资产数据结构 + """ + assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) + + class Meta: + model = Cluster + fields = ['id', 'assets'] + + +class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer): + """ + cluster + """ + assets_amount = serializers.SerializerMethodField() + admin_user_name = serializers.SerializerMethodField() + assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) + system_users = serializers.SerializerMethodField() + + class Meta: + model = Cluster + fields = '__all__' + + @staticmethod + def get_assets_amount(obj): + return obj.assets.count() + + @staticmethod + def get_admin_user_name(obj): + try: + return obj.admin_user.name + except AttributeError: + return '' + + @staticmethod + def get_system_users(obj): + return ', '.join(obj.name for obj in obj.systemuser_set.all()) diff --git a/apps/assets/serializers/label.py b/apps/assets/serializers/label.py new file mode 100644 index 000000000..b45e1e508 --- /dev/null +++ b/apps/assets/serializers/label.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import serializers +from rest_framework_bulk.serializers import BulkListSerializer + +from ..models import Label + + +class LabelSerializer(serializers.ModelSerializer): + asset_count = serializers.SerializerMethodField() + + class Meta: + model = Label + fields = '__all__' + list_serializer_class = BulkListSerializer + + @staticmethod + def get_asset_count(obj): + return obj.assets.count() + + def get_field_names(self, declared_fields, info): + fields = super().get_field_names(declared_fields, info) + fields.extend(['get_category_display']) + return fields + + +class LabelDistinctSerializer(serializers.ModelSerializer): + value = serializers.SerializerMethodField() + + class Meta: + model = Label + fields = ("name", "value") + + @staticmethod + def get_value(obj): + labels = Label.objects.filter(name=obj["name"]) + return ', '.join([label.value for label in labels]) diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py new file mode 100644 index 000000000..88a270917 --- /dev/null +++ b/apps/assets/serializers/node.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from rest_framework import serializers +from rest_framework_bulk.serializers import BulkListSerializer + +from common.mixins import BulkSerializerMixin +from ..models import Asset, Node +from .asset import AssetGrantedSerializer + + +class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): + """ + 授权资产组 + """ + assets_granted = AssetGrantedSerializer(many=True, read_only=True) + assets_amount = serializers.SerializerMethodField() + parent = serializers.SerializerMethodField() + + class Meta: + model = Node + fields = [ + 'id', 'key', 'value', 'parent', + 'assets_granted', 'assets_amount', + ] + + @staticmethod + def get_assets_amount(obj): + return len(obj.assets_granted) + + @staticmethod + def get_parent(obj): + return obj.parent.id + + +class NodeSerializer(serializers.ModelSerializer): + parent = serializers.SerializerMethodField() + + class Meta: + model = Node + fields = ['id', 'key', 'value', 'parent'] + list_serializer_class = BulkListSerializer + + @staticmethod + def get_parent(obj): + return obj.parent.id + + def get_fields(self): + fields = super().get_fields() + field = fields["key"] + field.required = False + return fields + + +class NodeAssetsSerializer(serializers.ModelSerializer): + assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) + + class Meta: + model = Node + fields = ['assets'] \ No newline at end of file diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py new file mode 100644 index 000000000..488d9bb66 --- /dev/null +++ b/apps/assets/serializers/system_user.py @@ -0,0 +1,57 @@ +from rest_framework import serializers + +from ..models import SystemUser + + +class SystemUserSerializer(serializers.ModelSerializer): + """ + 系统用户 + """ + unreachable_amount = serializers.SerializerMethodField() + reachable_amount = serializers.SerializerMethodField() + unreachable_assets = serializers.SerializerMethodField() + reachable_assets = serializers.SerializerMethodField() + assets_amount = serializers.SerializerMethodField() + + class Meta: + model = SystemUser + exclude = ('_password', '_private_key', '_public_key') + + @staticmethod + def get_unreachable_assets(obj): + return obj.unreachable_assets + + @staticmethod + def get_reachable_assets(obj): + return obj.reachable_assets + + def get_unreachable_amount(self, obj): + return len(self.get_unreachable_assets(obj)) + + def get_reachable_amount(self, obj): + return len(self.get_reachable_assets(obj)) + + @staticmethod + def get_assets_amount(obj): + amount = 0 + for cluster in obj.cluster.all(): + amount += cluster.assets.all().count() + return amount + + +class AssetSystemUserSerializer(serializers.ModelSerializer): + """ + 查看授权的资产系统用户的数据结构,这个和AssetSerializer不同,字段少 + """ + class Meta: + model = SystemUser + fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',) + + +class SystemUserSimpleSerializer(serializers.ModelSerializer): + """ + 系统用户最基本信息的数据结构 + """ + class Meta: + model = SystemUser + fields = ('id', 'name', 'username') \ No newline at end of file diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 0e14cda21..8847b6397 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -7,9 +7,9 @@ app_name = 'assets' router = BulkRouter() -router.register(r'v1/groups', api.AssetGroupViewSet, 'asset-group') +# router.register(r'v1/groups', api.AssetGroupViewSet, 'asset-group') router.register(r'v1/assets', api.AssetViewSet, 'asset') -router.register(r'v1/clusters', api.ClusterViewSet, 'cluster') +# router.register(r'v1/clusters', api.ClusterViewSet, 'cluster') router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user') router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user') router.register(r'v1/labels', api.LabelViewSet, 'label') @@ -26,15 +26,15 @@ urlpatterns = [ url(r'^v1/assets/user-assets/$', api.UserAssetListView.as_view(), name='user-asset-list'), # update the asset group, which add or delete the asset to the group - url(r'^v1/groups/(?P[0-9a-zA-Z\-]{36})/assets/$', - api.GroupUpdateAssetsApi.as_view(), name='group-update-assets'), - url(r'^v1/groups/(?P[0-9a-zA-Z\-]{36})/assets/add/$', - api.GroupAddAssetsApi.as_view(), name='group-add-assets'), + #url(r'^v1/groups/(?P[0-9a-zA-Z\-]{36})/assets/$', + # api.GroupUpdateAssetsApi.as_view(), name='group-update-assets'), + #url(r'^v1/groups/(?P[0-9a-zA-Z\-]{36})/assets/add/$', + # api.GroupAddAssetsApi.as_view(), name='group-add-assets'), # update the Cluster, and add or delete the assets to the Cluster - url(r'^v1/cluster/(?P[0-9a-zA-Z\-]{36})/assets/$', - api.ClusterAddAssetsApi.as_view(), name='cluster-add-assets'), - url(r'^v1/cluster/(?P[0-9a-zA-Z\-]{36})/assets/connective/$', - api.ClusterTestAssetsAliveApi.as_view(), name='cluster-test-connective'), + #url(r'^v1/cluster/(?P[0-9a-zA-Z\-]{36})/assets/$', + # api.ClusterAddAssetsApi.as_view(), name='cluster-add-assets'), + #url(r'^v1/cluster/(?P[0-9a-zA-Z\-]{36})/assets/connective/$', + # api.ClusterTestAssetsAliveApi.as_view(), name='cluster-test-connective'), url(r'^v1/admin-user/(?P[0-9a-zA-Z\-]{36})/clusters/$', api.AdminUserAddClustersApi.as_view(), name='admin-user-add-clusters'), url(r'^v1/admin-user/(?P[0-9a-zA-Z\-]{36})/connective/$', diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index 43d25300e..1704531d2 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -20,20 +20,20 @@ urlpatterns = [ # User asset view url(r'^user-asset/$', views.UserAssetListView.as_view(), name='user-asset-list'), - # Resource asset group url - url(r'^asset-group/$', views.AssetGroupListView.as_view(), name='asset-group-list'), - url(r'^asset-group/create/$', views.AssetGroupCreateView.as_view(), name='asset-group-create'), - url(r'^asset-group/(?P[0-9a-zA-Z\-]{36})/$', views.AssetGroupDetailView.as_view(), name='asset-group-detail'), - url(r'^asset-group/(?P[0-9a-zA-Z\-]{36})/update/$', views.AssetGroupUpdateView.as_view(), name='asset-group-update'), - url(r'^asset-group/(?P[0-9a-zA-Z\-]{36})/delete/$', views.AssetGroupDeleteView.as_view(), name='asset-group-delete'), + # # Resource asset group url + # url(r'^asset-group/$', views.AssetGroupListView.as_view(), name='asset-group-list'), + # url(r'^asset-group/create/$', views.AssetGroupCreateView.as_view(), name='asset-group-create'), + # url(r'^asset-group/(?P[0-9a-zA-Z\-]{36})/$', views.AssetGroupDetailView.as_view(), name='asset-group-detail'), + # url(r'^asset-group/(?P[0-9a-zA-Z\-]{36})/update/$', views.AssetGroupUpdateView.as_view(), name='asset-group-update'), + # url(r'^asset-group/(?P[0-9a-zA-Z\-]{36})/delete/$', views.AssetGroupDeleteView.as_view(), name='asset-group-delete'), # Resource cluster url - url(r'^cluster/$', views.ClusterListView.as_view(), name='cluster-list'), - url(r'^cluster/create/$', views.ClusterCreateView.as_view(), name='cluster-create'), - url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/$', views.ClusterDetailView.as_view(), name='cluster-detail'), - url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/update/', views.ClusterUpdateView.as_view(), name='cluster-update'), - url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/delete/$', views.ClusterDeleteView.as_view(), name='cluster-delete'), - url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/assets/$', views.ClusterAssetsView.as_view(), name='cluster-assets'), + # url(r'^cluster/$', views.ClusterListView.as_view(), name='cluster-list'), + # url(r'^cluster/create/$', views.ClusterCreateView.as_view(), name='cluster-create'), + # url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/$', views.ClusterDetailView.as_view(), name='cluster-detail'), + # url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/update/', views.ClusterUpdateView.as_view(), name='cluster-update'), + # url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/delete/$', views.ClusterDeleteView.as_view(), name='cluster-delete'), + # url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/assets/$', views.ClusterAssetsView.as_view(), name='cluster-assets'), # Resource admin user url url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'), diff --git a/apps/assets/views/__init__.py b/apps/assets/views/__init__.py index 597de79e9..3da4b6750 100644 --- a/apps/assets/views/__init__.py +++ b/apps/assets/views/__init__.py @@ -1,9 +1,8 @@ # coding:utf-8 from .asset import * -from .group import * -from .cluster import * +# from .group import * +# from .cluster import * from .system_user import * from .admin_user import * from .label import * from .tree import * - diff --git a/apps/perms/api.py b/apps/perms/api.py index 276ad0c43..0ae5b4e9e 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -3,16 +3,14 @@ from django.shortcuts import get_object_or_404 from rest_framework.views import APIView, Response -from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView +from rest_framework.generics import ListAPIView, get_object_or_404 from rest_framework import viewsets -from common.utils import get_object_or_none -from users.permissions import IsValidUser, IsSuperUser, IsAppUser, IsSuperUserOrAppUser -from .utils import get_user_granted_assets, get_user_granted_asset_groups, \ - get_user_group_granted_assets, get_user_group_granted_asset_groups -from .models import AssetPermission, NodePermission -from .hands import AssetGrantedSerializer, User, UserGroup, Node, Asset, \ - AssetGroup, AssetGroupGrantedSerializer, SystemUser, MyAssetGroupGrantedSerializer +from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser +from .utils import NodePermissionUtil +from .models import NodePermission +from .hands import AssetGrantedSerializer, User, UserGroup, Asset, \ + NodeGrantedSerializer, SystemUser from . import serializers @@ -37,80 +35,6 @@ class AssetPermissionViewSet(viewsets.ModelViewSet): return queryset -class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView): - """ - 将用户从授权中移除,Detail页面会调用 - """ - permission_classes = (IsSuperUser,) - serializer_class = serializers.AssetPermissionUpdateUserSerializer - queryset = AssetPermission.objects.all() - - def update(self, request, *args, **kwargs): - perm = self.get_object() - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - users = serializer.validated_data.get('users') - if users: - perm.users.remove(*tuple(users)) - return Response({"msg": "ok"}) - else: - return Response({"error": serializer.errors}) - - -class AssetPermissionAddUserApi(RetrieveUpdateAPIView): - permission_classes = (IsSuperUser,) - serializer_class = serializers.AssetPermissionUpdateUserSerializer - queryset = AssetPermission.objects.all() - - def update(self, request, *args, **kwargs): - perm = self.get_object() - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - users = serializer.validated_data.get('users') - if users: - perm.users.add(*tuple(users)) - return Response({"msg": "ok"}) - else: - return Response({"error": serializer.errors}) - - -class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView): - """ - 将用户从授权中移除,Detail页面会调用 - """ - permission_classes = (IsSuperUser,) - serializer_class = serializers.AssetPermissionUpdateAssetSerializer - queryset = AssetPermission.objects.all() - - def update(self, request, *args, **kwargs): - perm = self.get_object() - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - assets = serializer.validated_data.get('assets') - if assets: - perm.assets.remove(*tuple(assets)) - return Response({"msg": "ok"}) - else: - return Response({"error": serializer.errors}) - - -class AssetPermissionAddAssetApi(RetrieveUpdateAPIView): - permission_classes = (IsSuperUser,) - serializer_class = serializers.AssetPermissionUpdateAssetSerializer - queryset = AssetPermission.objects.all() - - def update(self, request, *args, **kwargs): - perm = self.get_object() - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - assets = serializer.validated_data.get('assets') - if assets: - perm.assets.add(*tuple(assets)) - return Response({"msg": "ok"}) - else: - return Response({"error": serializer.errors}) - - class UserGrantedAssetsApi(ListAPIView): """ 用户授权的所有资产 @@ -120,41 +44,25 @@ class UserGrantedAssetsApi(ListAPIView): def get_queryset(self): user_id = self.kwargs.get('pk', '') - queryset = [] + if user_id: user = get_object_or_404(User, id=user_id) - for k, v in get_user_granted_assets(user).items(): - k.system_users_granted = v - queryset.append(k) + else: + user = self.request.user + + for k, v in NodePermissionUtil.get_user_assets(user).items(): + k.system_users_granted = v + queryset.append(k) return queryset - -class UserGrantedAssetGroupsApi(APIView): - permission_classes = (IsValidUser,) - - def get(self, request, *args, **kwargs): - asset_groups = {} - user_id = kwargs.get('pk', '') - user = get_object_or_404(User, id=user_id) - - assets = get_user_granted_assets(user) - for asset in assets: - for asset_group in asset.groups.all(): - if asset_group.id in asset_groups: - asset_groups[asset_group.id]['assets_amount'] += 1 - else: - asset_groups[asset_group.id] = { - 'id': asset_group.id, - 'name': asset_group.name, - 'comment': asset_group.comment, - 'assets_amount': 1 - } - asset_groups_json = asset_groups.values() - return Response(asset_groups_json, status=200) + def get_permissions(self): + if self.kwargs.get('pk') is None: + self.permission_classes = (IsValidUser,) + return super().get_permissions() -class UserGrantedAssetGroupsWithAssetsApi(ListAPIView): +class UserGrantedNodesWithAssetsApi(ListAPIView): """ 授权用户的资产组,注:这里的资产组并非是授权列表中授权的, 而是把所有资产取出来,然后反查出所有资产组,然后合并得到, @@ -163,7 +71,7 @@ class UserGrantedAssetGroupsWithAssetsApi(ListAPIView): [ { "id": 1, - "name": "资产组1", + "value": "node", ... 其它属性 "assets_granted": [ { @@ -183,133 +91,28 @@ class UserGrantedAssetGroupsWithAssetsApi(ListAPIView): ] """ permission_classes = (IsSuperUserOrAppUser,) - serializer_class = AssetGroupGrantedSerializer + serializer_class = NodeGrantedSerializer def get_queryset(self): user_id = self.kwargs.get('pk', '') + queryset = [] if not user_id: - return [] + user = self.request.user + else: + user = get_object_or_404(User, id=user_id) - user = get_object_or_404(User, id=user_id) - asset_groups = get_user_granted_asset_groups(user) - - queryset = [] - for asset_group, assets_system_users in asset_groups.items(): - assets = [] - for asset, system_users in assets_system_users: - asset.system_users_granted = system_users - assets.append(asset) - asset_group.assets_granted = assets - queryset.append(asset_group) + nodes = NodePermissionUtil.get_user_nodes_with_assets(user) + for node, v in nodes.items(): + for asset in v['assets']: + asset.system_users_granted = v['system_users'] + node.assets_granted = v['assets'] + queryset.append(node) return queryset - -class MyGrantedAssetsApi(ListAPIView): - """ - 用户自己查询授权的资产列表 - """ - permission_classes = (IsValidUser,) - serializer_class = AssetGrantedSerializer - - def get_queryset(self): - queryset = [] - user = self.request.user - if user: - for asset, system_users in get_user_granted_assets(user).items(): - asset.system_users_granted = system_users - queryset.append(asset) - return queryset - - -class MyGrantedAssetGroupsApi(APIView): - """ - 授权的所有资产组,并非是授权列表中的,而是经过计算得来的 - """ - permission_classes = (IsValidUser,) - - def get(self, request, *args, **kwargs): - asset_groups = {} - user = request.user - - if user: - assets = get_user_granted_assets(user) - for asset in assets: - for asset_group in asset.groups.all(): - if asset_group.id in asset_groups: - asset_groups[asset_group.id]['assets_amount'] += 1 - else: - asset_groups[asset_group.id] = { - 'id': asset_group.id, - 'name': asset_group.name, - 'comment': asset_group.comment, - 'assets_amount': 1 - } - asset_groups_json = asset_groups.values() - return Response(asset_groups_json, status=200) - - -class MyGrantedAssetGroupsWithAssetsApi(ListAPIView): - """ - 授权当前用户的资产组,注:这里的资产组并非是授权列表中授权的, - 而是把所有资产取出来,然后反查出所有资产组,然后合并得到, - 结果里也包含资产组下授权的资产 - 数据结构如下: - [ - { - "id": 1, - "name": "资产组1", - ... 其它属性 - "assets_granted": [ - { - "id": 1, - "hostname": "testserver", - "system_users_granted": [ - "id": 1, - "name": "web", - "username": "web", - "protocol": "ssh", - ] - } - ] - } - ] - """ - permission_classes = (IsValidUser,) - serializer_class = MyAssetGroupGrantedSerializer - - def get_queryset(self): - user = self.request.user - asset_groups = get_user_granted_asset_groups(user) - - queryset = [] - for asset_group, assets_system_users in asset_groups.items(): - assets = [] - for asset, system_users in assets_system_users: - asset.system_users_granted = system_users - assets.append(asset) - asset_group.assets_granted = assets - queryset.append(asset_group) - return queryset - - -class MyAssetGroupOfAssetsApi(ListAPIView): - """授权用户资产组下的资产列表, 非该资产组的所有资产,而是被授权的""" - permission_classes = (IsValidUser,) - serializer_class = AssetGrantedSerializer - - def get_queryset(self): - queryset = [] - asset_group_id = self.kwargs.get('pk', -1) - user = self.request.user - asset_group = get_object_or_none(AssetGroup, id=asset_group_id) - - if user and asset_group: - assets = get_user_granted_assets(user) - for asset in asset_group.assets.all(): - if asset in assets: - asset.system_users_granted = assets[asset] - queryset.append(asset) - return queryset + def get_permissions(self): + if self.kwargs.get('pk') is None: + self.permission_classes = (IsValidUser,) + return super().get_permissions() class UserGroupGrantedAssetsApi(ListAPIView): @@ -318,27 +121,37 @@ class UserGroupGrantedAssetsApi(ListAPIView): def get_queryset(self): user_group_id = self.kwargs.get('pk', '') + queryset = [] - if user_group_id: - user_group = get_object_or_404(UserGroup, id=user_group_id) - queryset = get_user_group_granted_assets(user_group) - else: - queryset = [] + if not user_group_id: + return queryset + + user_group = get_object_or_404(UserGroup, id=user_group_id) + assets = NodePermissionUtil.get_user_group_assets(user_group) + for k, v in assets.items(): + k.system_users_granted = v + queryset.append(k) return queryset -class UserGroupGrantedAssetGroupsApi(ListAPIView): +class UserGroupGrantedNodeApi(ListAPIView): permission_classes = (IsSuperUser,) - serializer_class = AssetGroupGrantedSerializer + serializer_class = NodeGrantedSerializer def get_queryset(self): user_group_id = self.kwargs.get('pk', '') + queryset = [] - if user_group_id: - user_group = get_object_or_404(UserGroup, id=user_group_id) - queryset = get_user_group_granted_asset_groups(user_group) - else: - queryset = [] + if not user_group_id: + return queryset + + user_group = get_object_or_404(UserGroup, id=user_group_id) + nodes = NodePermissionUtil.get_user_group_nodes_with_assets(user_group) + for node, v in nodes.items(): + for asset in v['assets']: + asset.system_users_granted = v['system_users'] + node.assets_granted = v['assets'] + queryset.append(node) return queryset @@ -355,7 +168,7 @@ class ValidateUserAssetPermissionView(APIView): asset = get_object_or_404(Asset, id=asset_id) system_user = get_object_or_404(SystemUser, id=system_id) - assets_granted = get_user_granted_assets(user) + assets_granted = NodePermissionUtil.get_user_assets(user) if system_user in assets_granted.get(asset, []): return Response({'msg': True}, status=200) else: diff --git a/apps/perms/hands.py b/apps/perms/hands.py index 44c97b455..42bb90cef 100644 --- a/apps/perms/hands.py +++ b/apps/perms/hands.py @@ -4,7 +4,7 @@ from users.utils import AdminUserRequiredMixin from users.models import User, UserGroup from assets.models import Asset, AssetGroup, SystemUser, Node -from assets.serializers import AssetGrantedSerializer, AssetGroupGrantedSerializer, MyAssetGroupGrantedSerializer +from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index a9fad1eac..3c7870fa5 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -10,26 +10,13 @@ router = routers.DefaultRouter() router.register('v1/asset-permissions', api.AssetPermissionViewSet, 'asset-permission') urlpatterns = [ - # 用户可以使用自己的Token或其它认证查看自己授权的资产,资产组等 - url(r'^v1/user/my/assets/$', api.MyGrantedAssetsApi.as_view(), name='my-assets'), - url(r'^v1/user/my/asset-groups/$', api.MyGrantedAssetGroupsApi.as_view(), name='my-asset-groups'), - url(r'^v1/user/my/asset-groups-assets/$', api.MyGrantedAssetGroupsWithAssetsApi.as_view(), name='my-asset-group-assets'), - url(r'^v1/user/my/asset-group/(?P[0-9a-zA-Z\-]{36})/assets/$', api.MyAssetGroupOfAssetsApi.as_view(), name='my-asset-group-of-assets'), - # 查询某个用户授权的资产和资产组 - url(r'^v1/user/(?P[0-9a-zA-Z\-]{36})/assets/$', api.UserGrantedAssetsApi.as_view(), name='user-assets'), - url(r'^v1/user/(?P[0-9a-zA-Z\-]{36})/asset-groups/$', api.UserGrantedAssetGroupsApi.as_view(), name='user-asset-groups'), - url(r'^v1/user/(?P[0-9a-zA-Z\-]{36})/asset-groups-assets/$', api.UserGrantedAssetGroupsWithAssetsApi.as_view(), name='user-asset-groups'), + url(r'^v1/user/(?P[0-9a-zA-Z\-]{36})?/?assets/$', api.UserGrantedAssetsApi.as_view(), name='user-assets'), + url(r'^v1/user/(?P[0-9a-zA-Z\-]{36})?/?nodes/$', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes'), # 查询某个用户组授权的资产和资产组 url(r'^v1/user-group/(?P[0-9a-zA-Z\-]{36})/assets/$', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), - url(r'^v1/user-group/(?P[0-9a-zA-Z\-]{36})/asset-groups/$', api.UserGroupGrantedAssetGroupsApi.as_view(), name='user-group-asset-groups'), - - # 用户和资产授权变更 - url(r'^v1/asset-permissions/(?P[0-9a-zA-Z\-]{36})/user/remove/$', api.AssetPermissionRemoveUserApi.as_view(), name='asset-permission-remove-user'), - url(r'^v1/asset-permissions/(?P[0-9a-zA-Z\-]{36})/user/add/$', api.AssetPermissionAddUserApi.as_view(), name='asset-permission-add-user'), - url(r'^v1/asset-permissions/(?P[0-9a-zA-Z\-]{36})/asset/remove/$', api.AssetPermissionRemoveAssetApi.as_view(), name='asset-permission-remove-asset'), - url(r'^v1/asset-permissions/(?P[0-9a-zA-Z\-]{36})/asset/add/$', api.AssetPermissionAddAssetApi.as_view(), name='asset-permission-add-asset'), + url(r'^v1/user-group/(?P[0-9a-zA-Z\-]{36})/nodes/$', api.UserGroupGrantedNodeApi.as_view(), name='user-group-asset-groups'), # 验证用户是否有某个资产和系统用户的权限 url(r'v1/asset-permission/user/validate/$', api.ValidateUserAssetPermissionView.as_view(), name='validate-user-asset-permission'), diff --git a/apps/perms/utils.py b/apps/perms/utils.py index 0cd27dfd9..75299e761 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils.py @@ -20,6 +20,12 @@ class NodePermissionUtil: .filter(is_active=True) \ .filter(date_expired__gt=timezone.now()) + @staticmethod + def get_system_user_permissions(system_user): + return system_user.nodepermission_set.all() \ + .filter(is_active=True) \ + .filter(date_expired__gt=timezone.now()) + @classmethod def get_user_group_nodes(cls, user_group): """ @@ -35,171 +41,233 @@ class NodePermissionUtil: nodes = copy.deepcopy(nodes_directed) for node, system_users in nodes_directed.items(): - for child in node.get_all_children(): + for child in node.get_family(): nodes[child].update(system_users) return nodes @classmethod - def get_user_group(cls): - pass + def get_user_group_nodes_with_assets(cls, user_group): + """ + 获取用户组授权的节点和系统用户,节点下带有资产 + :param user_group: + :return: {"node": {"assets": "", "system_user": ""}, {}} + """ + nodes = cls.get_user_group_nodes(user_group) + nodes_with_assets = dict() + for node, system_users in nodes.items(): + nodes_with_assets[node] = { + 'assets': node.get_assets(), + 'system_users': system_users + } + return nodes_with_assets + + @classmethod + def get_user_group_assets(cls, user_group): + assets = collections.defaultdict(set) + permissions = cls.get_user_group_permissions(user_group) + + for perm in permissions: + for asset in perm.node.get_all_assets(): + assets[asset].add(perm.system_user) + return assets + + @classmethod + def get_user_nodes(cls, user): + nodes = collections.defaultdict(set) + groups = user.groups.all() + for group in groups: + group_nodes = cls.get_user_group_nodes(group) + for node, system_users in group_nodes.items(): + nodes[node].update(system_users) + return nodes + + @classmethod + def get_user_nodes_with_assets(cls, user): + nodes = cls.get_user_nodes(user) + nodes_with_assets = dict() + for node, system_users in nodes.items(): + nodes_with_assets[node] = { + 'assets': node.get_assets(), + 'system_users': system_users + } + return nodes_with_assets + + @classmethod + def get_user_assets(cls, user): + assets = collections.defaultdict(set) + nodes_with_assets = cls.get_user_nodes_with_assets(user) + + for v in nodes_with_assets.values(): + for asset in v['assets']: + assets[asset].update(v['system_users']) + return assets + + @classmethod + def get_system_user_assets(cls, system_user): + assets = set() + permissions = cls.get_system_user_permissions(system_user) + + for perm in permissions: + assets.update(perm.node.get_all_assets()) + return assets -def get_user_group_granted_asset_groups(user_group): - """Return asset groups granted of the user group - - :param user_group: Instance of :class: ``UserGroup`` - :return: {asset_group1: {system_user1, }, - asset_group2: {system_user1, system_user2}} - """ - asset_groups = {} - asset_permissions = user_group.asset_permissions.all() - - for asset_permission in asset_permissions: - if not asset_permission.is_valid: - continue - for asset_group in asset_permission.asset_groups.all(): - if asset_group in asset_groups: - asset_groups[asset_group] |= set(asset_permission.system_users.all()) - else: - asset_groups[asset_group] = set(asset_permission.system_users.all()) - return asset_groups - - -def get_user_group_granted_assets(user_group): - """Return assets granted of the user group - - :param user_group: Instance of :class: ``UserGroup`` - :return: {asset1: {system_user1, }, asset1: {system_user1, system_user2]} - """ - assets = {} - asset_permissions = user_group.asset_permissions.all() - - for asset_permission in asset_permissions: - if not asset_permission.is_valid: - continue - for asset in asset_permission.get_granted_assets(): - if not asset.is_active: - continue - if asset in assets: - assets[asset] |= set(asset_permission.system_users.all()) - else: - assets[asset] = set(asset_permission.system_users.all()) - return assets - - -def get_user_granted_assets_direct(user): - """Return assets granted of the user directly - - :param user: Instance of :class: ``User`` - :return: {asset1: {system_user1, system_user2}, asset2: {...}} - """ - assets = {} - asset_permissions_direct = user.asset_permissions.all() - - for asset_permission in asset_permissions_direct: - if not asset_permission.is_valid: - continue - for asset in asset_permission.get_granted_assets(): - if not asset.is_active: - continue - if asset in assets: - assets[asset] |= set(asset_permission.system_users.all()) - else: - setattr(asset, 'inherited', False) - assets[asset] = set(asset_permission.system_users.all()) - return assets - - -def get_user_granted_assets_inherit_from_user_groups(user): - """Return assets granted of the user inherit from user groups - - :param user: Instance of :class: ``User`` - :return: {asset1: {system_user1, system_user2}, asset2: {...}} - """ - assets = {} - user_groups = user.groups.all() - - for user_group in user_groups: - assets_inherited = get_user_group_granted_assets(user_group) - for asset in assets_inherited: - if not asset.is_active: - continue - if asset in assets: - assets[asset] |= assets_inherited[asset] - else: - setattr(asset, 'inherited', True) - assets[asset] = assets_inherited[asset] - return assets - - -def get_user_granted_assets(user): - """Return assets granted of the user inherit from user groups - - :param user: Instance of :class: ``User`` - :return: {asset1: {system_user1, system_user2}, asset2: {...}} - """ - assets_direct = get_user_granted_assets_direct(user) - assets_inherited = get_user_granted_assets_inherit_from_user_groups(user) - assets = assets_inherited - - for asset in assets_direct: - if not asset.is_active: - continue - if asset in assets: - assets[asset] |= assets_direct[asset] - else: - assets[asset] = assets_direct[asset] - return assets - - -def get_user_granted_asset_groups(user): - """Return asset groups with assets and system users, it's not the asset - group direct permed in rules. We get all asset and then get it asset group - - :param user: Instance of :class: ``User`` - :return: {asset_group1: [asset1, asset2], asset_group2: []} - """ - asset_groups = collections.defaultdict(list) - ungroups = [AssetGroup(name="UnGrouped")] - for asset, system_users in get_user_granted_assets(user).items(): - groups = asset.groups.all() - if not groups: - groups = ungroups - for asset_group in groups: - asset_groups[asset_group].append((asset, system_users)) - return asset_groups - - -def get_user_group_asset_permissions(user_group): - permissions = user_group.asset_permissions.all() - return permissions - - -def get_user_asset_permissions(user): - user_group_permissions = set() - direct_permissions = set(setattr_bulk(user.asset_permissions.all(), 'inherited', 0)) - - for user_group in user.groups.all(): - permissions = get_user_group_asset_permissions(user_group) - user_group_permissions |= set(permissions) - user_group_permissions = set(setattr_bulk(user_group_permissions, 'inherited', 1)) - return direct_permissions | user_group_permissions - - -def get_user_granted_system_users(user): - """ - :param user: the user - :return: {"system_user": ["asset", "asset1"], "system_user": []} - """ - assets = get_user_granted_assets(user) - system_users_dict = {} - for asset, system_users in assets.items(): - for system_user in system_users: - if system_user in system_users_dict: - system_users_dict[system_user].append(asset) - else: - system_users_dict[system_user] = [asset] - return system_users_dict +# def get_user_group_granted_asset_groups(user_group): +# """Return asset groups granted of the user group +# +# :param user_group: Instance of :class: ``UserGroup`` +# :return: {asset_group1: {system_user1, }, +# asset_group2: {system_user1, system_user2}} +# """ +# asset_groups = {} +# asset_permissions = user_group.asset_permissions.all() +# +# for asset_permission in asset_permissions: +# if not asset_permission.is_valid: +# continue +# for asset_group in asset_permission.asset_groups.all(): +# if asset_group in asset_groups: +# asset_groups[asset_group] |= set(asset_permission.system_users.all()) +# else: +# asset_groups[asset_group] = set(asset_permission.system_users.all()) +# return asset_groups +# +# +# def get_user_group_granted_assets(user_group): +# """Return assets granted of the user group +# +# :param user_group: Instance of :class: ``UserGroup`` +# :return: {asset1: {system_user1, }, asset1: {system_user1, system_user2]} +# """ +# assets = {} +# asset_permissions = user_group.asset_permissions.all() +# +# for asset_permission in asset_permissions: +# if not asset_permission.is_valid: +# continue +# for asset in asset_permission.get_granted_assets(): +# if not asset.is_active: +# continue +# if asset in assets: +# assets[asset] |= set(asset_permission.system_users.all()) +# else: +# assets[asset] = set(asset_permission.system_users.all()) +# return assets +# +# +# def get_user_granted_assets_direct(user): +# """Return assets granted of the user directly +# +# :param user: Instance of :class: ``User`` +# :return: {asset1: {system_user1, system_user2}, asset2: {...}} +# """ +# assets = {} +# asset_permissions_direct = user.asset_permissions.all() +# +# for asset_permission in asset_permissions_direct: +# if not asset_permission.is_valid: +# continue +# for asset in asset_permission.get_granted_assets(): +# if not asset.is_active: +# continue +# if asset in assets: +# assets[asset] |= set(asset_permission.system_users.all()) +# else: +# setattr(asset, 'inherited', False) +# assets[asset] = set(asset_permission.system_users.all()) +# return assets +# +# +# def get_user_granted_assets_inherit_from_user_groups(user): +# """Return assets granted of the user inherit from user groups +# +# :param user: Instance of :class: ``User`` +# :return: {asset1: {system_user1, system_user2}, asset2: {...}} +# """ +# assets = {} +# user_groups = user.groups.all() +# +# for user_group in user_groups: +# assets_inherited = get_user_group_granted_assets(user_group) +# for asset in assets_inherited: +# if not asset.is_active: +# continue +# if asset in assets: +# assets[asset] |= assets_inherited[asset] +# else: +# setattr(asset, 'inherited', True) +# assets[asset] = assets_inherited[asset] +# return assets +# +# +# def get_user_granted_assets(user): +# """Return assets granted of the user inherit from user groups +# +# :param user: Instance of :class: ``User`` +# :return: {asset1: {system_user1, system_user2}, asset2: {...}} +# """ +# assets_direct = get_user_granted_assets_direct(user) +# assets_inherited = get_user_granted_assets_inherit_from_user_groups(user) +# assets = assets_inherited +# +# for asset in assets_direct: +# if not asset.is_active: +# continue +# if asset in assets: +# assets[asset] |= assets_direct[asset] +# else: +# assets[asset] = assets_direct[asset] +# return assets +# +# +# def get_user_granted_asset_groups(user): +# """Return asset groups with assets and system users, it's not the asset +# group direct permed in rules. We get all asset and then get it asset group +# +# :param user: Instance of :class: ``User`` +# :return: {asset_group1: [asset1, asset2], asset_group2: []} +# """ +# asset_groups = collections.defaultdict(list) +# ungroups = [AssetGroup(name="UnGrouped")] +# for asset, system_users in get_user_granted_assets(user).items(): +# groups = asset.groups.all() +# if not groups: +# groups = ungroups +# for asset_group in groups: +# asset_groups[asset_group].append((asset, system_users)) +# return asset_groups +# +# +# def get_user_group_asset_permissions(user_group): +# permissions = user_group.asset_permissions.all() +# return permissions +# +# +# def get_user_asset_permissions(user): +# user_group_permissions = set() +# direct_permissions = set(setattr_bulk(user.asset_permissions.all(), 'inherited', 0)) +# +# for user_group in user.groups.all(): +# permissions = get_user_group_asset_permissions(user_group) +# user_group_permissions |= set(permissions) +# user_group_permissions = set(setattr_bulk(user_group_permissions, 'inherited', 1)) +# return direct_permissions | user_group_permissions +# +# +# def get_user_granted_system_users(user): +# """ +# :param user: the user +# :return: {"system_user": ["asset", "asset1"], "system_user": []} +# """ +# assets = get_user_granted_assets(user) +# system_users_dict = {} +# for asset, system_users in assets.items(): +# for system_user in system_users: +# if system_user in system_users_dict: +# system_users_dict[system_user].append(asset) +# else: +# system_users_dict[system_user] = [asset] +# return system_users_dict def push_system_user(assets, system_user): From f82d939d1536ecffa76c127b6b743b197b3ff5f2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 8 Feb 2018 11:34:21 +0800 Subject: [PATCH 113/197] =?UTF-8?q?[Update]=20=E4=BD=BF=E7=94=A8=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=A0=91=EF=BC=8C=E5=8E=BB=E6=8E=89=E9=9B=86=E7=BE=A4?= =?UTF-8?q?=E5=92=8C=E8=B5=84=E4=BA=A7=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms.py | 376 ------------ apps/assets/forms/__init__.py | 5 + apps/assets/forms/asset.py | 114 ++++ apps/assets/forms/label.py | 33 ++ apps/assets/forms/user.py | 135 +++++ apps/assets/models/asset.py | 11 +- apps/assets/serializers/asset.py | 5 - apps/assets/serializers/system_user.py | 2 - apps/assets/signals_handler.py | 151 +++-- .../assets/templates/assets/__asset_list.html | 280 +++++++++ .../assets/templates/assets/asset_create.html | 1 - apps/assets/templates/assets/asset_list.html | 414 +++++++++++--- .../assets/templates/assets/asset_update.html | 1 - apps/assets/templates/assets/tree.html | 537 ------------------ .../assets/views/{cluster.py => __cluster.py} | 0 apps/assets/views/{group.py => __group.py} | 0 apps/assets/views/__init__.py | 2 +- apps/assets/views/asset.py | 2 +- apps/assets/views/{tree.py => node.py} | 0 apps/perms/api.py | 35 +- apps/perms/hands.py | 2 +- apps/perms/urls/api_urls.py | 11 +- apps/perms/views.py | 81 +-- .../templates/users/user_granted_asset.html | 242 ++++---- .../templates/users/user_group_detail.html | 6 +- .../users/user_group_granted_asset.html | 260 +++++---- apps/users/views/group.py | 5 +- apps/users/views/user.py | 4 - 28 files changed, 1306 insertions(+), 1409 deletions(-) delete mode 100644 apps/assets/forms.py create mode 100644 apps/assets/forms/__init__.py create mode 100644 apps/assets/forms/asset.py create mode 100644 apps/assets/forms/label.py create mode 100644 apps/assets/forms/user.py create mode 100644 apps/assets/templates/assets/__asset_list.html delete mode 100644 apps/assets/templates/assets/tree.html rename apps/assets/views/{cluster.py => __cluster.py} (100%) rename apps/assets/views/{group.py => __group.py} (100%) rename apps/assets/views/{tree.py => node.py} (100%) diff --git a/apps/assets/forms.py b/apps/assets/forms.py deleted file mode 100644 index b693db3db..000000000 --- a/apps/assets/forms.py +++ /dev/null @@ -1,376 +0,0 @@ -# coding:utf-8 -from django import forms -from django.utils.translation import gettext_lazy as _ - -from .models import Cluster, Asset, AssetGroup, AdminUser, SystemUser, Label -from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, get_logger - -logger = get_logger(__file__) - - -class AssetCreateForm(forms.ModelForm): - class Meta: - model = Asset - fields = [ - 'hostname', 'ip', 'public_ip', 'port', 'comment', 'cluster', - 'nodes', 'is_active', 'admin_user', 'labels', - - ] - widgets = { - 'nodes': forms.SelectMultiple(attrs={ - 'class': 'select2', 'data-placeholder': _('Select nodes') - }), - 'cluster': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Select cluster') - }), - 'admin_user': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Select admin user') - }), - 'labels': forms.SelectMultiple(attrs={ - 'class': 'select2', 'data-placeholder': _('Select labels') - }), - 'port': forms.TextInput(), - } - help_texts = { - 'hostname': '* required', - 'ip': '* required', - 'port': '* required', - 'cluster': '* required', - 'admin_user': _('Host level admin user, If not set using cluster admin user default') - } - - def clean_admin_user(self): - cluster = self.cleaned_data.get('cluster') - admin_user = self.cleaned_data.get('admin_user') - if not admin_user and (cluster and not cluster.admin_user): - raise forms.ValidationError(_("You need set a admin user if cluster not have")) - return self.cleaned_data['admin_user'] - - def is_valid(self): - print(self.data) - result = super().is_valid() - if not result: - print(self.errors) - print(self.cleaned_data) - return result - - -class AssetUpdateForm(forms.ModelForm): - class Meta: - model = Asset - fields = [ - 'hostname', 'ip', 'port', 'nodes', "cluster", 'is_active', - 'public_ip', 'number', 'comment', 'admin_user', 'labels', - ] - widgets = { - 'nodes': forms.SelectMultiple(attrs={ - 'class': 'select2', 'data-placeholder': _('Select nodes') - }), - 'cluster': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Select cluster') - }), - 'admin_user': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Select admin user') - }), - 'labels': forms.SelectMultiple(attrs={ - 'class': 'select2', 'data-placeholder': _('Select labels') - }), - 'port': forms.TextInput(), - } - help_texts = { - 'hostname': '* required', - 'ip': '* required', - 'port': '* required', - 'cluster': '* required', - 'admin_user': _('Host level admin user, If not set using cluster admin user default') - } - - def clean_admin_user(self): - cluster = self.cleaned_data.get('cluster') - admin_user = self.cleaned_data.get('admin_user') - if not admin_user and (cluster and not cluster.admin_user): - raise forms.ValidationError(_("You need set a admin user if cluster not have")) - return self.cleaned_data['admin_user'] - - -class AssetBulkUpdateForm(forms.ModelForm): - assets = forms.ModelMultipleChoiceField( - required=True, help_text='* required', - label=_('Select assets'), queryset=Asset.objects.all(), - widget=forms.SelectMultiple( - attrs={ - 'class': 'select2', - 'data-placeholder': _('Select assets') - } - ) - ) - port = forms.IntegerField( - label=_('Port'), required=False, min_value=1, max_value=65535, - ) - cluster = forms.ModelChoiceField( - required=False, label=_("Cluster"), queryset=Cluster.objects.all(), - widget=forms.Select( - attrs={ - 'class': 'select2', - 'data-placeholder': _('Select cluster') - } - ) - ) - - class Meta: - model = Asset - fields = [ - 'assets', 'port', 'groups', "cluster", 'labels' - ] - widgets = { - 'groups': forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')} - ), - 'labels': forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Select labels')} - ), - } - - def save(self, commit=True): - changed_fields = [] - for field in self._meta.fields: - if self.data.get(field) is not None: - changed_fields.append(field) - - cleaned_data = {k: v for k, v in self.cleaned_data.items() - if k in changed_fields} - assets = cleaned_data.pop('assets') - groups = cleaned_data.pop('groups', []) - labels = cleaned_data.pop('labels', []) - assets = Asset.objects.filter(id__in=[asset.id for asset in assets]) - assets.update(**cleaned_data) - if groups: - for asset in assets: - asset.groups.set(groups) - if labels: - for asset in assets: - asset.labels.set(labels) - return assets - - -class AssetGroupForm(forms.ModelForm): - # See AdminUserForm comment same it - assets = forms.ModelMultipleChoiceField( - queryset=Asset.objects.all(), - label=_('Asset'), - required=False, - widget=forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Select assets')} - ) - ) - - def __init__(self, **kwargs): - instance = kwargs.get('instance') - if instance: - initial = kwargs.get('initial', {}) - initial.update({ - 'assets': instance.assets.all(), - }) - kwargs['initial'] = initial - super().__init__(**kwargs) - - def save(self, commit=True): - group = super().save(commit=commit) - assets = self.cleaned_data['assets'] - group.assets.set(assets) - return group - - class Meta: - model = AssetGroup - fields = [ - "name", "comment", - ] - help_texts = { - 'name': '* required', - } - - -class ClusterForm(forms.ModelForm): - system_users = forms.ModelMultipleChoiceField( - queryset=SystemUser.objects.all(), - widget=forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Select system users')} - ), - label=_('System users'), - required=False, - help_text=_("Selected system users will be create at cluster assets"), - ) - - class Meta: - model = Cluster - fields = ['name', "bandwidth", "operator", 'contact', 'admin_user', 'system_users', - 'phone', 'address', 'intranet', 'extranet', 'comment'] - widgets = { - 'name': forms.TextInput(attrs={'placeholder': _('Name')}), - 'intranet': forms.Textarea(attrs={'placeholder': 'IP段之间用逗号隔开,如:192.168.1.0/24,192.168.1.0/24'}), - 'extranet': forms.Textarea(attrs={'placeholder': 'IP段之间用逗号隔开,如:201.1.32.1/24,202.2.32.1/24'}) - } - help_texts = { - 'name': '* required', - 'admin_user': _("Cluster level admin user"), - } - - def __init__(self, *args, **kwargs): - if kwargs.get('instance', None): - initial = kwargs.get('initial', {}) - initial['system_users'] = kwargs['instance'].systemuser_set.all() - super().__init__(*args, **kwargs) - - def save(self, commit=True): - instance = super().save(commit=commit) - system_users = self.cleaned_data['system_users'] - instance.systemuser_set.set(system_users) - return instance - - -class PasswordAndKeyAuthForm(forms.ModelForm): - # Form field name can not start with `_`, so redefine it, - password = forms.CharField( - widget=forms.PasswordInput, max_length=128, - strip=True, required=False, - help_text=_('Password or private key passphrase'), - label=_("Password"), - ) - # Need use upload private key file except paste private key content - private_key_file = forms.FileField(required=False, label=_("Private key")) - - def clean_private_key_file(self): - private_key_file = self.cleaned_data['private_key_file'] - password = self.cleaned_data['password'] - - if private_key_file: - key_string = private_key_file.read() - private_key_file.seek(0) - if not validate_ssh_private_key(key_string, password): - raise forms.ValidationError(_('Invalid private key')) - return private_key_file - - def validate_password_key(self): - password = self.cleaned_data['password'] - private_key_file = self.cleaned_data.get('private_key_file', '') - - if not password and not private_key_file: - raise forms.ValidationError(_( - 'Password and private key file must be input one' - )) - - def gen_keys(self): - password = self.cleaned_data.get('password', '') or None - private_key_file = self.cleaned_data['private_key_file'] - public_key = private_key = None - - if private_key_file: - private_key = private_key_file.read().strip().decode('utf-8') - public_key = ssh_pubkey_gen(private_key=private_key, password=password) - return private_key, public_key - - -class AdminUserForm(PasswordAndKeyAuthForm): - def save(self, commit=True): - # Because we define custom field, so we need rewrite :method: `save` - admin_user = super().save(commit=commit) - password = self.cleaned_data.get('password', '') or None - private_key, public_key = super().gen_keys() - admin_user.set_auth(password=password, public_key=public_key, private_key=private_key) - return admin_user - - def clean(self): - super().clean() - if not self.instance: - super().validate_password_key() - - class Meta: - model = AdminUser - fields = ['name', 'username', 'password', 'private_key_file', 'comment'] - widgets = { - 'name': forms.TextInput(attrs={'placeholder': _('Name')}), - 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - } - help_texts = { - 'name': '* required', - 'username': '* required', - } - - -class SystemUserForm(PasswordAndKeyAuthForm): - # Admin user assets define, let user select, save it in form not in view - auto_generate_key = forms.BooleanField(initial=True, required=False) - - def save(self, commit=True): - # Because we define custom field, so we need rewrite :method: `save` - system_user = super().save() - password = self.cleaned_data.get('password', '') or None - auto_generate_key = self.cleaned_data.get('auto_generate_key', False) - private_key, public_key = super().gen_keys() - - if auto_generate_key: - logger.info('Auto generate key and set system user auth') - system_user.auto_gen_auth() - else: - system_user.set_auth(password=password, private_key=private_key, public_key=public_key) - return system_user - - def clean(self): - super().clean() - auto_generate = self.cleaned_data.get('auto_generate_key') - if not self.instance and not auto_generate: - super().validate_password_key() - - class Meta: - model = SystemUser - fields = [ - 'name', 'username', 'protocol', 'auto_generate_key', - 'password', 'private_key_file', 'auto_push', 'sudo', - 'comment', 'shell', 'cluster', 'priority', - ] - widgets = { - 'name': forms.TextInput(attrs={'placeholder': _('Name')}), - 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - 'cluster': forms.SelectMultiple( - attrs={ - 'class': 'select2', - 'data-placeholder': _(' Select clusters') - } - ), - } - help_texts = { - 'name': '* required', - 'username': '* required', - 'cluster': _('If auto push checked, system user will be create at cluster assets'), - 'auto_push': _('Auto push system user to asset'), - 'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'), - } - - -class FileForm(forms.Form): - file = forms.FileField() - - -class LabelForm(forms.ModelForm): - assets = forms.ModelMultipleChoiceField( - queryset=Asset.objects.all(), label=_('Asset'), required=False, - widget=forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Select assets')} - ) - ) - - class Meta: - model = Label - fields = ['name', 'value', 'assets'] - - def __init__(self, *args, **kwargs): - if kwargs.get('instance', None): - initial = kwargs.get('initial', {}) - initial['assets'] = kwargs['instance'].assets.all() - super().__init__(*args, **kwargs) - - def save(self, commit=True): - label = super().save(commit=commit) - assets = self.cleaned_data['assets'] - label.assets.set(assets) - return label diff --git a/apps/assets/forms/__init__.py b/apps/assets/forms/__init__.py new file mode 100644 index 000000000..9175d7c43 --- /dev/null +++ b/apps/assets/forms/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# +from .asset import * +from .label import * +from .user import * diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py new file mode 100644 index 000000000..593c35456 --- /dev/null +++ b/apps/assets/forms/asset.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# +from django import forms +from django.utils.translation import gettext_lazy as _ + +from ..models import Asset +from common.utils import get_logger + +logger = get_logger(__file__) +__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm'] + + +class AssetCreateForm(forms.ModelForm): + class Meta: + model = Asset + fields = [ + 'hostname', 'ip', 'public_ip', 'port', 'comment', + 'nodes', 'is_active', 'admin_user', 'labels', + + ] + widgets = { + 'nodes': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Nodes') + }), + 'admin_user': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Admin user') + }), + 'labels': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Labels') + }), + 'port': forms.TextInput(), + } + help_texts = { + 'hostname': '* required', + 'ip': '* required', + 'port': '* required', + 'admin_user': _('') + } + + +class AssetUpdateForm(forms.ModelForm): + class Meta: + model = Asset + fields = [ + 'hostname', 'ip', 'port', 'nodes', 'is_active', + 'public_ip', 'number', 'comment', 'admin_user', 'labels', + ] + widgets = { + 'nodes': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Nodes') + }), + 'admin_user': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Admin user') + }), + 'labels': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Labels') + }), + 'port': forms.TextInput(), + } + help_texts = { + 'hostname': '* required', + 'ip': '* required', + 'port': '* required', + 'cluster': '* required', + 'admin_user': _('') + } + + +class AssetBulkUpdateForm(forms.ModelForm): + assets = forms.ModelMultipleChoiceField( + required=True, help_text='* required', + label=_('Select assets'), queryset=Asset.objects.all(), + widget=forms.SelectMultiple( + attrs={ + 'class': 'select2', + 'data-placeholder': _('Select assets') + } + ) + ) + port = forms.IntegerField( + label=_('Port'), required=False, min_value=1, max_value=65535, + ) + + class Meta: + model = Asset + fields = [ + 'assets', 'port', 'labels', 'admin_user' + ] + widgets = { + 'admin_user': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('Admin user')} + ), + 'labels': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('Labels')} + ), + } + + def save(self, commit=True): + changed_fields = [] + for field in self._meta.fields: + if self.data.get(field) is not None: + changed_fields.append(field) + + cleaned_data = {k: v for k, v in self.cleaned_data.items() + if k in changed_fields} + assets = cleaned_data.pop('assets') + labels = cleaned_data.pop('labels', []) + assets = Asset.objects.filter(id__in=[asset.id for asset in assets]) + assets.update(**cleaned_data) + + if labels: + for asset in assets: + asset.labels.set(labels) + return assets diff --git a/apps/assets/forms/label.py b/apps/assets/forms/label.py new file mode 100644 index 000000000..ebdc9384e --- /dev/null +++ b/apps/assets/forms/label.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# +from django import forms +from django.utils.translation import gettext_lazy as _ + +from ..models import Label, Asset + +__all__ = ['LabelForm'] + + +class LabelForm(forms.ModelForm): + assets = forms.ModelMultipleChoiceField( + queryset=Asset.objects.all(), label=_('Asset'), required=False, + widget=forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('Select assets')} + ) + ) + + class Meta: + model = Label + fields = ['name', 'value', 'assets'] + + def __init__(self, *args, **kwargs): + if kwargs.get('instance', None): + initial = kwargs.get('initial', {}) + initial['assets'] = kwargs['instance'].assets.all() + super().__init__(*args, **kwargs) + + def save(self, commit=True): + label = super().save(commit=commit) + assets = self.cleaned_data['assets'] + label.assets.set(assets) + return label diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py new file mode 100644 index 000000000..6488a6004 --- /dev/null +++ b/apps/assets/forms/user.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +# +from django import forms +from django.utils.translation import gettext_lazy as _ + +from ..models import AdminUser, SystemUser +from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger + +logger = get_logger(__file__) +__all__ = [ + 'FileForm', 'SystemUserForm', 'AdminUserForm', +] + + +class FileForm(forms.Form): + file = forms.FileField() + + +class PasswordAndKeyAuthForm(forms.ModelForm): + # Form field name can not start with `_`, so redefine it, + password = forms.CharField( + widget=forms.PasswordInput, max_length=128, + strip=True, required=False, + help_text=_('Password or private key passphrase'), + label=_("Password"), + ) + # Need use upload private key file except paste private key content + private_key_file = forms.FileField(required=False, label=_("Private key")) + + def clean_private_key_file(self): + private_key_file = self.cleaned_data['private_key_file'] + password = self.cleaned_data['password'] + + if private_key_file: + key_string = private_key_file.read() + private_key_file.seek(0) + if not validate_ssh_private_key(key_string, password): + raise forms.ValidationError(_('Invalid private key')) + return private_key_file + + def validate_password_key(self): + password = self.cleaned_data['password'] + private_key_file = self.cleaned_data.get('private_key_file', '') + + if not password and not private_key_file: + raise forms.ValidationError(_( + 'Password and private key file must be input one' + )) + + def gen_keys(self): + password = self.cleaned_data.get('password', '') or None + private_key_file = self.cleaned_data['private_key_file'] + public_key = private_key = None + + if private_key_file: + private_key = private_key_file.read().strip().decode('utf-8') + public_key = ssh_pubkey_gen(private_key=private_key, password=password) + return private_key, public_key + + +class AdminUserForm(PasswordAndKeyAuthForm): + def save(self, commit=True): + # Because we define custom field, so we need rewrite :method: `save` + admin_user = super().save(commit=commit) + password = self.cleaned_data.get('password', '') or None + private_key, public_key = super().gen_keys() + admin_user.set_auth(password=password, public_key=public_key, private_key=private_key) + return admin_user + + def clean(self): + super().clean() + if not self.instance: + super().validate_password_key() + + class Meta: + model = AdminUser + fields = ['name', 'username', 'password', 'private_key_file', 'comment'] + widgets = { + 'name': forms.TextInput(attrs={'placeholder': _('Name')}), + 'username': forms.TextInput(attrs={'placeholder': _('Username')}), + } + help_texts = { + 'name': '* required', + 'username': '* required', + } + + +class SystemUserForm(PasswordAndKeyAuthForm): + # Admin user assets define, let user select, save it in form not in view + auto_generate_key = forms.BooleanField(initial=True, required=False) + + def save(self, commit=True): + # Because we define custom field, so we need rewrite :method: `save` + system_user = super().save() + password = self.cleaned_data.get('password', '') or None + auto_generate_key = self.cleaned_data.get('auto_generate_key', False) + private_key, public_key = super().gen_keys() + + if auto_generate_key: + logger.info('Auto generate key and set system user auth') + system_user.auto_gen_auth() + else: + system_user.set_auth(password=password, private_key=private_key, public_key=public_key) + return system_user + + def clean(self): + super().clean() + auto_generate = self.cleaned_data.get('auto_generate_key') + if not self.instance and not auto_generate: + super().validate_password_key() + + class Meta: + model = SystemUser + fields = [ + 'name', 'username', 'protocol', 'auto_generate_key', + 'password', 'private_key_file', 'auto_push', 'sudo', + 'comment', 'shell', 'cluster', 'priority', + ] + widgets = { + 'name': forms.TextInput(attrs={'placeholder': _('Name')}), + 'username': forms.TextInput(attrs={'placeholder': _('Username')}), + 'cluster': forms.SelectMultiple( + attrs={ + 'class': 'select2', + 'data-placeholder': _(' Select clusters') + } + ), + } + help_texts = { + 'name': '* required', + 'username': '* required', + 'cluster': _('If auto push checked, system user will be create at cluster assets'), + 'auto_push': _('Auto push system user to asset'), + 'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'), + } \ No newline at end of file diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index a9b371637..d8e4ebe82 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -28,19 +28,22 @@ def default_cluster(): return cluster.id +def default_node(): + from .tree import Node + return Node.root() + + class Asset(models.Model): # Important id = models.UUIDField(default=uuid.uuid4, primary_key=True) ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname')) port = models.IntegerField(default=22, verbose_name=_('Port')) - groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups')) - cluster = models.ForeignKey(Cluster, related_name='assets', default=default_cluster, on_delete=models.SET_DEFAULT, verbose_name=_('Cluster')) - nodes = models.ManyToManyField('assets.Node', blank=True, related_name='assets', verbose_name=_("Nodes")) + nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) # Auth - admin_user = models.ForeignKey('assets.AdminUser', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("Admin user")) + admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, verbose_name=_("Admin user")) # Some information public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP')) diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index 5b5588757..e479a7149 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -12,7 +12,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): """ 资产的数据结构 """ - cluster_name = serializers.SerializerMethodField() class Meta: model = Asset @@ -27,10 +26,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): ]) return fields - @staticmethod - def get_cluster_name(obj): - return obj.cluster.name - class AssetGrantedSerializer(serializers.ModelSerializer): """ diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 488d9bb66..e454cbbbc 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -34,8 +34,6 @@ class SystemUserSerializer(serializers.ModelSerializer): @staticmethod def get_assets_amount(obj): amount = 0 - for cluster in obj.cluster.all(): - amount += cluster.assets.all().count() return amount diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 171cfa6d9..659a40a40 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -39,91 +39,78 @@ def on_asset_created(sender, instance=None, created=False, **kwargs): logger.info("Asset `{}` create signal received".format(instance)) update_asset_hardware_info_on_created(instance) test_asset_conn_on_created(instance) - push_cluster_system_users_to_asset(instance) + # push_cluster_system_users_to_asset(instance) -@receiver(post_init, sender=Asset) -def on_asset_init(sender, instance, created=False, **kwargs): - if instance and created is False: - instance.__original_cluster = instance.cluster +# def push_to_cluster_assets_on_system_user_created_or_update(system_user): +# if not system_user.auto_push: +# return +# logger.debug("Push system user `{}` to cluster assets".format(system_user.name)) +# for cluster in system_user.cluster.all(): +# task_name = _("Push system user to cluster assets: {}->{}").format( +# cluster.name, system_user.name +# ) +# assets = cluster.assets.all() +# push_system_user_util.delay([system_user], assets, task_name) -@receiver(post_save, sender=Asset) -def on_asset_cluster_changed(sender, instance=None, created=False, **kwargs): - if instance and created is False and instance.cluster != instance.__original_cluster: - logger.info("Asset cluster changed signal received") - push_cluster_system_users_to_asset(instance) - - -def push_to_cluster_assets_on_system_user_created_or_update(system_user): - if not system_user.auto_push: - return - logger.debug("Push system user `{}` to cluster assets".format(system_user.name)) - for cluster in system_user.cluster.all(): - task_name = _("Push system user to cluster assets: {}->{}").format( - cluster.name, system_user.name - ) - assets = cluster.assets.all() - push_system_user_util.delay([system_user], assets, task_name) - - -@receiver(post_save, sender=SystemUser) -def on_system_user_created_or_updated(sender, instance=None, **kwargs): - if instance and instance.auto_push: - logger.info("System user `{}` create or update signal received".format(instance)) - push_to_cluster_assets_on_system_user_created_or_update(instance) - - -@receiver(post_init, sender=Cluster, dispatch_uid="my_unique_identifier") -def on_cluster_init(sender, instance, **kwargs): - instance.__original_assets = tuple(instance.assets.values_list('pk', flat=True)) - instance.__origin_system_users = tuple(instance.systemuser_set.values_list('pk', flat=True)) - - -@receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier") -def on_cluster_assets_changed(sender, instance, **kwargs): - assets_origin = instance.__original_assets - assets_new = instance.assets.values_list('pk', flat=True) - assets_added = set(assets_new) - set(assets_origin) - if assets_added: - logger.debug("Receive cluster change assets signal") - logger.debug("Push cluster `{}` system users to: {}".format( - instance, ', '.join([str(asset) for asset in assets_added]) - )) - assets = [] - for asset_id in assets_added: - try: - asset = Asset.objects.get(pk=asset_id) - except Asset.DoesNotExist: - continue - else: - assets.append(asset) - system_users = [s for s in instance.systemuser_set.all() if s.auto_push] - task_name = _("Push system user to assets") - push_system_user_util.delay(system_users, assets, task_name) - - -@receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier2") -def on_cluster_system_user_changed(sender, instance, **kwargs): - system_users_origin = instance.__origin_system_users - system_user_new = instance.systemuser_set.values_list('pk', flat=True) - system_users_added = set(system_user_new) - set(system_users_origin) - if system_users_added: - logger.debug("Receive cluster change system users signal") - system_users = [] - for system_user_id in system_users_added: - try: - system_user = SystemUser.objects.get(pk=system_user_id) - except SystemUser.DoesNotExist: - continue - else: - system_users.append(system_user) - logger.debug("Push new system users `{}` to cluster `{}` assets".format( - ','.join([s.name for s in system_users]), instance - )) - task_name = _( - "Push system user to cluster assets: {}->{}").format( - instance.name, ', '.join(s.name for s in system_users) - ) +# @receiver(post_save, sender=SystemUser) +# def on_system_user_created_or_updated(sender, instance=None, **kwargs): +# if instance and instance.auto_push: +# logger.info("System user `{}` create or update signal received".format(instance)) +# push_to_cluster_assets_on_system_user_created_or_update(instance) +# +# +# @receiver(post_init, sender=Cluster, dispatch_uid="my_unique_identifier") +# def on_cluster_init(sender, instance, **kwargs): +# instance.__original_assets = tuple(instance.assets.values_list('pk', flat=True)) +# instance.__origin_system_users = tuple(instance.systemuser_set.values_list('pk', flat=True)) +# +# +# @receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier") +# def on_cluster_assets_changed(sender, instance, **kwargs): +# assets_origin = instance.__original_assets +# assets_new = instance.assets.values_list('pk', flat=True) +# assets_added = set(assets_new) - set(assets_origin) +# if assets_added: +# logger.debug("Receive cluster change assets signal") +# logger.debug("Push cluster `{}` system users to: {}".format( +# instance, ', '.join([str(asset) for asset in assets_added]) +# )) +# assets = [] +# for asset_id in assets_added: +# try: +# asset = Asset.objects.get(pk=asset_id) +# except Asset.DoesNotExist: +# continue +# else: +# assets.append(asset) +# system_users = [s for s in instance.systemuser_set.all() if s.auto_push] +# task_name = _("Push system user to assets") +# push_system_user_util.delay(system_users, assets, task_name) +# +# +# @receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier2") +# def on_cluster_system_user_changed(sender, instance, **kwargs): +# system_users_origin = instance.__origin_system_users +# system_user_new = instance.systemuser_set.values_list('pk', flat=True) +# system_users_added = set(system_user_new) - set(system_users_origin) +# if system_users_added: +# logger.debug("Receive cluster change system users signal") +# system_users = [] +# for system_user_id in system_users_added: +# try: +# system_user = SystemUser.objects.get(pk=system_user_id) +# except SystemUser.DoesNotExist: +# continue +# else: +# system_users.append(system_user) +# logger.debug("Push new system users `{}` to cluster `{}` assets".format( +# ','.join([s.name for s in system_users]), instance +# )) +# task_name = _( +# "Push system user to cluster assets: {}->{}").format( +# instance.name, ', '.join(s.name for s in system_users) +# ) push_system_user_util.delay(system_users, instance.assets.all(), task_name) diff --git a/apps/assets/templates/assets/__asset_list.html b/apps/assets/templates/assets/__asset_list.html new file mode 100644 index 000000000..d4b2a5bb4 --- /dev/null +++ b/apps/assets/templates/assets/__asset_list.html @@ -0,0 +1,280 @@ +{% extends '_base_list.html' %} +{% load i18n %} +{% load static %} +{% block custom_head_css_js %} + + + +{% endblock %} +{% block content_left_head %}{% endblock %} + +{% block table_search %} + +{% endblock %} + +{% block table_container %} + +
    + + +
    + + + + + + + + + + + + + + + + +
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Cluster' %}{% trans 'Hardware' %}{% trans 'Active' %}{% trans 'Reachable' %}{% trans 'Action' %}
    +
    +
    + +
    + +
    +
    +
    +{% include 'assets/_asset_import_modal.html' %} +{% endblock %} + +{% block custom_foot_js %} + + +{% endblock %} diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html index e07c59d30..7091aa74a 100644 --- a/apps/assets/templates/assets/asset_create.html +++ b/apps/assets/templates/assets/asset_create.html @@ -17,7 +17,6 @@ {% bootstrap_field form.hostname layout="horizontal" %} {% bootstrap_field form.ip layout="horizontal" %} {% bootstrap_field form.port layout="horizontal" %} - {% bootstrap_field form.cluster layout="horizontal" %} {% bootstrap_field form.public_ip layout="horizontal" %}
    diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index d4b2a5bb4..5e113d879 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -1,75 +1,127 @@ -{% extends '_base_list.html' %} -{% load i18n %} +{% extends 'base.html' %} {% load static %} +{% load i18n %} + {% block custom_head_css_js %} - - + + + {% endblock %} -{% block content_left_head %}{% endblock %} -{% block table_search %} - -{% endblock %} +{% block content %} +
    +
    +
    +
    +
    +
    +
    +
    -{% block table_container %} - -
    - -
    +
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + + + + + + + + + +
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Hardware' %}{% trans 'Active' %}{% trans 'Reachable' %}{% trans 'Action' %}
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + + - - - - - - - - - - - - - - - - -
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Cluster' %}{% trans 'Hardware' %}{% trans 'Active' %}{% trans 'Reachable' %}{% trans 'Action' %}
    -
    -
    - -
    - -
    -
    -
    + {% include 'assets/_asset_import_modal.html' %} +{% include 'assets/_asset_list_modal.html' %} {% endblock %} {% block custom_foot_js %} - - -{% endblock %} + +{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/asset_update.html b/apps/assets/templates/assets/asset_update.html index 702df8cc9..515780f55 100644 --- a/apps/assets/templates/assets/asset_update.html +++ b/apps/assets/templates/assets/asset_update.html @@ -22,7 +22,6 @@ {% bootstrap_field form.hostname layout="horizontal" %} {% bootstrap_field form.ip layout="horizontal" %} {% bootstrap_field form.port layout="horizontal" %} - {% bootstrap_field form.cluster layout="horizontal" %} {% bootstrap_field form.public_ip layout="horizontal" %}
    diff --git a/apps/assets/templates/assets/tree.html b/apps/assets/templates/assets/tree.html deleted file mode 100644 index b6c33965a..000000000 --- a/apps/assets/templates/assets/tree.html +++ /dev/null @@ -1,537 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - - - - -{% endblock %} - -{% block content %} -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - - -
    - - -
    - - - - - - - - - - - - - - -
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Hardware' %}{% trans 'Active' %}{% trans 'Reachable' %}{% trans 'Action' %}
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - - - -{% include 'assets/_asset_import_modal.html' %} -{% include 'assets/_asset_list_modal.html' %} -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} \ No newline at end of file diff --git a/apps/assets/views/cluster.py b/apps/assets/views/__cluster.py similarity index 100% rename from apps/assets/views/cluster.py rename to apps/assets/views/__cluster.py diff --git a/apps/assets/views/group.py b/apps/assets/views/__group.py similarity index 100% rename from apps/assets/views/group.py rename to apps/assets/views/__group.py diff --git a/apps/assets/views/__init__.py b/apps/assets/views/__init__.py index 3da4b6750..488d04262 100644 --- a/apps/assets/views/__init__.py +++ b/apps/assets/views/__init__.py @@ -5,4 +5,4 @@ from .asset import * from .system_user import * from .admin_user import * from .label import * -from .tree import * +from .node import * diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index ba26591f0..b14282f74 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -41,7 +41,7 @@ logger = get_logger(__file__) class AssetListView(AdminUserRequiredMixin, TemplateView): - template_name = 'assets/tree.html' + template_name = 'assets/asset_list.html' def get_context_data(self, **kwargs): context = { diff --git a/apps/assets/views/tree.py b/apps/assets/views/node.py similarity index 100% rename from apps/assets/views/tree.py rename to apps/assets/views/node.py diff --git a/apps/perms/api.py b/apps/perms/api.py index 0ae5b4e9e..958ee6838 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -10,7 +10,7 @@ from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser from .utils import NodePermissionUtil from .models import NodePermission from .hands import AssetGrantedSerializer, User, UserGroup, Asset, \ - NodeGrantedSerializer, SystemUser + NodeGrantedSerializer, SystemUser, NodeSerializer from . import serializers @@ -30,8 +30,10 @@ class AssetPermissionViewSet(viewsets.ModelViewSet): def get_queryset(self): queryset = super().get_queryset() node_id = self.request.query_params.get('node_id') + if node_id: queryset = queryset.filter(node__id=node_id) + return queryset @@ -62,6 +64,20 @@ class UserGrantedAssetsApi(ListAPIView): return super().get_permissions() +class UserGrantedNodesApi(ListAPIView): + permission_classes = (IsSuperUser,) + serializer_class = NodeSerializer + + def get_queryset(self): + user_id = self.kwargs.get('pk', '') + if user_id: + user = get_object_or_404(User, id=user_id) + else: + user = self.request.user + nodes = NodePermissionUtil.get_user_nodes(user) + return nodes.keys() + + class UserGrantedNodesWithAssetsApi(ListAPIView): """ 授权用户的资产组,注:这里的资产组并非是授权列表中授权的, @@ -134,7 +150,22 @@ class UserGroupGrantedAssetsApi(ListAPIView): return queryset -class UserGroupGrantedNodeApi(ListAPIView): +class UserGroupGrantedNodesApi(ListAPIView): + permission_classes = (IsSuperUser,) + serializer_class = NodeSerializer + + def get_queryset(self): + group_id = self.kwargs.get('pk', '') + queryset = [] + + if group_id: + group = get_object_or_404(UserGroup, id=group_id) + nodes = NodePermissionUtil.get_user_group_nodes(group) + queryset = nodes.keys() + return queryset + + +class UserGroupGrantedNodesWithAssetsApi(ListAPIView): permission_classes = (IsSuperUser,) serializer_class = NodeGrantedSerializer diff --git a/apps/perms/hands.py b/apps/perms/hands.py index 42bb90cef..22f290069 100644 --- a/apps/perms/hands.py +++ b/apps/perms/hands.py @@ -4,7 +4,7 @@ from users.utils import AdminUserRequiredMixin from users.models import User, UserGroup from assets.models import Asset, AssetGroup, SystemUser, Node -from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer +from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer, NodeSerializer diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index 3c7870fa5..78c869b2e 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -11,12 +11,17 @@ router.register('v1/asset-permissions', api.AssetPermissionViewSet, 'asset-permi urlpatterns = [ # 查询某个用户授权的资产和资产组 - url(r'^v1/user/(?P[0-9a-zA-Z\-]{36})?/?assets/$', api.UserGrantedAssetsApi.as_view(), name='user-assets'), - url(r'^v1/user/(?P[0-9a-zA-Z\-]{36})?/?nodes/$', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes'), + url(r'^v1/user/(?P[0-9a-zA-Z\-]{36})/assets/$', api.UserGrantedAssetsApi.as_view(), name='user-assets'), + url(r'^v1/user/assets/$', api.UserGrantedAssetsApi.as_view(), name='my-assets'), + url(r'^v1/user/(?P[0-9a-zA-Z\-]{36})/nodes/$', api.UserGrantedNodesApi.as_view(), name='user-nodes'), + url(r'^v1/user/nodes/$', api.UserGrantedNodesApi.as_view(), name='my-nodes'), + url(r'^v1/user/(?P[0-9a-zA-Z\-]{36})/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'), + url(r'^v1/user/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(), name='my-nodes-assets'), # 查询某个用户组授权的资产和资产组 url(r'^v1/user-group/(?P[0-9a-zA-Z\-]{36})/assets/$', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), - url(r'^v1/user-group/(?P[0-9a-zA-Z\-]{36})/nodes/$', api.UserGroupGrantedNodeApi.as_view(), name='user-group-asset-groups'), + url(r'^v1/user-group/(?P[0-9a-zA-Z\-]{36})/nodes/$', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), + url(r'^v1/user-group/(?P[0-9a-zA-Z\-]{36})/nodes-assets/$', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'), # 验证用户是否有某个资产和系统用户的权限 url(r'v1/asset-permission/user/validate/$', api.ValidateUserAssetPermissionView.as_view(), name='validate-user-asset-permission'), diff --git a/apps/perms/views.py b/apps/perms/views.py index 2b691459b..33447db7b 100644 --- a/apps/perms/views.py +++ b/apps/perms/views.py @@ -3,15 +3,12 @@ from __future__ import unicode_literals, absolute_import from django.utils.translation import ugettext as _ -from django.conf import settings from django.views.generic import ListView, CreateView, UpdateView from django.views.generic.edit import DeleteView from django.urls import reverse_lazy -from django.views.generic.detail import DetailView, SingleObjectMixin from common.utils import get_object_or_none -from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \ - Asset, AssetGroup, Node +from .hands import AdminUserRequiredMixin, Node from .models import AssetPermission, NodePermission from .forms import AssetPermissionForm @@ -74,85 +71,9 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView): return super().get_context_data(**kwargs) -# class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView): -# template_name = 'perms/asset_permission_detail.html' -# context_object_name = 'asset_permission' -# model = AssetPermission -# -# def get_context_data(self, **kwargs): -# context = { -# 'app': _('Perms'), -# 'action': _('Asset permission detail'), -# 'system_users_remain': [ -# system_user for system_user in SystemUser.objects.all() -# if system_user not in self.object.system_users.all()], -# 'system_users': self.object.system_users.all(), -# } -# kwargs.update(context) -# return super().get_context_data(**kwargs) - - class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView): model = AssetPermission template_name = 'delete_confirm.html' success_url = reverse_lazy('perms:asset-permission-list') -# class AssetPermissionUserView(AdminUserRequiredMixin, -# SingleObjectMixin, -# ListView): -# template_name = 'perms/asset_permission_user.html' -# context_object_name = 'asset_permission' -# paginate_by = settings.DISPLAY_PER_PAGE -# object = None -# -# def get(self, request, *args, **kwargs): -# self.object = self.get_object(queryset=AssetPermission.objects.all()) -# return super().get(request, *args, **kwargs) -# -# def get_queryset(self): -# queryset = self.object.get_granted_users() -# return queryset -# -# def get_context_data(self, **kwargs): -# users_granted = self.get_queryset() -# groups_granted = self.object.user_groups.all() -# context = { -# 'app': _('Perms'), -# 'action': _('Asset permission user list'), -# 'users_remain': User.objects.exclude(id__in=[user.id for user in users_granted]), -# 'user_groups': self.object.user_groups.all(), -# 'user_groups_remain': UserGroup.objects.exclude(id__in=[group.id for group in groups_granted]) -# } -# kwargs.update(context) -# return super().get_context_data(**kwargs) - - -# class AssetPermissionAssetView(AdminUserRequiredMixin, -# SingleObjectMixin, -# ListView): -# template_name = 'perms/asset_permission_asset.html' -# context_object_name = 'asset_permission' -# paginate_by = settings.DISPLAY_PER_PAGE -# object = None -# -# def get(self, request, *args, **kwargs): -# self.object = self.get_object(queryset=AssetPermission.objects.all()) -# return super().get(request, *args, **kwargs) -# -# def get_queryset(self): -# queryset = self.object.get_granted_assets() -# return queryset -# -# def get_context_data(self, **kwargs): -# assets_granted = self.get_queryset() -# groups_granted = self.object.asset_groups.all() -# context = { -# 'app': _('Perms'), -# 'action': _('Asset permission asset list'), -# 'assets_remain': Asset.objects.exclude(id__in=[asset.id for asset in assets_granted]), -# 'asset_groups': self.object.asset_groups.all(), -# 'asset_groups_remain': AssetGroup.objects.exclude(id__in=[group.id for group in groups_granted]) -# } -# kwargs.update(context) -# return super().get_context_data(**kwargs) diff --git a/apps/users/templates/users/user_granted_asset.html b/apps/users/templates/users/user_granted_asset.html index 8bcbdc8a9..92f355d9f 100644 --- a/apps/users/templates/users/user_granted_asset.html +++ b/apps/users/templates/users/user_granted_asset.html @@ -4,8 +4,8 @@ {% load i18n %} {% block custom_head_css_js %} - - + + {% endblock %} {% block content %}
    @@ -15,77 +15,41 @@
    -
    -
    -
    - {% trans 'Assets granted of ' %} {{ user.name }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - - -
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'System user' %}
    -
    -
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    -
    -
    -
    - {% trans 'Asset groups granted of ' %} {{ user.name }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - -
    {% trans 'Name' %}{% trans 'Asset' %}
    -
    +
    +
    + + + + + + + + + + + + +
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Active' %}{% trans 'Reachable' %}
    @@ -96,42 +60,114 @@ {% endblock %} {% block custom_foot_js %} - + zNodes = data; + $.fn.zTree.init($("#assetTree"), setting, zNodes); + zTree = $.fn.zTree.getZTreeObj("assetTree"); + rMenu = $("#rMenu"); + selectQueryNode(); + }); +} + +$(document).ready(function () { + initTree(); +}); + {% endblock %} diff --git a/apps/users/templates/users/user_group_detail.html b/apps/users/templates/users/user_group_detail.html index bb10398a5..c0d99876f 100644 --- a/apps/users/templates/users/user_group_detail.html +++ b/apps/users/templates/users/user_group_detail.html @@ -21,9 +21,9 @@
  • {% trans 'User group detail' %}
  • -{#
  • #} -{# {% trans 'Asset granted' %}#} -{#
  • #} +
  • + {% trans 'Asset granted' %} +
  • {% trans 'Update' %}
  • diff --git a/apps/users/templates/users/user_group_granted_asset.html b/apps/users/templates/users/user_group_granted_asset.html index 7944101f2..ecb742ef1 100644 --- a/apps/users/templates/users/user_group_granted_asset.html +++ b/apps/users/templates/users/user_group_granted_asset.html @@ -4,8 +4,8 @@ {% load i18n %} {% block custom_head_css_js %} - - + + {% endblock %} {% block content %}
    @@ -15,81 +15,41 @@
    -
    -
    -
    - {% trans 'Assets granted of ' %} {{ user_group.name }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - - - - - -
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'System user' %}{% trans 'Valid' %}
    -
    -
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    -
    -
    -
    - {% trans 'Asset groups granted of ' %} {{ user_group.name }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - - -
    {% trans 'Name' %}{% trans 'Asset' %}
    -
    +
    +
    + + + + + + + + + + + + +
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Active' %}{% trans 'Reachable' %}
    @@ -100,54 +60,114 @@ {% endblock %} {% block custom_foot_js %} - + zNodes = data; + $.fn.zTree.init($("#assetTree"), setting, zNodes); + zTree = $.fn.zTree.getZTreeObj("assetTree"); + rMenu = $("#rMenu"); + selectQueryNode(); + }); +} + +$(document).ready(function () { + initTree(); +}); + {% endblock %} diff --git a/apps/users/views/group.py b/apps/users/views/group.py index 4b11e7901..fb279b582 100644 --- a/apps/users/views/group.py +++ b/apps/users/views/group.py @@ -88,10 +88,7 @@ class UserGroupGrantedAssetView(AdminUserRequiredMixin, DetailView): model = UserGroup template_name = 'users/user_group_granted_asset.html' context_object_name = 'user_group' - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=self.model.objects.all()) - return super().get(request, *args, **kwargs) + object = None def get_context_data(self, **kwargs): context = { diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 9110e2887..c95680f65 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -295,10 +295,6 @@ class UserGrantedAssetView(AdminUserRequiredMixin, DetailView): template_name = 'users/user_granted_asset.html' object = None - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=User.objects.all()) - return super().get(request, *args, **kwargs) - def get_context_data(self, **kwargs): context = { 'app': _('Users'), From c7296c2498a0e6c49efaf2071c8600c8539ad557 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 9 Feb 2018 11:12:40 +0800 Subject: [PATCH 114/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7=E6=8E=A8=E9=80=81=EF=BC=8C=E6=8B=86?= =?UTF-8?q?=E5=88=86assets=E7=9A=84=E9=83=A8=E5=88=86=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 8 +- apps/assets/api/system_user.py | 4 +- apps/assets/forms/asset.py | 4 +- apps/assets/forms/user.py | 8 +- apps/assets/hands.py | 5 +- apps/assets/models/__init__.py | 2 +- apps/assets/models/asset.py | 33 +--- apps/assets/models/{tree.py => node.py} | 15 +- apps/assets/models/user.py | 23 +-- apps/assets/signals.py | 1 - apps/assets/signals_handler.py | 115 ++++-------- apps/assets/tasks.py | 130 +++++++------ .../assets/templates/assets/_system_user.html | 1 - .../templates/assets/admin_user_assets.html | 8 +- .../assets/templates/assets/asset_detail.html | 6 +- apps/assets/utils.py | 15 +- apps/assets/views/admin_user.py | 9 +- apps/assets/views/system_user.py | 2 - apps/perms/apps.py | 4 + apps/perms/signals_handler.py | 18 ++ apps/perms/tasks.py | 3 - apps/perms/utils.py | 176 +----------------- apps/users/templates/users/login.html | 2 +- 23 files changed, 186 insertions(+), 406 deletions(-) rename apps/assets/models/{tree.py => node.py} (85%) create mode 100644 apps/perms/signals_handler.py diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 1f75b49fc..8e9edc3e2 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -33,7 +33,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): """ filter_fields = ("hostname", "ip") search_fields = filter_fields - ordering_fields = ("hostname", "ip", "port", "cluster", "cpu_cores") + ordering_fields = ("hostname", "ip", "port", "cpu_cores") queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer pagination_class = LimitOffsetPagination @@ -47,13 +47,9 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): if admin_user_id: admin_user = get_object_or_404(AdminUser, id=admin_user_id) - assets_direct = [asset.id for asset in admin_user.asset_set.all()] - clusters = [cluster.id for cluster in admin_user.cluster_set.all()] - queryset = queryset.filter(Q(cluster__id__in=clusters)|Q(id__in=assets_direct)) + queryset = queryset.filter(admin_user=admin_user) if system_user_id: system_user = get_object_or_404(SystemUser, id=system_user_id) - clusters = system_user.get_clusters() - queryset = queryset.filter(cluster__in=clusters) if node_id: node = get_object_or_404(Node, id=node_id) queryset = queryset.filter(nodes__key__startswith=node.key).distinct() diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index d19cf152c..c34690076 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -20,7 +20,7 @@ from common.utils import get_logger from ..hands import IsSuperUser, IsSuperUserOrAppUser from ..models import SystemUser from .. import serializers -from ..tasks import push_system_user_to_cluster_assets_manual, \ +from ..tasks import push_system_user_to_assets_manual, \ test_system_user_connectability_manual @@ -68,7 +68,7 @@ class SystemUserPushApi(generics.RetrieveAPIView): def retrieve(self, request, *args, **kwargs): system_user = self.get_object() - push_system_user_to_cluster_assets_manual.delay(system_user) + push_system_user_to_assets_manual.delay(system_user) return Response({"msg": "Task created"}) diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py index 593c35456..8aaf8158b 100644 --- a/apps/assets/forms/asset.py +++ b/apps/assets/forms/asset.py @@ -34,7 +34,9 @@ class AssetCreateForm(forms.ModelForm): 'hostname': '* required', 'ip': '* required', 'port': '* required', - 'admin_user': _('') + 'admin_user': _('Admin user is a privilege user exist on this asset,' + 'Example: root or other NOPASSWD sudo privilege user' + ) } diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py index 6488a6004..24807ca08 100644 --- a/apps/assets/forms/user.py +++ b/apps/assets/forms/user.py @@ -114,22 +114,22 @@ class SystemUserForm(PasswordAndKeyAuthForm): fields = [ 'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'auto_push', 'sudo', - 'comment', 'shell', 'cluster', 'priority', + 'comment', 'shell', 'nodes', 'priority', ] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - 'cluster': forms.SelectMultiple( + 'nodes': forms.SelectMultiple( attrs={ 'class': 'select2', - 'data-placeholder': _(' Select clusters') + 'data-placeholder': _('Nodes') } ), } help_texts = { 'name': '* required', 'username': '* required', - 'cluster': _('If auto push checked, system user will be create at cluster assets'), + 'nodes': _('If auto push checked, system user will be create at node assets'), 'auto_push': _('Auto push system user to asset'), 'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'), } \ No newline at end of file diff --git a/apps/assets/hands.py b/apps/assets/hands.py index 403a08633..ad44052d3 100644 --- a/apps/assets/hands.py +++ b/apps/assets/hands.py @@ -11,8 +11,7 @@ """ -from users.utils import AdminUserRequiredMixin -from users.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser +from common.mixins import AdminUserRequiredMixin +from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser from users.models import User, UserGroup from perms.utils import NodePermissionUtil -from perms.tasks import push_users \ No newline at end of file diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index b4b0b8707..dbc703706 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -5,6 +5,6 @@ from .user import AdminUser, SystemUser from .label import Label from .cluster import * from .group import * -from .tree import * +from .node import * from .asset import * from .utils import * diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index d8e4ebe82..b101d63f9 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -29,8 +29,11 @@ def default_cluster(): def default_node(): - from .tree import Node - return Node.root() + try: + from .tree import Node + return Node.root() + except: + return None class Asset(models.Model): @@ -43,7 +46,7 @@ class Asset(models.Model): is_active = models.BooleanField(default=True, verbose_name=_('Is active')) # Auth - admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, verbose_name=_("Admin user")) + admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, verbose_name=_("Admin user")) # Some information public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP')) @@ -102,30 +105,12 @@ class Asset(models.Model): else: return False - @property - def admin_user_avail(self): - if self.admin_user: - admin_user = self.admin_user - elif self.cluster and self.cluster.admin_user: - admin_user = self.cluster.admin_user - else: - return None - return admin_user - - @property - def is_has_private_admin_user(self): - if self.admin_user: - return True - else: - return False - def to_json(self): return { 'id': self.id, 'hostname': self.hostname, 'ip': self.ip, 'port': self.port, - 'groups': [group.name for group in self.groups.all()], } def _to_secret_json(self): @@ -136,13 +121,14 @@ class Asset(models.Model): Todo: May be move to ops implements it """ data = self.to_json() - if self.admin_user_avail: - admin_user = self.admin_user_avail + if self.admin_user: + admin_user = self.admin_user data.update({ 'username': admin_user.username, 'password': admin_user.password, 'private_key': admin_user.private_key_file, 'become': admin_user.become_info, + 'groups': [node.value for node in self.nodes.all()], }) return data @@ -161,7 +147,6 @@ class Asset(models.Model): asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i), hostname=forgery_py.internet.user_name(True), admin_user=choice(AdminUser.objects.all()), - cluster=choice(Cluster.objects.all()), port=22, created_by='Fake') try: diff --git a/apps/assets/models/tree.py b/apps/assets/models/node.py similarity index 85% rename from apps/assets/models/tree.py rename to apps/assets/models/node.py index a62b3c1bd..7c5e8f450 100644 --- a/apps/assets/models/tree.py +++ b/apps/assets/models/node.py @@ -24,7 +24,7 @@ class Node(models.Model): if self == self.__class__.root(): return self.value else: - return '{}/{}'.format( self.value, self.parent.full_value) + return '{}/{}'.format(self.value, self.parent.full_value) @property def level(self): @@ -78,6 +78,19 @@ class Node(models.Model): else: return parent + @property + def ancestor(self): + if self.parent == self.__class__.root(): + return [self.__class__.root()] + else: + return [self.parent, *tuple(self.parent.ancestor)] + + @property + def ancestor_with_node(self): + ancestor = self.ancestor + ancestor.insert(0, self) + return ancestor + @classmethod def root(cls): obj, created = cls.objects.get_or_create( diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index e536f7902..a8c24420f 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -218,7 +218,7 @@ class SystemUser(AssetUser): (RDP_PROTOCOL, 'rdp'), ) - cluster = models.ManyToManyField('assets.Cluster', blank=True, verbose_name=_("Cluster")) + nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) priority = models.IntegerField(default=10, verbose_name=_("Priority")) protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) @@ -228,21 +228,6 @@ class SystemUser(AssetUser): def __str__(self): return self.name - def get_clusters_assets(self): - from .asset import Asset - clusters = self.get_clusters() - return Asset.objects.filter(cluster__in=clusters) - - def get_clusters(self): - return self.cluster.all() - - def get_clusters_joined(self): - return ', '.join([cluster.name for cluster in self.get_clusters()]) - - @property - def assets_amount(self): - return len(self.get_clusters_assets()) - def to_json(self): return { 'id': self.id, @@ -266,6 +251,12 @@ class SystemUser(AssetUser): def reachable_assets(self): return self.assets_connective.get('contacted', []) + def is_need_push(self): + if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL: + return True + else: + return False + class Meta: ordering = ['name'] verbose_name = _("System user") diff --git a/apps/assets/signals.py b/apps/assets/signals.py index a02a16d20..20da657b1 100644 --- a/apps/assets/signals.py +++ b/apps/assets/signals.py @@ -3,4 +3,3 @@ from django.dispatch import Signal on_app_ready = Signal() -on_system_user_auth_changed = Signal(providing_args=['system_user']) diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 659a40a40..95c57e4cb 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -1,15 +1,14 @@ # -*- coding: utf-8 -*- # -from django.db.models.signals import post_save, post_init +from django.db.models.signals import post_save, m2m_changed from django.dispatch import receiver -from django.utils.translation import gettext as _ from common.utils import get_logger -from .models import Asset, SystemUser, Cluster +from .models import Asset, SystemUser, Node from .tasks import update_assets_hardware_info_util, \ - test_asset_connectability_util, \ - push_system_user_util + test_asset_connectability_util, push_system_user_to_node, \ + push_node_system_users_to_asset logger = get_logger(__file__) @@ -25,92 +24,42 @@ def test_asset_conn_on_created(asset): test_asset_connectability_util.delay(asset) -def push_cluster_system_users_to_asset(asset): - if asset.cluster: - logger.info("Push cluster system user to asset: {}".format(asset)) - task_name = _("Push cluster system users to asset") - system_users = asset.cluster.systemuser_set.all() - push_system_user_util.delay(system_users, [asset], task_name) - - @receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier") def on_asset_created(sender, instance=None, created=False, **kwargs): if instance and created: logger.info("Asset `{}` create signal received".format(instance)) update_asset_hardware_info_on_created(instance) test_asset_conn_on_created(instance) - # push_cluster_system_users_to_asset(instance) -# def push_to_cluster_assets_on_system_user_created_or_update(system_user): -# if not system_user.auto_push: -# return -# logger.debug("Push system user `{}` to cluster assets".format(system_user.name)) -# for cluster in system_user.cluster.all(): -# task_name = _("Push system user to cluster assets: {}->{}").format( -# cluster.name, system_user.name -# ) -# assets = cluster.assets.all() -# push_system_user_util.delay([system_user], assets, task_name) +@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier") +def on_system_user_update(sender, instance=None, created=True, **kwargs): + if instance and not created: + for node in instance.nodes.all(): + push_system_user_to_node(instance, node) -# @receiver(post_save, sender=SystemUser) -# def on_system_user_created_or_updated(sender, instance=None, **kwargs): -# if instance and instance.auto_push: -# logger.info("System user `{}` create or update signal received".format(instance)) -# push_to_cluster_assets_on_system_user_created_or_update(instance) -# -# -# @receiver(post_init, sender=Cluster, dispatch_uid="my_unique_identifier") -# def on_cluster_init(sender, instance, **kwargs): -# instance.__original_assets = tuple(instance.assets.values_list('pk', flat=True)) -# instance.__origin_system_users = tuple(instance.systemuser_set.values_list('pk', flat=True)) -# -# -# @receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier") -# def on_cluster_assets_changed(sender, instance, **kwargs): -# assets_origin = instance.__original_assets -# assets_new = instance.assets.values_list('pk', flat=True) -# assets_added = set(assets_new) - set(assets_origin) -# if assets_added: -# logger.debug("Receive cluster change assets signal") -# logger.debug("Push cluster `{}` system users to: {}".format( -# instance, ', '.join([str(asset) for asset in assets_added]) -# )) -# assets = [] -# for asset_id in assets_added: -# try: -# asset = Asset.objects.get(pk=asset_id) -# except Asset.DoesNotExist: -# continue -# else: -# assets.append(asset) -# system_users = [s for s in instance.systemuser_set.all() if s.auto_push] -# task_name = _("Push system user to assets") -# push_system_user_util.delay(system_users, assets, task_name) -# -# -# @receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier2") -# def on_cluster_system_user_changed(sender, instance, **kwargs): -# system_users_origin = instance.__origin_system_users -# system_user_new = instance.systemuser_set.values_list('pk', flat=True) -# system_users_added = set(system_user_new) - set(system_users_origin) -# if system_users_added: -# logger.debug("Receive cluster change system users signal") -# system_users = [] -# for system_user_id in system_users_added: -# try: -# system_user = SystemUser.objects.get(pk=system_user_id) -# except SystemUser.DoesNotExist: -# continue -# else: -# system_users.append(system_user) -# logger.debug("Push new system users `{}` to cluster `{}` assets".format( -# ','.join([s.name for s in system_users]), instance -# )) -# task_name = _( -# "Push system user to cluster assets: {}->{}").format( -# instance.name, ', '.join(s.name for s in system_users) -# ) - push_system_user_util.delay(system_users, instance.assets.all(), task_name) +@receiver(m2m_changed, sender=SystemUser.nodes.through) +def on_system_user_node_change(sender, instance=None, **kwargs): + if instance and kwargs["action"] == "post_add": + for pk in kwargs['pk_set']: + node = kwargs['model'].objects.get(pk=pk) + push_system_user_to_node(instance, node) + + +@receiver(m2m_changed, sender=Asset.nodes.through) +def on_asset_node_changed(sender, instance=None, **kwargs): + if isinstance(instance, Asset) and kwargs['action'] == 'post_add': + logger.debug("Asset node change signal received") + for pk in kwargs['pk_set']: + node = kwargs['model'].objects.get(pk=pk) + push_node_system_users_to_asset(node, [instance]) + + +@receiver(m2m_changed, sender=Asset.nodes.through) +def on_node_assets_changed(sender, instance=None, **kwargs): + if isinstance(instance, Node) and kwargs['action'] == 'post_add': + logger.debug("Node assets change signal received") + assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) + push_node_system_users_to_asset(instance, assets) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 4fc5b023b..25dec8af7 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -99,7 +99,7 @@ def update_assets_hardware_info_util(assets, task_name=None): result = task.run() # Todo: may be somewhere using # Manual run callback function - assets_updated = set_assets_hardware_info(result) + set_assets_hardware_info(result) return result @@ -262,21 +262,23 @@ def test_system_user_connectability_util(system_user, task_name): :param task_name: :return: """ - from ops.utils import update_or_create_ansible_task - assets = system_user.get_clusters_assets() - hosts = [asset.hostname for asset in assets] - tasks = const.TEST_SYSTEM_USER_CONN_TASKS - if not hosts: - logger.info("No hosts, passed") - return {} - task, created = update_or_create_ansible_task( - task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, - run_as=system_user.name, created_by="System", - ) - result = task.run() - set_system_user_connectablity_info(result, system_user=system_user.name) - return result + # todo + # from ops.utils import update_or_create_ansible_task + # assets = system_user.get_clusters_assets() + # hosts = [asset.hostname for asset in assets] + # tasks = const.TEST_SYSTEM_USER_CONN_TASKS + # if not hosts: + # logger.info("No hosts, passed") + # return {} + # task, created = update_or_create_ansible_task( + # task_name, hosts=hosts, tasks=tasks, pattern='all', + # options=const.TASK_OPTIONS, + # run_as=system_user.name, created_by="System", + # ) + # result = task.run() + # set_system_user_connectablity_info(result, system_user=system_user.name) + # return result + return {} @shared_task @@ -290,21 +292,23 @@ def test_system_user_connectability_manual(system_user): @after_app_ready_start @after_app_shutdown_clean def test_system_user_connectability_period(): - from ops.utils import update_or_create_ansible_task - system_users = SystemUser.objects.all() - for system_user in system_users: - task_name = _("Test system user connectability period: {}").format( - system_user.name - ) - assets = system_user.get_clusters_assets() - hosts = [asset.hostname for asset in assets] - tasks = const.TEST_SYSTEM_USER_CONN_TASKS - update_or_create_ansible_task( - task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=False, run_as=system_user.name, - created_by='System', interval=3600, is_periodic=True, - callback=set_admin_user_connectability_info.name, - ) + # Todo + pass + # from ops.utils import update_or_create_ansible_task + # system_users = SystemUser.objects.all() + # for system_user in system_users: + # task_name = _("Test system user connectability period: {}").format( + # system_user.name + # ) + # assets = system_user.get_clusters_assets() + # hosts = [asset.hostname for asset in assets] + # tasks = const.TEST_SYSTEM_USER_CONN_TASKS + # update_or_create_ansible_task( + # task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', + # options=const.TASK_OPTIONS, run_as_admin=False, run_as=system_user.name, + # created_by='System', interval=3600, is_periodic=True, + # callback=set_admin_user_connectability_info.name, + # ) #### Push system user tasks #### @@ -315,7 +319,6 @@ def get_push_system_user_tasks(system_user): return [] tasks = [] - if system_user.password: tasks.append({ 'name': 'Add user {}'.format(system_user.username), @@ -358,7 +361,8 @@ def push_system_user_util(system_users, assets, task_name): from ops.utils import update_or_create_ansible_task tasks = [] for system_user in system_users: - tasks.extend(get_push_system_user_tasks(system_user)) + if system_user.is_need_push(): + tasks.extend(get_push_system_user_tasks(system_user)) if not tasks: logger.info("Not tasks, passed") @@ -375,11 +379,41 @@ def push_system_user_util(system_users, assets, task_name): return task.run() +def get_node_push_system_user_task_name(system_user, node): + return _("Push system user to node: {} => {}").format( + system_user.name, + node.value + ) + + +def push_system_user_to_node(system_user, node): + assets = node.get_all_assets() + task_name = get_node_push_system_user_task_name(system_user, node) + push_system_user_util.delay([system_user], assets, task_name) + + @shared_task -def push_system_user_to_cluster_assets_manual(system_user): - task_name = _("Push system user to cluster assets: {}").format(system_user.name) - assets = system_user.get_clusters_assets() - return push_system_user_util([system_user], assets, task_name) +def push_system_user_related_nodes(system_user): + nodes = system_user.nodes.all() + for node in nodes: + push_system_user_to_node(system_user, node) + + +@shared_task +def push_system_user_to_assets_manual(system_user): + push_system_user_related_nodes(system_user) + + +def push_node_system_users_to_asset(node, assets): + system_users = [] + nodes = node.ancestor_with_node + # 获取该节点所有父节点有的系统用户, 然后推送 + for n in nodes: + system_users.extend(list(n.systemuser_set.all())) + + if system_users: + task_name = _("Push system users to node: {}").format(node.value) + push_system_user_util.delay(system_users, assets, task_name) @shared_task @@ -387,23 +421,5 @@ def push_system_user_to_cluster_assets_manual(system_user): @after_app_ready_start @after_app_shutdown_clean def push_system_user_period(): - from ops.utils import update_or_create_ansible_task - clusters = Cluster.objects.all() - - for cluster in clusters: - tasks = [] - system_users = [system_user for system_user in cluster.systemuser_set.all() if system_user.auto_push] - if not system_users: - return - for system_user in system_users: - tasks.extend(get_push_system_user_tasks(system_user)) - - task_name = _("Push cluster system users to assets period: {}").format( - cluster.name - ) - hosts = [asset.hostname for asset in cluster.assets.all()] - update_or_create_ansible_task( - task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=True, created_by='System', - interval=60*60*24, is_periodic=True, - ) + for system_user in SystemUser.objects.all(): + push_system_user_related_nodes(system_user) diff --git a/apps/assets/templates/assets/_system_user.html b/apps/assets/templates/assets/_system_user.html index 4ebb64279..fe06a6fc7 100644 --- a/apps/assets/templates/assets/_system_user.html +++ b/apps/assets/templates/assets/_system_user.html @@ -39,7 +39,6 @@ {% bootstrap_field form.username layout="horizontal" %} {% bootstrap_field form.priority layout="horizontal" %} {% bootstrap_field form.protocol layout="horizontal" %} - {% bootstrap_field form.cluster layout="horizontal" %} {% block auth %}

    {% trans 'Auth' %}

    diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index 7ec123fe2..3140c36c3 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -59,7 +59,6 @@ {% trans 'Hostname' %} {% trans 'IP' %} {% trans 'Port' %} - {% trans 'Type' %} {% trans 'Reachable' %} @@ -109,7 +108,7 @@ function initTable() { var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, - {targets: 5, createdCell: function (td, cellData) { + {targets: 4, createdCell: function (td, cellData) { if (!cellData) { $(td).html('') } else { @@ -117,8 +116,9 @@ function initTable() { } }}], ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}', - columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port" }, - {data: "type" }, {data: "is_connective" }], + columns: [ + {data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, + {data: "port" }, {data: "is_connective" }], op_html: $('#actions').html() }; jumpserver.initServerSideDataTable(options); diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html index e7b136555..675cb99a1 100644 --- a/apps/assets/templates/assets/asset_detail.html +++ b/apps/assets/templates/assets/asset_detail.html @@ -71,11 +71,7 @@ {% trans 'Admin user' %}: - {% if asset.admin_user_avail %} - {{ asset.admin_user_avail.name }} - {% else %} - - {% endif %} + {{ asset.admin_user }} {% trans 'Cluster' %}: diff --git a/apps/assets/utils.py b/apps/assets/utils.py index 79be4f82c..e3899bd8a 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -23,17 +23,6 @@ def get_system_user_by_name(name): return system_user -def check_assets_have_system_user(assets, system_users): - errors = defaultdict(list) - - for system_user in system_users: - clusters = system_user.cluster.all() - for asset in assets: - if asset.cluster not in clusters: - errors[asset].append(system_user) - return errors - - class LabelFilter: def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) @@ -53,3 +42,7 @@ class LabelFilter: for kwargs in conditions: queryset = queryset.filter(**kwargs) return queryset + + + + diff --git a/apps/assets/views/admin_user.py b/apps/assets/views/admin_user.py index ce7b8bfb4..ec27a1466 100644 --- a/apps/assets/views/admin_user.py +++ b/apps/assets/views/admin_user.py @@ -74,11 +74,9 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView): object = None def get_context_data(self, **kwargs): - cluster_remain = Cluster.objects.exclude(admin_user=self.object) context = { 'app': _('Assets'), 'action': _('Admin user detail'), - 'cluster_remain': cluster_remain, } kwargs.update(context) return super().get_context_data(**kwargs) @@ -95,11 +93,8 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView): return super().get(request, *args, **kwargs) def get_queryset(self): - queryset = [] - for cluster in self.object.cluster_set.all(): - queryset.extend([asset for asset in cluster.assets.all() if not asset.admin_user]) - self.queryset = queryset - return queryset + self.queryset = self.object.asset_set.all() + return self.queryset def get_context_data(self, **kwargs): context = { diff --git a/apps/assets/views/system_user.py b/apps/assets/views/system_user.py index b7744735a..3b79d0391 100644 --- a/apps/assets/views/system_user.py +++ b/apps/assets/views/system_user.py @@ -70,11 +70,9 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView): model = SystemUser def get_context_data(self, **kwargs): - cluster_remain = Cluster.objects.exclude(systemuser=self.object) context = { 'app': _('Assets'), 'action': _('System user detail'), - 'cluster_remain': cluster_remain, } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/apps/perms/apps.py b/apps/perms/apps.py index d40373e08..216e9c3d7 100644 --- a/apps/perms/apps.py +++ b/apps/perms/apps.py @@ -5,3 +5,7 @@ from django.apps import AppConfig class PermsConfig(AppConfig): name = 'perms' + + def ready(self): + from . import signals_handler + return super().ready() diff --git a/apps/perms/signals_handler.py b/apps/perms/signals_handler.py new file mode 100644 index 000000000..ab16a85e7 --- /dev/null +++ b/apps/perms/signals_handler.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# + +from django.db.models.signals import post_save, post_delete +from django.dispatch import receiver + +from common.utils import get_logger +from .models import NodePermission + + +logger = get_logger(__file__) + + +@receiver(post_save, sender=NodePermission, dispatch_uid="my_unique_identifier") +def on_asset_permission_create_or_update(sender, instance=None, **kwargs): + if instance and instance.node and instance.system_user: + instance.system_user.nodes.add(instance.node) + diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index 4b66b5727..b8696e7c8 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -7,7 +7,4 @@ from common.utils import get_logger, encrypt_password logger = get_logger(__file__) -@shared_task(bind=True) -def push_users(self, assets, users): - pass diff --git a/apps/perms/utils.py b/apps/perms/utils.py index 75299e761..c067bbd11 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils.py @@ -3,11 +3,11 @@ from __future__ import absolute_import, unicode_literals import collections from django.utils import timezone +from django.utils.translation import ugettext as _ +import copy from common.utils import setattr_bulk, get_logger -import copy -from .tasks import push_users -from .hands import AssetGroup +from .models import NodePermission logger = get_logger(__file__) @@ -111,173 +111,3 @@ class NodePermissionUtil: assets.update(perm.node.get_all_assets()) return assets - -# def get_user_group_granted_asset_groups(user_group): -# """Return asset groups granted of the user group -# -# :param user_group: Instance of :class: ``UserGroup`` -# :return: {asset_group1: {system_user1, }, -# asset_group2: {system_user1, system_user2}} -# """ -# asset_groups = {} -# asset_permissions = user_group.asset_permissions.all() -# -# for asset_permission in asset_permissions: -# if not asset_permission.is_valid: -# continue -# for asset_group in asset_permission.asset_groups.all(): -# if asset_group in asset_groups: -# asset_groups[asset_group] |= set(asset_permission.system_users.all()) -# else: -# asset_groups[asset_group] = set(asset_permission.system_users.all()) -# return asset_groups -# -# -# def get_user_group_granted_assets(user_group): -# """Return assets granted of the user group -# -# :param user_group: Instance of :class: ``UserGroup`` -# :return: {asset1: {system_user1, }, asset1: {system_user1, system_user2]} -# """ -# assets = {} -# asset_permissions = user_group.asset_permissions.all() -# -# for asset_permission in asset_permissions: -# if not asset_permission.is_valid: -# continue -# for asset in asset_permission.get_granted_assets(): -# if not asset.is_active: -# continue -# if asset in assets: -# assets[asset] |= set(asset_permission.system_users.all()) -# else: -# assets[asset] = set(asset_permission.system_users.all()) -# return assets -# -# -# def get_user_granted_assets_direct(user): -# """Return assets granted of the user directly -# -# :param user: Instance of :class: ``User`` -# :return: {asset1: {system_user1, system_user2}, asset2: {...}} -# """ -# assets = {} -# asset_permissions_direct = user.asset_permissions.all() -# -# for asset_permission in asset_permissions_direct: -# if not asset_permission.is_valid: -# continue -# for asset in asset_permission.get_granted_assets(): -# if not asset.is_active: -# continue -# if asset in assets: -# assets[asset] |= set(asset_permission.system_users.all()) -# else: -# setattr(asset, 'inherited', False) -# assets[asset] = set(asset_permission.system_users.all()) -# return assets -# -# -# def get_user_granted_assets_inherit_from_user_groups(user): -# """Return assets granted of the user inherit from user groups -# -# :param user: Instance of :class: ``User`` -# :return: {asset1: {system_user1, system_user2}, asset2: {...}} -# """ -# assets = {} -# user_groups = user.groups.all() -# -# for user_group in user_groups: -# assets_inherited = get_user_group_granted_assets(user_group) -# for asset in assets_inherited: -# if not asset.is_active: -# continue -# if asset in assets: -# assets[asset] |= assets_inherited[asset] -# else: -# setattr(asset, 'inherited', True) -# assets[asset] = assets_inherited[asset] -# return assets -# -# -# def get_user_granted_assets(user): -# """Return assets granted of the user inherit from user groups -# -# :param user: Instance of :class: ``User`` -# :return: {asset1: {system_user1, system_user2}, asset2: {...}} -# """ -# assets_direct = get_user_granted_assets_direct(user) -# assets_inherited = get_user_granted_assets_inherit_from_user_groups(user) -# assets = assets_inherited -# -# for asset in assets_direct: -# if not asset.is_active: -# continue -# if asset in assets: -# assets[asset] |= assets_direct[asset] -# else: -# assets[asset] = assets_direct[asset] -# return assets -# -# -# def get_user_granted_asset_groups(user): -# """Return asset groups with assets and system users, it's not the asset -# group direct permed in rules. We get all asset and then get it asset group -# -# :param user: Instance of :class: ``User`` -# :return: {asset_group1: [asset1, asset2], asset_group2: []} -# """ -# asset_groups = collections.defaultdict(list) -# ungroups = [AssetGroup(name="UnGrouped")] -# for asset, system_users in get_user_granted_assets(user).items(): -# groups = asset.groups.all() -# if not groups: -# groups = ungroups -# for asset_group in groups: -# asset_groups[asset_group].append((asset, system_users)) -# return asset_groups -# -# -# def get_user_group_asset_permissions(user_group): -# permissions = user_group.asset_permissions.all() -# return permissions -# -# -# def get_user_asset_permissions(user): -# user_group_permissions = set() -# direct_permissions = set(setattr_bulk(user.asset_permissions.all(), 'inherited', 0)) -# -# for user_group in user.groups.all(): -# permissions = get_user_group_asset_permissions(user_group) -# user_group_permissions |= set(permissions) -# user_group_permissions = set(setattr_bulk(user_group_permissions, 'inherited', 1)) -# return direct_permissions | user_group_permissions -# -# -# def get_user_granted_system_users(user): -# """ -# :param user: the user -# :return: {"system_user": ["asset", "asset1"], "system_user": []} -# """ -# assets = get_user_granted_assets(user) -# system_users_dict = {} -# for asset, system_users in assets.items(): -# for system_user in system_users: -# if system_user in system_users_dict: -# system_users_dict[system_user].append(asset) -# else: -# system_users_dict[system_user] = [asset] -# return system_users_dict - - -def push_system_user(assets, system_user): - logger.info('Push system user %s' % system_user.name) - for asset in assets: - logger.info('\tAsset: %s' % asset.ip) - if not assets: - return None - - assets = [asset._to_secret_json() for asset in assets] - system_user = system_user._to_secret_json() - task = push_users.delay(assets, system_user) - return task.id diff --git a/apps/users/templates/users/login.html b/apps/users/templates/users/login.html index 1259ba773..18339356a 100644 --- a/apps/users/templates/users/login.html +++ b/apps/users/templates/users/login.html @@ -75,7 +75,7 @@
    - Copyright Jumpserver.org + Copyright 北京堆栈科技有限公司
    © 2014-2018 From c8728cace4a3d353f51251703081f97cbcf9cb11 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 9 Feb 2018 15:24:44 +0800 Subject: [PATCH 115/197] =?UTF-8?q?[Update]=20=E5=AE=8C=E6=88=90=E6=A0=91?= =?UTF-8?q?=E5=BD=A2=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/__cluster.py | 73 ----- apps/assets/api/__group.py | 68 ----- apps/assets/api/admin_user.py | 21 +- apps/assets/api/asset.py | 3 - apps/assets/forms/asset.py | 5 +- apps/assets/models/asset.py | 2 +- apps/assets/models/node.py | 6 +- apps/assets/serializers/admin_user.py | 10 +- apps/assets/tasks.py | 2 +- .../assets/templates/assets/__asset_list.html | 280 ------------------ .../templates/assets/admin_user_assets.html | 16 - .../templates/assets/admin_user_detail.html | 62 ++-- .../assets/templates/assets/asset_detail.html | 60 ++-- .../templates/assets/asset_group_create.html | 31 -- .../templates/assets/asset_group_detail.html | 244 --------------- .../templates/assets/asset_group_list.html | 168 ----------- apps/assets/templates/assets/asset_list.html | 6 +- .../templates/assets/cluster_assets.html | 215 -------------- .../assets/cluster_create_update.html | 76 ----- .../templates/assets/cluster_detail.html | 164 ---------- .../assets/templates/assets/cluster_list.html | 119 -------- .../templates/assets/system_user_asset.html | 3 - .../templates/assets/system_user_detail.html | 47 ++- apps/assets/urls/api_urls.py | 4 +- apps/assets/urls/views_urls.py | 20 -- apps/assets/views/__cluster.py | 111 ------- apps/assets/views/__group.py | 97 ------ apps/assets/views/__init__.py | 3 - apps/assets/views/admin_user.py | 3 +- apps/assets/views/asset.py | 42 ++- apps/assets/views/node.py | 25 -- apps/assets/views/system_user.py | 3 +- .../perms/asset_permission_asset.html | 10 +- .../perms/asset_permission_list.html | 8 +- .../perms/asset_permission_user.html | 10 +- apps/users/templates/users/user_detail.html | 14 +- 36 files changed, 161 insertions(+), 1870 deletions(-) delete mode 100644 apps/assets/api/__cluster.py delete mode 100644 apps/assets/api/__group.py delete mode 100644 apps/assets/templates/assets/__asset_list.html delete mode 100644 apps/assets/templates/assets/asset_group_create.html delete mode 100644 apps/assets/templates/assets/asset_group_detail.html delete mode 100644 apps/assets/templates/assets/asset_group_list.html delete mode 100644 apps/assets/templates/assets/cluster_assets.html delete mode 100644 apps/assets/templates/assets/cluster_create_update.html delete mode 100644 apps/assets/templates/assets/cluster_detail.html delete mode 100644 apps/assets/templates/assets/cluster_list.html delete mode 100644 apps/assets/views/__cluster.py delete mode 100644 apps/assets/views/__group.py delete mode 100644 apps/assets/views/node.py diff --git a/apps/assets/api/__cluster.py b/apps/assets/api/__cluster.py deleted file mode 100644 index 3f0cd1585..000000000 --- a/apps/assets/api/__cluster.py +++ /dev/null @@ -1,73 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved. -# -# Licensed under the GNU General Public License v2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.gnu.org/licenses/gpl-2.0.html -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from rest_framework import generics -from rest_framework.response import Response -from rest_framework_bulk import BulkModelViewSet - - -from common.mixins import IDInFilterMixin -from common.utils import get_logger -from ..hands import IsSuperUser -from ..models import Cluster -from .. import serializers -from ..tasks import test_admin_user_connectability_manual - - -logger = get_logger(__file__) -__all__ = [ - 'ClusterViewSet', 'ClusterTestAssetsAliveApi', 'ClusterAddAssetsApi', -] - - -class ClusterViewSet(IDInFilterMixin, BulkModelViewSet): - """ - Cluster api set, for add,delete,update,list,retrieve resource - """ - queryset = Cluster.objects.all() - serializer_class = serializers.ClusterSerializer - permission_classes = (IsSuperUser,) - - -class ClusterTestAssetsAliveApi(generics.RetrieveAPIView): - """ - Test cluster asset can connect using admin user or not - """ - queryset = Cluster.objects.all() - permission_classes = (IsSuperUser,) - - def retrieve(self, request, *args, **kwargs): - cluster = self.get_object() - admin_user = cluster.admin_user - test_admin_user_connectability_manual.delay(admin_user) - return Response("Task has been send, seen left assets status") - - -class ClusterAddAssetsApi(generics.UpdateAPIView): - queryset = Cluster.objects.all() - serializer_class = serializers.ClusterUpdateAssetsSerializer - permission_classes = (IsSuperUser,) - - def update(self, request, *args, **kwargs): - cluster = self.get_object() - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - assets = serializer.validated_data['assets'] - for asset in assets: - asset.cluster = cluster - asset.save() - return Response({"msg": "ok"}) - else: - return Response({'error': serializer.errors}, status=400) \ No newline at end of file diff --git a/apps/assets/api/__group.py b/apps/assets/api/__group.py deleted file mode 100644 index eae848466..000000000 --- a/apps/assets/api/__group.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# -# ~*~ coding: utf-8 ~*~ -# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved. -# -# Licensed under the GNU General Public License v2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.gnu.org/licenses/gpl-2.0.html -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from rest_framework import generics -from rest_framework.response import Response -from rest_framework_bulk import BulkModelViewSet -from django.db.models import Count - -from common.mixins import IDInFilterMixin -from common.utils import get_logger -from ..hands import IsSuperUser -from ..models import AssetGroup -from .. import serializers - - -logger = get_logger(__file__) - -__all__ = [ - 'AssetGroupViewSet', 'GroupUpdateAssetsApi', 'GroupAddAssetsApi' -] - - -class AssetGroupViewSet(IDInFilterMixin, BulkModelViewSet): - """ - Asset group api set, for add,delete,update,list,retrieve resource - """ - queryset = AssetGroup.objects.all().annotate(asset_count=Count("assets")) - serializer_class = serializers.AssetGroupSerializer - permission_classes = (IsSuperUser,) - - -class GroupUpdateAssetsApi(generics.RetrieveUpdateAPIView): - """ - Asset group, update it's asset member - """ - queryset = AssetGroup.objects.all() - serializer_class = serializers.GroupUpdateAssetsSerializer - permission_classes = (IsSuperUser,) - - -class GroupAddAssetsApi(generics.UpdateAPIView): - queryset = AssetGroup.objects.all() - serializer_class = serializers.GroupUpdateAssetsSerializer - permission_classes = (IsSuperUser,) - - def update(self, request, *args, **kwargs): - group = self.get_object() - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - assets = serializer.validated_data['assets'] - group.assets.add(*tuple(assets)) - return Response({"msg": "ok"}) - else: - return Response({'error': serializer.errors}, status=400) \ No newline at end of file diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index 3960ab50c..a69d771b3 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from django.db import transaction from rest_framework import generics from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet @@ -20,14 +21,14 @@ from rest_framework_bulk import BulkModelViewSet from common.mixins import IDInFilterMixin from common.utils import get_logger from ..hands import IsSuperUser -from ..models import AdminUser +from ..models import AdminUser, Asset from .. import serializers from ..tasks import test_admin_user_connectability_manual logger = get_logger(__file__) __all__ = [ - 'AdminUserViewSet', 'AdminUserAddClustersApi', 'AdminUserTestConnectiveApi' + 'AdminUserViewSet', 'ReplaceNodesAdminUserApi', 'AdminUserTestConnectiveApi' ] @@ -40,19 +41,23 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet): permission_classes = (IsSuperUser,) -class AdminUserAddClustersApi(generics.UpdateAPIView): +class ReplaceNodesAdminUserApi(generics.UpdateAPIView): queryset = AdminUser.objects.all() - serializer_class = serializers.AdminUserUpdateClusterSerializer + serializer_class = serializers.ReplaceNodeAdminUserSerializer permission_classes = (IsSuperUser,) def update(self, request, *args, **kwargs): admin_user = self.get_object() serializer = self.serializer_class(data=request.data) if serializer.is_valid(): - clusters = serializer.validated_data['clusters'] - for cluster in clusters: - cluster.admin_user = admin_user - cluster.save() + nodes = serializer.validated_data['nodes'] + assets = [] + for node in nodes: + assets.extend([asset.id for asset in node.get_all_assets()]) + + with transaction.atomic(): + Asset.objects.filter(id__in=assets).update(admin_user=admin_user) + return Response({"msg": "ok"}) else: return Response({'error': serializer.errors}, status=400) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 8e9edc3e2..0a73a7fad 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -42,14 +42,11 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): def get_queryset(self): queryset = super().get_queryset() admin_user_id = self.request.query_params.get('admin_user_id') - system_user_id = self.request.query_params.get('system_user_id') node_id = self.request.query_params.get("node_id") if admin_user_id: admin_user = get_object_or_404(AdminUser, id=admin_user_id) queryset = queryset.filter(admin_user=admin_user) - if system_user_id: - system_user = get_object_or_404(SystemUser, id=system_user_id) if node_id: node = get_object_or_404(Node, id=node_id) queryset = queryset.filter(nodes__key__startswith=node.key).distinct() diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py index 8aaf8158b..f2d3d2783 100644 --- a/apps/assets/forms/asset.py +++ b/apps/assets/forms/asset.py @@ -86,7 +86,7 @@ class AssetBulkUpdateForm(forms.ModelForm): class Meta: model = Asset fields = [ - 'assets', 'port', 'labels', 'admin_user' + 'assets', 'port', 'admin_user', 'nodes', ] widgets = { 'admin_user': forms.SelectMultiple( @@ -95,6 +95,9 @@ class AssetBulkUpdateForm(forms.ModelForm): 'labels': forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('Labels')} ), + 'nodes': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('Nodes')} + ), } def save(self, commit=True): diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index b101d63f9..561628a75 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -30,7 +30,7 @@ def default_cluster(): def default_node(): try: - from .tree import Node + from .node import Node return Node.root() except: return None diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 7c5e8f450..9fb164250 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -17,7 +17,11 @@ class Node(models.Model): date_create = models.DateTimeField(auto_now_add=True) def __str__(self): - return self.full_value + return self.value + + @property + def name(self): + return self.value @property def full_value(self): diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index d38105106..d59ca64ae 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -2,7 +2,7 @@ # from django.core.cache import cache from rest_framework import serializers -from ..models import Cluster, AdminUser +from ..models import Node, AdminUser from ..const import ADMIN_USER_CONN_CACHE_KEY @@ -39,14 +39,14 @@ class AdminUserSerializer(serializers.ModelSerializer): return obj.assets_amount -class AdminUserUpdateClusterSerializer(serializers.ModelSerializer): +class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer): """ 管理用户更新关联到的集群 """ - clusters = serializers.PrimaryKeyRelatedField( - many=True, queryset=Cluster.objects.all() + nodes = serializers.PrimaryKeyRelatedField( + many=True, queryset=Node.objects.all() ) class Meta: model = AdminUser - fields = ['id', 'clusters'] \ No newline at end of file + fields = ['id', 'nodes'] diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 25dec8af7..a64eff231 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -46,7 +46,7 @@ def set_assets_hardware_info(result, **kwargs): continue ___vendor = info.get('ansible_system_vendor', 'Unknown') - ___model = info.get('ansible_product_version', 'Unknown') + ___model = info.get('ansible_product_name', 'Unknown') ___sn = info.get('ansible_product_serial', 'Unknown') for ___cpu_model in info.get('ansible_processor', []): diff --git a/apps/assets/templates/assets/__asset_list.html b/apps/assets/templates/assets/__asset_list.html deleted file mode 100644 index d4b2a5bb4..000000000 --- a/apps/assets/templates/assets/__asset_list.html +++ /dev/null @@ -1,280 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% block custom_head_css_js %} - - - -{% endblock %} -{% block content_left_head %}{% endblock %} - -{% block table_search %} - -{% endblock %} - -{% block table_container %} - -
    - - -
    - - - - - - - - - - - - - - - - -
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Cluster' %}{% trans 'Hardware' %}{% trans 'Active' %}{% trans 'Reachable' %}{% trans 'Action' %}
    -
    -
    - -
    - -
    -
    -
    -{% include 'assets/_asset_import_modal.html' %} -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index 3140c36c3..fa72abaa5 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -20,14 +20,6 @@
  • {% trans 'Assets list' %}
  • -
  • - {% trans 'Update' %} -
  • -
  • - - {% trans 'Delete' %} - -
  • @@ -127,14 +119,6 @@ function initTable() { $(document).ready(function () { initTable(); }) -.on('click', '.btn-delete-admin-user', function () { - var $this = $(this); - var name = "{{ admin_user.name }}"; - var uid = "{{ admin_user.id }}"; - var the_url = '{% url "api-assets:admin-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid); - var redirect_url = "{% url 'assets:admin-user-list' %}"; - objectDelete($this, name, the_url, redirect_url); -}) .on('click', '.btn-test-connective', function () { var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}"; var error = function (data) { diff --git a/apps/assets/templates/assets/admin_user_detail.html b/apps/assets/templates/assets/admin_user_detail.html index 18464dde4..899b69f26 100644 --- a/apps/assets/templates/assets/admin_user_detail.html +++ b/apps/assets/templates/assets/admin_user_detail.html @@ -31,7 +31,7 @@
    -
    +
    {{ admin_user.name }} @@ -77,11 +77,10 @@
    -
    - -
    +
    +
    - {% trans 'Using this as cluster admin user' %} + {% trans 'Replace assets admin user with this' %}
    @@ -89,25 +88,19 @@ - - {% for cluster in admin_user.cluster_set.all %} - - - - {% endfor %}
    - + {% for node in nodes %} + {% endfor %}
    - +
    {{ cluster.name }}
    @@ -123,29 +116,20 @@ {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html index 675cb99a1..80916340f 100644 --- a/apps/assets/templates/assets/asset_detail.html +++ b/apps/assets/templates/assets/asset_detail.html @@ -73,10 +73,6 @@ {% trans 'Admin user' %}: {{ asset.admin_user }} - - {% trans 'Cluster' %}: - {{ asset.cluster.name }} - {% trans 'Vendor' %}: {{ asset.vendor|default:"" }} @@ -182,7 +178,7 @@
    - {% trans 'Asset groups' %} + {% trans 'Nodes' %}
    @@ -190,25 +186,25 @@ - {% for asset_group in asset_groups %} + {% for node in asset.nodes.all %} - + {% endfor %} @@ -239,28 +235,28 @@ {% endblock %} {% block custom_foot_js %} -{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/asset_group_detail.html b/apps/assets/templates/assets/asset_group_detail.html deleted file mode 100644 index b779ce0f9..000000000 --- a/apps/assets/templates/assets/asset_group_detail.html +++ /dev/null @@ -1,244 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
    -
    -
    -
    - -
    -
    -
    -
    - {% trans 'Asset list of ' %} {{ asset_group.name }} {{ asset_group.assets.all.count }} -
    - - - - - - - - - - -
    -
    -
    -
    - + {% for node in nodes_remain %} + {% endfor %}
    - +
    {{ asset_group.name }}{{ node.name }} - +
    - - - - - - - - - - - -
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Alive' %}{% trans 'Action' %}
    -
    -
    -
    -
    -
    -
    - {% trans 'Add assets to this group' %} -
    -
    - - - - - - - - - - - -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_group_list.html b/apps/assets/templates/assets/asset_group_list.html deleted file mode 100644 index 449486e58..000000000 --- a/apps/assets/templates/assets/asset_group_list.html +++ /dev/null @@ -1,168 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %} -{% endblock %} -{% block table_container %} - - - - - - - - - - - - - -
    - - {% trans 'Name' %}{% trans 'Asset' %}{% trans 'Comment' %}{% trans 'Action' %}
    -{% include 'assets/_asset_group_bulk_update_modal.html' %} -{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 5e113d879..dbcb8a350 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -325,9 +325,9 @@ function initTree() { $.get("{% url 'api-assets:node-list' %}", function(data, status){ $.each(data, function (index, value) { value["pId"] = value["parent"]; - if (value["key"] === "0") { - value["open"] = true; - } + {#if (value["key"] === "0") {#} + value["open"] = true; + {# }#} value["name"] = value["value"] }); zNodes = data; diff --git a/apps/assets/templates/assets/cluster_assets.html b/apps/assets/templates/assets/cluster_assets.html deleted file mode 100644 index 0582ac643..000000000 --- a/apps/assets/templates/assets/cluster_assets.html +++ /dev/null @@ -1,215 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - - -{% endblock %} -{% block content %} -
    -
    -
    -
    - -
    -
    -
    -
    - {% trans 'Cluster assets' %} {{ cluster.name }} -
    - - - - - - - - - - -
    -
    - -
    - - - - - - - - - - - - -
    {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Alive' %}{% trans 'Action' %}
    -
    -
    -
    -
    -
    -
    - {% trans 'Quick update' %} -
    -
    - - - - - - - -
    {% trans 'Test assets connective' %}: - - - -
    -
    -
    -
    -
    - {% trans 'Add assets to' %} {{ cluster.name }} -
    -
    - - - - - - - - - - - -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -{% endblock %} -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/assets/templates/assets/cluster_create_update.html b/apps/assets/templates/assets/cluster_create_update.html deleted file mode 100644 index 66c36bece..000000000 --- a/apps/assets/templates/assets/cluster_create_update.html +++ /dev/null @@ -1,76 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
    -
    -
    -
    -
    -
    {{ action }}
    - -
    - -
    -
    -
    -
    -
    - {% csrf_token %} -

    {% trans 'Basic' %}

    - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.address layout="horizontal" %} - {% bootstrap_field form.contact layout="horizontal" %} - {% bootstrap_field form.phone layout="horizontal" %} - -

    {% trans 'Setting' %}

    - {% bootstrap_field form.admin_user layout="horizontal" %} - {% bootstrap_field form.system_users layout="horizontal" %} - -
    -

    {% trans 'Other' %}

    - {% bootstrap_field form.operator layout="horizontal" %} - {% bootstrap_field form.intranet layout="horizontal" %} - {% bootstrap_field form.extranet layout="horizontal" %} - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/cluster_detail.html b/apps/assets/templates/assets/cluster_detail.html deleted file mode 100644 index 837e36e68..000000000 --- a/apps/assets/templates/assets/cluster_detail.html +++ /dev/null @@ -1,164 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
    -
    -
    -
    - -
    -
    -
    -
    - {{ cluster.name }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    {% trans 'Name' %}:{{ cluster.name }}
    {% trans 'Bandwidth' %}:{{ cluster.bandwidth }}
    {% trans 'Contact' %}:{{ cluster.contact }}
    {% trans 'Phone' %}:{{ cluster.phone }}
    {% trans 'Address' %}:{{ cluster.address }}
    {% trans 'Intranet' %}:{{ cluster.Intranet }}
    {% trans 'Extranet' %}:{{ cluster.extranet }}
    {% trans 'Operator' %}:{{ cluster.operator }}
    {% trans 'Date created' %}:{{ system_user.date_created }}
    {% trans 'Created by' %}:{{ asset_group.created_by }}
    {% trans 'Comment' %}:{{ system_user.comment }}
    -
    -
    -
    -{#
    #} -{#
    #} -{#
    #} -{# {% trans 'Update admin user' %}#} -{#
    #} -{#
    #} -{# #} -{# #} -{# #} -{# #} -{# #} -{# #} -{# #} -{# #} -{# #} -{# #} -{##} -{# {% for cluster in system_user.cluster.all %}#} -{# #} -{# #} -{# #} -{# #} -{# {% endfor %}#} -{# #} -{#
    #} -{# #} -{#
    #} -{# #} -{#
    {{ cluster.name }}#} -{# #} -{#
    #} -{#
    #} -{#
    #} -{#
    #} -
    -
    -
    -
    -
    - -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/cluster_list.html b/apps/assets/templates/assets/cluster_list.html deleted file mode 100644 index e4743b346..000000000 --- a/apps/assets/templates/assets/cluster_list.html +++ /dev/null @@ -1,119 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block custom_head_css_js %} - - -{% endblock %} - -{% block table_search %}{% endblock %} -{% block table_container %} - - - - - - - - - - - - - - -
    - - {% trans 'Name' %}{% trans 'Admin user' %}{% trans 'Asset num' %}{% trans 'System users' %}{% trans 'Action' %}
    -{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - - - diff --git a/apps/assets/templates/assets/system_user_asset.html b/apps/assets/templates/assets/system_user_asset.html index 91c2ffbe0..f82f97ab8 100644 --- a/apps/assets/templates/assets/system_user_asset.html +++ b/apps/assets/templates/assets/system_user_asset.html @@ -21,9 +21,6 @@ {% trans 'Assets' %} -
  • - {% trans 'Update' %} -
  • diff --git a/apps/assets/templates/assets/system_user_detail.html b/apps/assets/templates/assets/system_user_detail.html index 0bf591bb2..46cabb336 100644 --- a/apps/assets/templates/assets/system_user_detail.html +++ b/apps/assets/templates/assets/system_user_detail.html @@ -17,11 +17,11 @@
  • {% trans 'Detail' %}
  • -
  • - - {% trans 'Attached assets' %} - -
  • +{#
  • #} +{# #} +{# {% trans 'Attached assets' %}#} +{# #} +{#
  • #}
  • {% trans 'Update' %}
  • @@ -130,6 +130,23 @@ + + {% trans 'Push system user manually' %}: + + + + + + + + {% trans 'Test assets connective' %}: + + + + + + + {# #} {# {% trans 'Change auth period' %}:#} {# #} @@ -144,7 +161,7 @@
    - {% trans 'Clusters' %} + {% trans 'Nodes' %}
    @@ -152,9 +169,9 @@ @@ -166,9 +183,9 @@ - {% for cluster in system_user.cluster.all %} + {% for node in system_user.nodes.all %} - + @@ -201,7 +218,7 @@ function updateSystemUserCluster(clusters) { // change tr html of user groups. $('.cluster_edit tbody').append( '' + - '' + + '' + '' + '' ) @@ -242,7 +259,7 @@ $(document).ready(function () { if (Object.keys(jumpserver.cluster_selected).length === 0) { return false; } - var clusters = $('.bdg_cluster').map(function() { + var clusters = $('.bdg_node').map(function() { return $(this).data('gid'); }).get(); $.map(jumpserver.cluster_selected, function(value, index) { @@ -253,14 +270,14 @@ $(document).ready(function () { .on('click', '.btn-remove-from-cluster', function() { var $this = $(this); var $tr = $this.closest('tr'); - var $badge = $tr.find('.bdg_cluster'); + var $badge = $tr.find('.bdg_node'); var gid = $badge.data('gid'); var cluster_name = $badge.html() || $badge.text(); $('#groups_selected').append( '' ); $tr.remove(); - var clusters = $('.bdg_cluster').map(function () { + var clusters = $('.bdg_node').map(function () { return $(this).data('gid'); }).get(); updateSystemUserCluster(clusters); diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 8847b6397..236a1f854 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -35,8 +35,8 @@ urlpatterns = [ # api.ClusterAddAssetsApi.as_view(), name='cluster-add-assets'), #url(r'^v1/cluster/(?P[0-9a-zA-Z\-]{36})/assets/connective/$', # api.ClusterTestAssetsAliveApi.as_view(), name='cluster-test-connective'), - url(r'^v1/admin-user/(?P[0-9a-zA-Z\-]{36})/clusters/$', - api.AdminUserAddClustersApi.as_view(), name='admin-user-add-clusters'), + url(r'^v1/admin-user/(?P[0-9a-zA-Z\-]{36})/nodes/$', + api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'), url(r'^v1/admin-user/(?P[0-9a-zA-Z\-]{36})/connective/$', api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'), url(r'^v1/system-user/(?P[0-9a-zA-Z\-]{36})/push/$', diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index 1704531d2..545e7b062 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -14,27 +14,11 @@ urlpatterns = [ url(r'^asset/(?P[0-9a-zA-Z\-]{36})/$', views.AssetDetailView.as_view(), name='asset-detail'), url(r'^asset/(?P[0-9a-zA-Z\-]{36})/update/$', views.AssetUpdateView.as_view(), name='asset-update'), url(r'^asset/(?P[0-9a-zA-Z\-]{36})/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'), - url(r'^asset-modal$', views.AssetModalListView.as_view(), name='asset-modal-list'), url(r'^asset/update/$', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'), # User asset view url(r'^user-asset/$', views.UserAssetListView.as_view(), name='user-asset-list'), - # # Resource asset group url - # url(r'^asset-group/$', views.AssetGroupListView.as_view(), name='asset-group-list'), - # url(r'^asset-group/create/$', views.AssetGroupCreateView.as_view(), name='asset-group-create'), - # url(r'^asset-group/(?P[0-9a-zA-Z\-]{36})/$', views.AssetGroupDetailView.as_view(), name='asset-group-detail'), - # url(r'^asset-group/(?P[0-9a-zA-Z\-]{36})/update/$', views.AssetGroupUpdateView.as_view(), name='asset-group-update'), - # url(r'^asset-group/(?P[0-9a-zA-Z\-]{36})/delete/$', views.AssetGroupDeleteView.as_view(), name='asset-group-delete'), - - # Resource cluster url - # url(r'^cluster/$', views.ClusterListView.as_view(), name='cluster-list'), - # url(r'^cluster/create/$', views.ClusterCreateView.as_view(), name='cluster-create'), - # url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/$', views.ClusterDetailView.as_view(), name='cluster-detail'), - # url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/update/', views.ClusterUpdateView.as_view(), name='cluster-update'), - # url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/delete/$', views.ClusterDeleteView.as_view(), name='cluster-delete'), - # url(r'^cluster/(?P[0-9a-zA-Z\-]{36})/assets/$', views.ClusterAssetsView.as_view(), name='cluster-assets'), - # Resource admin user url url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'), url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'), @@ -50,14 +34,10 @@ urlpatterns = [ url(r'^system-user/(?P[0-9a-zA-Z\-]{36})/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'), url(r'^system-user/(?P[0-9a-zA-Z\-]{36})/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'), url(r'^system-user/(?P[0-9a-zA-Z\-]{36})/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'), - # url(r'^system-user/(?P[0-9a-zA-Z\-]{36})/asset-group$', views.SystemUserAssetGroupView.as_view(), - # name='system-user-asset-group'), url(r'^label/$', views.LabelListView.as_view(), name='label-list'), url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'), url(r'^label/(?P[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'), url(r'^label/(?P[0-9a-zA-Z\-]{36})/delete/$', views.LabelDeleteView.as_view(), name='label-delete'), - - url(r'^tree/$', views.TreeView.as_view(), name='tree-view'), ] diff --git a/apps/assets/views/__cluster.py b/apps/assets/views/__cluster.py deleted file mode 100644 index 5df58953c..000000000 --- a/apps/assets/views/__cluster.py +++ /dev/null @@ -1,111 +0,0 @@ -# coding:utf-8 -from django.utils.translation import ugettext as _ -from django.views.generic import TemplateView, ListView, View -from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView -from django.urls import reverse_lazy -from django.views.generic.detail import DetailView, SingleObjectMixin -from django.contrib.messages.views import SuccessMessageMixin - -from common.const import create_success_msg, update_success_msg -from .. import forms -from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser -from ..hands import AdminUserRequiredMixin - - -__all__ = [ - 'ClusterListView', 'ClusterCreateView', 'ClusterUpdateView', - 'ClusterDetailView', 'ClusterDeleteView', 'ClusterAssetsView', -] - - -class ClusterListView(AdminUserRequiredMixin, TemplateView): - template_name = 'assets/cluster_list.html' - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Cluster list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class ClusterCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): - model = Cluster - form_class = forms.ClusterForm - template_name = 'assets/cluster_create_update.html' - success_url = reverse_lazy('assets:cluster-list') - success_message = create_success_msg - - def get_context_data(self, **kwargs): - context = { - 'app': _('assets'), - 'action': _('Create cluster'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def form_valid(self, form): - cluster = form.save() - cluster.created_by = self.request.user.username - cluster.save() - return super().form_valid(form) - - -class ClusterUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): - model = Cluster - form_class = forms.ClusterForm - template_name = 'assets/cluster_create_update.html' - context_object_name = 'cluster' - success_url = reverse_lazy('assets:cluster-list') - success_message = update_success_msg - - def get_context_data(self, **kwargs): - context = { - 'app': _('assets'), - 'action': _('Update Cluster'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class ClusterDetailView(AdminUserRequiredMixin, DetailView): - model = Cluster - template_name = 'assets/cluster_detail.html' - context_object_name = 'cluster' - - def get_context_data(self, **kwargs): - admin_user_list = AdminUser.objects.exclude() - context = { - 'app': _('Assets'), - 'action': _('Cluster detail'), - 'admin_user_list': admin_user_list, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class ClusterAssetsView(AdminUserRequiredMixin, DetailView): - model = Cluster - template_name = 'assets/cluster_assets.html' - context_object_name = 'cluster' - - def get_context_data(self, **kwargs): - assets_remain = Asset.objects.exclude(id__in=self.object.assets.all()) - - context = { - 'app': _('Assets'), - 'action': _('Asset detail'), - 'groups': AssetGroup.objects.all(), - 'system_users': SystemUser.objects.all(), - 'assets_remain': assets_remain, - 'assets': [asset for asset in Asset.objects.all() if asset not in assets_remain], - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class ClusterDeleteView(AdminUserRequiredMixin, DeleteView): - model = Cluster - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:cluster-list') diff --git a/apps/assets/views/__group.py b/apps/assets/views/__group.py deleted file mode 100644 index 0ae65eaa0..000000000 --- a/apps/assets/views/__group.py +++ /dev/null @@ -1,97 +0,0 @@ -# coding:utf-8 -from __future__ import absolute_import, unicode_literals - -from django.utils.translation import ugettext as _ -from django.views.generic import TemplateView, ListView, View -from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView -from django.urls import reverse_lazy -from django.views.generic.detail import DetailView, SingleObjectMixin -from django.shortcuts import get_object_or_404, reverse, redirect -from django.contrib.messages.views import SuccessMessageMixin - -from common.const import create_success_msg, update_success_msg -from .. import forms -from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser -from ..hands import AdminUserRequiredMixin - - -__all__ = [ - 'AssetGroupCreateView', 'AssetGroupDetailView', - 'AssetGroupUpdateView', 'AssetGroupListView', - 'AssetGroupDeleteView', -] - - -class AssetGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): - model = AssetGroup - form_class = forms.AssetGroupForm - template_name = 'assets/asset_group_create.html' - success_url = reverse_lazy('assets:asset-group-list') - success_message = create_success_msg - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create asset group'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def form_valid(self, form): - group = form.save() - group.created_by = self.request.user.username - group.save() - return super().form_valid(form) - - -class AssetGroupListView(AdminUserRequiredMixin, TemplateView): - template_name = 'assets/asset_group_list.html' - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Asset group list'), - 'assets': Asset.objects.all(), - 'system_users': SystemUser.objects.all(), - } - kwargs.update(context) - return super(AssetGroupListView, self).get_context_data(**kwargs) - - -class AssetGroupDetailView(AdminUserRequiredMixin, DetailView): - model = AssetGroup - template_name = 'assets/asset_group_detail.html' - context_object_name = 'asset_group' - - def get_context_data(self, **kwargs): - assets_remain = Asset.objects.exclude(groups__in=[self.object]) - context = { - 'app': _('Assets'), - 'action': _('Asset group detail'), - 'assets_remain': assets_remain, - 'assets': self.object.assets.all(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): - model = AssetGroup - form_class = forms.AssetGroupForm - template_name = 'assets/asset_group_create.html' - success_url = reverse_lazy('assets:asset-group-list') - success_message = update_success_msg - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create asset group'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView): - template_name = 'delete_confirm.html' - model = AssetGroup - success_url = reverse_lazy('assets:asset-group-list') diff --git a/apps/assets/views/__init__.py b/apps/assets/views/__init__.py index 488d04262..097dec3ae 100644 --- a/apps/assets/views/__init__.py +++ b/apps/assets/views/__init__.py @@ -1,8 +1,5 @@ # coding:utf-8 from .asset import * -# from .group import * -# from .cluster import * from .system_user import * from .admin_user import * from .label import * -from .node import * diff --git a/apps/assets/views/admin_user.py b/apps/assets/views/admin_user.py index ec27a1466..7d7878e88 100644 --- a/apps/assets/views/admin_user.py +++ b/apps/assets/views/admin_user.py @@ -10,7 +10,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin from common.const import create_success_msg, update_success_msg from .. import forms -from ..models import AdminUser, Cluster +from ..models import AdminUser, Node from ..hands import AdminUserRequiredMixin __all__ = [ @@ -77,6 +77,7 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView): context = { 'app': _('Assets'), 'action': _('Admin user detail'), + 'nodes': Node.objects.all() } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index b14282f74..62148d6b2 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -34,8 +34,7 @@ from ..hands import AdminUserRequiredMixin __all__ = [ 'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView', - 'AssetModalListView', 'AssetDeleteView', 'AssetExportView', - 'BulkImportAssetView', + 'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView', ] logger = get_logger(__file__) @@ -102,22 +101,22 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): return create_success_msg % ({"name": cleaned_data["hostname"]}) -class AssetModalListView(AdminUserRequiredMixin, ListView): - paginate_by = settings.DISPLAY_PER_PAGE - model = Asset - context_object_name = 'asset_modal_list' - template_name = 'assets/asset_modal_list.html' - - def get_context_data(self, **kwargs): - assets = Asset.objects.all() - assets_id = self.request.GET.get('assets_id', '') - assets_id_list = [i for i in assets_id.split(',') if i.isdigit()] - context = { - 'all_assets': assets_id_list, - 'assets': assets - } - kwargs.update(context) - return super().get_context_data(**kwargs) +# class AssetModalListView(AdminUserRequiredMixin, ListView): +# paginate_by = settings.DISPLAY_PER_PAGE +# model = Asset +# context_object_name = 'asset_modal_list' +# template_name = 'assets/_asset_list_modal.html' +# +# def get_context_data(self, **kwargs): +# assets = Asset.objects.all() +# assets_id = self.request.GET.get('assets_id', '') +# assets_id_list = [i for i in assets_id.split(',') if i.isdigit()] +# context = { +# 'all_assets': assets_id_list, +# 'assets': assets +# } +# kwargs.update(context) +# return super().get_context_data(**kwargs) class AssetBulkUpdateView(AdminUserRequiredMixin, ListView): @@ -191,14 +190,11 @@ class AssetDetailView(DetailView): template_name = 'assets/asset_detail.html' def get_context_data(self, **kwargs): - asset_groups = self.object.groups.all() + nodes_remain = Node.objects.exclude(assets=self.object) context = { 'app': _('Assets'), 'action': _('Asset detail'), - 'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all() - if asset_group not in asset_groups], - 'asset_groups': asset_groups, - 'system_users_all': SystemUser.objects.all(), + 'nodes_remain': nodes_remain, } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/apps/assets/views/node.py b/apps/assets/views/node.py deleted file mode 100644 index 8c8a463c7..000000000 --- a/apps/assets/views/node.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.views.generic import TemplateView -from django.utils.translation import ugettext_lazy as _ - -from common.mixins import AdminUserRequiredMixin -from ..models import Label - - -__all__ = ['TreeView'] - - -class TreeView(AdminUserRequiredMixin, TemplateView): - template_name = 'assets/tree.html' - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Tree view'), - 'labels': Label.objects.all(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - diff --git a/apps/assets/views/system_user.py b/apps/assets/views/system_user.py index 3b79d0391..2e4eacf56 100644 --- a/apps/assets/views/system_user.py +++ b/apps/assets/views/system_user.py @@ -9,7 +9,7 @@ from django.views.generic.detail import DetailView from common.const import create_success_msg, update_success_msg from ..forms import SystemUserForm -from ..models import SystemUser, Cluster +from ..models import SystemUser, Node from ..hands import AdminUserRequiredMixin @@ -73,6 +73,7 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView): context = { 'app': _('Assets'), 'action': _('System user detail'), + 'nodes_remain': Node.objects.exclude(systemuser=self.object) } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/apps/perms/templates/perms/asset_permission_asset.html b/apps/perms/templates/perms/asset_permission_asset.html index 5cfc44c05..12369574d 100644 --- a/apps/perms/templates/perms/asset_permission_asset.html +++ b/apps/perms/templates/perms/asset_permission_asset.html @@ -191,7 +191,7 @@ function updateGroup(groups) { } jumpserver.assets_selected = {}; -jumpserver.groups_selected = {}; +jumpserver.nodes_selected = {}; $(document).ready(function () { $('.select2.asset').select2() @@ -206,11 +206,11 @@ $(document).ready(function () { $('.select2.group').select2() .on('select2:select', function(evt) { var data = evt.params.data; - jumpserver.groups_selected[data.id] = data.text; + jumpserver.nodes_selected[data.id] = data.text; }) .on('select2:unselect', function(evt) { var data = evt.params.data; - delete jumpserver.groups_selected[data.id] + delete jumpserver.nodes_selected[data.id] }) }) .on('click', '.btn-add-assets', function () { @@ -232,7 +232,7 @@ $(document).ready(function () { removeAssets(assets) }) .on('click', '#btn-add-group', function () { - if (Object.keys(jumpserver.groups_selected).length === 0) { + if (Object.keys(jumpserver.nodes_selected).length === 0) { return false; } @@ -240,7 +240,7 @@ $(document).ready(function () { return $(this).data('gid'); }).get(); - $.map(jumpserver.groups_selected, function(group_name, index) { + $.map(jumpserver.nodes_selected, function(group_name, index) { groups.push(index); $('#opt_' + index).remove(); $('.group_edit tbody').append( diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html index 9f8e26a43..279b0eaaf 100644 --- a/apps/perms/templates/perms/asset_permission_list.html +++ b/apps/perms/templates/perms/asset_permission_list.html @@ -85,7 +85,7 @@ function initTable() { ele: $('#permission_list_table'), columnDefs: [ {targets: 1, createdCell: function (td, cellData) { - var html = '' + cellData.name + ''; + var html = '' + cellData.name + ''; $(td).html(html.replace("99899", cellData.pk)); }}, {targets: 2, createdCell: function (td, cellData) { @@ -178,9 +178,9 @@ function initTree() { $.get("{% url 'api-assets:node-list' %}", function(data, status){ $.each(data, function (index, value) { value["pId"] = value["parent"]; - if (value["key"] === "0") { - value["open"] = true; - } + {#if (value["key"] === "0") {#} + value["open"] = true; + {# }#} value["name"] = value["value"] }); zNodes = data; diff --git a/apps/perms/templates/perms/asset_permission_user.html b/apps/perms/templates/perms/asset_permission_user.html index 2e64368aa..bee5b0e35 100644 --- a/apps/perms/templates/perms/asset_permission_user.html +++ b/apps/perms/templates/perms/asset_permission_user.html @@ -150,7 +150,7 @@ {% block custom_foot_js %} + "].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f"); + + // Add expand links to all parents of nested ul + $('.wy-menu-vertical ul').not('.simple').siblings('a').each(function () { + var link = $(this); + expand = $(''); + expand.on('click', function (ev) { + self.toggleCurrent(link); + ev.stopPropagation(); + return false; + }); + link.prepend(expand); + }); + }; + + nav.reset = function () { + // Get anchor from URL and open up nested nav + var anchor = encodeURI(window.location.hash); + if (anchor) { + try { + var link = $('.wy-menu-vertical') + .find('[href="' + anchor + '"]'); + // If we didn't find a link, it may be because we clicked on + // something that is not in the sidebar (eg: when using + // sphinxcontrib.httpdomain it generates headerlinks but those + // aren't picked up and placed in the toctree). So let's find + // the closest header in the document and try with that one. + if (link.length === 0) { + var doc_link = $('.document a[href="' + anchor + '"]'); + var closest_section = doc_link.closest('div.section'); + // Try again with the closest section entry. + link = $('.wy-menu-vertical') + .find('[href="#' + closest_section.attr("id") + '"]'); + + } + $('.wy-menu-vertical li.toctree-l1 li.current') + .removeClass('current'); + link.closest('li.toctree-l2').addClass('current'); + link.closest('li.toctree-l3').addClass('current'); + link.closest('li.toctree-l4').addClass('current'); + } + catch (err) { + console.log("Error expanding nav for anchor", err); + } + } + }; + + nav.onScroll = function () { + this.winScroll = false; + var newWinPosition = this.win.scrollTop(), + winBottom = newWinPosition + this.winHeight, + navPosition = this.navBar.scrollTop(), + newNavPosition = navPosition + (newWinPosition - this.winPosition); + if (newWinPosition < 0 || winBottom > this.docHeight) { + return; + } + this.navBar.scrollTop(newNavPosition); + this.winPosition = newWinPosition; + }; + + nav.onResize = function () { + this.winResize = false; + this.winHeight = this.win.height(); + this.docHeight = $(document).height(); + }; + + nav.hashChange = function () { + this.linkScroll = true; + this.win.one('hashchange', function () { + this.linkScroll = false; + }); + }; + + nav.toggleCurrent = function (elem) { + var parent_li = elem.closest('li'); + parent_li.siblings('li.current').removeClass('current'); + parent_li.siblings().find('li.current').removeClass('current'); + parent_li.find('> ul li.current').removeClass('current'); + parent_li.toggleClass('current'); + } + + return nav; +}; + +module.exports.ThemeNav = ThemeNav(); + +if (typeof(window) != 'undefined') { + window.SphinxRtdTheme = { StickyNav: module.exports.ThemeNav }; +} + +},{"jquery":"jquery"}]},{},["sphinx-rtd-theme"]); diff --git a/docs/_build/html/_static/minus.png b/docs/_build/html/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..d96755fdaf8bb2214971e0db9c1fd3077d7c419d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu=nj kDsEF_5m^0CR;1wuP-*O&G^0G}KYk!hp00i_>zopr08q^qX#fBK literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/plus.png b/docs/_build/html/_static/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..7107cec93a979b9a5f64843235a16651d563ce2d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu>-2 m3q%Vub%g%s<8sJhVPMczOq}xhg9DJoz~JfX=d#Wzp$Pyb1r*Kz literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/pygments.css b/docs/_build/html/_static/pygments.css new file mode 100644 index 000000000..20c4814dc --- /dev/null +++ b/docs/_build/html/_static/pygments.css @@ -0,0 +1,69 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #208050 } /* Literal.Number.Bin */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_build/html/_static/searchtools.js b/docs/_build/html/_static/searchtools.js new file mode 100644 index 000000000..41b833677 --- /dev/null +++ b/docs/_build/html/_static/searchtools.js @@ -0,0 +1,761 @@ +/* + * searchtools.js_t + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + + +/* Non-minified version JS is _stemmer.js if file is provided */ +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + + +/** + * Simple result scoring code. + */ +var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + /* + score: function(result) { + return result[4]; + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: {0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5}, // used to be unimportantResults + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + // query found in terms + term: 5 +}; + + + + + +var splitChars = (function() { + var result = {}; + var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648, + 1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702, + 2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971, + 2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345, + 3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761, + 3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823, + 4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125, + 8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695, + 11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587, + 43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141]; + var i, j, start, end; + for (i = 0; i < singles.length; i++) { + result[singles[i]] = true; + } + var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709], + [722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161], + [1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568], + [1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807], + [1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047], + [2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383], + [2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450], + [2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547], + [2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673], + [2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820], + [2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946], + [2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023], + [3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173], + [3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332], + [3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481], + [3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718], + [3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791], + [3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095], + [4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205], + [4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687], + [4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968], + [4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869], + [5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102], + [6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271], + [6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592], + [6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822], + [6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167], + [7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959], + [7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143], + [8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318], + [8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483], + [8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101], + [10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567], + [11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292], + [12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444], + [12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783], + [12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311], + [19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511], + [42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774], + [42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071], + [43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263], + [43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519], + [43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647], + [43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967], + [44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295], + [57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274], + [64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007], + [65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381], + [65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]]; + for (i = 0; i < ranges.length; i++) { + start = ranges[i][0]; + end = ranges[i][1]; + for (j = start; j <= end; j++) { + result[j] = true; + } + } + return result; +})(); + +function splitQuery(query) { + var result = []; + var start = -1; + for (var i = 0; i < query.length; i++) { + if (splitChars[query.charCodeAt(i)]) { + if (start !== -1) { + result.push(query.slice(start, i)); + start = -1; + } + } else if (start === -1) { + start = i; + } + } + if (start !== -1) { + result.push(query.slice(start)); + } + return result; +} + + + + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, + dataType: "script", cache: true, + complete: function(jqxhr, textstatus) { + if (textstatus != "success") { + document.getElementById("searchindexloader").src = url; + } + }}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + var i; + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + } + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch : function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('

    ' + _('Searching') + '

    ').appendTo(this.out); + this.dots = $('').appendTo(this.title); + this.status = $('

    ').appendTo(this.out); + this.output = $('
    +{# #} {% endblock %} @@ -94,6 +95,7 @@ +{# #} or other required elements. - thead: [ 1, "
    - + {% for node in nodes_remain %} + {% endfor %}
    {{ cluster.name }}{{ node.name }}
    ' + cluster_name + '' + cluster_name + '
    {% trans 'Terminal' %} {% trans 'Command' %} {% trans 'Date start' %}{% trans 'Date last active' %}{% trans 'Duration' %} {% trans 'Action' %}{{ session.id | get_session_command_amount }} {{ session.date_start }}{{ session.date_last_active }}{{ session.date_start|time_util_with_seconds:session.date_end }} {% if session.is_finished %} From 1e5b9fb3ef78445549fc2f12337412344819fde5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 28 Feb 2018 10:39:38 +0800 Subject: [PATCH 134/197] =?UTF-8?q?[Update]=20terminal=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E6=9B=B4=E6=94=B9=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/forms.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/terminal/forms.py b/apps/terminal/forms.py index a3672eeef..ab4520b03 100644 --- a/apps/terminal/forms.py +++ b/apps/terminal/forms.py @@ -25,18 +25,22 @@ def get_all_replay_storage(): class TerminalForm(forms.ModelForm): - command_storage = forms.ChoiceField(choices=get_all_command_storage(), - label=_("Command storage")) - replay_storage = forms.ChoiceField(choices=get_all_replay_storage(), - label=_("Replay storage")) + command_storage = forms.ChoiceField( + choices=get_all_command_storage(), + label=_("Command storage") + ) + replay_storage = forms.ChoiceField( + choices=get_all_replay_storage(), + label=_("Replay storage") + ) class Meta: model = Terminal - fields = ['name', 'remote_addr', 'ssh_port', 'http_port', 'comment', 'command_storage', 'replay_storage'] + fields = [ + 'name', 'remote_addr', 'ssh_port', 'http_port', 'comment', + 'command_storage', 'replay_storage', + ] help_texts = { 'ssh_port': _("Coco ssh listen port"), 'http_port': _("Coco http/ws listen port"), } - widgets = { - 'name': forms.TextInput(attrs={'readonly': 'readonly'}) - } From 521a5c57a2f4540b10e54b6ca4cdf2ae1abc3651 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 28 Feb 2018 11:23:04 +0800 Subject: [PATCH 135/197] =?UTF-8?q?[Update]=20=E7=BB=9F=E4=B8=80copyright?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/templates/_copyright.html | 1 + apps/templates/flash_message_standalone.html | 2 +- apps/users/templates/users/forgot_password.html | 7 ++----- apps/users/templates/users/login.html | 7 ++----- apps/users/templates/users/reset_password.html | 7 ++----- 5 files changed, 8 insertions(+), 16 deletions(-) create mode 100644 apps/templates/_copyright.html diff --git a/apps/templates/_copyright.html b/apps/templates/_copyright.html new file mode 100644 index 000000000..ebff34379 --- /dev/null +++ b/apps/templates/_copyright.html @@ -0,0 +1 @@ +Copyright 北京堆栈科技有限公司 © 2014-2018 \ No newline at end of file diff --git a/apps/templates/flash_message_standalone.html b/apps/templates/flash_message_standalone.html index 539ab5429..c7bbe7843 100644 --- a/apps/templates/flash_message_standalone.html +++ b/apps/templates/flash_message_standalone.html @@ -49,7 +49,7 @@
    - Copyright Jumpserver.org + {% include '_copyright.html' %}
    2014-2018 diff --git a/apps/users/templates/users/forgot_password.html b/apps/users/templates/users/forgot_password.html index 1a5a9fb77..0ceed556e 100644 --- a/apps/users/templates/users/forgot_password.html +++ b/apps/users/templates/users/forgot_password.html @@ -51,11 +51,8 @@

    -
    - Copyright Jumpserver.org -
    -
    - © 2014-2018 +
    + {% include '_copyright.html' %}
    diff --git a/apps/users/templates/users/login.html b/apps/users/templates/users/login.html index 18339356a..ded2e3474 100644 --- a/apps/users/templates/users/login.html +++ b/apps/users/templates/users/login.html @@ -74,11 +74,8 @@

    -
    - Copyright 北京堆栈科技有限公司 -
    -
    - © 2014-2018 +
    + {% include '_copyright.html' %}
    diff --git a/apps/users/templates/users/reset_password.html b/apps/users/templates/users/reset_password.html index 9033a4c9e..0bab32809 100644 --- a/apps/users/templates/users/reset_password.html +++ b/apps/users/templates/users/reset_password.html @@ -70,11 +70,8 @@
    -
    - Copyright Jumpserver.org -
    -
    - © 2014-2018 +
    + {% include '_copyright.html' %}
    From 634b36c74bf379402fd9971e1c40c47fe07db5ab Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 1 Mar 2018 00:13:53 +0800 Subject: [PATCH 136/197] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docs/_build/doctrees/admin_asset.doctree | Bin 2264 -> 2264 bytes docs/_build/doctrees/admin_guide.doctree | Bin 2603 -> 2603 bytes docs/_build/doctrees/admin_user.doctree | Bin 2245 -> 2245 bytes docs/_build/doctrees/contact.doctree | Bin 2242 -> 4329 bytes docs/_build/doctrees/contributor.doctree | Bin 2237 -> 5409 bytes docs/_build/doctrees/development.doctree | Bin 2246 -> 2669 bytes docs/_build/doctrees/faq.doctree | Bin 2211 -> 2211 bytes docs/_build/doctrees/index.doctree | Bin 7221 -> 7221 bytes docs/_build/doctrees/installation.doctree | Bin 2606 -> 2621 bytes docs/_build/doctrees/quickstart.doctree | Bin 4478 -> 7967 bytes docs/_build/doctrees/step_by_step.doctree | Bin 2265 -> 25732 bytes docs/_build/doctrees/user_guide.doctree | Bin 2263 -> 2263 bytes docs/_build/html/_sources/contact.rst.txt | 30 ++ docs/_build/html/_sources/contributor.rst.txt | 13 +- docs/_build/html/_sources/development.rst.txt | 14 +- docs/_build/html/_sources/index.rst.txt | 2 +- .../_build/html/_sources/installation.rst.txt | 1 + docs/_build/html/_sources/quickstart.rst.txt | 32 +- .../_build/html/_sources/step_by_step.rst.txt | 292 ++++++++++++++++++ docs/_build/html/admin_asset.html | 1 - docs/_build/html/admin_guide.html | 1 - docs/_build/html/admin_user.html | 1 - docs/_build/html/contact.html | 33 +- docs/_build/html/contributor.html | 16 +- docs/_build/html/development.html | 25 +- docs/_build/html/faq.html | 1 - docs/_build/html/genindex.html | 2 +- docs/_build/html/index.html | 31 +- docs/_build/html/installation.html | 4 +- docs/_build/html/objects.inv | Bin 476 -> 576 bytes docs/_build/html/quickstart.html | 30 +- docs/_build/html/search.html | 2 +- docs/_build/html/searchindex.js | 2 +- docs/_build/html/step_by_step.html | 251 ++++++++++++++- docs/_build/html/user_guide.html | 5 +- docs/api_style_guide.rst | 166 ++++++++++ docs/conf.py | 1 + docs/contact.rst | 30 ++ docs/contributor.rst | 13 +- docs/development.rst | 14 +- docs/index.rst | 2 +- docs/installation.rst | 1 + docs/project_structure.rst | 51 +++ docs/python_style_guide.rst | 216 +++++++++++++ docs/quickstart.rst | 27 +- docs/step_by_step.rst | 292 ++++++++++++++++++ docs/upgrade.rst | 18 ++ 48 files changed, 1577 insertions(+), 44 deletions(-) create mode 100644 docs/api_style_guide.rst create mode 100644 docs/project_structure.rst create mode 100644 docs/python_style_guide.rst create mode 100644 docs/upgrade.rst diff --git a/.gitignore b/.gitignore index ccd5937f2..abf165e2e 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ celerybeat.pid django.db celerybeat-schedule.db data/static +_build/ diff --git a/docs/_build/doctrees/admin_asset.doctree b/docs/_build/doctrees/admin_asset.doctree index 7902cc71880b48e91ab260825e1989931925f227..cdbdab20b95b07cf2e9b49483a481d1a2c88ec18 100644 GIT binary patch delta 45 zcmca1ctdc)K1Q~U`&Y3}Ze;5a7cS0CEGj81%`ZtUj?c+YPRvOyo)SA{GY9(xMgXi} B5;Xt- delta 25 hcmca1ctdc)K1QL9`&Y3}{>UaVIg_npGbj6bMgWei3K9SS diff --git a/docs/_build/doctrees/admin_guide.doctree b/docs/_build/doctrees/admin_guide.doctree index eed230cf43e3df372be9768b9ccfcf3b04a747dc..ee3b007c8e184b981e722d9f226edc643ce71022 100644 GIT binary patch delta 25 hcmZ22vRY)qW=6J+TR7M!Ut#ZqcvNu0CPuc6n-{ZA&SUGC9L6TG`5W7LMgWH33KswX delta 25 hcmX>qcvNu0CPtx+n-{ZAzQ-mpIgG7i^EbBhi~x%`3X=c; diff --git a/docs/_build/doctrees/contact.doctree b/docs/_build/doctrees/contact.doctree index 2cefb258e7e1a327d342315eaf2791c26315df05..7941da16aeba774a7a1470cf5c5fe64b81b66474 100644 GIT binary patch literal 4329 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 SPfxGRccEIr^6#UQ9s4(f%>O3< delta 83 zcmZ3ewO5d}fo19@j)|;E6Ej>Hg(j{$$e6hK2yYYEuK<4CBwk4I71#t>u&xdtjY)gz1ba` diff --git a/docs/_build/doctrees/development.doctree b/docs/_build/doctrees/development.doctree index 860b3e179b9be084adbd2d301f94203a932eb7ab..826768bf25c41a037988cd13d0742f26cd3158c9 100644 GIT binary patch delta 563 zcmZXRJ!@1!6ozw?{c;ltQ9+CeAxLneBw}YLVj*E+E1N);W$vBho{@WJ#8;Q!aa*_vl{ah?v z{Cuua$iXJ#Tq+JZ`MOn!H2@db;#+j%02lo%hh0dO&Oq9Lhuw(K*ris-&oeE0xgDm^ z>pQ`L-crY3(L;+$UZ^id)lA&PM?y&9y=w2p@$_TwW4Io4MgV!(2{`}& delta 25 hcmZ21xL9z)0!E>Y3n#Nq-oqv_*@UfQ^A)!9i~xQx3AF$K diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree index 9016c187cb8545d363c42ac563d7e54716d26e77..0de5d8f8560dae56c2d659925c41b16dfee61320 100644 GIT binary patch delta 154 zcmdmLvDIQjATujlhG>TV*z&r;ik7cqrOFdX504QR#`7DbdBO}}9`>e@~ zV9^|~XeGNLNOUy^J4CbtBs%#e=X$W3X<#+yxy&JwOTb)P9$7|4rp?j3Q$RXD@NMLp Ryi>Ac@TV*Se<}82PltavN%gUYYe@~ta(7u9I$95yCEZE&F0k{?2N2kK+z76=;W82>se<2dDFmZ&U2YFvMvL1mw>sp zJhF_8jGLo*r!X@9-u!`YBbT^vac*K!NnvSzNosL?PJVJ?PHOR#*eR12NOo>^l={L5 E0Ek#TK>z>% diff --git a/docs/_build/doctrees/installation.doctree b/docs/_build/doctrees/installation.doctree index ea3c2ac378d1595ad8d023f5d4ba4e04e7bf0532..34768594f22faff8a579a65f0e828cd32fe5031a 100644 GIT binary patch delta 115 zcmZ1{vR8zqfn}=EM3(i8Y#XTE-^WtqfaI(BfPkWGcPeWwWP8DsH1jD SMwDN1hCGlC-^|IW$_M~#I4O+) delta 100 zcmdlhvQC7hfn}=lM3(i8LL0ZFFp8)4@MPvC=air;ken3iHDi7 zZnHOQGt=ZS4vER<*gGcYarDVVWP}#?aONfErj}F|0M*w{$%ybP&X5Pvp_?T*RT%-< C?jsri diff --git a/docs/_build/doctrees/quickstart.doctree b/docs/_build/doctrees/quickstart.doctree index 5590125f3b2dc9d246ea3c872294dc5e87ced443..57e9d5693ae4061036f2bc47fa7e9ea4271d354a 100644 GIT binary patch literal 7967 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 O=8x diff --git a/docs/_build/doctrees/step_by_step.doctree b/docs/_build/doctrees/step_by_step.doctree index 9e8816ec451989b9a9160e4719a1175fb15ce21d..ee53db0a9592eb6064af4476fd087c34330b3b1b 100644 GIT binary patch literal 25732 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 Django资深开发者,为用户模块贡献了很多代码 +- **sofia <周小侠>** 资深前端工程师, 前端代码贡献者 +- **liuz <刘正> 全栈工程师** 编写了Web terminal大部分代码 +- **jiaxiangkong <陈尚委>** Jumpserver测试运营 +- **halcyon <王墉>** DevOps 资深开发者, 0.3.2 核心开发者之一 +- **yumaojun03 <喻茂峻>** DevOps 资深开发者,擅长Python, Go以及PAAS平台开发 +- **kelianchun <柯连春>** DevOps 资产开发者,fix了很多bug \ No newline at end of file diff --git a/docs/_build/html/_sources/development.rst.txt b/docs/_build/html/_sources/development.rst.txt index ff2f3e6f1..9e2411ea9 100644 --- a/docs/_build/html/_sources/development.rst.txt +++ b/docs/_build/html/_sources/development.rst.txt @@ -1,2 +1,12 @@ -开发指南 -------------------- \ No newline at end of file +开发文档 +====================================== + +.. toctree:: + :maxdepth: 1 + :caption: 开发文档 + + api_style_guide + python_style_guide + project_structure + + diff --git a/docs/_build/html/_sources/index.rst.txt b/docs/_build/html/_sources/index.rst.txt index be7e63f8d..82f80166d 100644 --- a/docs/_build/html/_sources/index.rst.txt +++ b/docs/_build/html/_sources/index.rst.txt @@ -28,7 +28,7 @@ Jumpserver 支持容器化部署,windows,LDAP, s3, elasticsearch存储等功 .. toctree:: - :maxdepth: 1 + :maxdepth: 2 :caption: 文档: installation diff --git a/docs/_build/html/_sources/installation.rst.txt b/docs/_build/html/_sources/installation.rst.txt index 9229a4602..e9dde1f48 100644 --- a/docs/_build/html/_sources/installation.rst.txt +++ b/docs/_build/html/_sources/installation.rst.txt @@ -6,3 +6,4 @@ quickstart step_by_step + upgrade diff --git a/docs/_build/html/_sources/quickstart.rst.txt b/docs/_build/html/_sources/quickstart.rst.txt index ac8586970..7892999fe 100644 --- a/docs/_build/html/_sources/quickstart.rst.txt +++ b/docs/_build/html/_sources/quickstart.rst.txt @@ -1,7 +1,10 @@ 快速安装 ========================== -Jumpserver 封装了一个All in one Docker,可以快速启动。 +Jumpserver 封装了一个All in one Docker,可以快速启动。该镜像集成了所有需要的组件,可以使用外置db和redis + +Tips: 不建议在生产中使用 + Docker 安装见: `Docker官方安装文档 `_ @@ -17,4 +20,29 @@ Docker 安装见: `Docker官方安装文档 `_ 浏览器访问: http://localhost:8080 -ssh访问: ssh -p 2222 localhost \ No newline at end of file +ssh访问: ssh -p 2222 localhost + + +额外环境变量 +``````````````` + +- DB_ENGINE = mysql +- DB_HOST = mysql_host +- DB_PORT = 3306 +- DB_USER = xxx +- DB_PASSWORD = xxxx +- DB_NAME = jumpserver + +- REDIS_HOST = '' +- REDIS_PORT = '' +- REDIS_PASSWORD = '' + + :: + + docker run -p 8080:80 -p 2222:2222 -e DB_ENGINE=mysql -e DB_HOST=192.168.1.1 -e DB_PORT=3306 -e DB_USER=root -e DB_PASSWORD=xxx -e DB_NAME=jumpserver jumpserver/jumpserver:0.5.0-beta2 + + +仓库地址 +``````````````` + +https://github.com/jumpserver/Dockerfile diff --git a/docs/_build/html/_sources/step_by_step.rst.txt b/docs/_build/html/_sources/step_by_step.rst.txt index 4e8234b62..db4b08608 100644 --- a/docs/_build/html/_sources/step_by_step.rst.txt +++ b/docs/_build/html/_sources/step_by_step.rst.txt @@ -1,2 +1,294 @@ 一步一步安装 -------------------------- + +环境 +~~~~ + +- 系统: CentOS 7 +- IP: 192.168.244.144 +- 关闭 selinux和防火墙 + +:: + + # CentOS 7 + $ setenforce 0 # 可以设置配置文件永久关闭 + $ systemctl stop iptables.service + $ systemctl stop firewalld.service + + # CentOS6 + $ setenforce 0 + $ service iptables stop + +一. 准备Python3和Python虚拟环境 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**1.1 安装依赖包** + +:: + + $ yum -y install wget sqlite-devel xz gcc automake zlib-devel openssl-devel epel-release + +**1.2 编译安装** + +:: + + $ wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tar.xz + $ tar xvf Python-3.6.1.tar.xz && cd Python-3.6.1 + $ ./configure && make && make install + +**1.3 建立python虚拟环境** + +因为CentOS +6/7自带的是Python2,而Yum等工具依赖原来的Python,为了不扰乱原来的环境我们来使用Python虚拟环境 + +:: + + $ cd /opt + $ python3 -m venv py3 + $ source /opt/py3/bin/activate + + # 看到下面的提示符代表成功,以后运行jumpserver都要先运行以上source命令,以下所有命令均在该虚拟环境中运行 + (py3) [root@localhost py3]# + +二. 安装Jumpserver 0.5.0 +~~~~~~~~~~~~~~~~~~~~~~~~ + +**2.1 下载或clone项目** + +项目提交较多git clone时较大,你可以选择去github项目页面直接下载 +zip包,我的网速好,我直接clone了 + +:: + + $ cd /opt/ + $ git clone --depth=1 https://github.com/jumpserver/jumpserver.git && cd jumpserver && git checkout dev + +**2.2 安装依赖rpm包** + +:: + + $ cd /opt/jumpserver/requirements + $ yum -y install $(cat rpm_requirements.txt) # 如果没有任何报错请继续 + +**2.3 安装python库依赖** + +:: + + $ pip install -r requirements.txt # 不要指定-i参数,因为镜像上可能没有最新的包,如果没有任何报错请继续 + +**2.4 安装Redis, jumpserver使用redis做cache和celery broker** + +:: + + $ yum -y install redis + $ service redis start + +**2.5 安装MySQL** + +本教程使用mysql作为数据库,如果不使用mysql可以跳过相关mysql安装和配置 + +:: + + # centos7 + $ yum -y install mariadb mariadb-devel mariadb-server # centos7下安装的是mariadb + $ service mariadb start + + # centos6 + $ yum -y install mysql mysql-devel mysql-server + $ service mysqld start + +**2.6 创建数据库 jumpserver并授权** + +:: + + $ mysql + > create database jumpserver default charset 'utf8'; + > grant all on jumpserver.* to 'jumpserver'@'127.0.0.1' identified by 'somepassword'; + +**2.7 修改jumpserver配置文件** + +:: + + $ cd /opt/jumpserver + $ cp config_example.py config.py + $ vi config.py # 我们计划修改 DevelopmentConfig中的配置,因为默认jumpserver是使用该配置,它继承自Config + +**注意: 配置文件是python格式,不要用tab,而要用空格** **注意: +配置文件是python格式,不要用tab,而要用空格** **注意: +配置文件是python格式,不要用tab,而要用空格** + +:: + + class DevelopmentConfig(Config): + DEBUG = True + DB_ENGINE = 'mysql' + DB_HOST = '127.0.0.1' + DB_PORT = 3306 + DB_USER = 'jumpserver' + DB_PASSWORD = 'somepassword' + DB_NAME = 'jumpserver' + + ... + + config = DevelopmentConfig() # 确保使用的是刚才设置的配置文件 + +**2.8 生成数据库表结构和初始化数据** + +:: + + $ cd /opt/jumpserver/utils + $ bash make_migrations.sh + +**2.9 运行Jumpserver** + +:: + + $ cd /opt/jumpserver + $ python run_server.py all + +运行不报错,请浏览器访问 http://192.168.244.144:8080/ +(这里只是jumpserver, 没有web terminal,所以访问web terminal会报错) + +账号:admin 密码: admin + +三. 安装 SSH Server和Web Socket Server: Coco +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**3.1 下载clone项目** + +新开一个终端,连接测试机,别忘了 source /opt/py3/bin/activate + +:: + + $ cd /opt + $ git clone https://github.com/jumpserver/coco.git && cd coco && git checkout dev + +**3.2 安装依赖** + +:: + + $ cd /opt/coco/requirements $ yum -y install $(cat rpm_requirements.txt) $ pip install requirements.txt + + +**3.2 安装依赖** + +:: + + $ cd /opt/coco/requirements + $ yum -y install $(cat rpm_requirements.txt) + $ pip install -r requirements.txt + +**3.3 查看配置文件并运行** + +:: + + $ cd /opt/coco + $ cp conf_example.py conf.py + $ python run_server.py + +这时需要去 +jumpserver管理后台-终端-终端(http://192.168.244.144:8080/terminal/terminal/)接受coco的注册 + +:: + + Coco version 0.4.0, more see https://www.jumpserver.org + Starting ssh server at 0.0.0.0:2222 + Quit the server with CONTROL-C. + +**3.4 测试连接** + +:: + + $ ssh -p2222 admin@192.168.244.144 + 密码: admin + + 如果是用在windows下,Xshell terminal登录语法如下 + $ssh admin@192.168.244.144 2222 + 密码: admin + 如果能登陆代表部署成功 + +四. 安装 Web Terminal 前端: Luna +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Luna已改为纯前端,需要nginx来运行访问 + +下载 release包,直接解压,不需要编译 + +访问 https://github.com/jumpserver/luna/releases,下载对应release包 + +4.1 解压luna + +:: + + $ pwd + /opt/ + + $ tar xvf luna.tar.gz + $ ls /opt/luna + ... + +五. 安装Windows支持组件 +~~~~~~~~~~~~~~~~~~~~~~~ + +使用docker启动 guacamole + +.. code:: shell + + docker run \ + -p 8080:8080 \ + -e JUMPSERVER_SERVER=http://:8080 \ + jumpserver/guacamole + +这里所需要注意的是guacamole暴露出来的端口是8080,若与jumpserver部署在同一主机上自定义一下。 + +修改JUMPSERVER_SERVER的配置,填上jumpserver的内网地址 + +六. 配置 nginx 整合各组件 +~~~~~~~~~~~~~~~~~~~~~~~~~ + +6.1 安装nginx 根据喜好选择安装方式和版本 + +6.2 配置文件 + +:: + + server { + listen 80; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + location /luna/ { + try_files $uri / /index.html; + alias /opt/luna/; + } + + location /media/ { + add_header Content-Encoding gzip; + root /opt/jumpserver/data/; + } + + location /static/ { + root /opt/jumpserver/data/; + } + + location /socket.io/ { + proxy_pass http://localhost:5000/socket.io/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location /guacamole/ { + proxy_pass http://:8080/; + } + + location / { + proxy_pass http://localhost:8080; + } + } + +6.3 运行 nginx + +6.4 访问 http://192.168.244.144 \ No newline at end of file diff --git a/docs/_build/html/admin_asset.html b/docs/_build/html/admin_asset.html index 5125d1a62..45a865de1 100644 --- a/docs/_build/html/admin_asset.html +++ b/docs/_build/html/admin_asset.html @@ -96,7 +96,6 @@
  • 用户使用文档
  • -
  • 开发指南
  • 贡献者
  • 联系方式
  • FAQ
  • diff --git a/docs/_build/html/admin_guide.html b/docs/_build/html/admin_guide.html index 08d10b2c3..1fbd1c0fd 100644 --- a/docs/_build/html/admin_guide.html +++ b/docs/_build/html/admin_guide.html @@ -95,7 +95,6 @@
  • 用户使用文档
  • -
  • 开发指南
  • 贡献者
  • 联系方式
  • FAQ
  • diff --git a/docs/_build/html/admin_user.html b/docs/_build/html/admin_user.html index c6a60d71d..b77aa1fb5 100644 --- a/docs/_build/html/admin_user.html +++ b/docs/_build/html/admin_user.html @@ -96,7 +96,6 @@
  • 用户使用文档
  • -
  • 开发指南
  • 贡献者
  • 联系方式
  • FAQ
  • diff --git a/docs/_build/html/contact.html b/docs/_build/html/contact.html index 9606dabb1..8f2a4696a 100644 --- a/docs/_build/html/contact.html +++ b/docs/_build/html/contact.html @@ -91,9 +91,16 @@
  • 安装文档
  • 管理文档
  • 用户使用文档
  • -
  • 开发指南
  • +
  • 开发文档
  • 贡献者
  • -
  • 联系方式
  • +
  • 联系方式 +
  • FAQ
  • @@ -160,6 +167,28 @@

    联系方式

    +
    +

    QQ群

    +

    群1: 390139816 +群2: 399218702 +群3: 552054376

    +
    + + + +
    +

    邮件

    +

    ibuler#fit2cloud.com (#替换为@)

    +
    diff --git a/docs/_build/html/contributor.html b/docs/_build/html/contributor.html index 202ef1f01..fdcc460cb 100644 --- a/docs/_build/html/contributor.html +++ b/docs/_build/html/contributor.html @@ -37,7 +37,7 @@ - + @@ -91,7 +91,7 @@
  • 安装文档
  • 管理文档
  • 用户使用文档
  • -
  • 开发指南
  • +
  • 开发文档
  • 贡献者
  • 联系方式
  • FAQ
  • @@ -160,6 +160,16 @@

    贡献者

    +

    感谢一下朋友为Jumpserver做出的贡献,世界因你们而不同,排名不分先后

    +
      +
    • 小彧 <李磊> Django资深开发者,为用户模块贡献了很多代码
    • +
    • sofia <周小侠> 资深前端工程师, 前端代码贡献者
    • +
    • liuz <刘正> 全栈工程师 编写了Web terminal大部分代码
    • +
    • jiaxiangkong <陈尚委> Jumpserver测试运营
    • +
    • halcyon <王墉> DevOps 资深开发者, 0.3.2 核心开发者之一
    • +
    • yumaojun03 <喻茂峻> DevOps 资深开发者,擅长Python, Go以及PAAS平台开发
    • +
    • kelianchun <柯连春> DevOps 资产开发者,fix了很多bug
    • +
    @@ -175,7 +185,7 @@ - + diff --git a/docs/_build/html/development.html b/docs/_build/html/development.html index 1eaead62d..3c2b8d0ad 100644 --- a/docs/_build/html/development.html +++ b/docs/_build/html/development.html @@ -8,7 +8,7 @@ - 开发指南 — jumpserver 0.5.0 文档 + 开发文档 — jumpserver 0.5.0 文档 @@ -36,7 +36,7 @@ href="genindex.html"/> - + @@ -91,7 +91,12 @@
  • 安装文档
  • 管理文档
  • 用户使用文档
  • -
  • 开发指南
  • +
  • 开发文档 +
  • 贡献者
  • 联系方式
  • FAQ
  • @@ -139,7 +144,7 @@
  • Docs »
  • -
  • 开发指南
  • +
  • 开发文档
  • @@ -159,7 +164,15 @@
    -

    开发指南

    +

    开发文档

    +
    @@ -172,7 +185,7 @@
  • 安装文档
  • 管理文档
  • 用户使用文档
  • -
  • 开发指南
  • 贡献者
  • 联系方式
  • FAQ
  • diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html index 0fb4d6015..8e1176bc2 100644 --- a/docs/_build/html/genindex.html +++ b/docs/_build/html/genindex.html @@ -90,7 +90,7 @@
  • 安装文档
  • 管理文档
  • 用户使用文档
  • -
  • 开发指南
  • +
  • 开发文档
  • 贡献者
  • 联系方式
  • FAQ
  • diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html index 464c51f5f..135621198 100644 --- a/docs/_build/html/index.html +++ b/docs/_build/html/index.html @@ -90,7 +90,7 @@
  • 安装文档
  • 管理文档
  • 用户使用文档
  • -
  • 开发指南
  • +
  • 开发文档
  • 贡献者
  • 联系方式
  • FAQ
  • @@ -171,12 +171,33 @@ diff --git a/docs/_build/html/installation.html b/docs/_build/html/installation.html index 069fbcc30..e7a9fb6a1 100644 --- a/docs/_build/html/installation.html +++ b/docs/_build/html/installation.html @@ -91,11 +91,12 @@
  • 安装文档
  • 管理文档
  • 用户使用文档
  • -
  • 开发指南
  • +
  • 开发文档
  • 贡献者
  • 联系方式
  • FAQ
  • @@ -168,6 +169,7 @@ diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv index da9bb9afc02e7fe887304b2eac688ce4cc5f7c0e..31be07064b13262d4420663e9d9da477908d7e3d 100644 GIT binary patch delta 466 zcmV;@0WJRA1Hc53cz<cZ$V_4gnt$_2?~;*6Gb;vqUBm-V6O1oy0?D_Sd}T10LG=Y9vUo$(5}8GFt1A@w0g?R?oIBA&)4zH}MvYZ^s|Mu>kHUWT^t7PibKZN*K0PS1DG?a5$bGG4?&P^k$?4v3{|g?$aD@?J%n zhPIkrF2A0mi63>Y!m%F=`bc@oE+cdo-N*_BsMier5kRh>&VMeGsDO0G;q^P@=oXh) z&#ea%taj0j-oC@$XE1MX%nmEL(sKR+AOke@qt;~QH4vdSYFM^Oc`hO&DptjIet~9qr($arkrs?&o>4Gm#C3gwkrJKbQsLeC^NHgCP=<+Qm}6nB21oSzTev4=;RuQddYy&dKh%OV#WdTK+7O L2i3$chR8-Q*>SRu diff --git a/docs/_build/html/quickstart.html b/docs/_build/html/quickstart.html index 27b9c7edd..f5f45bb3c 100644 --- a/docs/_build/html/quickstart.html +++ b/docs/_build/html/quickstart.html @@ -93,6 +93,8 @@
  • 快速安装
  • 一步一步安装
  • @@ -100,7 +102,7 @@
  • 管理文档
  • 用户使用文档
  • -
  • 开发指南
  • +
  • 开发文档
  • 贡献者
  • 联系方式
  • FAQ
  • @@ -171,7 +173,8 @@

    快速安装

    -

    Jumpserver 封装了一个All in one Docker,可以快速启动。

    +

    Jumpserver 封装了一个All in one Docker,可以快速启动。该镜像集成了所有需要的组件,可以使用外置db和redis

    +

    Tips: 不建议在生产中使用

    Docker 安装见: Docker官方安装文档

    快速启动

    @@ -185,6 +188,29 @@

    浏览器访问: http://localhost:8080

    ssh访问: ssh -p 2222 localhost

    +
    +

    额外环境变量

    +
      +
    • DB_ENGINE = mysql
    • +
    • DB_HOST = mysql_host
    • +
    • DB_PORT = 3306
    • +
    • DB_USER = xxx
    • +
    • DB_PASSWORD = xxxx
    • +
    • DB_NAME = jumpserver
    • +
    • REDIS_HOST = ‘’
    • +
    • REDIS_PORT = ‘’
    • +
    • REDIS_PASSWORD = ‘’
    • +
    +
    +
    docker run -p 8080:80 -p 2222:2222 -e DB_ENGINE=mysql -e DB_HOST=192.168.1.1 -e DB_PORT=3306 -e DB_USER=root -e DB_PASSWORD=xxx -e DB_NAME=jumpserver  jumpserver/jumpserver:0.5.0-beta2
    +
    +
    +
    +
    +
    diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html index f44192655..cd058fd4b 100644 --- a/docs/_build/html/search.html +++ b/docs/_build/html/search.html @@ -89,7 +89,7 @@
  • 安装文档
  • 管理文档
  • 用户使用文档
  • -
  • 开发指南
  • +
  • 开发文档
  • 贡献者
  • 联系方式
  • FAQ
  • diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js index 425c3615b..dfaafad30 100644 --- a/docs/_build/html/searchindex.js +++ b/docs/_build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["admin_asset","admin_guide","admin_user","contact","contributor","development","faq","index","installation","quickstart","step_by_step","user_guide"],envversion:53,filenames:["admin_asset.rst","admin_guide.rst","admin_user.rst","contact.rst","contributor.rst","development.rst","faq.rst","index.rst","installation.rst","quickstart.rst","step_by_step.rst","user_guide.rst"],objects:{},objnames:{},objtypes:{},terms:{"\u4e00\u4e2a\u4e2d\u5fc3":7,"\u4e00\u6b65\u4e00\u6b65\u5b89\u88c5":8,"\u4e0d\u540cregion\u4e0d\u540c\u767b\u5f55\u70b9":7,"\u4ece\u8bbe\u8ba1\u65f6\u8003\u8651\u5206\u5e03\u5f0f":7,"\u4f7f\u7528python":7,"\u4f7f\u7528root\u547d\u4ee4\u884c\u8f93\u5165":9,"\u5185\u5bb9":[],"\u53ef\u4ee5\u5feb\u901f\u542f\u52a8":9,"\u5821\u5792\u673a":7,"\u5b89\u88c5\u6587\u6863":7,"\u5b89\u88c5\u89c1":9,"\u5ba1\u8ba1audit":7,"\u5c01\u88c5\u4e86\u4e00\u4e2aall":9,"\u5e76\u63d0\u4f9b\u4e86\u5f3a\u5927api\u65b9\u4fbf\u5bf9\u63a5\u5176\u5b83\u7cfb\u7edf":7,"\u5e76\u6709\u9886\u5148\u7684web":7,"\u5f00\u53d1\u6307\u5357":7,"\u5feb\u901f\u542f\u52a8":[],"\u5feb\u901f\u5b89\u88c5":8,"\u6388\u6743author":7,"\u641c\u7d22\u9875\u9762":7,"\u652f\u6301\u5bb9\u5668\u5316\u90e8\u7f72":7,"\u652f\u6301\u81ea\u52a8\u5316\u8fd0\u7ef4\u4efb\u52a1":7,"\u662f\u4e00\u6b3e\u5b8c\u5168\u5f00\u6e90\u7684\u8df3\u677f\u673a":7,"\u662f\u7b26\u54084a":7,"\u6a21\u5757\u7d22\u5f15":7,"\u6b22\u8fce\u6765\u5230":7,"\u6ca1\u6709\u6027\u80fd\u74f6\u9888":7,"\u6d4f\u89c8\u5668\u8bbf\u95ee":9,"\u6df1\u5ea6\u96c6\u6210\u4e86ansibl":7,"\u6ee1\u8db3\u6df7\u5408\u4e91\u67b6\u6784":7,"\u7528\u6237\u4f7f\u7528\u6587\u6863":7,"\u7528\u6237\u6a21\u5757":1,"\u754c\u9762\u6f02\u4eae":7,"\u7684\u4e13\u4e1a\u8fd0\u7ef4\u5ba1\u8ba1\u7cfb\u7edf":7,"\u7b80\u5355\u6613\u7528":7,"\u7ba1\u7406\u5458\u6587\u6863":[],"\u7ba1\u7406\u6587\u6863":7,"\u7cfb\u7edf":7,"\u7d22\u5f15":[],"\u8054\u7cfb\u65b9\u5f0f":7,"\u8ba4\u8bc1authent":7,"\u8d21\u732e\u8005":7,"\u8d26\u53f7account":7,"\u8d44\u4ea7\u7ba1\u7406\u6a21\u5757":1,"\u9075\u5faagpl":7,"django\u5f00\u53d1":7,"docker\u5b98\u65b9\u5b89\u88c5\u6587\u6863":9,"docker\u5b98\u65b9\u5b89\u88c5\u6587\u6863http":[],"elasticsearch\u5b58\u50a8\u7b49\u529f\u80fd":7,"ssh\u8bbf\u95ee":9,"terminal\u89e3\u51b3\u65b9\u6848":7,"v2\u534f\u8bae":7,beta2:9,com:[],content:[],doc:[],docker:9,faq:7,http:9,instal:[],jumpserv:9,ldap:7,localhost:9,one:9,run:9,ssh:9,window:7},titles:["\u8d44\u4ea7\u7ba1\u7406\u6a21\u5757","\u7ba1\u7406\u6587\u6863","\u7528\u6237\u6a21\u5757","\u8054\u7cfb\u65b9\u5f0f","\u8d21\u732e\u8005","\u5f00\u53d1\u6307\u5357","FAQ","Jumpserver \u6587\u6863","\u5b89\u88c5\u6587\u6863","\u5feb\u901f\u5b89\u88c5","\u4e00\u6b65\u4e00\u6b65\u5b89\u88c5","\u7528\u6237\u4f7f\u7528\u6587\u6863"],titleterms:{"\u4e00\u6b65\u4e00\u6b65\u5b89\u88c5":10,"\u5b89\u88c5\u6587\u6863":8,"\u5f00\u53d1\u6307\u5357":5,"\u5feb\u901f\u542f\u52a8":9,"\u5feb\u901f\u5b89\u88c5":9,"\u6587\u6863":7,"\u6709\u5173jumpserv":7,"\u7528\u6237\u4f7f\u7528\u6587\u6863":11,"\u7528\u6237\u6587\u6863":[],"\u7528\u6237\u6a21\u5757":2,"\u7ba1\u7406\u6587\u6863":1,"\u7d22\u5f15":7,"\u8054\u7cfb\u65b9\u5f0f":3,"\u8bbf\u95ee":9,"\u8d21\u732e\u8005":4,"\u8d44\u4ea7\u7ba1\u7406\u6a21\u5757":0,faq:6,indic:[],jumpserv:7,tabl:[]}}) \ No newline at end of file +Search.setIndex({docnames:["admin_asset","admin_guide","admin_user","api_style_guide","contact","contributor","development","faq","index","installation","project_structure","python_style_guide","quickstart","step_by_step","upgrade","user_guide"],envversion:53,filenames:["admin_asset.rst","admin_guide.rst","admin_user.rst","api_style_guide.rst","contact.rst","contributor.rst","development.rst","faq.rst","index.rst","installation.rst","project_structure.rst","python_style_guide.rst","quickstart.rst","step_by_step.rst","upgrade.rst","user_guide.rst"],objects:{},objnames:{},objtypes:{},terms:{"0\u6846\u67b6":3,"2\u7a7a\u683c\u53ef\u4ee5\u663e\u8457\u964d\u4f4e\u89c6\u89c9\u4e0a\u7684\u8d1f\u62c5":11,"7\u81ea\u5e26\u7684\u662fpython2":13,"8\u7f16\u7801\u58f0\u660e":11,"\u4e00\u4e2a":11,"\u4e00\u4e2a\u4e2d\u5fc3":8,"\u4e00\u6b65\u4e00\u6b65\u5b89\u88c5":[8,9],"\u4e00\u822c\u6027\u7684\u589e\u5220\u67e5\u6539":3,"\u4e00\u822c\u6765\u8bf4":3,"\u4e00\u822c\u7528\u6765\u4f20\u9012\u8be5api\u64cd\u4f5c\u7684\u6838\u5fc3\u5b9e\u4f53\u5bf9\u8c61\u7684\u552f\u4e00id":3,"\u4e00\u9879\u6216\u591a\u9879":3,"\u4e0a\u9762\u4ee3\u7801\u8868\u793a":3,"\u4e0b\u4e00\u884c\u5e94\u8be5\u4e0e\u4e0a\u4e00\u884c\u7684\u6700\u540e":11,"\u4e0b\u8f7d":13,"\u4e0b\u8f7d\u5bf9\u5e94release\u5305":13,"\u4e0b\u8f7d\u6216clone\u9879\u76ee":13,"\u4e0b\u8f7dclone\u9879\u76ee":13,"\u4e0d\u4f7f\u7528\u62fc\u97f3":11,"\u4e0d\u4f7f\u7528\u65e0\u610f\u4e49\u7b80\u5355\u5b57\u6bcd\u547d\u540d":11,"\u4e0d\u540cregion\u4e0d\u540c\u767b\u5f55\u70b9":8,"\u4e0d\u5efa\u8bae\u5728\u751f\u4ea7\u4e2d\u4f7f\u7528":12,"\u4e0d\u8981\u4f7f\u7528\u9ed8\u8ba4":11,"\u4e0d\u8981\u5728\u4ee3\u7801\u4e2d\u4f7f\u7528\u592a\u591a\u7684\u7a7a\u884c\u6765\u533a\u5206\u4e0d\u540c\u7684\u903b\u8f91\u6a21\u5757":11,"\u4e0d\u8981\u5728\u53d8\u91cf\u540d\u540e\u6dfb\u52a0\u4e0b\u5212\u7ebf\u8fdb\u884c\u533a\u5206":11,"\u4e0d\u8981\u6307\u5b9a":[13,14],"\u4e0d\u8981\u7528tab":13,"\u4e0d\u8981\u786c\u7f16\u7801":11,"\u4e0d\u8981\u8fd9\u6837\u5199":11,"\u4e0d\u9700\u8981\u7f16\u8bd1":13,"\u4e0e401\u9519\u8bef\u76f8\u5bf9":3,"\u4e0e\u5355\u4f8b":11,"\u4e14\u4e0d\u4f1a\u518d\u5f97\u5230\u7684":3,"\u4e16\u754c\u56e0\u4f60\u4eec\u800c\u4e0d\u540c":5,"\u4e3a\u4e86\u4e0d\u6270\u4e71\u539f\u6765\u7684\u73af\u5883\u6211\u4eec\u6765\u4f7f\u7528python\u865a\u62df\u73af\u5883":13,"\u4e3a\u7528\u6237\u6a21\u5757\u8d21\u732e\u4e86\u5f88\u591a\u4ee3\u7801":5,"\u4e3a\u957f\u8bed\u53e5\u6362\u884c":11,"\u4e3e\u4f8b\u6765\u8bf4":3,"\u4e4b\u540e\u9a6c\u4e0a\u6362\u884c":11,"\u4e4b\u6240\u4ee5\u4e0epython\u4e0d\u540c":11,"\u4e5f\u662f\u5404app\u6240\u5728\u76ee\u5f55":10,"\u4e5f\u77e5\u9053\u4e0b\u4e00\u6b65\u5e94\u8be5\u505a\u4ec0\u4e48":3,"\u4e8c\u8005\u5747\u4ee5restructuredtext\u683c\u5f0f\u7f16\u5199":11,"\u4ece\u670d\u52a1\u5668\u5220\u9664\u8d44\u6e90":3,"\u4ece\u670d\u52a1\u5668\u53d6\u51fa\u8d44\u6e90":3,"\u4ece\u8bbe\u8ba1\u65f6\u8003\u8651\u5206\u5e03\u5f0f":8,"\u4ee3\u7801\u4f18\u96c5\u7b80\u6d01":11,"\u4ee3\u8868\u5b57\u7b26\u4e32\u7ed3\u675f\u7684\u4e09\u4e2a\u5f15\u53f7\u4e0e\u4ee3\u8868\u5b57\u7b26\u4e32\u5f00\u59cb\u7684\u4e09\u4e2a\u5f15\u53f7\u5728\u540c\u4e00\u884c":11,"\u4ee3\u8868\u5b57\u7b26\u4e32\u7ed3\u675f\u7684\u4e09\u4e2a\u5f15\u53f7\u5219\u81ea\u5df1\u72ec\u7acb\u6210\u4e00\u884c":11,"\u4ee4\u724c":3,"\u4ee5\u4e0b\u6240\u6709\u547d\u4ee4\u5747\u5728\u8be5\u865a\u62df\u73af\u5883\u4e2d\u8fd0\u884c":13,"\u4ee5\u53ca\u6392\u5e8f\u987a\u5e8f":3,"\u4ee5\u53ca\u6807\u51c6\u7684\u6587\u6863\u5b57\u7b26\u4e32":11,"\u4ee5\u53ca\u6bcf\u9875\u7684\u8bb0\u5f55\u6570":3,"\u4ee5\u540e\u8fd0\u884cjumpserver\u90fd\u8981\u5148\u8fd0\u884c\u4ee5\u4e0asource\u547d\u4ee4":13,"\u4efb\u4f55python\u4ee3\u7801\u90fd\u90fd\u5fc5\u987b\u9075\u5b88\u6b64\u89c4\u5b9a":11,"\u4efb\u610f\u7c7b\u578b\u4e4b\u95f4\u7684\u6bd4\u8f83":11,"\u4f1a\u5f97\u5230\u8fd9\u6837\u4e00\u4e2a\u6587\u6863":3,"\u4f46\u662f\u53ea\u6709xml\u683c\u5f0f":3,"\u4f46\u662f\u6709\u4e9b\u7ec6\u8282\u90e8\u5206\u4f1a\u5c3d\u91cf\u653e\u5f00":11,"\u4f46\u662f\u8bbf\u95ee\u662f\u88ab\u7981\u6b62\u7684":3,"\u4f46\u662fdjango\u7684\u547d\u540d":11,"\u4f5c\u4e3a\u7c7b\u540d\u79f0":11,"\u4f60\u53ef\u4ee5\u9009\u62e9\u53bbgithub\u9879\u76ee\u9875\u9762\u76f4\u63a5\u4e0b\u8f7d":13,"\u4f7f\u5f97\u7528\u6237\u4e0d\u67e5\u6587\u6863":3,"\u4f7f\u7528":11,"\u4f7f\u7528django":11,"\u4f7f\u7528docker\u542f\u52a8":13,"\u4f7f\u7528foo":11,"\u4f7f\u7528is\u548ci":11,"\u4f7f\u7528python":8,"\u4f7f\u7528root\u547d\u4ee4\u884c\u8f93\u5165":12,"\u4f8b\u5982":[3,11],"\u4fee\u6539jumpserver\u914d\u7f6e\u6587\u4ef6":13,"\u4fee\u6539jumpserver_server\u7684\u914d\u7f6e":13,"\u5141\u8bb8\u4e0e\u5185\u5efa\u53d8\u91cf\u91cd\u540d":11,"\u5168\u6808\u5de5\u7a0b\u5e08":5,"\u5173\u95ed":13,"\u5185\u5bb9":[],"\u51fa\u9519\u4fe1\u606f\u4f5c\u4e3a\u952e\u503c\u5373\u53ef":3,"\u5218\u6b63":5,"\u521b\u5efa\u6570\u636e\u5e93":13,"\u521d\u59cb\u5316\u6570\u636e\u76ee\u5f55":10,"\u521d\u59cb\u5316\u9879\u76ee\u6570\u636e\u5e93":10,"\u522b\u5fd8\u4e86":13,"\u524d\u7aef\u4ee3\u7801\u8d21\u732e\u8005":5,"\u533f\u540d\u51fd\u6570\u7684\u7b2c\u4e00\u4e2a\u53c2\u6570\u53ef\u4ee5\u7528x\u66ff\u4ee3":11,"\u5347\u7ea7":[8,9],"\u5355\u76ee\u8fd0\u7b97\u7b26\u4e0e\u8fd0\u7b97\u5bf9\u8c61\u4e4b\u95f4\u4e0d\u7a7a\u683c":11,"\u5373\u4f7f\u5355\u76ee\u8fd0\u7b97\u7b26\u4f4d\u4e8e\u62ec\u53f7\u5185\u90e8\u4e5f\u4e00\u6837":11,"\u5373\u8fd4\u56de\u7ed3\u679c\u4e2d\u63d0\u4f9b\u94fe\u63a5":3,"\u53c2\u8003":3,"\u53c2\u8003\u56fd\u5185\u7ffb\u8bd1":11,"\u53cc\u4e0b\u5212\u7ebf\u524d\u7f00\u53ea\u6709\u5b9a\u4e49\u6df7\u5165\u7c7b":11,"\u53cc\u76ee\u8fd0\u7b97\u7b26\u4e0e\u8fd0\u7b97\u5bf9\u8c61\u4e4b\u95f4\u8981\u7a7a\u683c":11,"\u53d1\u751f\u4e00\u4e2a\u9a8c\u8bc1\u9519\u8bef":3,"\u53d7\u4fdd\u62a4\u7684\u5143\u7d20\u4ee5\u4e00\u4e2a\u4e0b\u5212\u7ebf\u4e3a\u524d\u7f00":11,"\u53d8\u91cf\u540d":11,"\u53e5\u70b9\u6216":11,"\u53ea\u80fd\u6709\u540d\u8bcd":3,"\u53ef\u4ee5\u4f7f\u7528\u5916\u7f6edb\u548credi":12,"\u53ef\u4ee5\u4f7f\u7528\u6362\u884c\u7b26":11,"\u53ef\u4ee5\u5feb\u901f\u542f\u52a8":12,"\u53ef\u4ee5\u8bbe\u7f6e\u914d\u7f6e\u6587\u4ef6\u6c38\u4e45\u5173\u95ed":13,"\u540c\u6837\u4e0d\u4f7f\u7528tab":11,"\u540c\u7406static\u4e5f\u662f":11,"\u5426\u5219\u8bf7\u4e2d\u6587\u4f18\u96c5\u6ce8\u91ca":11,"\u5468\u5c0f\u4fa0":5,"\u547d\u540d\u7f29\u5199\u8981\u8c28\u614e":11,"\u547d\u540d\u8981\u6709\u5bd3\u610f":11,"\u548c":11,"\u548c\u8be6\u7ec6\u4ecb\u7ecd":11,"\u55bb\u8302\u5cfb":5,"\u56e0\u4e3a\u955c\u50cf\u4e0a\u53ef\u80fd\u6ca1\u6709\u6700\u65b0\u7684\u5305":13,"\u56e0\u4e3a\u9ed8\u8ba4jumpserver\u662f\u4f7f\u7528\u8be5\u914d\u7f6e":13,"\u56e0\u4e3acento":13,"\u5728\u670d\u52a1\u5668\u65b0\u5efa\u4e00\u4e2a\u8d44\u6e90":3,"\u5728\u670d\u52a1\u5668\u66f4\u65b0\u8d44\u6e90":3,"\u5728\u7b2c\u4e00\u4e2a":11,"\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b":11,"\u5728restful\u67b6\u6784\u4e2d":3,"\u5821\u5792\u673a":8,"\u586b\u4e0ajumpserver\u7684\u5185\u7f51\u5730\u5740":13,"\u591a\u4e00\u5c42\u76ee\u5f55":10,"\u5927\u5199_\u4ee5\u53ca_\u4e0b\u5212\u7ebf":11,"\u5982\u679c\u4e0d\u4f7f\u7528mysql\u53ef\u4ee5\u8df3\u8fc7\u76f8\u5173mysql\u5b89\u88c5\u548c\u914d\u7f6e":13,"\u5982\u679c\u4e3a\u591a\u884c":11,"\u5982\u679c\u4f60\u4f7f\u7528\u62ec\u53f7":11,"\u5982\u679c\u4f7f\u7528\u5173\u952e\u8bcd":11,"\u5982\u679c\u4f7f\u7528\u6ce8\u91ca\u6765\u7f16\u5199\u7c7b\u5c5e\u6027\u7684\u6587\u6863":11,"\u5982\u679c\u51fd\u6570\u9700\u8981\u8bbf\u95ee\u91cd\u540d\u7684\u5185\u5efa\u53d8\u91cf":11,"\u5982\u679c\u53ea\u6709\u4e00\u884c":11,"\u5982\u679c\u5fc5\u8981\u7684\u8bdd":11,"\u5982\u679c\u662f\u7528\u5728windows\u4e0b":13,"\u5982\u679c\u6709\u66f4\u591a\u7684\u53c2\u6570\u9700\u8981\u63d0\u4f9b":3,"\u5982\u679c\u6a21\u5757\u4e2d\u4f7f\u7528\u4e86\u975eascii\u7f16\u7801\u7684\u5b57\u7b26":11,"\u5982\u679c\u6ca1\u6709\u4efb\u4f55\u62a5\u9519\u8bf7\u7ee7\u7eed":13,"\u5982\u679c\u72b6\u6001\u7801\u662f4xx":3,"\u5982\u679c\u80fd\u767b\u9646\u4ee3\u8868\u90e8\u7f72\u6210\u529f":13,"\u5982\u679c\u9700\u8981":10,"\u5b83\u7ee7\u627f\u81eaconfig":13,"\u5b89\u88c5\u4f9d\u8d56":13,"\u5b89\u88c5\u4f9d\u8d56\u5305":13,"\u5b89\u88c5\u4f9d\u8d56rpm\u5305":13,"\u5b89\u88c5\u6587\u6863":8,"\u5b89\u88c5\u89c1":12,"\u5b89\u88c5\u8bf4\u660e":10,"\u5b89\u88c5mysql":13,"\u5b89\u88c5nginx":13,"\u5b89\u88c5python\u5e93\u4f9d\u8d56":13,"\u5b89\u88c5redi":13,"\u5b8c\u5168\u4f7f\u7528http":3,"\u5b98\u7f51":8,"\u5b9e\u4f8b\u65b9\u6cd5":11,"\u5ba1\u8ba1audit":8,"\u5ba2\u6237\u7aef\u63d0\u4f9b\u6539\u53d8\u540e\u7684\u5b8c\u6574\u8d44\u6e90":3,"\u5ba2\u6237\u7aef\u63d0\u4f9b\u6539\u53d8\u7684\u5c5e\u6027":3,"\u5bc6\u7801":13,"\u5bc6\u7801\u9519\u8bef":3,"\u5bf9\u4e8e\u5143\u7d20\u4f17\u591a\u7684\u5217\u8868\u6216\u5143\u7ec4":11,"\u5bf9\u4e8e\u8d44\u6e90\u7684\u5177\u4f53\u64cd\u4f5c\u7c7b\u578b":3,"\u5bf9\u5916\u66b4\u9732\u7684\u63a5\u53e3":10,"\u5bf9\u9f50":11,"\u5c01\u88c5\u4e86\u4e00\u4e2aall":12,"\u5c06\u7248\u672c\u53f7\u653e\u5230app\u540e\u9762":3,"\u5c06api\u7684\u7248\u672c\u53f7\u653e\u5165url\u4e2d":3,"\u5c06views\u548capi\u53ef\u590d\u7528\u7684\u4ee3\u7801\u653e\u5728\u8fd9\u91cc":10,"\u5c0f\u5199_\u4ee5\u53ca_\u4e0b\u5212\u7ebf":11,"\u5c0f\u5f67":5,"\u5c31\u5e94\u8be5\u5411\u7528\u6237\u8fd4\u56de\u51fa\u9519\u4fe1\u606f":3,"\u5c3d\u53ef\u80fd\u5229\u7528django\u9020\u597d\u7684\u8f6e\u5b50":11,"\u5c3d\u91cf\u4e00\u884c":11,"\u5c3d\u91cf\u4f7f\u7528class":11,"\u5c3d\u91cf\u662f\u5927\u5bb6\u8ba4\u53ef\u7684\u7f29\u5199":11,"\u5e03\u5c40\u4e5f\u4e0d\u4e00\u6837":11,"\u5e38\u7528\u7684http\u52a8\u8bcd\u6709\u4e0b\u9762\u4e94\u4e2a":3,"\u5e38\u89c1\u53c2\u6570\u7ea6\u5b9a":3,"\u5e38\u89c1\u7684\u6709\u4ee5\u4e0b\u4e00\u4e9b":3,"\u5e38\u91cf":11,"\u5e42\u7b49":3,"\u5e76\u63d0\u4f9b\u4e86\u5f3a\u5927api\u65b9\u4fbf\u5bf9\u63a5\u5176\u5b83\u7cfb\u7edf":8,"\u5e76\u6709\u9886\u5148\u7684web":8,"\u5e76\u7ed9\u51fa\u8be5collection\u7684\u7f51\u5740":3,"\u5e94\u5728\u540d\u79f0\u540e\u6dfb\u52a0\u540e\u7f6e\u4e0b\u5212\u7ebf":11,"\u5e94\u8be5\u5c3d\u91cf\u4f7f\u7528json":3,"\u5efa\u7acbpython\u865a\u62df\u73af\u5883":13,"\u5efa\u8bae\u8fdb\u884c\u58f0\u660e":11,"\u5f00\u53d1\u6307\u5357":[],"\u5f00\u53d1\u6587\u6863":8,"\u5f02\u6b65\u4efb\u52a1":3,"\u5f53\u521b\u5efa\u4e00\u4e2a\u5bf9\u8c61\u65f6":3,"\u5f53\u524d\u6700\u65b0":11,"\u5f53\u7528\u6237\u5411api":3,"\u5faa\u73af\u4e2d\u8ba1\u6570\u4f8b\u5916":11,"\u5feb\u901f\u542f\u52a8":[],"\u5feb\u901f\u5b89\u88c5":[8,9],"\u603b\u662f\u4f7f\u7528https\u534f\u8bae":3,"\u611f\u8c22\u4e00\u4e0b\u670b\u53cb\u4e3ajumpserver\u505a\u51fa\u7684\u8d21\u732e":5,"\u6211\u4eec\u8ba1\u5212\u4fee\u6539":13,"\u6211\u4eec\u91c7\u7528pocoo\u7684":11,"\u6211\u7684\u7f51\u901f\u597d":13,"\u6211\u76f4\u63a5clone\u4e86":13,"\u6216":11,"\u6216\u8005\u662f\u7f29\u8fdb4\u4e2a\u7a7a\u683c\u7b26":11,"\u6216\u82b1\u62ec\u53f7":11,"\u6240\u4ee5\u6211\u4eec\u9650\u5236\u6700\u5927120\u5b57\u7b26":11,"\u6240\u4ee5\u653e\u5728\u4e3b\u57df\u540d\u4e0b":3,"\u6240\u4ee5\u7f51\u5740\u4e2d\u4e0d\u80fd\u6709\u52a8\u8bcd":3,"\u6240\u4ee5\u8bbf\u95eeweb":13,"\u6240\u4ee5api\u4e2d\u7684\u540d\u8bcd\u4e5f\u5e94\u8be5\u4f7f\u7528\u590d\u6570":3,"\u6240\u6709\u6587\u6863\u5b57\u7b26\u4e32\u5747\u4ee5restructuredtext\u683c\u5f0f\u7f16\u5199":11,"\u6240\u6709doc\u6587\u4ef6\u653e\u5230\u8be5\u76ee\u5f55":10,"\u62ec\u53f7\u91cc\u662f\u5bf9\u5e94\u7684sql\u547d\u4ee4":3,"\u6307\u5b9a\u7b2c\u51e0\u9875":3,"\u6307\u5b9a\u7b5b\u9009\u6761\u4ef6":3,"\u6307\u5b9a\u8fd4\u56de\u7ed3\u679c\u6309\u7167\u54ea\u4e2a\u5c5e\u6027\u6392\u5e8f":3,"\u6307\u5b9a\u8fd4\u56de\u8bb0\u5f55\u7684\u5f00\u59cb\u4f4d\u7f6e":3,"\u6307\u5b9a\u8fd4\u56de\u8bb0\u5f55\u7684\u6570\u91cf":3,"\u6309pep8\u89c4\u8303":11,"\u6362\u884c":11,"\u6388\u6743author":8,"\u6392\u540d\u4e0d\u5206\u5148\u540e":5,"\u63a5\u53d7coco\u7684\u6ce8\u518c":13,"\u641c\u7d22\u9875\u9762":8,"\u6458\u8981\u4e0e\u8be6\u7ec6\u4ecb\u7ecd\u4e4b\u95f4\u7a7a\u4e00\u884c":11,"\u64c5\u957fpython":5,"\u652f\u6301\u5bb9\u5668\u5316\u90e8\u7f72":8,"\u652f\u6301\u81ea\u52a8\u5316\u8fd0\u7ef4\u4efb\u52a1":8,"\u653e\u5230\u8be5\u6587\u4ef6\u4e2d":10,"\u6570\u636e\u5e93\u4e2d\u7684\u8868\u90fd\u662f\u540c\u79cd\u8bb0\u5f55\u7684":3,"\u6570\u636e\u5e93\u8868\u540d\u624b\u52a8\u6307\u5b9a":11,"\u6570\u636e\u6a21\u578b\u76ee\u5f55":10,"\u6570\u7ec4":3,"\u6587\u6863\u4e2d\u6709\u4e00\u4e2alink\u5c5e\u6027":3,"\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u7684\u6587\u672c\u7d27\u63a5\u7740\u4ee3\u8868\u5b57\u7b26\u4e32\u5f00\u59cb\u7684\u4e09\u4e2a\u5f15\u53f7\u7f16\u5199":11,"\u6587\u6863\u5b57\u7b26\u4e32\u5e94\u5206\u6210\u7b80\u77ed\u6458\u8981":11,"\u6587\u6863\u5b57\u7b26\u4e32\u7684\u884c\u6570\u4e0d\u540c":11,"\u65b0\u5f00\u4e00\u4e2a\u7ec8\u7aef":13,"\u65b0\u7248\u672cdjango":10,"\u65b9\u4fbf\u522b\u7684app\u5f15\u7528":10,"\u65b9\u4fbfsphinx\u5904\u7406":11,"\u65b9\u62ec\u53f7\u4e2d\u662f\u8be5\u72b6\u6001\u7801\u5bf9\u5e94\u7684http\u52a8\u8bcd":3,"\u65b9\u6cd5\u4e0e\u51fd\u6570\u540d":11,"\u65e5\u5fd7\u76ee\u5f55":10,"\u65f6":11,"\u65f6\u624d\u4f7f\u7528":11,"\u662f\u4e00\u6b3e\u5b8c\u5168\u5f00\u6e90\u7684\u8df3\u677f\u673a":8,"\u662f\u56e0\u4e3ajs\u4e2d\u6709\u5927\u91cf\u56de\u8c03\u5f0f\u7684\u5199\u6cd5":11,"\u662f\u7b26\u54084a":8,"\u66f4\u5c11\u4ee3\u7801":11,"\u66ff\u6362\u4e3a":4,"\u6709\u80fd\u529b\u5c3d\u53ef\u80fd\u7528\u82f1\u6587":11,"\u670d\u52a1\u5668\u53d1\u751f\u9519\u8bef":3,"\u670d\u52a1\u5668\u5411\u7528\u6237\u8fd4\u56de\u7684\u72b6\u6001\u7801\u548c\u63d0\u793a\u4fe1\u606f":3,"\u670d\u52a1\u5668\u5411\u7528\u6237\u8fd4\u56de\u7684\u7ed3\u679c\u5e94\u8be5\u7b26\u5408\u4ee5\u4e0b\u89c4\u8303":3,"\u670d\u52a1\u5668\u6210\u529f\u8fd4\u56de\u7528\u6237\u8bf7\u6c42\u7684\u6570\u636e":3,"\u670d\u52a1\u5668\u6ca1\u6709\u8fdb\u884c\u64cd\u4f5c":3,"\u670d\u52a1\u5668\u6ca1\u6709\u8fdb\u884c\u65b0\u5efa\u6216\u4fee\u6539\u6570\u636e\u7684\u64cd\u4f5c":3,"\u670d\u52a1\u5668\u8fd4\u56de\u7684\u6570\u636e\u683c\u5f0f":3,"\u672c\u6559\u7a0b\u4f7f\u7528mysql\u4f5c\u4e3a\u6570\u636e\u5e93":13,"\u674e\u78ca":5,"\u67e5\u770b\u914d\u7f6e\u6587\u4ef6\u5e76\u8fd0\u884c":13,"\u67ef\u8fde\u6625":5,"\u6838\u5fc3\u5f00\u53d1\u8005\u4e4b\u4e00":5,"\u6839\u636e\u559c\u597d\u9009\u62e9\u5b89\u88c5\u65b9\u5f0f\u548c\u7248\u672c":13,"\u6a21\u5757\u6587\u4ef6\u7684\u5934\u90e8\u5305\u542b\u6709utf":11,"\u6a21\u5757\u7d22\u5f15":8,"\u6a21\u677f\u6807\u7b7e\u76ee\u5f55":10,"\u6a21\u7cca\u641c\u7d22":3,"\u6b22\u8fce\u6765\u5230":8,"\u6b64\u5916\u90fd\u53ea\u7a7a\u4e00\u884c":11,"\u6bcf\u4e2a\u7f51\u5740\u4ee3\u8868\u4e00\u79cd\u8d44\u6e90":3,"\u6bcf\u4e2aurl\u72ec\u7acb\u547d\u540d":11,"\u6bd4\u5982":3,"\u6bd4\u5982\u4e0a\u9762\u63d0\u5230\u7684":3,"\u6bd4\u5982\u7528\u6237\u8bf7\u6c42json\u683c\u5f0f":3,"\u6c38\u8fdc\u4e0d\u8981\u4e0etrue\u6216false\u8fdb\u884c\u6bd4\u8f83":11,"\u6ca1\u6709\u524d\u540e\u7aef\u5206\u79bb":3,"\u6ca1\u6709\u6027\u80fd\u74f6\u9888":8,"\u6ca1\u6709\u72ec\u7acbapp":3,"\u6ca1\u6709web":13,"\u6ce8\u610f":13,"\u6ce8\u91ca\u660e\u786e\u4f18\u7f8e":11,"\u6ce8\u91ca\u7684\u89c4\u8303\u4e0e\u6587\u6863\u5b57\u7b26\u4e32\u7f16\u5199\u89c4\u8303\u7c7b\u4f3c":11,"\u6d4b\u8bd5\u6848\u4f8b\u5c3d\u53ef\u80fd\u5b8c\u6574":11,"\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6":10,"\u6d4b\u8bd5\u8fde\u63a5":13,"\u6d4f\u89c8\u5668\u8bbf\u95ee":12,"\u6df1\u5ea6\u96c6\u6210\u4e86ansibl":8,"\u6ee1\u8db3\u6df7\u5408\u4e91\u67b6\u6784":8,"\u738b\u5889":5,"\u751f\u6210\u5927\u91cf\u6d4b\u8bd5\u6570\u636e":10,"\u751f\u6210\u6570\u636e\u5e93\u8868\u7ed3\u6784\u548c\u521d\u59cb\u5316\u6570\u636e":13,"\u7528\u6237\u4f7f\u7528\u6587\u6863":8,"\u7528\u6237\u5220\u9664\u6570\u636e\u6210\u529f":3,"\u7528\u6237\u53d1\u51fa\u7684\u8bf7\u6c42\u6709\u9519\u8bef":3,"\u7528\u6237\u53d1\u51fa\u7684\u8bf7\u6c42\u9488\u5bf9\u7684\u662f\u4e0d\u5b58\u5728\u7684\u8bb0\u5f55":3,"\u7528\u6237\u540d":3,"\u7528\u6237\u5c06\u65e0\u6cd5\u5224\u65ad\u53d1\u51fa\u7684\u8bf7\u6c42\u662f\u5426\u6210\u529f":3,"\u7528\u6237\u65b0\u5efa\u6216\u4fee\u6539\u6570\u636e\u6210\u529f":3,"\u7528\u6237\u6a21\u5757":[1,8],"\u7528\u6237\u8bf7\u6c42\u7684\u683c\u5f0f\u4e0d\u53ef\u5f97":3,"\u7528\u6237\u8bf7\u6c42\u7684\u8d44\u6e90\u88ab\u6c38\u4e45\u5220\u9664":3,"\u7528\u6237\u8bfb\u53d6\u8fd9\u4e2a\u5c5e\u6027\u5c31\u77e5\u9053\u4e0b\u4e00\u6b65\u8be5\u8c03\u7528\u4ec0\u4e48api\u4e86":3,"\u7531\u4e8e\u4e00\u4e2a\u9879\u76ee\u591a\u4e2aapp\u6240\u4ee5jumpserver\u4f7f\u7528\u4ee5\u4e0b\u98ce\u683c":3,"\u7531http\u52a8\u8bcd\u8868\u793a":3,"\u754c\u9762\u6f02\u4eae":8,"\u7684\u4e13\u4e1a\u8fd0\u7ef4\u5ba1\u8ba1\u7cfb\u7edf":8,"\u76f4\u63a5\u89e3\u538b":13,"\u770b\u5230\u4e0b\u9762\u7684\u63d0\u793a\u7b26\u4ee3\u8868\u6210\u529f":13,"\u786e\u4fdd\u4f7f\u7528\u7684\u662f\u521a\u624d\u8bbe\u7f6e\u7684\u914d\u7f6e\u6587\u4ef6":13,"\u7b26\u53f7\u540e\u6dfb\u52a0\u4e00\u4e2a\u5192\u53f7":11,"\u7b49":11,"\u7b80\u5355\u6613\u7528":8,"\u7ba1\u7406\u540e\u53f0\u76ee\u5f55":10,"\u7ba1\u7406\u5458\u6587\u6863":[],"\u7ba1\u7406\u6587\u6863":8,"\u7c7b\u540d\u79f0":11,"\u7c7b\u65b9\u6cd5":11,"\u7cfb\u7edf":[8,13],"\u7d22\u5f15":[],"\u7ec8\u70b9":3,"\u7ec8\u7aef":13,"\u7f16\u5199\u4e86luna\u5927\u90e8\u5206\u4ee3\u7801":[],"\u7f16\u5199\u4e86web":5,"\u7f16\u5199\u957f\u8bed\u53e5\u65f6":11,"\u7f16\u8bd1\u5b89\u88c5":13,"\u7fa41":4,"\u7fa42":4,"\u7fa43":4,"\u800c\u4e0d\u662fhttpwriter":11,"\u800c\u4e0d\u662fnot":11,"\u800c\u4e1421\u4e16\u7eaa\u90fd\u662f\u5bbd\u5c4f\u4e86":11,"\u800c\u4e14\u6240\u7528\u7684\u540d\u8bcd\u5f80\u5f80\u4e0e\u6570\u636e\u5e93\u7684\u8868\u683c\u540d\u5bf9\u5e94":3,"\u800c\u5e94\u8be5\u8fd9\u6837\u5199":11,"\u800c\u8981\u7528\u7a7a\u683c":13,"\u800cyum\u7b49\u5de5\u5177\u4f9d\u8d56\u539f\u6765\u7684python":13,"\u8054\u7cfb\u65b9\u5f0f":8,"\u82e5\u4e0ejumpserver\u90e8\u7f72\u5728\u540c\u4e00\u4e3b\u673a\u4e0a\u81ea\u5b9a\u4e49\u4e00\u4e0b":13,"\u8865\u5145\u8bf4\u660e":11,"\u8868\u793a\u4e00\u4e2a\u8bf7\u6c42\u5df2\u7ecf\u8fdb\u5165\u540e\u53f0\u6392\u961f":3,"\u8868\u793a\u7528\u6237\u5f97\u5230\u6388\u6743":3,"\u8868\u793a\u7528\u6237\u6ca1\u6709\u6743\u9650":3,"\u8868\u793aapi\u7684\u5177\u4f53\u7f51\u5740":3,"\u89e3\u538bluna":13,"\u8ba4\u8bc1authent":8,"\u8bbe\u8ba1\u6307\u5357":3,"\u8bbf\u95ee":13,"\u8be5\u64cd\u4f5c\u662f\u5e42\u7b49\u7684":3,"\u8be5\u955c\u50cf\u96c6\u6210\u4e86\u6240\u6709\u9700\u8981\u7684\u7ec4\u4ef6":12,"\u8bf4\u660e\u5982\u4e0b":10,"\u8bf7\u5728":11,"\u8bf7\u5c06\u5185\u5efa\u53d8\u91cf\u91cd\u65b0\u7ed1\u5b9a\u4e3a\u5176\u4ed6\u540d\u79f0":11,"\u8bf7\u6d4f\u89c8\u5668\u8bbf\u95ee":13,"\u8d21\u732e\u8005":8,"\u8d26\u53f7":13,"\u8d26\u53f7account":8,"\u8d44\u4ea7\u5f00\u53d1\u8005":5,"\u8d44\u4ea7\u7ba1\u7406\u6a21\u5757":[1,8],"\u8d44\u6df1\u524d\u7aef\u5de5\u7a0b\u5e08":5,"\u8d44\u6df1\u5f00\u53d1\u8005":5,"\u8def\u5f84\u53c8\u79f0":3,"\u8fd0\u884c":13,"\u8fd0\u884c\u4e0d\u62a5\u9519":13,"\u8fd0\u884cjumpserv":13,"\u8fd4\u56de\u4e00\u4e2a\u7a7a\u6587\u6863":3,"\u8fd4\u56de\u5355\u4e2a\u8d44\u6e90\u5bf9\u8c61":3,"\u8fd4\u56de\u5b8c\u6574\u7684\u8d44\u6e90\u5bf9\u8c61":3,"\u8fd4\u56de\u65b0\u751f\u6210\u7684\u8d44\u6e90\u5bf9\u8c61":3,"\u8fd4\u56de\u7684\u4fe1\u606f\u4e2d\u5c06error\u4f5c\u4e3a\u952e\u540d":3,"\u8fd4\u56de\u8d44\u6e90\u5bf9\u8c61\u7684\u5217\u8868":3,"\u8fd9\u65f6\u9700\u8981\u53bb":13,"\u8fd9\u7248api\u76f8\u5bf9\u7b80\u5355":3,"\u8fd9\u91cc\u4ec5\u8003\u8651rest":3,"\u8fd9\u91cc\u53ea\u662fjumpserv":13,"\u8fd9\u91cc\u6240\u9700\u8981\u6ce8\u610f\u7684\u662fguacamole\u66b4\u9732\u51fa\u6765\u7684\u7aef\u53e3\u662f8080":13,"\u8fdb\u884c\u6bd4\u8f83\u65f6":11,"\u8fde\u5411\u5176\u4ed6api\u65b9\u6cd5":3,"\u8fde\u63a5\u6d4b\u8bd5\u673a":13,"\u901a\u7528\u7684\u51fd\u6570\u65b9\u6cd5":10,"\u901a\u7528templat":10,"\u9075\u5faagpl":8,"\u90a3\u4e48\u4e0b\u4e00\u884c\u5e94\u4e0e\u62ec\u53f7\u6216\u82b1\u62ec\u53f7\u5bf9\u9f50":11,"\u90ae\u4ef6":8,"\u914d\u7f6e\u6587\u4ef6":13,"\u914d\u7f6e\u6587\u4ef6\u662fpython\u683c\u5f0f":13,"\u914d\u7f6e\u6587\u4ef6\u6837\u4f8b":10,"\u91c7\u7528\u9a86\u9a7c\u62fc\u5199\u6cd5":11,"\u91cd\u65b0\u4e0b\u8f7drelease\u5305":14,"\u9488\u5bf9\u4e0d\u540c\u64cd\u4f5c":3,"\u9632\u6b62\u8d44\u6e90\u91cd\u540d":10,"\u9648\u5c1a\u59d4":5,"\u96c6\u5408":3,"\u9700\u8981nginx\u6765\u8fd0\u884c\u8bbf\u95ee":13,"\u9876\u5c42\u51fd\u6570\u4e0e\u7c7b\u4e4b\u95f4\u7a7a\u4e24\u884c":11,"\u9879\u76ee\u5165\u53e3urlconf":10,"\u9879\u76ee\u591a\u8bed\u8a00\u76ee\u5f55":10,"\u9879\u76ee\u63d0\u4ea4\u8f83\u591agit":13,"\u9879\u76ee\u6a21\u677f\u76ee\u5f55":10,"\u9879\u76ee\u89c4\u8303":[6,8],"\u9879\u76ee\u8bbe\u7f6e\u6587\u4ef6":10,"\u9879\u76ee\u8bbe\u7f6e\u76ee\u5f55":10,"\u9879\u76ee\u9759\u6001\u8d44\u6e90\u76ee\u5f55":10,"\u9879\u76ee\u9aa8\u67b6":[6,8],"\u9884\u7f16\u8bd1\u7684\u6b63\u5219\u8868\u8fbe\u5f0f":11,"\u9996\u5b57\u6bcd\u7f29\u7565\u8bcd\u4fdd\u6301\u5927\u5199\u4e0d\u53d8":11,"api\u4e0e\u7528\u6237\u7684\u901a\u4fe1\u534f\u8bae":3,"api\u548cviews\u53ea\u662f\u8bf7\u6c42\u548c\u8fd4\u56de\u4e0d\u540c":10,"api\u6587\u4ef6":10,"api\u6587\u6863":3,"api\u6700\u597d\u505a\u5230hypermedia":3,"api\u7684\u57fa\u672c\u60c5\u51b5":3,"api\u7684\u8bbe\u8ba1\u88ab\u79f0\u4e3ahateoa":3,"api\u7684\u8eab\u4efd\u8ba4\u8bc1\u5e94\u8be5\u4f7f\u7528oauth":3,"api\u89c4\u8303\u7ea6\u5b9a":[6,8],"app\u4e0b\u6a21\u677f\u76ee\u5f55":10,"app\u4e0b\u9759\u6001\u8d44\u6e90\u76ee\u5f55":10,"app\u76ee\u5f55":10,"app\u8bbe\u7f6e\u6587\u4ef6":10,"case":11,"centos7\u4e0b\u5b89\u88c5\u7684\u662fmariadb":13,"class":[11,13],"clone\u65f6\u8f83\u5927":13,"cls\u4e3a\u7b2c\u4e00\u4e2a\u53c2\u6570":11,"cmdb\u4e2d\u7684assets\u5217\u8868":3,"collection\u5173\u7cfb":3,"com\u7684\u6839\u76ee\u5f55\u53d1\u51fa\u8bf7\u6c42":3,"default":13,"delete\u65b9\u6cd5\u8bf7\u4f7f\u7528\u8bf7\u6c42\u4f53\u4f20\u9012\u53c2\u6570":3,"developmentconfig\u4e2d\u7684\u914d\u7f6e":13,"django\u5f00\u53d1":8,"django\u8d44\u6df1\u5f00\u53d1\u8005":5,"docker\u5b98\u65b9\u5b89\u88c5\u6587\u6863":12,"docker\u5b98\u65b9\u5b89\u88c5\u6587\u6863http":[],"elasticsearch\u5b58\u50a8\u7b49\u529f\u80fd":8,"fix\u4e86\u5f88\u591abug":5,"get\u65b9\u6cd5\u8bf7\u4f7f\u7528url":3,"github\u7684api\u5c31\u662f\u8fd9\u79cd\u8bbe\u8ba1":3,"go\u4ee5\u53capaas\u5e73\u53f0\u5f00\u53d1":5,"href\u8868\u793aapi\u7684\u8def\u5f84":3,"html\u4ee3\u7801\u4e0d\u53d7\u6b64\u89c4\u8303\u7ea6\u675f":11,"i\u53c2\u6570":[13,14],"idc\u5217\u8868":3,"jperm\u5f00\u53d1\u8005":[],"js\u91c7\u75282\u7a7a\u683c\u7f29\u8fdb":11,"jumpserver\u4f7f\u7528redis\u505acache\u548cceleri":13,"jumpserver\u5e76\u6388\u6743":13,"jumpserver\u6d4b\u8bd5\u8fd0\u8425":5,"jumpserver\u7ba1\u7406\u540e\u53f0":13,"luna\u524d\u7aef\u4ee3\u7801\u8d21\u732e\u8005\u548c\u73b0\u5728\u7ef4\u62a4\u8005":[],"luna\u5df2\u6539\u4e3a\u7eaf\u524d\u7aef":13,"method\u52a0\u4e0aurl\u63d0\u4f9b\u7684\u8bed\u4e49":3,"migrations\u7248\u672c\u63a7\u5236\u76ee\u5f55":10,"property\u51fd\u6570\u4e2d\u4f7f\u7528\u533f\u540d\u51fd\u6570":11,"python\u4e00\u822c\u9650\u5236\u6700\u592779\u4e2a\u5b57\u7b26":11,"python\u4e25\u683c\u91c7\u75284\u4e2a\u7a7a\u683c\u7684\u7f29\u8fdb":11,"python\u65b9\u9762\u5927\u81f4\u7684\u98ce\u683c":11,"qq\u7fa4":8,"rel\u8868\u793a\u8fd9\u4e2aapi\u4e0e\u5f53\u524d\u7f51\u5740\u7684\u5173\u7cfb":3,"release\u5305":13,"return":11,"self\u4e3a\u7b2c\u4e00\u4e2a\u53c2\u6570":11,"selinux\u548c\u9632\u706b\u5899":13,"ssh\u8bbf\u95ee":12,"static":[10,13],"terminal\u4f1a\u62a5\u9519":13,"terminal\u5927\u90e8\u5206\u4ee3\u7801":5,"terminal\u767b\u5f55\u8bed\u6cd5\u5982\u4e0b":13,"terminal\u89e3\u51b3\u65b9\u6848":8,"title\u8868\u793aapi\u7684\u6807\u9898":3,"true":13,"type\u8868\u793a\u8fd4\u56de\u7c7b\u578b":3,"url\u4e2d\u7684\u53ef\u53d8\u90e8\u5206":3,"url\u7b49\u901a\u5e38\u6bd4\u8f83\u957f":11,"urlconf\u6587\u4ef6":10,"v2\u534f\u8bae":8,"view\u7f16\u7a0b":11,"views\u6587\u4ef6":10,"web\u90e8\u5206\u4ee3\u7801":11,"zip\u5305":13,For:13,NOT:3,Not:3,__init__:[10,11],accept:3,activ:13,add_head:13,admin:[10,13],alia:13,all:13,api:10,app:[3,10],app_id:3,applic:3,asc:3,asset:[3,10],asset_id:3,asset_list:10,author:11,automak:13,bar:11,base:11,bash:13,beta2:12,bin:13,brief:11,broker:13,camelcas:11,cat:13,cento:13,centos6:13,centos7:13,charset:13,checkout:13,client_id:3,clone:13,close:11,coco:[11,14],code:11,collect:3,column:11,com:[3,4,12,13],come:11,common:10,conf:13,conf_exampl:13,config:[10,13],config_exampl:13,configur:13,connect:13,content:[3,13],control:13,copyright:11,creat:[3,13],crud:3,css:11,data:13,databas:13,db_engin:[12,13],db_host:[12,13],db_name:[12,13],db_password:[12,13],db_port:[12,13],db_user:[12,13],debug:13,def:11,delet:3,demo:8,depth:13,desc:11,descript:11,detail:11,dev:13,devel:13,developmentconfig:13,devop:5,display_nam:11,django:11,doc:10,docker:[12,13],dockerfil:12,draft:[6,8],encod:13,endpoint:3,entiti:3,epel:13,error:3,even:11,exampl:[3,10],exp:11,fake:10,fals:11,faq:8,filter:11,firewalld:13,first:11,fit2cloud:4,fixtur:10,flask:11,foo:11,forbidden:3,form:11,forward:13,found:3,ftp:13,function_cal:11,gcc:13,get:3,get_annoying_nam:11,git:[4,13,14],github:[3,8,12,13],goe:11,gone:3,goodby:11,grant:13,guacamol:13,guidanc:11,gzip:13,halcyon:5,hash:11,hello:11,here:11,host:13,href:3,html:[10,11,13],http:[4,12,13],http_upgrad:13,httpwriter:11,i18n:10,ibul:4,idc:3,idempot:3,identifi:13,index:[11,13],inform:11,init:10,inlin:11,inst:[],instal:[10,13,14],intern:3,invalid:3,iptabl:13,item:11,item_count:11,item_valu:11,its:11,javascript:11,jiaxiangkong:5,json:[3,10],jumpserv:[4,6,10,12,13,14],jumpserver_serv:13,kei:[3,11],kelianchun:5,keyword:[3,11],lambda:11,latex:11,ldap:8,licens:[10,11],license_fil:11,license_nam:11,like:11,limit:[3,11],line:11,link:3,list:3,listen:13,liuz:5,localhost:[3,12,13],locat:13,log:10,longer:11,lowercase_with_underscor:11,luna:[11,14],make:13,make_migr:[13,14],manag:10,mani:11,mariadb:13,media:13,migrat:10,mixin:11,model:10,modul:11,more:[11,13],much:11,my_dict:11,my_list:11,myclass:11,mymodel:11,mysql:[12,13],mysql_host:12,mysqld:13,name:[3,11],name_r:11,node:11,object:11,offset:[3,11],one:12,openssl:13,opt:13,order:3,order_bi:11,org:[3,4,13],own:11,p2222:13,packag:11,page:3,paramet:[3,11],paramiko:11,password:11,patch:3,per_pag:3,pip:[13,14],png:10,post:3,print:11,properti:11,proxy_add_x_forwarded_for:13,proxy_http_vers:13,proxy_pass:13,proxy_set_head:13,pull:14,put:3,pw_hash:11,pwd:13,py3:13,python3:13,python:[11,13],queri:11,quit:13,quot:11,raw:11,readm:10,real:13,real_nam:11,redi:13,redis_host:12,redis_password:12,redis_port:12,rel:3,releas:13,remote_addr:13,request:3,requir:[13,14],resourc:3,rest:[6,8],root:[12,13],rpm_requir:13,run:[12,13],run_serv:13,salt:11,scalar:11,see:[11,13],select:3,self:11,server:3,servic:13,set:[10,11],setenforc:13,sha1:11,simpl:11,singleton:11,sofia:5,some_imag:10,somepassword:13,sort:3,sourc:13,span:11,sqlite:13,ssh:12,start:13,stop:13,string:11,style:11,systemctl:13,tag:10,tar:13,templat:10,templatetag:10,termin:[],test:10,that_returns_an_object_with_an_attribut:11,thi:11,this_is_a_very_long:11,three:11,tip:12,titl:3,trail:11,tripl:11,try_fil:13,txt:[13,14],type:3,unauthor:3,underscor:11,unicod:11,unproces:3,updat:3,upgrad:13,upper:11,uppercase_with_underscor:11,uri:13,url:10,user:11,usernam:11,utf8:13,utf:11,util:[10,13,14],valu:11,venv:13,version:[3,13],view:10,vnd:3,wget:13,window:8,wsgi:10,www:[3,4,13],xshell:13,xvf:13,xxx:12,xxxx:12,xxxxx:3,xxxxxx:3,year:11,you:11,yourformat:3,yum:13,yumaojun03:5,zlib:13,zoo:3},titles:["\u8d44\u4ea7\u7ba1\u7406\u6a21\u5757","\u7ba1\u7406\u6587\u6863","\u7528\u6237\u6a21\u5757","REST API\u89c4\u8303\u7ea6\u5b9a","\u8054\u7cfb\u65b9\u5f0f","\u8d21\u732e\u8005","\u5f00\u53d1\u6587\u6863","FAQ","Jumpserver \u6587\u6863","\u5b89\u88c5\u6587\u6863","\u9879\u76ee\u9aa8\u67b6","Jumpserver \u9879\u76ee\u89c4\u8303\uff08Draft\uff09","\u5feb\u901f\u5b89\u88c5","\u4e00\u6b65\u4e00\u6b65\u5b89\u88c5","\u5347\u7ea7","\u7528\u6237\u4f7f\u7528\u6587\u6863"],titleterms:{"\u4e00":13,"\u4e00\u6b65\u4e00\u6b65\u5b89\u88c5":13,"\u4e00\u822c\u7a7a\u683c\u89c4\u5219":11,"\u4e09":13,"\u4e8c":13,"\u4e94":13,"\u4ed3\u5e93\u5730\u5740":12,"\u4ee3\u7801\u98ce\u683c":11,"\u516d":13,"\u5176\u5b83":3,"\u51c6\u5907python3\u548cpython\u865a\u62df\u73af\u5883":13,"\u51fd\u6570\u548c\u65b9\u6cd5\u7684\u53c2\u6570":11,"\u524d\u7aef":13,"\u5347\u7ea7":14,"\u534f\u8bae":3,"\u5373\u5404\u65b9\u6cd5":11,"\u5426\u5b9a\u6210\u5458\u5173\u7cfb\u68c0\u67e5":11,"\u547d\u540d\u7ea6\u5b9a":11,"\u56db":13,"\u57df\u540d":3,"\u57fa\u672c\u7684\u4ee3\u7801\u5e03\u5c40":11,"\u5b89\u88c5":13,"\u5b89\u88c5\u6587\u6863":9,"\u5b89\u88c5jumpserv":13,"\u5b89\u88c5windows\u652f\u6301\u7ec4\u4ef6":13,"\u5b98\u7f51":4,"\u5f00\u53d1\u6307\u5357":[],"\u5f00\u53d1\u6587\u6863":6,"\u5feb\u901f\u542f\u52a8":12,"\u5feb\u901f\u5b89\u88c5":12,"\u6574\u5408\u5404\u7ec4\u4ef6":13,"\u6587\u6863":8,"\u6587\u6863\u6ce8\u91ca":11,"\u6700\u5927\u884c\u957f\u5ea6":11,"\u6709\u5173jumpserv":8,"\u6a21\u5757\u5934\u90e8":11,"\u6bd4\u8f83":11,"\u6ce8\u91ca":11,"\u7248\u672c":3,"\u72b6\u6001\u7801":3,"\u73af\u5883":13,"\u7528\u6237\u4f7f\u7528\u6587\u6863":15,"\u7528\u6237\u6587\u6863":[],"\u7528\u6237\u6a21\u5757":2,"\u7a7a\u884c":11,"\u7ba1\u7406\u6587\u6863":1,"\u7c7b\u7684\u8bf4\u660e\u6587\u6863\u6ce8\u91ca":11,"\u7d22\u5f15":8,"\u7f29\u8fdb":11,"\u8054\u7cfb\u65b9\u5f0f":4,"\u8bbf\u95ee":12,"\u8bed\u53e5\u548c\u8868\u8fbe\u5f0f":11,"\u8bed\u8a00\u6846\u67b6":11,"\u8d21\u732e\u8005":5,"\u8d44\u4ea7\u7ba1\u7406\u6a21\u5757":0,"\u8def\u5f84":3,"\u8fc7\u6ee4\u4fe1\u606f":3,"\u8fd4\u56de\u7ed3\u679c":3,"\u90ae\u4ef6":4,"\u914d\u7f6e":13,"\u9519\u8bef\u5904\u7406":3,"\u957f\u8bed\u53e5\u7f29\u8fdb":11,"\u9879\u76ee\u89c4\u8303":11,"\u9879\u76ee\u9aa8\u67b6":10,"\u989d\u5916\u73af\u5883\u53d8\u91cf":12,"api\u89c4\u8303\u7ea6\u5b9a":3,"django\u89c4\u8303":11,"qq\u7fa4":4,"server\u548cweb":13,api:3,coco:13,comment:11,demo:4,docstr:11,draft:11,faq:7,github:4,http:3,hypermedia:3,indic:[],jumpserv:[8,11],luna:13,method:3,nginx:13,rest:3,server:13,socket:13,ssh:13,tabl:[],termin:13,web:13}}) \ No newline at end of file diff --git a/docs/_build/html/step_by_step.html b/docs/_build/html/step_by_step.html index 42d9023b9..f1fc536d3 100644 --- a/docs/_build/html/step_by_step.html +++ b/docs/_build/html/step_by_step.html @@ -37,7 +37,7 @@ - + @@ -91,12 +91,22 @@
    n%nr@Ea}KU-3@g8l2;h(3 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 ALjV8( diff --git a/docs/_build/html/_static/fonts/fontawesome-webfont.woff b/docs/_build/html/_static/fonts/fontawesome-webfont.woff deleted file mode 100644 index 6e7483cf61b490c08ed644d6ef802c69472eb247..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90412 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%0Ded40&XED0GYFoIh+AV*?9!MR5pBW?X_8Bp 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 IH>08b4`|GD*#H0l diff --git a/docs/_build/html/_static/jquery-3.2.1.js b/docs/_build/html/_static/jquery-3.2.1.js deleted file mode 100644 index d2d8ca479..000000000 --- a/docs/_build/html/_static/jquery-3.2.1.js +++ /dev/null @@ -1,10253 +0,0 @@ -/*! - * jQuery JavaScript Library v3.2.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2017-03-20T18:59Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var document = window.document; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var concat = arr.concat; - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - - - - function DOMEval( code, doc ) { - doc = doc || document; - - var script = doc.createElement( "script" ); - - script.text = code; - doc.head.appendChild( script ).parentNode.removeChild( script ); - } -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.2.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && Array.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // As of jQuery 3.0, isNumeric is limited to - // strings and numbers (primitives or objects) - // that can be coerced to finite numbers (gh-2662) - var type = jQuery.type( obj ); - return ( type === "number" || type === "string" ) && - - // parseFloat NaNs numeric-cast false positives ("") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - !isNaN( obj - parseFloat( obj ) ); - }, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE <=9 - 11, Edge 12 - 13 - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.3 - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-08-08 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - disabledAncestor = addCombinator( - function( elem ) { - return elem.disabled === true && ("form" in elem || "label" in elem); - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement("fieldset"); - - try { - return !!fn( el ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - disabledAncestor( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9-11, Edge - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( preferredDoc !== document && - (subWindow = document.defaultView) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert(function( el ) { - el.className = "i"; - return !el.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( el ) { - el.appendChild( document.createComment("") ); - return !el.getElementsByTagName("*").length; - }); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); - - // ID filter and find - if ( support.getById ) { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( (elem = elems[i++]) ) { - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( el ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll("[msallowcapture^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - - assert(function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll(":enabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll(":disabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( el ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch (e) {} - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return (sel + "").replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - // Use previously-cached element index if available - if ( useCache ) { - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - // Don't keep the element (issue #299) - input[0] = null; - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( (oldCache = uniqueCache[ key ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context === document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - if ( !context && elem.ownerDocument !== document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( el ) { - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( el ) { - return el.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -return Sizzle; - -})( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -var risSimple = /^.[^:#\[\.,]*$/; - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Simple selector that can be filtered directly, removing non-Elements - if ( risSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - // Complex selector, compare the two sets, removing non-Elements - qualifier = jQuery.filter( qualifier, elements ); - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1; - } ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( nodeName( elem, "iframe" ) ) { - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( jQuery.isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && jQuery.isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( jQuery.isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - jQuery.isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - jQuery.isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - jQuery.isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ jQuery.camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ jQuery.camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( jQuery.camelCase ); - } else { - key = jQuery.camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - jQuery.contains( elem.ownerDocument, elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - -var swap = function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, - scale = 1, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - do { - - // If previous iteration zeroed out, double until we get *something*. - // Use string for doubling so we don't accidentally see scale as unchanged below - scale = scale || ".5"; - - // Adjust and apply - initialInUnit = initialInUnit / scale; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Update scale, tolerating zero or NaN from tween.cur() - // Break the loop if scale is unchanged or perfect, or if we've just had enough. - } while ( - scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations - ); - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); - -var rscriptType = ( /^$|\/(?:java|ecma)script/i ); - - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // Support: IE <=9 only - option: [ 1, "" ], - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting
    ", "
    " ], - col: [ 2, "", "
    " ], - tr: [ 2, "", "
    " ], - td: [ 3, "", "
    " ], - - _default: [ 0, "", "" ] -}; - -// Support: IE <=9 only -wrapMap.optgroup = wrapMap.option; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); -var documentElement = document.documentElement; - - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 only -// See #13393 for more info -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = {}; - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - // Make a writable jQuery.Event from the native event object - var event = jQuery.event.fix( nativeEvent ); - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: jQuery.isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - /* eslint-disable max-len */ - - // See https://github.com/eslint/eslint/issues/3229 - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, - - /* eslint-enable */ - - // Support: IE <=10 - 11, Edge 12 - 13 - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( ">tbody", elem )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - - if ( match ) { - elem.type = match[ 1 ]; - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); - events = pdataOld.events; - - if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( isFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rmargin = ( /^margin/ ); - -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - div.style.cssText = - "box-sizing:border-box;" + - "position:relative;display:block;" + - "margin:auto;border:1px;padding:1px;" + - "top:1%;width:50%"; - div.innerHTML = ""; - documentElement.appendChild( container ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = divStyle.marginLeft === "2px"; - boxSizingReliableVal = divStyle.width === "4px"; - - // Support: Android 4.0 - 4.3 only - // Some styles come back with percentage values, even though they shouldn't - div.style.marginRight = "50%"; - pixelMarginRightVal = divStyle.marginRight === "4px"; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + - "padding:0;margin-top:1px;position:absolute"; - container.appendChild( div ); - - jQuery.extend( support, { - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelMarginRight: function() { - computeStyleTests(); - return pixelMarginRightVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; - -// Return a css property mapped to a potentially vendor prefixed property -function vendorPropName( name ) { - - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a property mapped along what jQuery.cssProps suggests or to -// a vendor prefixed property. -function finalPropName( name ) { - var ret = jQuery.cssProps[ name ]; - if ( !ret ) { - ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; - } - return ret; -} - -function setPositiveNumber( elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { - var i, - val = 0; - - // If we already have the right measurement, avoid augmentation - if ( extra === ( isBorderBox ? "border" : "content" ) ) { - i = 4; - - // Otherwise initialize for horizontal or vertical properties - } else { - i = name === "width" ? 1 : 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin, so add it if we want it - if ( extra === "margin" ) { - val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); - } - - if ( isBorderBox ) { - - // border-box includes padding, so remove it if we want content - if ( extra === "content" ) { - val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // At this point, extra isn't border nor margin, so remove border - if ( extra !== "margin" ) { - val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } else { - - // At this point, extra isn't content, so add padding - val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // At this point, extra isn't content nor padding, so add border - if ( extra !== "padding" ) { - val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - return val; -} - -function getWidthOrHeight( elem, name, extra ) { - - // Start with computed style - var valueIsBorderBox, - styles = getStyles( elem ), - val = curCSS( elem, name, styles ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Computed unit is not pixels. Stop here and return. - if ( rnumnonpx.test( val ) ) { - return val; - } - - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && - ( support.boxSizingReliable() || val === elem.style[ name ] ); - - // Fall back to offsetWidth/Height when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - if ( val === "auto" ) { - val = elem[ "offset" + name[ 0 ].toUpperCase() + name.slice( 1 ) ]; - } - - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; - - // Use the active box-sizing model to add/subtract irrelevant styles - return ( val + - augmentWidthOrHeight( - elem, - name, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - "float": "cssFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = jQuery.camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = jQuery.camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( i, name ) { - jQuery.cssHooks[ name ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, name, extra ); - } ) : - getWidthOrHeight( elem, name, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = extra && getStyles( elem ), - subtract = extra && augmentWidthOrHeight( - elem, - name, - extra, - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - styles - ); - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ name ] = value; - value = jQuery.css( elem, name ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( !rmargin.test( prefix ) ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = jQuery.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 13 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = jQuery.camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( jQuery.isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - jQuery.proxy( result.stop, result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( jQuery.isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( jQuery.isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( jQuery.isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue && type !== false ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = jQuery.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( jQuery.isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; - - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( jQuery.isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; - - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value; - - if ( typeof stateVal === "boolean" && type === "string" ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( jQuery.isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( type === "string" ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = value.match( rnothtmlwhite ) || []; - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, isFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup contextmenu" ).split( " " ), - function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; -} ); - -jQuery.fn.extend( { - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -} ); - - - - -support.focusin = "onfocusin" in window; - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = jQuery.now(); - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && jQuery.type( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = jQuery.isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( jQuery.isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; - } - } - match = responseHeaders[ key.toLowerCase() ]; - } - return match == null ? null : match; - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 13 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available, append data to url - if ( s.data ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - - -jQuery._evalUrl = function( url ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - "throws": true - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( jQuery.isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain requests - if ( s.crossDomain ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    资产管理模块

    -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/admin_guide.html b/docs/_build/html/admin_guide.html deleted file mode 100644 index 4c3305822..000000000 --- a/docs/_build/html/admin_guide.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - - - 管理文档 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    管理文档

    -

    这里介绍管理员功能。

    - -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/admin_user.html b/docs/_build/html/admin_user.html deleted file mode 100644 index 1fd845f2b..000000000 --- a/docs/_build/html/admin_user.html +++ /dev/null @@ -1,294 +0,0 @@ - - - - - - - - - - - 用户管理 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    用户管理

    -

    这里介绍用户管理模块的功能。

    -

    点击页面左侧“用户列表”菜单下的“用户列表,进入用户列表页面。

    - -
    -

    创建用户

    -

    点击页面左上角“创建用户”按钮,进入创建用户页面,填写账户,角色安全,个人等信息,点击“提交”按钮,用户创建完成。

    -
    -
    -

    更新用户

    -

    点击页面右边的“更新”按钮,进入编辑用户页面,编辑用户信息,点击“提交”按钮,更新用户完成。

    -
    -
    -

    删除用户

    -

    点击页面右边的“删除”按钮,弹出是否删除确认框,点击“确定”按钮,删除用户完成。

    -
    -
    -

    导出用户

    -

    选中用户,点击右上角的“导出”按钮,导出用户完成。

    -
    -
    -

    导入用户

    -

    点击右上角的“导入”按钮,弹出导入对话框,选择要导入的CSV格式文件,点击“确认”按钮,导入用户完成。

    -
    -
    -

    批量操作

    -

    选中用户,选择页面左下角的批量操作选项,点击”提交“按钮,批量操作完成。

    -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/contact.html b/docs/_build/html/contact.html deleted file mode 100644 index 49a19626a..000000000 --- a/docs/_build/html/contact.html +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - - - - - - 联系方式 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    联系方式

    -
    -

    QQ群

    -

    群1: 390139816 -群2: 399218702 -群3: 552054376

    -
    - - - -
    -

    邮件

    -

    ibuler#fit2cloud.com (#替换为@)

    -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/contributor.html b/docs/_build/html/contributor.html deleted file mode 100644 index 8586c2204..000000000 --- a/docs/_build/html/contributor.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - - - - - - 贡献者 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    贡献者

    -

    感谢一下朋友为Jumpserver做出的贡献,世界因你们而不同,排名不分先后

    -
      -
    • 小彧 <李磊> Django资深开发者,为用户模块贡献了很多代码
    • -
    • sofia <周小侠> 资深前端工程师, 前端代码贡献者
    • -
    • liuz <刘正> 全栈工程师 编写了Web terminal大部分代码
    • -
    • jiaxiangkong <陈尚委> Jumpserver测试运营
    • -
    • halcyon <王墉> DevOps 资深开发者, 0.3.2 核心开发者之一
    • -
    • yumaojun03 <喻茂峻> DevOps 资深开发者,擅长Python, Go以及PAAS平台开发
    • -
    • kelianchun <柯连春> DevOps 资产开发者,fix了很多bug
    • -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/development.html b/docs/_build/html/development.html deleted file mode 100644 index a757f5b96..000000000 --- a/docs/_build/html/development.html +++ /dev/null @@ -1,252 +0,0 @@ - - - - - - - - - - - 开发文档 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - - - - -
    -
    - -
    -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/faq.html b/docs/_build/html/faq.html deleted file mode 100644 index e3806aa56..000000000 --- a/docs/_build/html/faq.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - - - - - - FAQ — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    FAQ

    -
    - - -
    -
    - -
    -
    -
    - - - - -
    - -
    -

    - © Copyright 北京堆栈科技有限公司 © 2014-2018. - -

    -
    - Built with Sphinx using a theme provided by Read the Docs. - -
    - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html deleted file mode 100644 index d58cf47d9..000000000 --- a/docs/_build/html/genindex.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - - - - - - 索引 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - -
      - -
    • Docs »
    • - -
    • 索引
    • - - -
    • - - - -
    • - -
    - - -
    -
    -
    -
    - - -

    索引

    - -
    - -
    - - -
    -
    - -
    -
    -
    - - -
    - -
    -

    - © Copyright 北京堆栈科技有限公司 © 2014-2018. - -

    -
    - Built with Sphinx using a theme provided by Read the Docs. - -
    - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html deleted file mode 100644 index bec13fe1a..000000000 --- a/docs/_build/html/index.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - - - - - - Jumpserver 文档 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    Jumpserver 文档

    -
    -

    有关Jumpserver

    -

    欢迎来到 Jumpserver 文档。

    -

    Jumpserver 是一款完全开源的跳板机(堡垒机)系统,遵循GPL v2协议,使用Python,Django开发。

    -

    Jumpserver 是符合4A(认证Authentication,账号Account,授权Authorization,审计Audit) 的专业运维审计系统。

    -

    Jumpserver 从设计时考虑分布式,没有性能瓶颈,满足混合云架构,一个中心,不同Region不同登录点。

    -

    Jumpserver 界面漂亮、简单易用,并有领先的Web terminal解决方案。

    -

    Jumpserver 深度集成了Ansible,支持自动化运维任务。

    -

    Jumpserver 支持容器化部署,windows,LDAP, s3, elasticsearch存储等功能,并提供了强大API方便对接其它系统。

    - -
    -
    - - - -
    -
    - -
    -
    -
    - - - - -
    - -
    -

    - © Copyright 北京堆栈科技有限公司 © 2014-2018. - -

    -
    - Built with Sphinx using a theme provided by Read the Docs. - -
    - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/installation.html b/docs/_build/html/installation.html deleted file mode 100644 index 360ef6eb6..000000000 --- a/docs/_build/html/installation.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - 安装文档 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    安装文档

    - -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv deleted file mode 100644 index f724386482f11f89be5db92ac8115abee113ea8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 898 zcmV-|1AY7>AX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkvb!~8S zWpZ|9atb3LR%LQ?X>V>iAPOTORA^-&a%F8{X>Md?av*PJAarPHb0B7EY-J#6b0A}H zZE$jBb8}^6Aa!$TZf78RY-wUH3V7O$Rm)D>Kos52S48UCmAdT?sjbwm`r2ELJxN>~ zj~S1F?1Bme1PIzvDoQ8>nu4erDivDd1m$P^h*$hVZO=INU>@1zaeU6XueoDMsj9jt znI7 zLm*k&y3<<$Rivn;niy_^iF8R5RlS66Xi5ar9H~G^1VSuu1|4s+>s*{Tw(SgB-rQ+y zoS&yr6Osos4SLnE!5SF7gE9#>9$PZ8Fn1N?9#Vs#)%ABe!O5}r?JqEDq=NL@E|dJz z+{CCGy;!dLOHZ&=B3J!oFlDLoWY}xW?MsTX<_~@b^*uTQU_uGY6~26mvC21^Vc9|J zJlH(|CV_@ntclTgHbNGsGUuZ1{c6)s7RfS{=teQZlRJa3a_9IcR~T`;BOre=xF{pX#T#6+rP=iM^OSmKArAlB%$hHoE%bYD3p zzr_W5Eb04=N^&P7MYFFb=W-DJpu04xKc^ctrNAw~vo_;qA5k Y<_cE>?|kpZ^0qNOYs%9758~~NU>``dj{pDw diff --git a/docs/_build/html/quickstart.html b/docs/_build/html/quickstart.html deleted file mode 100644 index 398cf2f3c..000000000 --- a/docs/_build/html/quickstart.html +++ /dev/null @@ -1,291 +0,0 @@ - - - - - - - - - - - 快速安装 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    快速安装

    -

    Jumpserver 封装了一个All in one Docker,可以快速启动。该镜像集成了所有需要的组件,可以使用外置db和redis

    -

    Tips: 不建议在生产中使用

    -

    Docker 安装见: Docker官方安装文档

    -
    -

    快速启动

    -

    使用root命令行输入:

    -
    $ docker run -p 8080:80 -p 2222:2222 jumpserver/jumpserver:0.5.0-beta2
    -
    -
    -
    -
    -

    访问

    -

    浏览器访问: http://localhost:8080

    -

    ssh访问: ssh -p 2222 localhost

    -
    -
    -

    额外环境变量

    -
      -
    • DB_ENGINE = mysql
    • -
    • DB_HOST = mysql_host
    • -
    • DB_PORT = 3306
    • -
    • DB_USER = xxx
    • -
    • DB_PASSWORD = xxxx
    • -
    • DB_NAME = jumpserver
    • -
    • REDIS_HOST = ‘’
    • -
    • REDIS_PORT = ‘’
    • -
    • REDIS_PASSWORD = ‘’
    • -
    -
    -
    docker run -p 8080:80 -p 2222:2222 -e DB_ENGINE=mysql -e DB_HOST=192.168.1.1 -e DB_PORT=3306 -e DB_USER=root -e DB_PASSWORD=xxx -e DB_NAME=jumpserver  jumpserver/jumpserver:0.5.0-beta2
    -
    -
    -
    -
    - -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html deleted file mode 100644 index 084fd30d2..000000000 --- a/docs/_build/html/search.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - - - - - - 搜索 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - -
      - -
    • Docs »
    • - -
    • 搜索
    • - - -
    • - -
    • - -
    - - -
    -
    -
    -
    - - - - -
    - -
    - -
    -
    - -
    -
    -
    - - -
    - -
    -

    - © Copyright 北京堆栈科技有限公司 © 2014-2018. - -

    -
    - Built with Sphinx using a theme provided by Read the Docs. - -
    - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js deleted file mode 100644 index c4ba7f5fe..000000000 --- a/docs/_build/html/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({docnames:["admin_asset","admin_guide","admin_user","api_style_guide","contact","contributor","development","faq","index","installation","intro","project_structure","python_style_guide","quickstart","step_by_step","upgrade","user_asset","user_guide","user_info"],envversion:53,filenames:["admin_asset.rst","admin_guide.rst","admin_user.rst","api_style_guide.rst","contact.rst","contributor.rst","development.rst","faq.rst","index.rst","installation.rst","intro.rst","project_structure.rst","python_style_guide.rst","quickstart.rst","step_by_step.rst","upgrade.rst","user_asset.rst","user_guide.rst","user_info.rst"],objects:{},objnames:{},objtypes:{},terms:{"0\u6846\u67b6":3,"2\u7a7a\u683c\u53ef\u4ee5\u663e\u8457\u964d\u4f4e\u89c6\u89c9\u4e0a\u7684\u8d1f\u62c5":12,"7\u81ea\u5e26\u7684\u662fpython2":14,"8\u7f16\u7801\u58f0\u660e":12,"\u4e00\u4e2a":[12,16],"\u4e00\u4e2a\u4e2d\u5fc3":8,"\u4e00\u6b65\u4e00\u6b65\u5b89\u88c5":[8,9],"\u4e00\u822c\u6027\u7684\u589e\u5220\u67e5\u6539":3,"\u4e00\u822c\u6765\u8bf4":3,"\u4e00\u822c\u7528\u6765\u4f20\u9012\u8be5api\u64cd\u4f5c\u7684\u6838\u5fc3\u5b9e\u4f53\u5bf9\u8c61\u7684\u552f\u4e00id":3,"\u4e00\u9879\u6216\u591a\u9879":3,"\u4e0a\u9762\u4ee3\u7801\u8868\u793a":3,"\u4e0b\u4e00\u884c\u5e94\u8be5\u4e0e\u4e0a\u4e00\u884c\u7684\u6700\u540e":12,"\u4e0b\u8f7d":14,"\u4e0b\u8f7d\u5bf9\u5e94release\u5305":14,"\u4e0b\u8f7d\u6216clone\u9879\u76ee":14,"\u4e0b\u8f7dclone\u9879\u76ee":14,"\u4e0d\u4f7f\u7528\u62fc\u97f3":12,"\u4e0d\u4f7f\u7528\u65e0\u610f\u4e49\u7b80\u5355\u5b57\u6bcd\u547d\u540d":12,"\u4e0d\u540cregion\u4e0d\u540c\u767b\u5f55\u70b9":8,"\u4e0d\u5efa\u8bae\u5728\u751f\u4ea7\u4e2d\u4f7f\u7528":13,"\u4e0d\u8981\u4f7f\u7528\u9ed8\u8ba4":12,"\u4e0d\u8981\u5728\u4ee3\u7801\u4e2d\u4f7f\u7528\u592a\u591a\u7684\u7a7a\u884c\u6765\u533a\u5206\u4e0d\u540c\u7684\u903b\u8f91\u6a21\u5757":12,"\u4e0d\u8981\u5728\u53d8\u91cf\u540d\u540e\u6dfb\u52a0\u4e0b\u5212\u7ebf\u8fdb\u884c\u533a\u5206":12,"\u4e0d\u8981\u6307\u5b9a":[14,15],"\u4e0d\u8981\u7528tab":14,"\u4e0d\u8981\u786c\u7f16\u7801":12,"\u4e0d\u8981\u8fd9\u6837\u5199":12,"\u4e0d\u9700\u8981\u7f16\u8bd1":14,"\u4e0e401\u9519\u8bef\u76f8\u5bf9":3,"\u4e0e\u5355\u4f8b":12,"\u4e14\u4e0d\u4f1a\u518d\u5f97\u5230\u7684":3,"\u4e16\u754c\u56e0\u4f60\u4eec\u800c\u4e0d\u540c":5,"\u4e2a\u4eba\u4fe1\u606f":[8,17],"\u4e2a\u4eba\u7b49\u4fe1\u606f":2,"\u4e2a\u4eba\u8d44\u4ea7":[8,17],"\u4e3a\u4e86\u4e0d\u6270\u4e71\u539f\u6765\u7684\u73af\u5883\u6211\u4eec\u6765\u4f7f\u7528python\u865a\u62df\u73af\u5883":14,"\u4e3a\u7528\u6237\u6a21\u5757\u8d21\u732e\u4e86\u5f88\u591a\u4ee3\u7801":5,"\u4e3a\u957f\u8bed\u53e5\u6362\u884c":12,"\u4e3e\u4f8b\u6765\u8bf4":3,"\u4e4b\u540e\u9a6c\u4e0a\u6362\u884c":12,"\u4e4b\u6240\u4ee5\u4e0epython\u4e0d\u540c":12,"\u4e5f\u662f\u5404app\u6240\u5728\u76ee\u5f55":11,"\u4e5f\u77e5\u9053\u4e0b\u4e00\u6b65\u5e94\u8be5\u505a\u4ec0\u4e48":3,"\u4e8c\u8005\u5747\u4ee5restructuredtext\u683c\u5f0f\u7f16\u5199":12,"\u4ea7\u770b\u7528\u6237\u7684\u4e2a\u4eba\u4fe1\u606f":[],"\u4ece\u670d\u52a1\u5668\u5220\u9664\u8d44\u6e90":3,"\u4ece\u670d\u52a1\u5668\u53d6\u51fa\u8d44\u6e90":3,"\u4ece\u8bbe\u8ba1\u65f6\u8003\u8651\u5206\u5e03\u5f0f":8,"\u4ee3\u7801\u4f18\u96c5\u7b80\u6d01":12,"\u4ee3\u8868\u5b57\u7b26\u4e32\u7ed3\u675f\u7684\u4e09\u4e2a\u5f15\u53f7\u4e0e\u4ee3\u8868\u5b57\u7b26\u4e32\u5f00\u59cb\u7684\u4e09\u4e2a\u5f15\u53f7\u5728\u540c\u4e00\u884c":12,"\u4ee3\u8868\u5b57\u7b26\u4e32\u7ed3\u675f\u7684\u4e09\u4e2a\u5f15\u53f7\u5219\u81ea\u5df1\u72ec\u7acb\u6210\u4e00\u884c":12,"\u4ee4\u724c":3,"\u4ee5\u4e0b\u6240\u6709\u547d\u4ee4\u5747\u5728\u8be5\u865a\u62df\u73af\u5883\u4e2d\u8fd0\u884c":14,"\u4ee5\u53ca\u6392\u5e8f\u987a\u5e8f":3,"\u4ee5\u53ca\u6807\u51c6\u7684\u6587\u6863\u5b57\u7b26\u4e32":12,"\u4ee5\u53ca\u6bcf\u9875\u7684\u8bb0\u5f55\u6570":3,"\u4ee5\u540e\u8fd0\u884cjumpserver\u90fd\u8981\u5148\u8fd0\u884c\u4ee5\u4e0asource\u547d\u4ee4":14,"\u4efb\u4f55python\u4ee3\u7801\u90fd\u90fd\u5fc5\u987b\u9075\u5b88\u6b64\u89c4\u5b9a":12,"\u4efb\u610f\u7c7b\u578b\u4e4b\u95f4\u7684\u6bd4\u8f83":12,"\u4f1a\u5f97\u5230\u8fd9\u6837\u4e00\u4e2a\u6587\u6863":3,"\u4f46\u662f\u53ea\u6709xml\u683c\u5f0f":3,"\u4f46\u662f\u6709\u4e9b\u7ec6\u8282\u90e8\u5206\u4f1a\u5c3d\u91cf\u653e\u5f00":12,"\u4f46\u662f\u8bbf\u95ee\u662f\u88ab\u7981\u6b62\u7684":3,"\u4f46\u662fdjango\u7684\u547d\u540d":12,"\u4f5c\u4e3a\u7c7b\u540d\u79f0":12,"\u4f60\u53ef\u4ee5\u9009\u62e9\u53bbgithub\u9879\u76ee\u9875\u9762\u76f4\u63a5\u4e0b\u8f7d":14,"\u4f7f\u5f97\u7528\u6237\u4e0d\u67e5\u6587\u6863":3,"\u4f7f\u7528":12,"\u4f7f\u7528django":12,"\u4f7f\u7528docker\u542f\u52a8":14,"\u4f7f\u7528foo":12,"\u4f7f\u7528is\u548ci":12,"\u4f7f\u7528python":8,"\u4f7f\u7528root\u547d\u4ee4\u884c\u8f93\u5165":13,"\u4f8b\u5982":[3,12],"\u4fee\u6539jumpserver\u914d\u7f6e\u6587\u4ef6":14,"\u4fee\u6539jumpserver_server\u7684\u914d\u7f6e":14,"\u5141\u8bb8\u4e0e\u5185\u5efa\u53d8\u91cf\u91cd\u540d":12,"\u5168\u6808\u5de5\u7a0b\u5e08":5,"\u5173\u95ed":14,"\u51fa\u73b0\u4e24\u4e2a\u9009\u9879":16,"\u51fa\u9519\u4fe1\u606f\u4f5c\u4e3a\u952e\u503c\u5373\u53ef":3,"\u5218\u6b63":5,"\u521b\u5efa\u6570\u636e\u5e93":14,"\u521b\u5efa\u7528\u6237":[],"\u521d\u59cb\u5316\u6570\u636e\u76ee\u5f55":11,"\u521d\u59cb\u5316\u9879\u76ee\u6570\u636e\u5e93":11,"\u5220\u9664":2,"\u5220\u9664\u7528\u6237\u5b8c\u6210":2,"\u522b\u5fd8\u4e86":14,"\u524d\u7aef\u4ee3\u7801\u8d21\u732e\u8005":5,"\u533f\u540d\u51fd\u6570\u7684\u7b2c\u4e00\u4e2a\u53c2\u6570\u53ef\u4ee5\u7528x\u66ff\u4ee3":12,"\u5347\u7ea7":[8,9],"\u5355\u76ee\u8fd0\u7b97\u7b26\u4e0e\u8fd0\u7b97\u5bf9\u8c61\u4e4b\u95f4\u4e0d\u7a7a\u683c":12,"\u5373\u4f7f\u5355\u76ee\u8fd0\u7b97\u7b26\u4f4d\u4e8e\u62ec\u53f7\u5185\u90e8\u4e5f\u4e00\u6837":12,"\u5373\u53ef\u8fdb\u5165\u4e3b\u673a\u767b\u5f55\u9875":[],"\u5373\u8fd4\u56de\u7ed3\u679c\u4e2d\u63d0\u4f9b\u94fe\u63a5":3,"\u53c2\u8003":3,"\u53c2\u8003\u56fd\u5185\u7ffb\u8bd1":12,"\u53cc\u4e0b\u5212\u7ebf\u524d\u7f00\u53ea\u6709\u5b9a\u4e49\u6df7\u5165\u7c7b":12,"\u53cc\u76ee\u8fd0\u7b97\u7b26\u4e0e\u8fd0\u7b97\u5bf9\u8c61\u4e4b\u95f4\u8981\u7a7a\u683c":12,"\u53d1\u751f\u4e00\u4e2a\u9a8c\u8bc1\u9519\u8bef":3,"\u53d7\u4fdd\u62a4\u7684\u5143\u7d20\u4ee5\u4e00\u4e2a\u4e0b\u5212\u7ebf\u4e3a\u524d\u7f00":12,"\u53d8\u91cf\u540d":12,"\u53e5\u70b9\u6216":12,"\u53e6\u4e00\u4e2a":16,"\u53ea\u80fd\u6709\u540d\u8bcd":3,"\u53ef\u4ee5\u4ea7\u770b\u8d44\u4ea7\u7684\u8be6\u7ec6\u4fe1\u606f":[],"\u53ef\u4ee5\u4f7f\u7528\u5916\u7f6edb\u548credi":13,"\u53ef\u4ee5\u4f7f\u7528\u6362\u884c\u7b26":12,"\u53ef\u4ee5\u5feb\u901f\u542f\u52a8":13,"\u53ef\u4ee5\u8bbe\u7f6e\u914d\u7f6e\u6587\u4ef6\u6c38\u4e45\u5173\u95ed":14,"\u540c\u6837\u4e0d\u4f7f\u7528tab":12,"\u540c\u7406static\u4e5f\u662f":12,"\u5426\u5219\u8bf7\u4e2d\u6587\u4f18\u96c5\u6ce8\u91ca":12,"\u5468\u5c0f\u4fa0":5,"\u547d\u540d\u7f29\u5199\u8981\u8c28\u614e":12,"\u547d\u540d\u8981\u6709\u5bd3\u610f":12,"\u548c":12,"\u548c\u8be6\u7ec6\u4ecb\u7ecd":12,"\u55bb\u8302\u5cfb":5,"\u56e0\u4e3a\u955c\u50cf\u4e0a\u53ef\u80fd\u6ca1\u6709\u6700\u65b0\u7684\u5305":14,"\u56e0\u4e3a\u9ed8\u8ba4jumpserver\u662f\u4f7f\u7528\u8be5\u914d\u7f6e":14,"\u56e0\u4e3acento":14,"\u5728\u4e2a\u4eba\u4fe1\u606f\u9875":18,"\u5728\u4e3b\u673a\u767b\u5f55\u9875\u9762":16,"\u5728\u670d\u52a1\u5668\u65b0\u5efa\u4e00\u4e2a\u8d44\u6e90":3,"\u5728\u670d\u52a1\u5668\u66f4\u65b0\u8d44\u6e90":3,"\u5728\u7b2c\u4e00\u4e2a":12,"\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b":12,"\u5728restful\u67b6\u6784\u4e2d":3,"\u5821\u5792\u673a":8,"\u586b\u4e0ajumpserver\u7684\u5185\u7f51\u5730\u5740":14,"\u586b\u5199\u4e2a\u4eba\u4fe1\u606f":18,"\u586b\u5199\u539f\u6765\u5bc6\u7801":18,"\u586b\u5199\u8d26\u6237":2,"\u586b\u5199ssh\u516c\u94a5":18,"\u591a\u4e00\u5c42\u76ee\u5f55":11,"\u5927\u5199_\u4ee5\u53ca_\u4e0b\u5212\u7ebf":12,"\u5982\u679c\u4e0d\u4f7f\u7528mysql\u53ef\u4ee5\u8df3\u8fc7\u76f8\u5173mysql\u5b89\u88c5\u548c\u914d\u7f6e":14,"\u5982\u679c\u4e3a\u591a\u884c":12,"\u5982\u679c\u4f60\u4f7f\u7528\u62ec\u53f7":12,"\u5982\u679c\u4f7f\u7528\u5173\u952e\u8bcd":12,"\u5982\u679c\u4f7f\u7528\u6ce8\u91ca\u6765\u7f16\u5199\u7c7b\u5c5e\u6027\u7684\u6587\u6863":12,"\u5982\u679c\u51fd\u6570\u9700\u8981\u8bbf\u95ee\u91cd\u540d\u7684\u5185\u5efa\u53d8\u91cf":12,"\u5982\u679c\u53ea\u6709\u4e00\u884c":12,"\u5982\u679c\u5fc5\u8981\u7684\u8bdd":12,"\u5982\u679c\u662f\u7528\u5728windows\u4e0b":14,"\u5982\u679c\u6709\u66f4\u591a\u7684\u53c2\u6570\u9700\u8981\u63d0\u4f9b":3,"\u5982\u679c\u6a21\u5757\u4e2d\u4f7f\u7528\u4e86\u975eascii\u7f16\u7801\u7684\u5b57\u7b26":12,"\u5982\u679c\u6ca1\u6709\u4efb\u4f55\u62a5\u9519\u8bf7\u7ee7\u7eed":14,"\u5982\u679c\u72b6\u6001\u7801\u662f4xx":3,"\u5982\u679c\u80fd\u767b\u9646\u4ee3\u8868\u90e8\u7f72\u6210\u529f":14,"\u5982\u679c\u9700\u8981":11,"\u5b83\u7ee7\u627f\u81eaconfig":14,"\u5b89\u88c5\u4f9d\u8d56":14,"\u5b89\u88c5\u4f9d\u8d56\u5305":14,"\u5b89\u88c5\u4f9d\u8d56rpm\u5305":14,"\u5b89\u88c5\u6587\u6863":8,"\u5b89\u88c5\u89c1":13,"\u5b89\u88c5\u8bf4\u660e":11,"\u5b89\u88c5mysql":14,"\u5b89\u88c5nginx":14,"\u5b89\u88c5python\u5e93\u4f9d\u8d56":14,"\u5b89\u88c5redi":14,"\u5b8c\u5168\u4f7f\u7528http":3,"\u5b8c\u6210\u4e2a\u4eba\u4fe1\u606f\u4fee\u6539":18,"\u5b8c\u6210\u5bc6\u7801\u66f4\u65b0":18,"\u5b8c\u6210\u5bc6\u94a5\u66f4\u65b0":18,"\u5b8c\u6210\u7528\u6237\u521b\u5efa":[],"\u5b98\u7f51":8,"\u5b9e\u4f8b\u65b9\u6cd5":12,"\u5ba1\u8ba1audit":8,"\u5ba2\u6237\u7aef\u63d0\u4f9b\u6539\u53d8\u540e\u7684\u5b8c\u6574\u8d44\u6e90":3,"\u5ba2\u6237\u7aef\u63d0\u4f9b\u6539\u53d8\u7684\u5c5e\u6027":3,"\u5bc6\u7801":14,"\u5bc6\u7801\u9519\u8bef":3,"\u5bf9\u4e8e\u5143\u7d20\u4f17\u591a\u7684\u5217\u8868\u6216\u5143\u7ec4":12,"\u5bf9\u4e8e\u8d44\u6e90\u7684\u5177\u4f53\u64cd\u4f5c\u7c7b\u578b":3,"\u5bf9\u5916\u66b4\u9732\u7684\u63a5\u53e3":11,"\u5bf9\u9f50":12,"\u5bfc\u5165":2,"\u5bfc\u5165\u7528\u6237\u5b8c\u6210":2,"\u5bfc\u51fa":2,"\u5bfc\u51fa\u7528\u6237\u5b8c\u6210":2,"\u5c01\u88c5\u4e86\u4e00\u4e2aall":13,"\u5c06\u7248\u672c\u53f7\u653e\u5230app\u540e\u9762":3,"\u5c06api\u7684\u7248\u672c\u53f7\u653e\u5165url\u4e2d":3,"\u5c06views\u548capi\u53ef\u590d\u7528\u7684\u4ee3\u7801\u653e\u5728\u8fd9\u91cc":11,"\u5c0f\u5199_\u4ee5\u53ca_\u4e0b\u5212\u7ebf":12,"\u5c0f\u5f67":5,"\u5c31\u5e94\u8be5\u5411\u7528\u6237\u8fd4\u56de\u51fa\u9519\u4fe1\u606f":3,"\u5c3d\u53ef\u80fd\u5229\u7528django\u9020\u597d\u7684\u8f6e\u5b50":12,"\u5c3d\u91cf\u4e00\u884c":12,"\u5c3d\u91cf\u4f7f\u7528class":12,"\u5c3d\u91cf\u662f\u5927\u5bb6\u8ba4\u53ef\u7684\u7f29\u5199":12,"\u5e03\u5c40\u4e5f\u4e0d\u4e00\u6837":12,"\u5e38\u7528\u7684http\u52a8\u8bcd\u6709\u4e0b\u9762\u4e94\u4e2a":3,"\u5e38\u89c1\u53c2\u6570\u7ea6\u5b9a":3,"\u5e38\u89c1\u7684\u6709\u4ee5\u4e0b\u4e00\u4e9b":3,"\u5e38\u91cf":12,"\u5e42\u7b49":3,"\u5e76\u63d0\u4f9b\u4e86\u5f3a\u5927api\u65b9\u4fbf\u5bf9\u63a5\u5176\u5b83\u7cfb\u7edf":8,"\u5e76\u6709\u9886\u5148\u7684web":8,"\u5e76\u7ed9\u51fa\u8be5collection\u7684\u7f51\u5740":3,"\u5e94\u5728\u540d\u79f0\u540e\u6dfb\u52a0\u540e\u7f6e\u4e0b\u5212\u7ebf":12,"\u5e94\u8be5\u5c3d\u91cf\u4f7f\u7528json":3,"\u5efa\u7acbpython\u865a\u62df\u73af\u5883":14,"\u5efa\u8bae\u8fdb\u884c\u58f0\u660e":12,"\u5f00\u53d1\u6587\u6863":8,"\u5f02\u6b65\u4efb\u52a1":3,"\u5f39\u51fa\u5bfc\u5165\u5bf9\u8bdd\u6846":2,"\u5f39\u51fa\u662f\u5426\u5220\u9664\u786e\u8ba4\u6846":2,"\u5f53\u521b\u5efa\u4e00\u4e2a\u5bf9\u8c61\u65f6":3,"\u5f53\u524d\u6700\u65b0":12,"\u5f53\u7528\u6237\u5411api":3,"\u5faa\u73af\u4e2d\u8ba1\u6570\u4f8b\u5916":12,"\u5feb\u901f\u5b89\u88c5":[8,9],"\u603b\u662f\u4f7f\u7528https\u534f\u8bae":3,"\u611f\u8c22\u4e00\u4e0b\u670b\u53cb\u4e3ajumpserver\u505a\u51fa\u7684\u8d21\u732e":5,"\u6211\u4eec\u8ba1\u5212\u4fee\u6539":14,"\u6211\u4eec\u91c7\u7528pocoo\u7684":12,"\u6211\u7684\u7f51\u901f\u597d":14,"\u6211\u76f4\u63a5clone\u4e86":14,"\u6216":12,"\u6216\u8005\u662f\u7f29\u8fdb4\u4e2a\u7a7a\u683c\u7b26":12,"\u6216\u82b1\u62ec\u53f7":12,"\u6240\u4ee5\u6211\u4eec\u9650\u5236\u6700\u5927120\u5b57\u7b26":12,"\u6240\u4ee5\u653e\u5728\u4e3b\u57df\u540d\u4e0b":3,"\u6240\u4ee5\u7f51\u5740\u4e2d\u4e0d\u80fd\u6709\u52a8\u8bcd":3,"\u6240\u4ee5\u8bbf\u95eeweb":14,"\u6240\u4ee5api\u4e2d\u7684\u540d\u8bcd\u4e5f\u5e94\u8be5\u4f7f\u7528\u590d\u6570":3,"\u6240\u6709\u6587\u6863\u5b57\u7b26\u4e32\u5747\u4ee5restructuredtext\u683c\u5f0f\u7f16\u5199":12,"\u6240\u6709doc\u6587\u4ef6\u653e\u5230\u8be5\u76ee\u5f55":11,"\u6279\u91cf\u64cd\u4f5c\u5b8c\u6210":2,"\u62ec\u53f7\u91cc\u662f\u5bf9\u5e94\u7684sql\u547d\u4ee4":3,"\u6307\u5b9a\u7b2c\u51e0\u9875":3,"\u6307\u5b9a\u7b5b\u9009\u6761\u4ef6":3,"\u6307\u5b9a\u8fd4\u56de\u7ed3\u679c\u6309\u7167\u54ea\u4e2a\u5c5e\u6027\u6392\u5e8f":3,"\u6307\u5b9a\u8fd4\u56de\u8bb0\u5f55\u7684\u5f00\u59cb\u4f4d\u7f6e":3,"\u6307\u5b9a\u8fd4\u56de\u8bb0\u5f55\u7684\u6570\u91cf":3,"\u6309\u94ae":[2,16,18],"\u6309pep8\u89c4\u8303":12,"\u6362\u884c":12,"\u6388\u6743author":8,"\u6392\u540d\u4e0d\u5206\u5148\u540e":5,"\u63a5\u53d7coco\u7684\u6ce8\u518c":14,"\u63d0\u4ea4":[2,18],"\u63d0\u4ea4\u6309\u94ae":[],"\u641c\u7d22\u9875\u9762":8,"\u6458\u8981\u4e0e\u8be6\u7ec6\u4ecb\u7ecd\u4e4b\u95f4\u7a7a\u4e00\u884c":12,"\u64c5\u957fpython":5,"\u652f\u6301\u5bb9\u5668\u5316\u90e8\u7f72":8,"\u652f\u6301\u81ea\u52a8\u5316\u8fd0\u7ef4\u4efb\u52a1":8,"\u653e\u5230\u8be5\u6587\u4ef6\u4e2d":11,"\u6570\u636e\u5e93\u4e2d\u7684\u8868\u90fd\u662f\u540c\u79cd\u8bb0\u5f55\u7684":3,"\u6570\u636e\u5e93\u8868\u540d\u624b\u52a8\u6307\u5b9a":12,"\u6570\u636e\u6a21\u578b\u76ee\u5f55":11,"\u6570\u7ec4":3,"\u6587\u6863\u4e2d\u6709\u4e00\u4e2alink\u5c5e\u6027":3,"\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u7684\u6587\u672c\u7d27\u63a5\u7740\u4ee3\u8868\u5b57\u7b26\u4e32\u5f00\u59cb\u7684\u4e09\u4e2a\u5f15\u53f7\u7f16\u5199":12,"\u6587\u6863\u5b57\u7b26\u4e32\u5e94\u5206\u6210\u7b80\u77ed\u6458\u8981":12,"\u6587\u6863\u5b57\u7b26\u4e32\u7684\u884c\u6570\u4e0d\u540c":12,"\u65ad\u5f00\u5f53\u524d\u6240\u6709\u8fde\u63a5\u7684\u4e3b\u673a":16,"\u65ad\u5f00\u5f53\u524d\u8fde\u63a5\u7684\u4e3b\u673a":16,"\u65ad\u5f00\u6240\u6709\u94fe\u63a5":16,"\u65ad\u5f00\u94fe\u63a5":16,"\u65b0\u5bc6\u7801\u7b49\u4fe1\u606f":18,"\u65b0\u5f00\u4e00\u4e2a\u7ec8\u7aef":14,"\u65b0\u7248\u672cdjango":11,"\u65b9\u4fbf\u522b\u7684app\u5f15\u7528":11,"\u65b9\u4fbfsphinx\u5904\u7406":12,"\u65b9\u62ec\u53f7\u4e2d\u662f\u8be5\u72b6\u6001\u7801\u5bf9\u5e94\u7684http\u52a8\u8bcd":3,"\u65b9\u6cd5\u4e0e\u51fd\u6570\u540d":12,"\u65e5\u5fd7\u76ee\u5f55":11,"\u65f6":12,"\u65f6\u624d\u4f7f\u7528":12,"\u662f\u4e00\u6b3e\u5b8c\u5168\u5f00\u6e90\u7684\u8df3\u677f\u673a":8,"\u662f\u56e0\u4e3ajs\u4e2d\u6709\u5927\u91cf\u56de\u8c03\u5f0f\u7684\u5199\u6cd5":12,"\u662f\u7b26\u54084a":8,"\u66f4\u5c11\u4ee3\u7801":12,"\u66f4\u65b0":2,"\u66f4\u65b0\u7528\u6237\u5b8c\u6210":2,"\u66ff\u6362\u4e3a":4,"\u6709\u80fd\u529b\u5c3d\u53ef\u80fd\u7528\u82f1\u6587":12,"\u670d\u52a1\u5668":16,"\u670d\u52a1\u5668\u53d1\u751f\u9519\u8bef":3,"\u670d\u52a1\u5668\u5411\u7528\u6237\u8fd4\u56de\u7684\u72b6\u6001\u7801\u548c\u63d0\u793a\u4fe1\u606f":3,"\u670d\u52a1\u5668\u5411\u7528\u6237\u8fd4\u56de\u7684\u7ed3\u679c\u5e94\u8be5\u7b26\u5408\u4ee5\u4e0b\u89c4\u8303":3,"\u670d\u52a1\u5668\u6210\u529f\u8fd4\u56de\u7528\u6237\u8bf7\u6c42\u7684\u6570\u636e":3,"\u670d\u52a1\u5668\u6ca1\u6709\u8fdb\u884c\u64cd\u4f5c":3,"\u670d\u52a1\u5668\u6ca1\u6709\u8fdb\u884c\u65b0\u5efa\u6216\u4fee\u6539\u6570\u636e\u7684\u64cd\u4f5c":3,"\u670d\u52a1\u5668\u8fd4\u56de\u7684\u6570\u636e\u683c\u5f0f":3,"\u672c\u6559\u7a0b\u4f7f\u7528mysql\u4f5c\u4e3a\u6570\u636e\u5e93":14,"\u674e\u78ca":5,"\u67e5\u770b\u7528\u6237\u7684\u4e2a\u4eba\u4fe1\u606f":18,"\u67e5\u770b\u8d44\u4ea7\u7684\u8be6\u7ec6\u4fe1\u606f":16,"\u67e5\u770b\u914d\u7f6e\u6587\u4ef6\u5e76\u8fd0\u884c":14,"\u67ef\u8fde\u6625":5,"\u6838\u5fc3\u5f00\u53d1\u8005\u4e4b\u4e00":5,"\u6839\u636e\u559c\u597d\u9009\u62e9\u5b89\u88c5\u65b9\u5f0f\u548c\u7248\u672c":14,"\u6a21\u5757\u6587\u4ef6\u7684\u5934\u90e8\u5305\u542b\u6709utf":12,"\u6a21\u5757\u7d22\u5f15":8,"\u6a21\u677f\u6807\u7b7e\u76ee\u5f55":11,"\u6a21\u7cca\u641c\u7d22":3,"\u6b22\u8fce\u6765\u5230":8,"\u6b64\u5916\u90fd\u53ea\u7a7a\u4e00\u884c":12,"\u6bcf\u4e2a\u7f51\u5740\u4ee3\u8868\u4e00\u79cd\u8d44\u6e90":3,"\u6bcf\u4e2aurl\u72ec\u7acb\u547d\u540d":12,"\u6bd4\u5982":3,"\u6bd4\u5982\u4e0a\u9762\u63d0\u5230\u7684":3,"\u6bd4\u5982\u7528\u6237\u8bf7\u6c42json\u683c\u5f0f":3,"\u6c38\u8fdc\u4e0d\u8981\u4e0etrue\u6216false\u8fdb\u884c\u6bd4\u8f83":12,"\u6ca1\u6709\u524d\u540e\u7aef\u5206\u79bb":3,"\u6ca1\u6709\u6027\u80fd\u74f6\u9888":8,"\u6ca1\u6709\u72ec\u7acbapp":3,"\u6ca1\u6709web":14,"\u6ce8\u610f":14,"\u6ce8\u91ca\u660e\u786e\u4f18\u7f8e":12,"\u6ce8\u91ca\u7684\u89c4\u8303\u4e0e\u6587\u6863\u5b57\u7b26\u4e32\u7f16\u5199\u89c4\u8303\u7c7b\u4f3c":12,"\u6d4b\u8bd5\u6848\u4f8b\u5c3d\u53ef\u80fd\u5b8c\u6574":12,"\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6":11,"\u6d4b\u8bd5\u8fde\u63a5":14,"\u6d4f\u89c8\u5668\u8bbf\u95ee":13,"\u6df1\u5ea6\u96c6\u6210\u4e86ansibl":8,"\u6dfb\u52a0\u7528\u6237":[],"\u6ee1\u8db3\u6df7\u5408\u4e91\u67b6\u6784":8,"\u70b9\u51fb":[2,18],"\u70b9\u51fb\u4e3b\u673a\u540d":16,"\u70b9\u51fb\u53f3\u4e0a\u89d2\u7684":2,"\u70b9\u51fb\u9875\u9762\u53f3\u4e0a\u89d2\u7684":18,"\u70b9\u51fb\u9875\u9762\u53f3\u8fb9\u7684":2,"\u70b9\u51fb\u9875\u9762\u5de6\u4e0a\u89d2":2,"\u70b9\u51fb\u9875\u9762\u5de6\u4e0a\u89d2\u7684":18,"\u70b9\u51fb\u9875\u9762\u5de6\u4fa7":2,"\u70b9\u51fb\u9875\u9762\u5de6\u4fa7\u7684":18,"\u70b9\u89e3\u9875\u9762\u53f3\u8fb9\u7684":[],"\u70b9\u89e3\u9875\u9762\u5de6\u4fa7\u7684":16,"\u7136\u540e\u70b9\u51fb\u9875\u9762\u53f3\u4fa7\u7684\u4e3b\u673aip\u5730\u5740":16,"\u738b\u5889":5,"\u751f\u6210\u5927\u91cf\u6d4b\u8bd5\u6570\u636e":11,"\u751f\u6210\u6570\u636e\u5e93\u8868\u7ed3\u6784\u548c\u521d\u59cb\u5316\u6570\u636e":14,"\u7528\u6237\u4f7f\u7528\u6587\u6863":8,"\u7528\u6237\u5217\u8868":2,"\u7528\u6237\u521b\u5efa\u5b8c\u6210":2,"\u7528\u6237\u5220\u9664\u6570\u636e\u6210\u529f":3,"\u7528\u6237\u53d1\u51fa\u7684\u8bf7\u6c42\u6709\u9519\u8bef":3,"\u7528\u6237\u53d1\u51fa\u7684\u8bf7\u6c42\u9488\u5bf9\u7684\u662f\u4e0d\u5b58\u5728\u7684\u8bb0\u5f55":3,"\u7528\u6237\u540d":3,"\u7528\u6237\u5c06\u65e0\u6cd5\u5224\u65ad\u53d1\u51fa\u7684\u8bf7\u6c42\u662f\u5426\u6210\u529f":3,"\u7528\u6237\u65b0\u5efa\u6216\u4fee\u6539\u6570\u636e\u6210\u529f":3,"\u7528\u6237\u6a21\u5757":[],"\u7528\u6237\u7ba1\u7406":[1,8],"\u7528\u6237\u7ba1\u7406\u6a21\u5757":[],"\u7528\u6237\u8bf7\u6c42\u7684\u683c\u5f0f\u4e0d\u53ef\u5f97":3,"\u7528\u6237\u8bf7\u6c42\u7684\u8d44\u6e90\u88ab\u6c38\u4e45\u5220\u9664":3,"\u7528\u6237\u8bfb\u53d6\u8fd9\u4e2a\u5c5e\u6027\u5c31\u77e5\u9053\u4e0b\u4e00\u6b65\u8be5\u8c03\u7528\u4ec0\u4e48api\u4e86":3,"\u7531\u4e8e\u4e00\u4e2a\u9879\u76ee\u591a\u4e2aapp\u6240\u4ee5jumpserver\u4f7f\u7528\u4ee5\u4e0b\u98ce\u683c":3,"\u7531http\u52a8\u8bcd\u8868\u793a":3,"\u754c\u9762\u6f02\u4eae":8,"\u767b\u5f55\u4e2a\u4eba\u7528\u6237":16,"\u767b\u5f55\u5bc6\u7801":[],"\u7684\u4e13\u4e1a\u8fd0\u7ef4\u5ba1\u8ba1\u7cfb\u7edf":8,"\u76f4\u63a5\u89e3\u538b":14,"\u770b\u5230\u4e0b\u9762\u7684\u63d0\u793a\u7b26\u4ee3\u8868\u6210\u529f":14,"\u786e\u4fdd\u4f7f\u7528\u7684\u662f\u521a\u624d\u8bbe\u7f6e\u7684\u914d\u7f6e\u6587\u4ef6":14,"\u786e\u5b9a":2,"\u786e\u8ba4":2,"\u7b26\u53f7\u540e\u6dfb\u52a0\u4e00\u4e2a\u5192\u53f7":12,"\u7b49":12,"\u7b80\u4ecb":8,"\u7b80\u5355\u6613\u7528":8,"\u7ba1\u7406\u540e\u53f0\u76ee\u5f55":11,"\u7ba1\u7406\u6587\u6863":8,"\u7c7b\u540d\u79f0":12,"\u7c7b\u65b9\u6cd5":12,"\u7cfb\u7edf":[8,14],"\u7ec8\u70b9":3,"\u7ec8\u7aef":14,"\u7f16\u5199\u4e86web":5,"\u7f16\u5199\u957f\u8bed\u53e5\u65f6":12,"\u7f16\u8bd1\u5b89\u88c5":14,"\u7f16\u8f91\u7528\u6237\u4fe1\u606f":2,"\u7fa41":4,"\u7fa42":4,"\u7fa43":4,"\u800c\u4e0d\u662fhttpwriter":12,"\u800c\u4e0d\u662fnot":12,"\u800c\u4e1421\u4e16\u7eaa\u90fd\u662f\u5bbd\u5c4f\u4e86":12,"\u800c\u4e14\u6240\u7528\u7684\u540d\u8bcd\u5f80\u5f80\u4e0e\u6570\u636e\u5e93\u7684\u8868\u683c\u540d\u5bf9\u5e94":3,"\u800c\u5e94\u8be5\u8fd9\u6837\u5199":12,"\u800c\u8981\u7528\u7a7a\u683c":14,"\u800cyum\u7b49\u5de5\u5177\u4f9d\u8d56\u539f\u6765\u7684python":14,"\u8054\u7cfb\u65b9\u5f0f":8,"\u82e5\u4e0ejumpserver\u90e8\u7f72\u5728\u540c\u4e00\u4e3b\u673a\u4e0a\u81ea\u5b9a\u4e49\u4e00\u4e0b":14,"\u83dc\u5355\u4e0b\u7684":2,"\u8865\u5145\u8bf4\u660e":12,"\u8868\u793a\u4e00\u4e2a\u8bf7\u6c42\u5df2\u7ecf\u8fdb\u5165\u540e\u53f0\u6392\u961f":3,"\u8868\u793a\u7528\u6237\u5f97\u5230\u6388\u6743":3,"\u8868\u793a\u7528\u6237\u6ca1\u6709\u6743\u9650":3,"\u8868\u793aapi\u7684\u5177\u4f53\u7f51\u5740":3,"\u89d2\u8272\u5b89\u5168":2,"\u89e3\u538bluna":14,"\u8ba4\u8bc1authent":8,"\u8bbe\u7f6e":18,"\u8bbe\u8ba1\u6307\u5357":3,"\u8bbf\u95ee":14,"\u8be5\u64cd\u4f5c\u662f\u5e42\u7b49\u7684":3,"\u8be5\u955c\u50cf\u96c6\u6210\u4e86\u6240\u6709\u9700\u8981\u7684\u7ec4\u4ef6":13,"\u8bf4\u660e\u5982\u4e0b":11,"\u8bf7\u5728":12,"\u8bf7\u5c06\u5185\u5efa\u53d8\u91cf\u91cd\u65b0\u7ed1\u5b9a\u4e3a\u5176\u4ed6\u540d\u79f0":12,"\u8bf7\u6d4f\u89c8\u5668\u8bbf\u95ee":14,"\u8d21\u732e\u8005":8,"\u8d26\u53f7":14,"\u8d26\u53f7account":8,"\u8d44\u4ea7\u5f00\u53d1\u8005":5,"\u8d44\u4ea7\u7ba1\u7406\u6a21\u5757":[1,8],"\u8d44\u6df1\u524d\u7aef\u5de5\u7a0b\u5e08":5,"\u8d44\u6df1\u5f00\u53d1\u8005":5,"\u8def\u5f84\u53c8\u79f0":3,"\u8fd0\u884c":14,"\u8fd0\u884c\u4e0d\u62a5\u9519":14,"\u8fd0\u884cjumpserv":14,"\u8fd4\u56de\u4e00\u4e2a\u7a7a\u6587\u6863":3,"\u8fd4\u56de\u5355\u4e2a\u8d44\u6e90\u5bf9\u8c61":3,"\u8fd4\u56de\u5b8c\u6574\u7684\u8d44\u6e90\u5bf9\u8c61":3,"\u8fd4\u56de\u65b0\u751f\u6210\u7684\u8d44\u6e90\u5bf9\u8c61":3,"\u8fd4\u56de\u7684\u4fe1\u606f\u4e2d\u5c06error\u4f5c\u4e3a\u952e\u540d":3,"\u8fd4\u56de\u8d44\u6e90\u5bf9\u8c61\u7684\u5217\u8868":3,"\u8fd9\u65f6\u9700\u8981\u53bb":14,"\u8fd9\u7248api\u76f8\u5bf9\u7b80\u5355":3,"\u8fd9\u90e8\u5206\u7ed9\u60a8\u4ecb\u7ecdjumpserver\u7684\u7528\u6237\u4f7f\u7528\u65b9\u6cd5":17,"\u8fd9\u91cc\u4ec5\u8003\u8651rest":3,"\u8fd9\u91cc\u4ecb\u7ecd\u4e2a\u4eba\u4fe1\u606f\u76f8\u5173\u7684\u529f\u80fd":18,"\u8fd9\u91cc\u4ecb\u7ecd\u7528\u6237\u4e2a\u4eba\u8d44\u4ea7\u76f8\u5173\u7684\u529f\u80fd":16,"\u8fd9\u91cc\u4ecb\u7ecd\u7528\u6237\u7ba1\u7406\u6a21\u5757\u7684\u529f\u80fd":2,"\u8fd9\u91cc\u4ecb\u7ecd\u7ba1\u7406\u5458\u529f\u80fd":1,"\u8fd9\u91cc\u53ea\u662fjumpserv":14,"\u8fd9\u91cc\u6240\u9700\u8981\u6ce8\u610f\u7684\u662fguacamole\u66b4\u9732\u51fa\u6765\u7684\u7aef\u53e3\u662f8080":14,"\u8fdb\u5165\u4e2a\u4eba\u4fe1\u606f\u4fee\u6539\u9875\u9762":18,"\u8fdb\u5165\u4e3b\u673a\u767b\u5f55\u9875":16,"\u8fdb\u5165\u521b\u5efa\u7528\u6237\u9875\u9762":2,"\u8fdb\u5165\u5bc6\u7801\u66f4\u65b0\u9875\u9762":18,"\u8fdb\u5165\u5bc6\u94a5\u66f4\u65b0\u9875\u9762":18,"\u8fdb\u5165\u7528\u6237\u5217\u8868\u9875\u9762":2,"\u8fdb\u5165\u7f16\u8f91\u7528\u6237\u9875\u9762":2,"\u8fdb\u884c\u6bd4\u8f83\u65f6":12,"\u8fde\u5411\u5176\u4ed6api\u65b9\u6cd5":3,"\u8fde\u63a5\u4e3b\u673a":16,"\u8fde\u63a5\u6d4b\u8bd5\u673a":14,"\u9009\u4e2d\u7528\u6237":2,"\u9009\u62e9\u5de6\u4e0a\u89d2\u7684":16,"\u9009\u62e9\u8981\u5bfc\u5165\u7684csv\u683c\u5f0f\u6587\u4ef6":2,"\u9009\u62e9\u9875\u9762\u5de6\u4e0b\u89d2\u7684\u6279\u91cf\u64cd\u4f5c\u9009\u9879":2,"\u901a\u7528\u7684\u51fd\u6570\u65b9\u6cd5":11,"\u901a\u7528templat":11,"\u9075\u5faagpl":8,"\u90a3\u4e48\u4e0b\u4e00\u884c\u5e94\u4e0e\u62ec\u53f7\u6216\u82b1\u62ec\u53f7\u5bf9\u9f50":12,"\u90ae\u4ef6":8,"\u914d\u7f6e\u6587\u4ef6":14,"\u914d\u7f6e\u6587\u4ef6\u662fpython\u683c\u5f0f":14,"\u914d\u7f6e\u6587\u4ef6\u6837\u4f8b":11,"\u91c7\u7528\u9a86\u9a7c\u62fc\u5199\u6cd5":12,"\u91cd\u65b0\u4e0b\u8f7drelease\u5305":15,"\u91cd\u7f6e\u5bc6\u7801":18,"\u91cd\u7f6essh\u5bc6\u94a5":18,"\u9488\u5bf9\u4e0d\u540c\u64cd\u4f5c":3,"\u94fe\u63a5\u4e3b\u673a":[],"\u9632\u6b62\u8d44\u6e90\u91cd\u540d":11,"\u9648\u5c1a\u59d4":5,"\u96c6\u5408":3,"\u9700\u8981nginx\u6765\u8fd0\u884c\u8bbf\u95ee":14,"\u9875\u9762\u53f3\u4fa7\u4f1a\u5c55\u793a\u5f53\u524d\u8fde\u63a5\u7684\u7ec8\u7aef\u4fe1\u606f":16,"\u9876\u5c42\u51fd\u6570\u4e0e\u7c7b\u4e4b\u95f4\u7a7a\u4e24\u884c":12,"\u9879\u76ee\u5165\u53e3urlconf":11,"\u9879\u76ee\u591a\u8bed\u8a00\u76ee\u5f55":11,"\u9879\u76ee\u63d0\u4ea4\u8f83\u591agit":14,"\u9879\u76ee\u6a21\u677f\u76ee\u5f55":11,"\u9879\u76ee\u89c4\u8303":[6,8],"\u9879\u76ee\u8bbe\u7f6e\u6587\u4ef6":11,"\u9879\u76ee\u8bbe\u7f6e\u76ee\u5f55":11,"\u9879\u76ee\u9759\u6001\u8d44\u6e90\u76ee\u5f55":11,"\u9879\u76ee\u9aa8\u67b6":[6,8],"\u9884\u7f16\u8bd1\u7684\u6b63\u5219\u8868\u8fbe\u5f0f":12,"\u9996\u5b57\u6bcd\u7f29\u7565\u8bcd\u4fdd\u6301\u5927\u5199\u4e0d\u53d8":12,"\u9ed8\u8ba4\u5c55\u793a\u4e2a\u4eba\u8d44\u4ea7\u5217\u8868":16,"api\u4e0e\u7528\u6237\u7684\u901a\u4fe1\u534f\u8bae":3,"api\u548cviews\u53ea\u662f\u8bf7\u6c42\u548c\u8fd4\u56de\u4e0d\u540c":11,"api\u6587\u4ef6":11,"api\u6587\u6863":3,"api\u6700\u597d\u505a\u5230hypermedia":3,"api\u7684\u57fa\u672c\u60c5\u51b5":3,"api\u7684\u8bbe\u8ba1\u88ab\u79f0\u4e3ahateoa":3,"api\u7684\u8eab\u4efd\u8ba4\u8bc1\u5e94\u8be5\u4f7f\u7528oauth":3,"api\u89c4\u8303\u7ea6\u5b9a":[6,8],"app\u4e0b\u6a21\u677f\u76ee\u5f55":11,"app\u4e0b\u9759\u6001\u8d44\u6e90\u76ee\u5f55":11,"app\u76ee\u5f55":11,"app\u8bbe\u7f6e\u6587\u4ef6":11,"case":12,"centos7\u4e0b\u5b89\u88c5\u7684\u662fmariadb":14,"class":[12,14],"clone\u65f6\u8f83\u5927":14,"cls\u4e3a\u7b2c\u4e00\u4e2a\u53c2\u6570":12,"cmdb\u4e2d\u7684assets\u5217\u8868":3,"collection\u5173\u7cfb":3,"com\u7684\u6839\u76ee\u5f55\u53d1\u51fa\u8bf7\u6c42":3,"default":14,"delete\u65b9\u6cd5\u8bf7\u4f7f\u7528\u8bf7\u6c42\u4f53\u4f20\u9012\u53c2\u6570":3,"developmentconfig\u4e2d\u7684\u914d\u7f6e":14,"django\u5f00\u53d1":8,"django\u8d44\u6df1\u5f00\u53d1\u8005":5,"docker\u5b98\u65b9\u5b89\u88c5\u6587\u6863":13,"elasticsearch\u5b58\u50a8\u7b49\u529f\u80fd":8,"fix\u4e86\u5f88\u591abug":5,"get\u65b9\u6cd5\u8bf7\u4f7f\u7528url":3,"github\u7684api\u5c31\u662f\u8fd9\u79cd\u8bbe\u8ba1":3,"go\u4ee5\u53capaas\u5e73\u53f0\u5f00\u53d1":5,"href\u8868\u793aapi\u7684\u8def\u5f84":3,"html\u4ee3\u7801\u4e0d\u53d7\u6b64\u89c4\u8303\u7ea6\u675f":12,"i\u53c2\u6570":[14,15],"idc\u5217\u8868":3,"js\u91c7\u75282\u7a7a\u683c\u7f29\u8fdb":12,"jumpserver\u4f7f\u7528redis\u505acache\u548cceleri":14,"jumpserver\u5e76\u6388\u6743":14,"jumpserver\u6d4b\u8bd5\u8fd0\u8425":5,"jumpserver\u7ba1\u7406\u540e\u53f0":14,"luna\u5df2\u6539\u4e3a\u7eaf\u524d\u7aef":14,"method\u52a0\u4e0aurl\u63d0\u4f9b\u7684\u8bed\u4e49":3,"migrations\u7248\u672c\u63a7\u5236\u76ee\u5f55":11,"property\u51fd\u6570\u4e2d\u4f7f\u7528\u533f\u540d\u51fd\u6570":12,"python\u4e00\u822c\u9650\u5236\u6700\u592779\u4e2a\u5b57\u7b26":12,"python\u4e25\u683c\u91c7\u75284\u4e2a\u7a7a\u683c\u7684\u7f29\u8fdb":12,"python\u65b9\u9762\u5927\u81f4\u7684\u98ce\u683c":12,"qq\u7fa4":8,"rel\u8868\u793a\u8fd9\u4e2aapi\u4e0e\u5f53\u524d\u7f51\u5740\u7684\u5173\u7cfb":3,"release\u5305":14,"return":12,"self\u4e3a\u7b2c\u4e00\u4e2a\u53c2\u6570":12,"selinux\u548c\u9632\u706b\u5899":14,"ssh\u5bc6\u94a5":18,"ssh\u8bbf\u95ee":13,"static":[11,14],"terminal\u4f1a\u62a5\u9519":14,"terminal\u5927\u90e8\u5206\u4ee3\u7801":5,"terminal\u767b\u5f55\u8bed\u6cd5\u5982\u4e0b":14,"terminal\u89e3\u51b3\u65b9\u6848":8,"title\u8868\u793aapi\u7684\u6807\u9898":3,"true":14,"type\u8868\u793a\u8fd4\u56de\u7c7b\u578b":3,"url\u4e2d\u7684\u53ef\u53d8\u90e8\u5206":3,"url\u7b49\u901a\u5e38\u6bd4\u8f83\u957f":12,"urlconf\u6587\u4ef6":11,"v2\u534f\u8bae":8,"view\u7f16\u7a0b":12,"views\u6587\u4ef6":11,"web\u7ec8\u7aef":16,"web\u90e8\u5206\u4ee3\u7801":12,"zip\u5305":14,For:14,NOT:3,Not:3,__init__:[11,12],accept:3,activ:14,add_head:14,admin:[11,14],alia:14,all:14,api:11,app:[3,11],app_id:3,applic:3,asc:3,asset:[3,11],asset_id:3,asset_list:11,author:12,automak:14,bar:12,base:12,bash:14,beta2:13,bin:14,brief:12,broker:14,camelcas:12,cat:14,cento:14,centos6:14,centos7:14,charset:14,checkout:14,client_id:3,clone:14,close:12,coco:[12,15],code:12,collect:3,column:12,com:[3,4,13,14],come:12,common:11,conf:14,conf_exampl:14,config:[11,14],config_exampl:14,configur:14,connect:14,content:[3,14],control:14,copyright:12,creat:[3,14],crud:3,css:12,data:14,databas:14,db_engin:[13,14],db_host:[13,14],db_name:[13,14],db_password:[13,14],db_port:[13,14],db_user:[13,14],debug:14,def:12,delet:3,demo:8,depth:14,desc:12,descript:12,detail:12,dev:14,devel:14,developmentconfig:14,devop:5,display_nam:12,django:12,doc:11,docker:[13,14],dockerfil:13,draft:[6,8],encod:14,endpoint:3,entiti:3,epel:14,error:3,even:12,exampl:[3,11],exp:12,fake:11,fals:12,faq:8,filter:12,firewalld:14,first:12,fit2cloud:4,fixtur:11,flask:12,foo:12,forbidden:3,form:12,forward:14,found:3,ftp:14,function_cal:12,gcc:14,get:3,get_annoying_nam:12,git:[4,14,15],github:[3,8,13,14],goe:12,gone:3,goodby:12,grant:14,guacamol:14,guidanc:12,gzip:14,halcyon:5,hash:12,hello:12,here:12,host:14,href:3,html:[11,12,14],http:[4,13,14],http_upgrad:14,httpwriter:12,i18n:11,ibul:4,idc:3,idempot:3,identifi:14,index:[12,14],inform:12,init:11,inlin:12,instal:[11,14,15],intern:3,invalid:3,iptabl:14,item:12,item_count:12,item_valu:12,its:12,javascript:12,jiaxiangkong:5,json:[3,11],jumpserv:[4,6,11,13,14,15],jumpserver_serv:14,kei:[3,12],kelianchun:5,keyword:[3,12],lambda:12,latex:12,ldap:8,licens:[11,12],license_fil:12,license_nam:12,like:12,limit:[3,12],line:12,link:3,list:3,listen:14,liuz:5,localhost:[3,13,14],locat:14,log:11,longer:12,lowercase_with_underscor:12,luna:[12,15],make:14,make_migr:[14,15],manag:11,mani:12,mariadb:14,media:14,migrat:11,mixin:12,model:11,modul:12,more:[12,14],much:12,my_dict:12,my_list:12,myclass:12,mymodel:12,mysql:[13,14],mysql_host:13,mysqld:14,name:[3,12],name_r:12,node:12,object:12,offset:[3,12],one:13,openssl:14,opt:14,order:3,order_bi:12,org:[3,4,14],own:12,p2222:14,packag:12,page:3,paramet:[3,12],paramiko:12,password:12,patch:3,per_pag:3,pip:[14,15],png:11,post:3,print:12,properti:12,proxy_add_x_forwarded_for:14,proxy_http_vers:14,proxy_pass:14,proxy_set_head:14,pull:15,put:3,pw_hash:12,pwd:14,py3:14,python3:14,python:[12,14],queri:12,quit:14,quot:12,raw:12,readm:11,real:14,real_nam:12,redi:14,redis_host:13,redis_password:13,redis_port:13,rel:3,releas:14,remote_addr:14,request:3,requir:[14,15],resourc:3,rest:[6,8],root:[13,14],rpm_requir:14,run:[13,14],run_serv:14,salt:12,scalar:12,see:[12,14],select:3,self:12,server:3,servic:14,set:[11,12],setenforc:14,sha1:12,simpl:12,singleton:12,sofia:5,some_imag:11,somepassword:14,sort:3,sourc:14,span:12,sqlite:14,ssh:13,start:14,stop:14,string:12,style:12,systemctl:14,tag:11,tar:14,templat:11,templatetag:11,test:11,that_returns_an_object_with_an_attribut:12,thi:12,this_is_a_very_long:12,three:12,tip:13,titl:3,trail:12,tripl:12,try_fil:14,txt:[14,15],type:3,unauthor:3,underscor:12,unicod:12,unproces:3,updat:3,upgrad:14,upper:12,uppercase_with_underscor:12,uri:14,url:11,user:12,usernam:12,utf8:14,utf:12,util:[11,14,15],valu:12,venv:14,version:[3,14],view:11,vnd:3,wget:14,window:8,wsgi:11,www:[3,4,14],xshell:14,xvf:14,xxx:13,xxxx:13,xxxxx:3,xxxxxx:3,year:12,you:12,yourformat:3,yum:14,yumaojun03:5,zlib:14,zoo:3},titles:["\u8d44\u4ea7\u7ba1\u7406\u6a21\u5757","\u7ba1\u7406\u6587\u6863","\u7528\u6237\u7ba1\u7406","REST API\u89c4\u8303\u7ea6\u5b9a","\u8054\u7cfb\u65b9\u5f0f","\u8d21\u732e\u8005","\u5f00\u53d1\u6587\u6863","FAQ","Jumpserver \u6587\u6863","\u5b89\u88c5\u6587\u6863","\u7b80\u4ecb","\u9879\u76ee\u9aa8\u67b6","Jumpserver \u9879\u76ee\u89c4\u8303\uff08Draft\uff09","\u5feb\u901f\u5b89\u88c5","\u4e00\u6b65\u4e00\u6b65\u5b89\u88c5","\u5347\u7ea7","\u4e2a\u4eba\u8d44\u4ea7","\u7528\u6237\u4f7f\u7528\u6587\u6863","\u4e2a\u4eba\u4fe1\u606f"],titleterms:{"\u4e00":14,"\u4e00\u6b65\u4e00\u6b65\u5b89\u88c5":14,"\u4e00\u822c\u7a7a\u683c\u89c4\u5219":12,"\u4e09":14,"\u4e2a\u4eba\u4fe1\u606f":18,"\u4e2a\u4eba\u8d44\u4ea7":16,"\u4e3b\u673a\u767b\u51fa":16,"\u4e3b\u673a\u767b\u5f55":16,"\u4e8c":14,"\u4e94":14,"\u4ed3\u5e93\u5730\u5740":13,"\u4ee3\u7801\u98ce\u683c":12,"\u4fee\u6539\u4e2a\u4eba\u4fe1\u606f":18,"\u516d":14,"\u5176\u5b83":3,"\u51c6\u5907python3\u548cpython\u865a\u62df\u73af\u5883":14,"\u51fd\u6570\u548c\u65b9\u6cd5\u7684\u53c2\u6570":12,"\u521b\u5efa\u7528\u6237":2,"\u5220\u9664\u7528\u6237":2,"\u524d\u7aef":14,"\u5347\u7ea7":15,"\u534f\u8bae":3,"\u5373\u5404\u65b9\u6cd5":12,"\u5426\u5b9a\u6210\u5458\u5173\u7cfb\u68c0\u67e5":12,"\u547d\u540d\u7ea6\u5b9a":12,"\u56db":14,"\u57df\u540d":3,"\u57fa\u672c\u7684\u4ee3\u7801\u5e03\u5c40":12,"\u5b89\u88c5":14,"\u5b89\u88c5\u6587\u6863":9,"\u5b89\u88c5jumpserv":14,"\u5b89\u88c5windows\u652f\u6301\u7ec4\u4ef6":14,"\u5b98\u7f51":4,"\u5bc6\u94a5\u66f4\u65b0":18,"\u5bfc\u5165\u7528\u6237":2,"\u5bfc\u51fa\u7528\u6237":2,"\u5f00\u53d1\u6587\u6863":6,"\u5feb\u901f\u542f\u52a8":13,"\u5feb\u901f\u5b89\u88c5":13,"\u6279\u91cf\u64cd\u4f5c":2,"\u6574\u5408\u5404\u7ec4\u4ef6":14,"\u6587\u6863":8,"\u6587\u6863\u6ce8\u91ca":12,"\u66f4\u65b0\u5bc6\u7801":18,"\u66f4\u65b0\u7528\u6237":2,"\u6700\u5927\u884c\u957f\u5ea6":12,"\u6709\u5173jumpserv":8,"\u67e5\u770b\u4e2a\u4eba\u4fe1\u606f":18,"\u67e5\u770b\u4e2a\u4eba\u8d44\u4ea7":16,"\u6a21\u5757\u5934\u90e8":12,"\u6bd4\u8f83":12,"\u6ce8\u91ca":12,"\u6dfb\u52a0\u7528\u6237":[],"\u7248\u672c":3,"\u72b6\u6001\u7801":3,"\u73af\u5883":14,"\u7528\u6237\u4f7f\u7528\u6587\u6863":17,"\u7528\u6237\u6a21\u5757":[],"\u7528\u6237\u7ba1\u7406":2,"\u7528\u6237\u7ba1\u7406\u6a21\u5757":[],"\u7a7a\u884c":12,"\u7b80\u4ecb":10,"\u7ba1\u7406\u6587\u6863":1,"\u7c7b\u7684\u8bf4\u660e\u6587\u6863\u6ce8\u91ca":12,"\u7d22\u5f15":8,"\u7f29\u8fdb":12,"\u8054\u7cfb\u65b9\u5f0f":4,"\u8bbf\u95ee":13,"\u8bed\u53e5\u548c\u8868\u8fbe\u5f0f":12,"\u8bed\u8a00\u6846\u67b6":12,"\u8d21\u732e\u8005":5,"\u8d44\u4ea7\u7ba1\u7406\u6a21\u5757":0,"\u8def\u5f84":3,"\u8fc7\u6ee4\u4fe1\u606f":3,"\u8fd4\u56de\u7ed3\u679c":3,"\u90ae\u4ef6":4,"\u914d\u7f6e":14,"\u9519\u8bef\u5904\u7406":3,"\u957f\u8bed\u53e5\u7f29\u8fdb":12,"\u9879\u76ee\u89c4\u8303":12,"\u9879\u76ee\u9aa8\u67b6":11,"\u989d\u5916\u73af\u5883\u53d8\u91cf":13,"api\u89c4\u8303\u7ea6\u5b9a":3,"django\u89c4\u8303":12,"qq\u7fa4":4,"server\u548cweb":14,api:3,coco:14,comment:12,demo:4,docstr:12,draft:12,faq:7,github:4,http:3,hypermedia:3,jumpserv:[8,12],luna:14,method:3,nginx:14,rest:3,server:14,socket:14,ssh:14,termin:14,topic:[2,16,18],web:14}}) \ No newline at end of file diff --git a/docs/_build/html/step_by_step.html b/docs/_build/html/step_by_step.html deleted file mode 100644 index f591b4bcc..000000000 --- a/docs/_build/html/step_by_step.html +++ /dev/null @@ -1,489 +0,0 @@ - - - - - - - - - - - 一步一步安装 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    一步一步安装

    -
    -

    环境

    -
      -
    • 系统: CentOS 7
    • -
    • IP: 192.168.244.144
    • -
    • 关闭 selinux和防火墙
    • -
    -
    # CentOS 7
    -$ setenforce 0  # 可以设置配置文件永久关闭
    -$ systemctl stop iptables.service
    -$ systemctl stop firewalld.service
    -
    -# CentOS6
    -$ setenforce 0
    -$ service iptables stop
    -
    -
    -
    -
    -

    一. 准备Python3和Python虚拟环境

    -

    1.1 安装依赖包

    -
    $ yum -y install wget sqlite-devel xz gcc automake zlib-devel openssl-devel epel-release
    -
    -
    -

    1.2 编译安装

    -
    $ wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tar.xz
    -$ tar xvf Python-3.6.1.tar.xz  && cd Python-3.6.1
    -$ ./configure && make && make install
    -
    -
    -

    1.3 建立python虚拟环境

    -

    因为CentOS -6/7自带的是Python2,而Yum等工具依赖原来的Python,为了不扰乱原来的环境我们来使用Python虚拟环境

    -
    $ cd /opt
    -$ python3 -m venv py3
    -$ source /opt/py3/bin/activate
    -
    -# 看到下面的提示符代表成功,以后运行jumpserver都要先运行以上source命令,以下所有命令均在该虚拟环境中运行
    -(py3) [root@localhost py3]#
    -
    -
    -
    -
    -

    二. 安装Jumpserver 0.5.0

    -

    2.1 下载或clone项目

    -

    项目提交较多git clone时较大,你可以选择去github项目页面直接下载 -zip包,我的网速好,我直接clone了

    -
    $ cd /opt/
    -$ git clone --depth=1 https://github.com/jumpserver/jumpserver.git && cd jumpserver && git checkout dev
    -
    -
    -

    2.2 安装依赖rpm包

    -
    $ cd /opt/jumpserver/requirements
    -$ yum -y install $(cat rpm_requirements.txt)  # 如果没有任何报错请继续
    -
    -
    -

    2.3 安装python库依赖

    -
    $ pip install -r requirements.txt  # 不要指定-i参数,因为镜像上可能没有最新的包,如果没有任何报错请继续
    -
    -
    -

    2.4 安装Redis, jumpserver使用redis做cache和celery broker

    -
    $ yum -y install redis
    -$ service redis start
    -
    -
    -

    2.5 安装MySQL

    -

    本教程使用mysql作为数据库,如果不使用mysql可以跳过相关mysql安装和配置

    -
    # centos7
    -$ yum -y install mariadb mariadb-devel mariadb-server # centos7下安装的是mariadb
    -$ service mariadb start
    -
    -# centos6
    -$ yum -y install mysql mysql-devel mysql-server
    -$ service mysqld start
    -
    -
    -

    2.6 创建数据库 jumpserver并授权

    -
    $ mysql
    -> create database jumpserver default charset 'utf8';
    -> grant all on jumpserver.* to 'jumpserver'@'127.0.0.1' identified by 'somepassword';
    -
    -
    -

    2.7 修改jumpserver配置文件

    -
    $ cd /opt/jumpserver
    -$ cp config_example.py config.py
    -$ vi config.py  # 我们计划修改 DevelopmentConfig中的配置,因为默认jumpserver是使用该配置,它继承自Config
    -
    -
    -

    注意: 配置文件是python格式,不要用tab,而要用空格 注意: -配置文件是python格式,不要用tab,而要用空格 注意: -配置文件是python格式,不要用tab,而要用空格

    -
    class DevelopmentConfig(Config):
    -    DEBUG = True
    -    DB_ENGINE = 'mysql'
    -    DB_HOST = '127.0.0.1'
    -    DB_PORT = 3306
    -    DB_USER = 'jumpserver'
    -    DB_PASSWORD = 'somepassword'
    -    DB_NAME = 'jumpserver'
    -
    -...
    -
    -config = DevelopmentConfig()  # 确保使用的是刚才设置的配置文件
    -
    -
    -

    2.8 生成数据库表结构和初始化数据

    -
    $ cd /opt/jumpserver/utils
    -$ bash make_migrations.sh
    -
    -
    -

    2.9 运行Jumpserver

    -
    $ cd /opt/jumpserver
    -$ python run_server.py all
    -
    -
    -

    运行不报错,请浏览器访问 http://192.168.244.144:8080/ -(这里只是jumpserver, 没有web terminal,所以访问web terminal会报错)

    -

    账号:admin 密码: admin

    -
    -
    -

    三. 安装 SSH Server和Web Socket Server: Coco

    -

    3.1 下载clone项目

    -

    新开一个终端,连接测试机,别忘了 source /opt/py3/bin/activate

    -
    $ cd /opt
    -$ git clone https://github.com/jumpserver/coco.git && cd coco && git checkout dev
    -
    -
    -

    3.2 安装依赖

    -
    $ cd /opt/coco/requirements $ yum -y install $(cat rpm_requirements.txt) $ pip install requirements.txt
    -
    -
    -

    3.2 安装依赖

    -
    $ cd /opt/coco/requirements
    -$ yum -y  install $(cat rpm_requirements.txt)
    -$ pip install -r requirements.txt
    -
    -
    -

    3.3 查看配置文件并运行

    -
    $ cd /opt/coco
    -$ cp conf_example.py conf.py
    -$ python run_server.py
    -
    -
    -

    这时需要去 -jumpserver管理后台-终端-终端(http://192.168.244.144:8080/terminal/terminal/)接受coco的注册

    -
    Coco version 0.4.0, more see https://www.jumpserver.org
    -Starting ssh server at 0.0.0.0:2222
    -Quit the server with CONTROL-C.
    -
    -
    -

    3.4 测试连接

    -
    $ ssh -p2222 admin@192.168.244.144
    -密码: admin
    -
    -如果是用在windows下,Xshell terminal登录语法如下
    -$ssh admin@192.168.244.144 2222
    -密码: admin
    -如果能登陆代表部署成功
    -
    -
    -
    -
    -

    四. 安装 Web Terminal 前端: Luna

    -

    Luna已改为纯前端,需要nginx来运行访问

    -

    下载 release包,直接解压,不需要编译

    -

    访问 https://github.com/jumpserver/luna/releases,下载对应release包

    -

    4.1 解压luna

    -
    $ pwd
    -/opt/
    -
    -$ tar xvf luna.tar.gz
    -$ ls /opt/luna
    -...
    -
    -
    -
    -
    -

    五. 安装Windows支持组件

    -

    使用docker启动 guacamole

    -
    docker run \
    -  -p 8080:8080 \
    -  -e JUMPSERVER_SERVER=http://<jumpserver>:8080 \
    -  jumpserver/guacamole
    -
    -
    -

    这里所需要注意的是guacamole暴露出来的端口是8080,若与jumpserver部署在同一主机上自定义一下。

    -

    修改JUMPSERVER_SERVER的配置,填上jumpserver的内网地址

    -
    -
    -

    六. 配置 nginx 整合各组件

    -

    6.1 安装nginx 根据喜好选择安装方式和版本

    -

    6.2 配置文件

    -
    server {
    -    listen 80;
    -
    -    proxy_set_header X-Real-IP $remote_addr;
    -    proxy_set_header Host $host;
    -    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    -
    -    location /luna/ {
    -        try_files $uri / /index.html;
    -        alias /opt/luna/;
    -    }
    -
    -    location /media/ {
    -        add_header Content-Encoding gzip;
    -        root /opt/jumpserver/data/;
    -    }
    -
    -    location /static/ {
    -        root /opt/jumpserver/data/;
    -    }
    -
    -    location /socket.io/ {
    -        proxy_pass       http://localhost:5000/socket.io/;
    -        proxy_http_version 1.1;
    -        proxy_set_header Upgrade $http_upgrade;
    -        proxy_set_header Connection "upgrade";
    -    }
    -
    -    location /guacamole/ {
    -        proxy_pass http://<guacamole>:8080/;
    -    }
    -
    -    location / {
    -        proxy_pass http://localhost:8080;
    -    }
    -}
    -
    -
    -

    6.3 运行 nginx

    -

    6.4 访问 http://192.168.244.144

    -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/user_guide.html b/docs/_build/html/user_guide.html deleted file mode 100644 index b3311dca2..000000000 --- a/docs/_build/html/user_guide.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - - - 用户使用文档 — jumpserver 0.5.0 文档 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - -
    -
    - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    用户使用文档

    -

    这部分给您介绍Jumpserver的用户使用方法。

    - -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From d07b427037ebb8cd28668ceef52684051ace6c97 Mon Sep 17 00:00:00 2001 From: fit2cloud-fengyi Date: Mon, 5 Mar 2018 13:10:02 +0800 Subject: [PATCH 150/197] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/_build/html/_sources/admin_user.rst.txt | 53 ++--------------- docs/_build/html/_sources/index.rst.txt | 15 ++--- docs/_build/html/admin_asset.html | 6 +- docs/_build/html/admin_user.html | 57 ++++--------------- docs/_build/html/index.html | 10 ++-- docs/_build/html/objects.inv | Bin 898 -> 920 bytes docs/_build/html/searchindex.js | 2 +- docs/admin_user.rst | 53 ++--------------- docs/index.rst | 15 ++--- 9 files changed, 38 insertions(+), 173 deletions(-) diff --git a/docs/_build/html/_sources/admin_user.rst.txt b/docs/_build/html/_sources/admin_user.rst.txt index cdce938e1..410a1d330 100644 --- a/docs/_build/html/_sources/admin_user.rst.txt +++ b/docs/_build/html/_sources/admin_user.rst.txt @@ -1,51 +1,10 @@ 用户管理 -======== +========= -这里介绍用户管理模块的功能。 +这里介绍用户管理功能。 -点击页面左侧“用户列表”菜单下的“用户列表,进入用户列表页面。 +.. toctree:: + :maxdepth: 1 -.. contents:: Topics - -.. _create_user: - -创建用户 -```````` - -点击页面左上角“创建用户”按钮,进入创建用户页面,填写账户,角色安全,个人等信息,点击“提交”按钮,用户创建完成。 - - -.. _update_user: - -更新用户 -```````` - -点击页面右边的“更新”按钮,进入编辑用户页面,编辑用户信息,点击“提交”按钮,更新用户完成。 - -.. _delete_user: - -删除用户 -```````` - -点击页面右边的“删除”按钮,弹出是否删除确认框,点击“确定”按钮,删除用户完成。 - -.. _export_user: - -导出用户 -```````` - -选中用户,点击右上角的“导出”按钮,导出用户完成。 - -.. _inport_user: - -导入用户 -```````` - -点击右上角的“导入”按钮,弹出导入对话框,选择要导入的CSV格式文件,点击“确认”按钮,导入用户完成。 - -.. _batch_operation: - -批量操作 -```````` - -选中用户,选择页面左下角的批量操作选项,点击”提交“按钮,批量操作完成。 \ No newline at end of file + user + user_group \ No newline at end of file diff --git a/docs/_build/html/_sources/index.rst.txt b/docs/_build/html/_sources/index.rst.txt index 162062e5a..ca17fe97a 100644 --- a/docs/_build/html/_sources/index.rst.txt +++ b/docs/_build/html/_sources/index.rst.txt @@ -11,20 +11,13 @@ Jumpserver 文档 欢迎来到 Jumpserver 文档。 -Jumpserver 是一款完全开源的跳板机(堡垒机)系统,遵循GPL v2协议,使用Python,Django开发。 - -Jumpserver 是符合4A(认证Authentication,账号Account,授权Authorization,审计Audit) 的专业运维审计系统。 - -Jumpserver 从设计时考虑分布式,没有性能瓶颈,满足混合云架构,一个中心,不同Region不同登录点。 - -Jumpserver 界面漂亮、简单易用,并有领先的Web terminal解决方案。 - -Jumpserver 深度集成了Ansible,支持自动化运维任务。 - -Jumpserver 支持容器化部署,windows,LDAP, s3, elasticsearch存储等功能,并提供了强大API方便对接其它系统。 +Jumpserver是全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。 +Jumpserver使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。 +Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。 +改变世界,从一点点开始。 .. toctree:: diff --git a/docs/_build/html/admin_asset.html b/docs/_build/html/admin_asset.html index e006082a9..4f7abace5 100644 --- a/docs/_build/html/admin_asset.html +++ b/docs/_build/html/admin_asset.html @@ -38,7 +38,7 @@ - + @@ -92,7 +92,7 @@
  • 简介
  • 安装文档
  • 管理文档
  • @@ -183,7 +183,7 @@ - +
    diff --git a/docs/_build/html/admin_user.html b/docs/_build/html/admin_user.html index 1fd845f2b..33f51febf 100644 --- a/docs/_build/html/admin_user.html +++ b/docs/_build/html/admin_user.html @@ -37,7 +37,7 @@ - + @@ -93,12 +93,8 @@
  • 安装文档
  • 管理文档