diff --git a/README.md b/README.md index 21fc8e36a..07e6f6c91 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,29 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 ## 特色优势 -- 开源: 零门槛,线上快速获取和安装; -- 分布式: 轻松支持大规模并发访问; +- 开源: 零门槛,线上快速获取和安装, 修复版本视情况而定; +, 修复版本视情况而定- 分布式: 轻松支持大规模并发访问; - 无插件: 仅需浏览器,极致的 Web Terminal 使用体验; - 多云支持: 一套系统,同时管理不同云上面的资产; - 云端存储: 审计录像云端存储,永不丢失; - 多租户: 一套系统,多个子公司和部门同时使用。 +## 版本说明 + +自 v2.0.0 发布后, JumpServer 版本号命名将变更为:v大版本.功能版本.Bug修复版本。比如: + +``` +v2.0.1 是 v2.0.0 之后的Bug修复版本; +v2.1.0 是 v2.0.0 之后的功能版本。 +``` + +像其它优秀开源项目一样,JumpServer 每个月会发布一个功能版本,并同时维护 3 个功能版本。比如: + +``` +在 v2.4 发布前,我们会同时维护 v2.1、v2.2、v2.3; +在 v2.4 发布后,我们会同时维护 v2.2、v2.3、v2.4;v2.1 会停止维护。 +``` + ## 功能列表 diff --git a/apps/applications/api/remote_app.py b/apps/applications/api/remote_app.py index 79beef8fc..4bd9109fb 100644 --- a/apps/applications/api/remote_app.py +++ b/apps/applications/api/remote_app.py @@ -15,7 +15,7 @@ __all__ = [ class RemoteAppViewSet(OrgBulkModelViewSet): model = RemoteApp - filter_fields = ('name',) + filter_fields = ('name', 'type', 'comment') search_fields = filter_fields permission_classes = (IsOrgAdmin,) serializer_class = RemoteAppSerializer diff --git a/apps/applications/forms/__init__.py b/apps/applications/forms/__init__.py deleted file mode 100644 index a707cfde6..000000000 --- a/apps/applications/forms/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .remote_app import * -from .database_app import * diff --git a/apps/applications/forms/database_app.py b/apps/applications/forms/database_app.py deleted file mode 100644 index 2b5a4c0cf..000000000 --- a/apps/applications/forms/database_app.py +++ /dev/null @@ -1,26 +0,0 @@ -# coding: utf-8 -# - - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from .. import models - -__all__ = ['DatabaseAppMySQLForm'] - - -class BaseDatabaseAppForm(forms.ModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['type'].widget.attrs['disabled'] = True - - class Meta: - model = models.DatabaseApp - fields = [ - 'name', 'type', 'host', 'port', 'database', 'comment' - ] - - -class DatabaseAppMySQLForm(BaseDatabaseAppForm): - pass diff --git a/apps/applications/forms/remote_app.py b/apps/applications/forms/remote_app.py deleted file mode 100644 index 7a097fcc8..000000000 --- a/apps/applications/forms/remote_app.py +++ /dev/null @@ -1,120 +0,0 @@ -# coding: utf-8 -# - -from django.utils.translation import ugettext as _ -from django import forms - -from orgs.mixins.forms import OrgModelForm - -from ..models import RemoteApp - - -__all__ = [ - 'RemoteAppChromeForm', 'RemoteAppMySQLWorkbenchForm', - 'RemoteAppVMwareForm', 'RemoteAppCustomForm' -] - - -class BaseRemoteAppForm(OrgModelForm): - default_initial_data = {} - - def __init__(self, *args, **kwargs): - # 过滤RDP资产和系统用户 - super().__init__(*args, **kwargs) - field_asset = self.fields['asset'] - field_asset.queryset = field_asset.queryset.has_protocol('rdp') - self.fields['type'].widget.attrs['disabled'] = True - self.fields.move_to_end('comment') - self.initial_default() - - def initial_default(self): - for name, value in self.default_initial_data.items(): - field = self.fields.get(name) - if not field: - continue - field.initial = value - - class Meta: - model = RemoteApp - fields = [ - 'name', 'asset', 'type', 'path', 'comment' - ] - widgets = { - 'asset': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Asset') - }), - } - - -class RemoteAppChromeForm(BaseRemoteAppForm): - default_initial_data = { - 'path': r'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe' - } - - chrome_target = forms.CharField( - max_length=128, label=_('Target URL'), required=False - ) - chrome_username = forms.CharField( - max_length=128, label=_('Login username'), required=False - ) - chrome_password = forms.CharField( - widget=forms.PasswordInput, strip=True, - max_length=128, label=_('Login password'), required=False - ) - - -class RemoteAppMySQLWorkbenchForm(BaseRemoteAppForm): - default_initial_data = { - 'path': r'C:\Program Files\MySQL\MySQL Workbench 8.0 CE' - r'\MySQLWorkbench.exe' - } - - mysql_workbench_ip = forms.CharField( - max_length=128, label=_('Database IP'), required=False - ) - mysql_workbench_name = forms.CharField( - max_length=128, label=_('Database name'), required=False - ) - mysql_workbench_username = forms.CharField( - max_length=128, label=_('Database username'), required=False - ) - mysql_workbench_password = forms.CharField( - widget=forms.PasswordInput, strip=True, - max_length=128, label=_('Database password'), required=False - ) - - -class RemoteAppVMwareForm(BaseRemoteAppForm): - default_initial_data = { - 'path': r'C:\Program Files (x86)\VMware\Infrastructure' - r'\Virtual Infrastructure Client\Launcher\VpxClient.exe' - } - - vmware_target = forms.CharField( - max_length=128, label=_('Target address'), required=False - ) - vmware_username = forms.CharField( - max_length=128, label=_('Login username'), required=False - ) - vmware_password = forms.CharField( - widget=forms.PasswordInput, strip=True, - max_length=128, label=_('Login password'), required=False - ) - - -class RemoteAppCustomForm(BaseRemoteAppForm): - - custom_cmdline = forms.CharField( - max_length=128, label=_('Operating parameter'), required=False - ) - custom_target = forms.CharField( - max_length=128, label=_('Target address'), required=False - ) - custom_username = forms.CharField( - max_length=128, label=_('Login username'), required=False - ) - custom_password = forms.CharField( - widget=forms.PasswordInput, strip=True, - max_length=128, label=_('Login password'), required=False - ) - diff --git a/apps/applications/templates/applications/database_app_create_update.html b/apps/applications/templates/applications/database_app_create_update.html deleted file mode 100644 index 84635e2d0..000000000 --- a/apps/applications/templates/applications/database_app_create_update.html +++ /dev/null @@ -1,55 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - -{% block form %} - - {% bootstrap_form form layout="horizontal" %} -
-
-
- - -
-
- -{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} \ No newline at end of file diff --git a/apps/applications/templates/applications/database_app_detail.html b/apps/applications/templates/applications/database_app_detail.html deleted file mode 100644 index 153bfa98a..000000000 --- a/apps/applications/templates/applications/database_app_detail.html +++ /dev/null @@ -1,103 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ database_app.name }} -
- - - - - - - - - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ database_app.name }}
{% trans 'Type' %}:{{ database_app.get_type_display }}
{% trans 'Host' %}:{{ database_app.host }}
{% trans 'Port' %}:{{ database_app.port }}
{% trans 'Database' %}:{{ database_app.database }}
{% trans 'Date created' %}:{{ database_app.date_created }}
{% trans 'Created by' %}:{{ database_app.created_by }}
{% trans 'Comment' %}:{{ database_app.comment }}
- - - - - - - - -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/templates/applications/database_app_list.html b/apps/applications/templates/applications/database_app_list.html deleted file mode 100644 index 74a5c907e..000000000 --- a/apps/applications/templates/applications/database_app_list.html +++ /dev/null @@ -1,88 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block help_message %} -{% endblock %} -{% block table_search %}{% endblock %} -{% block table_container %} -
- - -
- - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Type' %}{% trans 'Host' %}{% trans 'Port' %}{% trans 'Database' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/templates/applications/remote_app_create_update.html b/apps/applications/templates/applications/remote_app_create_update.html deleted file mode 100644 index 440219936..000000000 --- a/apps/applications/templates/applications/remote_app_create_update.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
- {% bootstrap_form form layout="horizontal" %} -
-
-
- - - -
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} \ No newline at end of file diff --git a/apps/applications/templates/applications/remote_app_detail.html b/apps/applications/templates/applications/remote_app_detail.html deleted file mode 100644 index da7c2e72d..000000000 --- a/apps/applications/templates/applications/remote_app_detail.html +++ /dev/null @@ -1,100 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ remote_app.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ remote_app.name }}
{% trans 'Asset' %}:{{ remote_app.asset.hostname }}
{% trans 'App type' %}:{{ remote_app.get_type_display }}
{% trans 'App path' %}:{{ remote_app.path }}
{% trans 'Date created' %}:{{ remote_app.date_created }}
{% trans 'Created by' %}:{{ remote_app.created_by }}
{% trans 'Comment' %}:{{ remote_app.comment }}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/templates/applications/remote_app_list.html b/apps/applications/templates/applications/remote_app_list.html deleted file mode 100644 index 709152489..000000000 --- a/apps/applications/templates/applications/remote_app_list.html +++ /dev/null @@ -1,92 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block help_message %} - {% trans 'Before using this feature, make sure that the application loader has been uploaded to the application server and successfully published as a RemoteApp application' %} - {% trans 'Download application loader' %} -{% endblock %} -{% block table_search %}{% endblock %} -{% block table_container %} -
- - -
- - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'App type' %}{% trans 'Asset' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/templates/applications/user_database_app_list.html b/apps/applications/templates/applications/user_database_app_list.html deleted file mode 100644 index 1edaacd76..000000000 --- a/apps/applications/templates/applications/user_database_app_list.html +++ /dev/null @@ -1,83 +0,0 @@ -{% extends 'base.html' %} -{% load i18n static %} - -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
- - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Type' %}{% trans 'Host' %}{% trans 'Database' %}{% trans 'Comment' %}{% trans 'Action' %}
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/templates/applications/user_remote_app_list.html b/apps/applications/templates/applications/user_remote_app_list.html deleted file mode 100644 index 576c7ed14..000000000 --- a/apps/applications/templates/applications/user_remote_app_list.html +++ /dev/null @@ -1,73 +0,0 @@ -{% extends 'base.html' %} -{% load i18n static %} - -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
- - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'App type' %}{% trans 'Asset' %}{% trans 'Comment' %}{% trans 'Action' %}
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/urls/views_urls.py b/apps/applications/urls/views_urls.py index 663b0878d..2b09baf9f 100644 --- a/apps/applications/urls/views_urls.py +++ b/apps/applications/urls/views_urls.py @@ -1,23 +1,7 @@ # coding:utf-8 from django.urls import path -from .. import views app_name = 'applications' urlpatterns = [ - # RemoteApp - path('remote-app/', views.RemoteAppListView.as_view(), name='remote-app-list'), - path('remote-app/create/', views.RemoteAppCreateView.as_view(), name='remote-app-create'), - path('remote-app//update/', views.RemoteAppUpdateView.as_view(), name='remote-app-update'), - path('remote-app//', views.RemoteAppDetailView.as_view(), name='remote-app-detail'), - # User RemoteApp view - path('user-remote-app/', views.UserRemoteAppListView.as_view(), name='user-remote-app-list'), - - path('database-app/', views.DatabaseAppListView.as_view(), name='database-app-list'), - path('database-app/create/', views.DatabaseAppCreateView.as_view(), name='database-app-create'), - path('database-app//update/', views.DatabaseAppUpdateView.as_view(), name='database-app-update'), - path('database-app//', views.DatabaseAppDetailView.as_view(), name='database-app-detail'), - # User DatabaseApp view - path('user-database-app/', views.UserDatabaseAppListView.as_view(), name='user-database-app-list'), - ] diff --git a/apps/applications/views/__init__.py b/apps/applications/views/__init__.py deleted file mode 100644 index a707cfde6..000000000 --- a/apps/applications/views/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .remote_app import * -from .database_app import * diff --git a/apps/applications/views/database_app.py b/apps/applications/views/database_app.py deleted file mode 100644 index 21d2b4f7c..000000000 --- a/apps/applications/views/database_app.py +++ /dev/null @@ -1,115 +0,0 @@ -# coding: utf-8 -# - -from django.http import Http404 -from django.views.generic import TemplateView -from django.views.generic.edit import CreateView, UpdateView -from django.utils.translation import ugettext_lazy as _ -from django.views.generic.detail import DetailView - -from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser - -from .. import models, const, forms - -__all__ = [ - 'DatabaseAppListView', 'DatabaseAppCreateView', 'DatabaseAppUpdateView', - 'DatabaseAppDetailView', 'UserDatabaseAppListView', -] - - -class DatabaseAppListView(PermissionsMixin, TemplateView): - template_name = 'applications/database_app_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _("Application"), - 'action': _('DatabaseApp list'), - 'type_choices': const.DATABASE_APP_TYPE_CHOICES - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class BaseDatabaseAppCreateUpdateView: - template_name = 'applications/database_app_create_update.html' - model = models.DatabaseApp - permission_classes = [IsOrgAdmin] - default_type = const.DATABASE_APP_TYPE_MYSQL - form_class = forms.DatabaseAppMySQLForm - form_class_choices = { - const.DATABASE_APP_TYPE_MYSQL: forms.DatabaseAppMySQLForm, - } - - def get_initial(self): - return {'type': self.get_type()} - - def get_type(self): - return self.default_type - - def get_form_class(self): - tp = self.get_type() - form_class = self.form_class_choices.get(tp) - if not form_class: - raise Http404() - return form_class - - -class DatabaseAppCreateView(BaseDatabaseAppCreateUpdateView, CreateView): - - def get_type(self): - tp = self.request.GET.get("type") - if tp: - return tp.lower() - return super().get_type() - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('Create DatabaseApp'), - 'api_action': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppUpdateView(BaseDatabaseAppCreateUpdateView, UpdateView): - - def get_type(self): - return self.object.type - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('Create DatabaseApp'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppDetailView(PermissionsMixin, DetailView): - template_name = 'applications/database_app_detail.html' - model = models.DatabaseApp - context_object_name = 'database_app' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('DatabaseApp detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserDatabaseAppListView(PermissionsMixin, TemplateView): - template_name = 'applications/user_database_app_list.html' - permission_classes = [IsValidUser] - - def get_context_data(self, **kwargs): - context = { - 'action': _('My DatabaseApp'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/applications/views/remote_app.py b/apps/applications/views/remote_app.py deleted file mode 100644 index 92b436005..000000000 --- a/apps/applications/views/remote_app.py +++ /dev/null @@ -1,128 +0,0 @@ -# coding: utf-8 -# - -from django.http import Http404 -from django.utils.translation import ugettext as _ -from django.views.generic import TemplateView -from django.views.generic.edit import CreateView, UpdateView -from django.views.generic.detail import DetailView - -from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser - -from ..models import RemoteApp -from .. import forms, const - - -__all__ = [ - 'RemoteAppListView', 'RemoteAppCreateView', 'RemoteAppUpdateView', - 'RemoteAppDetailView', 'UserRemoteAppListView', -] - - -class RemoteAppListView(PermissionsMixin, TemplateView): - template_name = 'applications/remote_app_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('RemoteApp list'), - 'type_choices': const.REMOTE_APP_TYPE_CHOICES, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class BaseRemoteAppCreateUpdateView: - template_name = 'applications/remote_app_create_update.html' - model = RemoteApp - permission_classes = [IsOrgAdmin] - default_type = const.REMOTE_APP_TYPE_CHROME - form_class = forms.RemoteAppChromeForm - form_class_choices = { - const.REMOTE_APP_TYPE_CHROME: forms.RemoteAppChromeForm, - const.REMOTE_APP_TYPE_MYSQL_WORKBENCH: forms.RemoteAppMySQLWorkbenchForm, - const.REMOTE_APP_TYPE_VMWARE_CLIENT: forms.RemoteAppVMwareForm, - const.REMOTE_APP_TYPE_CUSTOM: forms.RemoteAppCustomForm - } - - def get_initial(self): - return {'type': self.get_type()} - - def get_type(self): - return self.default_type - - def get_form_class(self): - tp = self.get_type() - form_class = self.form_class_choices.get(tp) - if not form_class: - raise Http404() - return form_class - - -class RemoteAppCreateView(BaseRemoteAppCreateUpdateView, - PermissionsMixin, CreateView): - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('Create RemoteApp'), - 'api_action': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def get_type(self): - tp = self.request.GET.get("type") - if tp: - return tp.lower() - return super().get_type() - - -class RemoteAppUpdateView(BaseRemoteAppCreateUpdateView, - PermissionsMixin, UpdateView): - - def get_initial(self): - initial_data = super().get_initial() - params = {k: v for k, v in self.object.params.items()} - initial_data.update(params) - return initial_data - - def get_type(self): - return self.object.type - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('Update RemoteApp'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppDetailView(PermissionsMixin, DetailView): - template_name = 'applications/remote_app_detail.html' - model = RemoteApp - context_object_name = 'remote_app' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('RemoteApp detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserRemoteAppListView(PermissionsMixin, TemplateView): - template_name = 'applications/user_remote_app_list.html' - permission_classes = [IsValidUser] - - def get_context_data(self, **kwargs): - context = { - 'action': _('My RemoteApp'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index c77da1ae4..58fc78f71 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -1,17 +1,4 @@ -# ~*~ 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 django.db import transaction from django.db.models import Count @@ -49,7 +36,7 @@ class AdminUserViewSet(OrgBulkModelViewSet): def get_queryset(self): queryset = super().get_queryset() - queryset = queryset.annotate(_assets_amount=Count('assets')) + queryset = queryset.annotate(assets_amount=Count('assets')) return queryset def destroy(self, request, *args, **kwargs): diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 6eb8ebeaf..dee95ed06 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- # -import random - -from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from rest_framework.generics import RetrieveAPIView from django.shortcuts import get_object_or_404 @@ -33,7 +30,10 @@ class AssetViewSet(OrgBulkModelViewSet): API endpoint that allows Asset to be viewed or edited. """ model = Asset - filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id") + filter_fields = ( + "hostname", "ip", "systemuser__id", "admin_user__id", "platform__base", + "is_active" + ) search_fields = ("hostname", "ip") ordering_fields = ("hostname", "ip", "port", "cpu_cores") serializer_classes = { @@ -74,12 +74,16 @@ class AssetPlatformViewSet(ModelViewSet): queryset = Platform.objects.all() permission_classes = (IsSuperUser,) serializer_class = serializers.PlatformSerializer - filterset_fields = ['name', 'base'] + filter_fields = ['name', 'base'] search_fields = ['name'] + def get_permissions(self): + if self.request.method.lower() in ['get', 'options']: + self.permission_classes = (IsOrgAdmin,) + return super().get_permissions() + def check_object_permissions(self, request, obj): - if request.method.lower() in ['delete', 'put', 'patch'] and \ - obj.internal: + if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: self.permission_denied( request, message={"detail": "Internal platform"} ) diff --git a/apps/assets/api/asset_user.py b/apps/assets/api/asset_user.py index c0d834737..932d5b77e 100644 --- a/apps/assets/api/asset_user.py +++ b/apps/assets/api/asset_user.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +import coreapi from django.conf import settings from rest_framework.response import Response from rest_framework import generics, filters @@ -54,6 +55,15 @@ class AssetUserSearchBackend(filters.BaseFilterBackend): class AssetUserLatestFilterBackend(filters.BaseFilterBackend): + def get_schema_fields(self, view): + return [ + coreapi.Field( + name='latest', location='query', required=False, + type='string', example='1', + description='Only the latest version' + ) + ] + def filter_queryset(self, request, queryset, view): latest = request.GET.get('latest') == '1' if latest: @@ -64,7 +74,7 @@ class AssetUserLatestFilterBackend(filters.BaseFilterBackend): class AssetUserViewSet(CommonApiMixin, BulkModelViewSet): serializer_classes = { 'default': serializers.AssetUserWriteSerializer, - 'list': serializers.AssetUserReadSerializer, + 'display': serializers.AssetUserReadSerializer, 'retrieve': serializers.AssetUserReadSerializer, } permission_classes = [IsOrgAdminOrAppUser] @@ -84,12 +94,15 @@ class AssetUserViewSet(CommonApiMixin, BulkModelViewSet): def get_object(self): pk = self.kwargs.get("pk") + if pk is None: + return queryset = self.get_queryset() obj = queryset.get(id=pk) return obj def get_exception_handler(self): def handler(e, context): + logger.error(e, exc_info=True) return Response({"error": str(e)}, status=400) return handler diff --git a/apps/assets/api/gathered_user.py b/apps/assets/api/gathered_user.py index b9f137648..e024fedb1 100644 --- a/apps/assets/api/gathered_user.py +++ b/apps/assets/api/gathered_user.py @@ -18,5 +18,5 @@ class GatheredUserViewSet(OrgModelViewSet): permission_classes = [IsOrgAdmin] extra_filter_backends = [AssetRelatedByNodeFilterBackend] - filter_fields = ['asset', 'username', 'present'] + filter_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname'] search_fields = ['username', 'asset__ip', 'asset__hostname'] diff --git a/apps/assets/api/system_user_relation.py b/apps/assets/api/system_user_relation.py index 69605b74a..bffa283ec 100644 --- a/apps/assets/api/system_user_relation.py +++ b/apps/assets/api/system_user_relation.py @@ -65,7 +65,7 @@ class SystemUserAssetRelationViewSet(BaseRelationViewSet): serializer_class = serializers.SystemUserAssetRelationSerializer model = models.SystemUser.assets.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'asset', 'systemuser', ] search_fields = [ @@ -91,7 +91,7 @@ class SystemUserNodeRelationViewSet(BaseRelationViewSet): serializer_class = serializers.SystemUserNodeRelationSerializer model = models.SystemUser.nodes.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'node', 'systemuser', ] search_fields = [ @@ -112,7 +112,7 @@ class SystemUserUserRelationViewSet(BaseRelationViewSet): serializer_class = serializers.SystemUserUserRelationSerializer model = models.SystemUser.users.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'user', 'systemuser', ] search_fields = [ diff --git a/apps/assets/filters.py b/apps/assets/filters.py index 94a49a3f5..13d8f9e60 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -65,7 +65,7 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend): class LabelFilterBackend(filters.BaseFilterBackend): - sep = '#' + sep = ':' query_arg = 'label' def get_schema_fields(self, view): @@ -84,6 +84,8 @@ class LabelFilterBackend(filters.BaseFilterBackend): q = None for kv in labels_query: + if '#' in kv: + self.sep = '#' if self.sep not in kv: continue key, value = kv.strip().split(self.sep)[:2] diff --git a/apps/assets/forms/__init__.py b/apps/assets/forms/__init__.py deleted file mode 100644 index 39b39a45a..000000000 --- a/apps/assets/forms/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -# -from .asset import * -from .label import * -from .user import * -from .domain import * -from .cmd_filter import * -from .platform import * diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py deleted file mode 100644 index 54d97f036..000000000 --- a/apps/assets/forms/asset.py +++ /dev/null @@ -1,172 +0,0 @@ -# -*- coding: utf-8 -*- -# -from itertools import groupby -from django import forms -from django.utils.translation import gettext_lazy as _ - -from common.utils import get_logger -from orgs.mixins.forms import OrgModelForm - -from ..models import Asset, Platform - - -logger = get_logger(__file__) -__all__ = [ - 'AssetCreateUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm', -] - - -class ProtocolForm(forms.Form): - name = forms.ChoiceField( - choices=Asset.PROTOCOL_CHOICES, label=_("Name"), initial='ssh', - widget=forms.Select(attrs={'class': 'form-control protocol-name'}) - ) - port = forms.IntegerField( - max_value=65534, min_value=1, label=_("Port"), initial=22, - widget=forms.TextInput(attrs={'class': 'form-control protocol-port'}) - ) - - -class AssetCreateUpdateForm(OrgModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_platform_to_name() - self.set_fields_queryset() - - def set_fields_queryset(self): - nodes_field = self.fields['nodes'] - nodes_choices = [] - if self.instance: - nodes_choices = [ - (n.id, n.full_value) for n in - self.instance.nodes.all() - ] - nodes_field.choices = nodes_choices - - @staticmethod - def sorted_platform(platform): - if platform['base'] == 'Other': - return 'zz' - return platform['base'] - - def set_platform_to_name(self): - choices = [] - platforms = Platform.objects.all().values('name', 'base') - platforms_sorted = sorted(platforms, key=self.sorted_platform) - platforms_grouped = groupby(platforms_sorted, key=lambda x: x['base']) - for i in platforms_grouped: - base = i[0] - grouped = sorted(i[1], key=lambda x: x['name']) - grouped = [(j['name'], j['name']) for j in grouped] - choices.append( - (base, grouped) - ) - platform_field = self.fields['platform'] - platform_field.choices = choices - if self.instance: - self.initial['platform'] = self.instance.platform.name - - def add_nodes_initial(self, node): - nodes_field = self.fields['nodes'] - nodes_field.choices.append((node.id, node.full_value)) - nodes_field.initial = [node] - - class Meta: - model = Asset - fields = [ - 'hostname', 'ip', 'public_ip', 'protocols', 'comment', - 'nodes', 'is_active', 'admin_user', 'labels', 'platform', - 'domain', 'number', - ] - widgets = { - 'nodes': forms.SelectMultiple(attrs={ - 'class': 'nodes-select2', 'data-placeholder': _('Nodes') - }), - 'admin_user': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Admin user') - }), - 'labels': forms.SelectMultiple(attrs={ - 'class': 'select2', 'data-placeholder': _('Label') - }), - 'domain': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Domain') - }), - 'platform': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Platform') - }), - } - labels = { - 'nodes': _("Node"), - } - help_texts = { - 'admin_user': _( - 'root or other NOPASSWD sudo privilege user existed in asset,' - 'If asset is windows or other set any one, more see admin user left menu' - ), - 'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"), - 'domain': _("If your have some network not connect with each other, you can set domain") - } - - -class AssetBulkUpdateForm(OrgModelForm): - assets = forms.ModelMultipleChoiceField( - required=True, - label=_('Select assets'), queryset=Asset.objects, - widget=forms.SelectMultiple( - attrs={ - 'class': 'select2', - 'data-placeholder': _('Select assets') - } - ) - ) - - class Meta: - model = Asset - fields = [ - 'assets', 'admin_user', 'labels', 'platform', - 'domain', - ] - widgets = { - 'labels': forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Label')} - ), - 'nodes': forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Node')} - ), - } - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_fields_queryset() - - # 重写其他字段为不再required - for name, field in self.fields.items(): - if name != 'assets': - field.required = False - - def set_fields_queryset(self): - assets_field = self.fields['assets'] - if hasattr(self, 'data'): - assets_field.queryset = Asset.objects.all() - - def save(self, commit=True): - changed_fields = [] - for field in self._meta.fields: - if self.data.get(field) not in [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', []) - nodes = cleaned_data.pop('nodes', None) - 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) - if nodes: - for asset in assets: - asset.nodes.set(nodes) - return assets diff --git a/apps/assets/forms/cmd_filter.py b/apps/assets/forms/cmd_filter.py deleted file mode 100644 index 46f8fc244..000000000 --- a/apps/assets/forms/cmd_filter.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django import forms -from django.core.exceptions import ValidationError -from django.utils.translation import ugettext_lazy as _ -import re - -from orgs.mixins.forms import OrgModelForm -from ..models import CommandFilter, CommandFilterRule - -__all__ = ['CommandFilterForm', 'CommandFilterRuleForm'] - - -class CommandFilterForm(OrgModelForm): - class Meta: - model = CommandFilter - fields = ['name', 'comment'] - - -class CommandFilterRuleForm(OrgModelForm): - invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]') - - class Meta: - model = CommandFilterRule - fields = [ - 'filter', 'type', 'content', 'priority', 'action', 'comment' - ] - widgets = { - 'content': forms.Textarea(attrs={ - 'placeholder': 'eg:\r\nreboot\r\nrm -rf' - }), - } - - def clean_content(self): - content = self.cleaned_data.get("content") - if self.invalid_pattern.search(content): - invalid_char = self.invalid_pattern.pattern.replace('\\', '') - msg = _("Content should not be contain: {}").format(invalid_char) - raise ValidationError(msg) - return content diff --git a/apps/assets/forms/domain.py b/apps/assets/forms/domain.py deleted file mode 100644 index 71d9f6cfa..000000000 --- a/apps/assets/forms/domain.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django import forms -from django.utils.translation import gettext_lazy as _ - -from orgs.mixins.forms import OrgModelForm -from ..models import Domain, Asset, Gateway -from .user import PasswordAndKeyAuthForm - -__all__ = ['DomainForm', 'GatewayForm'] - - -class DomainForm(forms.ModelForm): - assets = forms.ModelMultipleChoiceField( - queryset=Asset.objects, label=_('Asset'), required=False, - widget=forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Select assets')} - ) - ) - - class Meta: - model = Domain - fields = ['name', 'comment', 'assets'] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_fields_queryset() - - def set_fields_queryset(self): - assets_field = self.fields.get('assets') - - # 没有data代表是渲染表单, 有data代表是提交创建/更新表单 - if not self.data: - # 有instance 代表渲染更新表单, 否则是创建表单 - # 前端渲染优化, 防止过多资产, 设置assets queryset为none - if self.instance: - assets_field.initial = self.instance.assets.all() - assets_field.queryset = self.instance.assets.all() - else: - assets_field.queryset = Asset.objects.none() - else: - assets_field.queryset = Asset.objects.all() - - def save(self, commit=True): - instance = super().save(commit=commit) - assets = self.cleaned_data['assets'] - instance.assets.set(assets) - return instance - - -class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - password_field = self.fields.get('password') - password_field.help_text = _('Password should not contain special characters') - protocol_field = self.fields.get('protocol') - protocol_field.choices = [Gateway.PROTOCOL_CHOICES[0]] - - def save(self, commit=True): - # Because we define custom field, so we need rewrite :method: `save` - instance = super().save() - password = self.cleaned_data.get('password') - private_key, public_key = super().gen_keys() - instance.set_auth(password=password, private_key=private_key) - return instance - - class Meta: - model = Gateway - fields = [ - 'name', 'ip', 'port', 'username', 'protocol', 'domain', 'password', - 'private_key', 'is_active', 'comment', - ] - help_texts = { - 'protocol': _("SSH gateway support proxy SSH,RDP,VNC") - } - widgets = { - 'name': forms.TextInput(attrs={'placeholder': _('Name')}), - 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - } diff --git a/apps/assets/forms/label.py b/apps/assets/forms/label.py deleted file mode 100644 index 1f2f13987..000000000 --- a/apps/assets/forms/label.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- 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.none(), 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): - super().__init__(*args, **kwargs) - self.set_fields_queryset() - - def set_fields_queryset(self): - assets_field = self.fields.get('assets') - - # 没有data代表是渲染表单, 有data代表是提交创建/更新表单 - if not self.data: - # 有instance 代表渲染更新表单, 否则是创建表单 - # 前端渲染优化, 防止过多资产, 设置assets queryset为none - if self.instance: - assets_field.initial = self.instance.assets.all() - assets_field.queryset = self.instance.assets.all() - else: - assets_field.queryset = Asset.objects.none() - else: - assets_field.queryset = Asset.objects.all() - - 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/platform.py b/apps/assets/forms/platform.py deleted file mode 100644 index 88c4365d4..000000000 --- a/apps/assets/forms/platform.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from ..models import Platform - - -__all__ = ['PlatformForm', 'PlatformMetaForm'] - - -class PlatformMetaForm(forms.Form): - SECURITY_CHOICES = ( - ('rdp', "RDP"), - ('nla', "NLA"), - ('tls', 'TLS'), - ('any', "Any"), - ) - CONSOLE_CHOICES = ( - (True, _('Yes')), - (False, _('No')), - ) - security = forms.ChoiceField( - choices=SECURITY_CHOICES, initial='any', label=_("RDP security"), - required=False, - ) - console = forms.ChoiceField( - choices=CONSOLE_CHOICES, initial=False, label=_("RDP console"), - required=False, - ) - - -class PlatformForm(forms.ModelForm): - class Meta: - model = Platform - fields = [ - 'name', 'base', 'comment', - ] - labels = { - 'base': _("Base platform") - } - diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py deleted file mode 100644 index ff8d27dfd..000000000 --- a/apps/assets/forms/user.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django import forms -from django.utils.translation import gettext_lazy as _ - -from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger -from orgs.mixins.forms import OrgModelForm -from ..models import AdminUser, SystemUser - -logger = get_logger(__file__) -__all__ = [ - 'FileForm', 'SystemUserForm', 'AdminUserForm', 'PasswordAndKeyAuthForm', -] - - -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 = forms.FileField(required=False, label=_("Private key")) - - def clean_private_key(self): - private_key_f = self.cleaned_data['private_key'] - password = self.cleaned_data['password'] - - if private_key_f: - key_string = private_key_f.read() - private_key_f.seek(0) - key_string = key_string.decode() - - if not validate_ssh_private_key(key_string, password): - msg = _('Invalid private key, Only support ' - 'RSA/DSA format key') - raise forms.ValidationError(msg) - return private_key_f - - def validate_password_key(self): - password = self.cleaned_data['password'] - private_key_f = self.cleaned_data.get('private_key', '') - - if not password and not private_key_f: - 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_f = self.cleaned_data['private_key'] - public_key = private_key = None - - if private_key_f: - private_key = private_key_f.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): - raise forms.ValidationError("Use api to save") - - class Meta: - model = AdminUser - fields = ['name', 'username', 'password', 'private_key', 'comment'] - widgets = { - 'name': forms.TextInput(attrs={'placeholder': _('Name')}), - 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - } - - -class SystemUserForm(OrgModelForm, 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): - raise forms.ValidationError("Use api to save") - - class Meta: - model = SystemUser - fields = [ - 'name', 'username', 'protocol', 'auto_generate_key', - 'password', 'private_key', 'auto_push', 'sudo', - 'username_same_with_user', - 'comment', 'shell', 'priority', 'login_mode', 'cmd_filters', - 'sftp_root', - ] - widgets = { - 'name': forms.TextInput(attrs={'placeholder': _('Name')}), - 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - 'cmd_filters': forms.SelectMultiple(attrs={ - 'class': 'select2', 'data-placeholder': _('Command filter') - }), - } - labels = { - 'username_same_with_user': _("Username same with user"), - } - help_texts = { - 'auto_push': _('Auto push system user to asset'), - 'priority': _('1-100, High level will be using login asset as default, ' - 'if user was granted more than 2 system user'), - 'login_mode': _('If you choose manual login mode, you do not ' - 'need to fill in the username and password.'), - 'sudo': _("Use comma split multi command, ex: /bin/whoami,/bin/ifconfig"), - 'sftp_root': _("SFTP root dir, tmp, home or custom"), - 'username_same_with_user': _("Username is dynamic, When connect asset, using current user's username"), - # 'username_same_with_user': _("用户名是动态的,登录资产时使用当前用户的用户名登录"), - } diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 6b5e716e0..20c040f74 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -244,6 +244,10 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): def platform_base(self): return self.platform.base + @lazyproperty + def admin_user_display(self): + return self.admin_user.name + @lazyproperty def admin_user_username(self): """求可连接性时,直接用用户名去取,避免再查一次admin user diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index 82d6964a4..e36f41946 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from rest_framework import serializers -from django.db.models import Prefetch, F +from django.db.models import Prefetch, F, Count from django.utils.translation import ugettext_lazy as _ @@ -73,21 +73,35 @@ class AssetSerializer(BulkOrgResourceModelSerializer): class Meta: model = Asset list_serializer_class = AdaptedBulkListSerializer - fields = [ - 'id', 'ip', 'hostname', 'protocol', 'port', - 'protocols', 'platform', 'is_active', 'public_ip', 'domain', - 'admin_user', 'nodes', 'labels', 'number', 'vendor', 'model', 'sn', - 'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory', - 'disk_total', 'disk_info', 'os', 'os_version', 'os_arch', - 'hostname_raw', 'comment', 'created_by', 'date_created', - 'hardware_info', + fields_mini = ['id', 'hostname', 'ip'] + fields_small = fields_mini + [ + 'protocol', 'port', 'protocols', 'is_active', 'public_ip', + 'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', + 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', + 'os', 'os_version', 'os_arch', 'hostname_raw', 'comment', + 'created_by', 'date_created', 'hardware_info', ] - read_only_fields = ( + fields_fk = [ + 'admin_user', 'admin_user_display', 'domain', 'platform' + ] + fk_only_fields = { + 'platform': ['name'] + } + fields_m2m = [ + 'nodes', 'labels', + ] + annotates_fields = { + # 'admin_user_display': 'admin_user__name' + } + fields_as = list(annotates_fields.keys()) + fields = fields_small + fields_fk + fields_m2m + fields_as + read_only_fields = [ 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', 'os', 'os_version', 'os_arch', 'hostname_raw', 'created_by', 'date_created', - ) + ] + fields_as + extra_kwargs = { 'protocol': {'write_only': True}, 'port': {'write_only': True}, @@ -98,11 +112,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related( - Prefetch('nodes', queryset=Node.objects.all().only('id')), - Prefetch('labels', queryset=Label.objects.all().only('id')), - ).select_related('admin_user', 'domain', 'platform') \ - .annotate(platform_base=F('platform__base')) + queryset = queryset.select_related('admin_user', 'domain', 'platform') return queryset def compatible_with_old_protocol(self, validated_data): @@ -134,14 +144,8 @@ class AssetDisplaySerializer(AssetSerializer): connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity")) class Meta(AssetSerializer.Meta): - fields = [ - 'id', 'ip', 'hostname', 'protocol', 'port', - 'protocols', 'is_active', 'public_ip', - 'number', 'vendor', 'model', 'sn', - 'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory', - 'disk_total', 'disk_info', 'os', 'os_version', 'os_arch', - 'hostname_raw', 'comment', 'created_by', 'date_created', - 'hardware_info', 'connectivity', + fields = AssetSerializer.Meta.fields + [ + 'connectivity', ] @classmethod diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py index 6559a8189..5a045df9c 100644 --- a/apps/assets/serializers/cmd_filter.py +++ b/apps/assets/serializers/cmd_filter.py @@ -2,7 +2,6 @@ # import re from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ from common.fields import ChoiceDisplayField from common.serializers import AdaptedBulkListSerializer @@ -27,11 +26,20 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer): class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): - serializer_choice_field = ChoiceDisplayField + # serializer_choice_field = ChoiceDisplayField invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]') + type_display = serializers.ReadOnlyField(source='get_type_display') + action_display = serializers.ReadOnlyField(source='get_action_display') class Meta: model = CommandFilterRule + fields_mini = ['id'] + fields_small = fields_mini + [ + 'type', 'type_display', 'content', 'priority', + 'action', 'action_display', + 'comment', 'created_by', 'date_created', 'date_updated' + ] + fields_fk = ['filter'] fields = '__all__' list_serializer_class = AdaptedBulkListSerializer diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 630941860..64c5eae66 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -15,11 +15,18 @@ class DomainSerializer(BulkOrgResourceModelSerializer): class Meta: model = Domain - fields = [ - 'id', 'name', 'asset_count', 'gateway_count', 'comment', 'assets', - 'date_created' + fields_mini = ['id', 'name'] + fields_small = fields_mini + [ + 'comment', 'date_created' ] - read_only_fields = ( 'asset_count', 'gateway_count', 'date_created') + fields_m2m = [ + 'asset_count', 'assets', 'gateway_count', + ] + fields = fields_small + fields_m2m + read_only_fields = ('asset_count', 'gateway_count', 'date_created') + extra_kwargs = { + 'assets': {'required': False} + } list_serializer_class = AdaptedBulkListSerializer @staticmethod @@ -41,6 +48,16 @@ class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): 'date_updated', 'created_by', 'comment', ] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.protocol_limit_to_ssh() + + def protocol_limit_to_ssh(self): + protocol_field = self.fields['protocol'] + choices = protocol_field.choices + choices.pop('rdp') + protocol_field._choices = choices + class GatewayWithAuthSerializer(GatewaySerializer): def get_field_names(self, declared_fields, info): @@ -51,6 +68,8 @@ class GatewayWithAuthSerializer(GatewaySerializer): return fields + + class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer): gateways = GatewayWithAuthSerializer(many=True, read_only=True) diff --git a/apps/assets/serializers/gathered_user.py b/apps/assets/serializers/gathered_user.py index c055e25bd..2629a8327 100644 --- a/apps/assets/serializers/gathered_user.py +++ b/apps/assets/serializers/gathered_user.py @@ -16,7 +16,7 @@ class GatheredUserSerializer(OrgResourceModelSerializerMixin): 'present', 'date_created', 'date_updated' ] read_only_fields = fields - labels = { - 'hostname': _("Hostname"), - 'ip': "IP" + extra_kwargs = { + 'hostname': {'label': _("Hostname")}, + 'ip': {'label': 'IP'}, } diff --git a/apps/assets/serializers/label.py b/apps/assets/serializers/label.py index de5ab7ea2..448018eab 100644 --- a/apps/assets/serializers/label.py +++ b/apps/assets/serializers/label.py @@ -20,6 +20,9 @@ class LabelSerializer(BulkOrgResourceModelSerializer): read_only_fields = ( 'category', 'date_created', 'asset_count', 'get_category_display' ) + extra_kwargs = { + 'assets': {'required': False} + } list_serializer_class = AdaptedBulkListSerializer @staticmethod diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 6a9a31b9f..7f3ad5372 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -34,7 +34,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): 'priority', 'username_same_with_user', 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'auto_generate_key', 'sftp_root', - 'assets_amount', + 'assets_amount', 'date_created', 'created_by' ] extra_kwargs = { 'password': {"write_only": True}, diff --git a/apps/assets/templates/assets/_asset_group_bulk_update_modal.html b/apps/assets/templates/assets/_asset_group_bulk_update_modal.html deleted file mode 100644 index 7df6c4ede..000000000 --- a/apps/assets/templates/assets/_asset_group_bulk_update_modal.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}asset_group_bulk_update_modal{% endblock %} -{% block modal_class %}modal-lg{% endblock %} -{% block modal_title%}{% trans "Update asset group" %}{% endblock %} -{% block modal_body %} -{% load bootstrap3 %} -

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

-
-
- -
- -
-
-
- -
- -
-
- -
-
-
- -
-
-
- -
-{% endblock %} -{% block modal_confirm_id %}btn_asset_group_bulk_update{% endblock %} diff --git a/apps/assets/templates/assets/_asset_list_modal.html b/apps/assets/templates/assets/_asset_list_modal.html deleted file mode 100644 index dea2c3e1e..000000000 --- a/apps/assets/templates/assets/_asset_list_modal.html +++ /dev/null @@ -1,224 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% load static %} - -{% block modal_class %}modal-lg{% endblock %} -{% block modal_id %}asset_list_modal{% endblock %} -{% block modal_title%}{% trans "Asset list" %}{% endblock %} -{% block modal_body %} - - - - - -
-
-
-
-
-
-
- {% trans 'Loading' %} ... -
-
-
-
-
-
-
-
- - - - - - - - - - -
{% trans 'Hostname' %}{% trans 'IP' %}
-
-
-
-
- - -{% endblock %} - -{% block modal_button %} - {{ block.super }} -{% endblock %} -{% block modal_confirm_id %}btn_asset_modal_confirm{% endblock %} - - - diff --git a/apps/assets/templates/assets/_asset_user_auth_update_modal.html b/apps/assets/templates/assets/_asset_user_auth_update_modal.html deleted file mode 100644 index fe88b7426..000000000 --- a/apps/assets/templates/assets/_asset_user_auth_update_modal.html +++ /dev/null @@ -1,87 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}asset_user_auth_update_modal{% endblock %} -{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %} -{% block modal_body %} -
- {% csrf_token %} -
- -
-

-
-
-
- -
-

-
-
-
- -
- -
-
-
- -
-
-
- -
-
-
-
-
- -{% endblock %} -{% block modal_confirm_id %}btn_asset_user_auth_update_modal_confirm{% endblock %} diff --git a/apps/assets/templates/assets/_asset_user_auth_view_modal.html b/apps/assets/templates/assets/_asset_user_auth_view_modal.html deleted file mode 100644 index 8cc3a78de..000000000 --- a/apps/assets/templates/assets/_asset_user_auth_view_modal.html +++ /dev/null @@ -1,102 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% load static %} -{% block modal_id %}asset_user_auth_view{% endblock %} -{% block modal_title%}{% trans "Asset user auth" %}{% endblock %} -{% block modal_body %} - -
-
-
- -
-

-
-
-
- -
-

-
-
-
- -
- -
-
- - -
-
-
-
- - -{% endblock %} -{% block modal_button %} - -{% endblock %} diff --git a/apps/assets/templates/assets/_asset_user_list.html b/apps/assets/templates/assets/_asset_user_list.html deleted file mode 100644 index d43cea10f..000000000 --- a/apps/assets/templates/assets/_asset_user_list.html +++ /dev/null @@ -1,188 +0,0 @@ -{% load i18n %} - - - - - - - - - - -{# #} - - - - - - -
- - {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Username' %}{% trans 'Version' %}{% trans 'Connectivity'%}{% trans 'Datetime' %}{% trans 'Action' %}
-{% include 'assets/_asset_user_auth_update_modal.html' %} -{% include 'assets/_asset_user_auth_view_modal.html' %} -{% include 'authentication/_mfa_confirm_modal.html' %} - - diff --git a/apps/assets/templates/assets/_gateway_test_modal.html b/apps/assets/templates/assets/_gateway_test_modal.html deleted file mode 100644 index 2eef52c7e..000000000 --- a/apps/assets/templates/assets/_gateway_test_modal.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}gateway_test{% endblock %} -{% block modal_title%}{% trans "Test gateway test connection" %}{% endblock %} -{% block modal_body %} -{% load bootstrap3 %} -
-
- - -
- - {% trans 'If use nat, set the ssh real port' %} -
-
-
-{% endblock %} -{% block modal_confirm_id %}btn_gateway_test{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/_node_detail_modal.html b/apps/assets/templates/assets/_node_detail_modal.html deleted file mode 100644 index f1f6f2dda..000000000 --- a/apps/assets/templates/assets/_node_detail_modal.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% load static %} - -{% block modal_id %}node_detail_modal{% endblock %} - -{% block modal_title %}{% trans "Node detail" %}{% endblock %} - - -{% block modal_body %} -
-
-
- -
-

-
-
- -
-
-
- -
-

-
-
-
- -
-

-
-
-
- -
-

-
-
-
-
- - - -{% endblock %} - -{% block modal_button %} - -{% endblock %} diff --git a/apps/assets/templates/assets/_node_tree.html b/apps/assets/templates/assets/_node_tree.html deleted file mode 100644 index c500f55f8..000000000 --- a/apps/assets/templates/assets/_node_tree.html +++ /dev/null @@ -1,339 +0,0 @@ -{% load static %} -{% load i18n %} - -{# #} - - - -
-
-
-
- {% trans 'Loading' %} ... -
-
-
-
-
- - - diff --git a/apps/assets/templates/assets/_system_user.html b/apps/assets/templates/assets/_system_user.html deleted file mode 100644 index e05a9448b..000000000 --- a/apps/assets/templates/assets/_system_user.html +++ /dev/null @@ -1,282 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
-
- {% csrf_token %} - {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} -

{% trans 'Basic' %}

- {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.login_mode layout="horizontal" %} - {% bootstrap_field form.username layout="horizontal" %} - {% bootstrap_field form.username_same_with_user layout="horizontal" %} - {% bootstrap_field form.priority layout="horizontal" %} - {% bootstrap_field form.protocol layout="horizontal" %} - -

{% trans 'Auth' %}

- {% block auth %} -
-
- -
- {{ form.auto_generate_key}} -
-
-
-
- {% bootstrap_field form.password layout="horizontal" %} - {% bootstrap_field form.private_key layout="horizontal" %} -
-
- -
- {{ form.auto_push}} -
-
- {% endblock %} -
-

{% trans 'Command filter' %}

- {% bootstrap_field form.cmd_filters layout="horizontal" %} -
-

{% trans 'Other' %}

- {% bootstrap_field form.sftp_root layout="horizontal" %} - {% bootstrap_field form.sudo layout="horizontal" %} - {% bootstrap_field form.shell layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} -
-
- - -
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/_user_asset_detail_modal.html b/apps/assets/templates/assets/_user_asset_detail_modal.html deleted file mode 100644 index ca2b8f252..000000000 --- a/apps/assets/templates/assets/_user_asset_detail_modal.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% load static %} - -{% block modal_id %}user_asset_detail_modal{% endblock %} - -{% block modal_title %}{% trans "Asset detail" %}{% endblock %} - -{% block modal_body %} -
- - - -
-
-{% endblock %} - -{% block modal_button %} - -{% endblock %} diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html deleted file mode 100644 index a42c6032f..000000000 --- a/apps/assets/templates/assets/admin_user_assets.html +++ /dev/null @@ -1,93 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Asset list of ' %} {{ admin_user.name }} -
- - - - - - - - - - -
-
-
- {% include 'assets/_asset_user_list.html' %} -
-
-
-
-
-
- {% trans 'Quick update' %} -
-
- - - - - - - -
{% trans 'Test connective' %}: - - - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/admin_user_create_update.html b/apps/assets/templates/assets/admin_user_create_update.html deleted file mode 100644 index 213f038d1..000000000 --- a/apps/assets/templates/assets/admin_user_create_update.html +++ /dev/null @@ -1,86 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} -
- {% csrf_token %} - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.username layout="horizontal" %} - {% bootstrap_field form.password layout="horizontal" %} - {% bootstrap_field form.private_key layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} - -
-
- - -
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/admin_user_detail.html b/apps/assets/templates/assets/admin_user_detail.html deleted file mode 100644 index 198b1e765..000000000 --- a/apps/assets/templates/assets/admin_user_detail.html +++ /dev/null @@ -1,166 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ admin_user.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ admin_user.name }}
{% trans 'Username' %}:{{ admin_user.username }}
{% trans 'Date created' %}:{{ admin_user.date_created }}
{% trans 'Created by' %}:{{ admin_user.created_by }}
{% trans 'Comment' %}:{{ admin_user.comment }}
-
-
-
-
-
-
- {% trans 'Replace node assets admin user with this' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
-
-
-
-
-
-
- - -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html deleted file mode 100644 index aaee06d69..000000000 --- a/apps/assets/templates/assets/admin_user_list.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block help_message %} - {% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%} - {% trans 'JumpServer users of the system using the user to `push system user`, `get assets hardware information`, etc. '%} -{% endblock %} -{% block table_search %} - {% include '_csv_import_export.html' %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Username' %}{% trans 'Asset' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_asset_user_list.html b/apps/assets/templates/assets/asset_asset_user_list.html deleted file mode 100644 index 4ad5a2c43..000000000 --- a/apps/assets/templates/assets/asset_asset_user_list.html +++ /dev/null @@ -1,97 +0,0 @@ -{% extends 'base.html' %} -{% load common_tags %} -{% load static %} -{% load i18n %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Asset users of' %} {{ asset.hostname }} -
- - - - - - - - - - -
-
-
- {% include 'assets/_asset_user_list.html' %} -
-
-
-
-
-
- {% trans 'Quick modify' %} -
-
- - - {% if asset.is_support_ansible %} - - - - - {% endif %} - -
{% trans 'Test connective' %}: - - - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_bulk_update.html b/apps/assets/templates/assets/asset_bulk_update.html deleted file mode 100644 index 207d5eb52..000000000 --- a/apps/assets/templates/assets/asset_bulk_update.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
-

{% trans 'Select properties that need to be modified' %}

-
- {% trans 'Select all' %} - {% for field in form %} - {% if field.name != 'assets' %} - {{ field.label }} - {% endif %} - {% endfor %} -
-
-
- {% csrf_token %} - {% bootstrap_form form layout="horizontal" %} -
-
- - -
-
-
-{% include 'assets/_asset_list_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 deleted file mode 100644 index 0f50b2b86..000000000 --- a/apps/assets/templates/assets/asset_create.html +++ /dev/null @@ -1,249 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load asset_tags %} -{% load common_tags %} - -{% block form %} -
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} -

{% trans 'Basic' %}

- {% bootstrap_field form.hostname layout="horizontal" %} - {% bootstrap_field form.ip layout="horizontal" %} - {% bootstrap_field form.platform layout="horizontal" %} - {% bootstrap_field form.public_ip layout="horizontal" %} - {% bootstrap_field form.domain layout="horizontal" %} -
- -

{% trans 'Protocols' %}

-
- {% for fm in formset.forms %} -
-
{{ fm.name }}
-
{{ fm.port }}
-
- - -
-
- {% endfor %} -
-
-

{% trans 'Auth' %}

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

{% trans 'Node' %}

- {% bootstrap_field form.nodes layout="horizontal" %} - -
-

{% trans 'Labels' %}

-
- -
- - {% if form.errors.labels %} - {% for e in form.errors.labels %} -
{{ e }}
- {% endfor %} - {% endif %} -
-
- {% block extra %} - {% endblock %} - -
-

{% trans 'Other' %}

- {% bootstrap_field form.comment layout="horizontal" %} - {% bootstrap_field form.is_active layout="horizontal" %} - -
-
-
- - -
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html deleted file mode 100644 index 53717c643..000000000 --- a/apps/assets/templates/assets/asset_detail.html +++ /dev/null @@ -1,362 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ asset.hostname }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Hostname' %}:{{ asset.hostname }}
{% trans 'IP' %}:{{ asset.ip }}
{% trans 'Public IP' %}:{{ asset.public_ip|default:"" }}
{% trans 'Protocol' %}{{ asset.protocols }}
{% trans 'Admin user' %}:{{ asset.admin_user }}
{% trans 'Domain' %}:{{ asset.domain|default:"" }}
{% trans 'Vendor' %}:{{ asset.vendor|default:"" }}
{% trans 'Model' %}:{{ asset.model|default:"" }}
{% trans 'CPU' %}:{{ asset.cpu_info }}
{% trans 'Memory' %}:{{ asset.memory|default:"" }}
{% trans 'Disk' %}:{{ asset.disk_total|default:"" }}
{% trans 'Platform' %}:{{ asset.platform|default:"" }}
{% trans 'OS' %}:{{ asset.os|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:"" }}
{% trans 'Is active' %}:{{ asset.is_active|yesno:"Yes,No" }}
{% trans 'Serial number' %}:{{ asset.sn|default:"" }}
{% trans 'Asset number' %}:{{ asset.number|default:"" }}
{% trans 'Created by' %}:{{ asset.created_by }}
{% trans 'Date joined' %}:{{ asset.date_joined|date:"Y-m-j H:i:s" }}
{% trans 'Comment' %}:{{ asset.comment }}
-
-
-
- {% if user.is_superuser or user.is_org_admin %} -
-
-
- {% trans 'Quick modify' %} -
-
- - - - - - - {% if asset.is_support_ansible %} - - - - - - - - - {% endif %} - -
{% trans 'Active' %}: - -
-
- - -
-
-
-
{% trans 'Refresh hardware' %}: - - - -
{% trans 'Test connective' %}: - - - -
-
-
- -
-
- {% trans 'Nodes' %} -
-
- - - - - - - - - - - - {% for node in asset.nodes.all %} - - - - - {% endfor %} - -
- -
- -
{{ node.full_value }} - -
-
-
- -
-
- {% trans 'Labels' %} -
-
- -
-
- {% endif %} -
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html deleted file mode 100644 index 2eeda8f69..000000000 --- a/apps/assets/templates/assets/asset_list.html +++ /dev/null @@ -1,416 +0,0 @@ -{% extends '_base_asset_tree_list.html' %} -{% load static %} -{% load i18n %} - -{% block help_message %} - {% trans 'The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node' %} -{% endblock %} - -{% block table_container %} - - {% include '_csv_import_export.html' %} -
- - -
- - - - - - - - - - - - - -
{% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Hardware' %}{% trans 'Reachable' %}{% trans 'Action' %}
-
-
- -
- -
-
-
-{% include 'assets/_asset_list_modal.html' %} -{% include 'assets/_node_detail_modal.html' %} -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_update.html b/apps/assets/templates/assets/asset_update.html deleted file mode 100644 index 4c23f7095..000000000 --- a/apps/assets/templates/assets/asset_update.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends 'assets/asset_create.html' %} -{% load bootstrap3 %} -{% load i18n %} - -{% block extra %} -
-

{% trans 'Configuration' %}

- {% bootstrap_field form.number layout="horizontal" %} -{% endblock %} - -{% block formUrl %} - var the_url = '{% url 'api-assets:asset-detail' pk=object.id %}'; - var redirect_to = '{% url "assets:asset-list" %}'; - var method = 'PUT'; -{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/cmd_filter_create_update.html b/apps/assets/templates/assets/cmd_filter_create_update.html deleted file mode 100644 index 678e1e3eb..000000000 --- a/apps/assets/templates/assets/cmd_filter_create_update.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
- {% csrf_token %} - {% bootstrap_field form.name 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/templates/assets/cmd_filter_detail.html b/apps/assets/templates/assets/cmd_filter_detail.html deleted file mode 100644 index 24e192253..000000000 --- a/apps/assets/templates/assets/cmd_filter_detail.html +++ /dev/null @@ -1,173 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'Comment' %}:{{ object.comment }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Date updated' %}:{{ object.date_updated }}
{% trans 'Created by' %}:{{ object.created_by }}
-
-
-
- -
-
-
- {% trans 'System users' %} -
-
- - - - - - - - - - - {% for system_user in object.system_users.all %} - - - - - {% endfor %} - -
- -
- -
{{ system_user }} - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/cmd_filter_list.html b/apps/assets/templates/assets/cmd_filter_list.html deleted file mode 100644 index 1d98d5500..000000000 --- a/apps/assets/templates/assets/cmd_filter_list.html +++ /dev/null @@ -1,89 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} -{% block help_message %} - {% trans 'System user bound some command filter, each command filter has some rules,'%} - {% trans 'When user login asset with this system user, then run a command,' %} - {% trans 'The command will be filter by rules, higher priority rule run first,' %} - {% trans 'When a rule matched, if rule action is allow, then allow command execute,' %} - {% trans 'else if action is deny, then command with be deny,' %} - {% trans 'else match next rule, if none matched, allowed' %} -{% endblock %} -{% block table_container %} - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Rules' %}{% trans 'System users' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - - - diff --git a/apps/assets/templates/assets/cmd_filter_rule_create_update.html b/apps/assets/templates/assets/cmd_filter_rule_create_update.html deleted file mode 100644 index 21279b410..000000000 --- a/apps/assets/templates/assets/cmd_filter_rule_create_update.html +++ /dev/null @@ -1,90 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
-
- {% csrf_token %} - {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% bootstrap_form form layout="horizontal" %} -
-
- - -
-
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/cmd_filter_rule_list.html b/apps/assets/templates/assets/cmd_filter_rule_list.html deleted file mode 100644 index 6076cb2ac..000000000 --- a/apps/assets/templates/assets/cmd_filter_rule_list.html +++ /dev/null @@ -1,110 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
-
-
- {% trans 'Command filter rule list' %} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - -
- - {% trans 'Type' %}{% trans 'Content' %}{% trans 'Priority' %}{% trans 'Strategy' %}{% trans 'Comment' %}{% trans 'Action' %}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/delete_confirm.html b/apps/assets/templates/assets/delete_confirm.html deleted file mode 100644 index 94f017ca2..000000000 --- a/apps/assets/templates/assets/delete_confirm.html +++ /dev/null @@ -1,15 +0,0 @@ -{% load i18n %} - - - - - {% trans 'Confirm delete' %} - - -
- {% csrf_token %} -

{% trans 'Are you sure delete' %} {{ object.name }} ?

- -
- - \ No newline at end of file diff --git a/apps/assets/templates/assets/domain_create_update.html b/apps/assets/templates/assets/domain_create_update.html deleted file mode 100644 index 39939c8ca..000000000 --- a/apps/assets/templates/assets/domain_create_update.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
- {% csrf_token %} - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.assets layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} - -
-
-
- - -
-
-
-{% include 'assets/_asset_list_modal.html' %} -{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/domain_detail.html b/apps/assets/templates/assets/domain_detail.html deleted file mode 100644 index 119b58080..000000000 --- a/apps/assets/templates/assets/domain_detail.html +++ /dev/null @@ -1,132 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'Asset' %}:{{ object.assets.count }}
{% trans 'Gateway' %}:{{ object.gateway_set.count }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Comment' %}:{{ object.comment }}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/domain_gateway_list.html b/apps/assets/templates/assets/domain_gateway_list.html deleted file mode 100644 index 8917ef810..000000000 --- a/apps/assets/templates/assets/domain_gateway_list.html +++ /dev/null @@ -1,141 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
-
-
- {% trans 'Gateway list' %} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Protocol' %}{% trans 'Username' %}{% trans 'Comment' %}{% trans 'Action' %}
-
-
-
-
-
-
-
-
-{% include 'assets/_gateway_test_modal.html' %} -{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/domain_list.html b/apps/assets/templates/assets/domain_list.html deleted file mode 100644 index 623f1bea2..000000000 --- a/apps/assets/templates/assets/domain_list.html +++ /dev/null @@ -1,79 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} - -{% block help_message %} - {% trans 'The domain function is added to address the fact that some environments (such as the hybrid cloud) cannot be connected directly by jumping on the gateway server.' %} -
- {% trans 'JMS => Domain gateway => Target assets' %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Asset' %}{% trans 'Gateway' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/gateway_create_update.html b/apps/assets/templates/assets/gateway_create_update.html deleted file mode 100644 index 43dc07b1b..000000000 --- a/apps/assets/templates/assets/gateway_create_update.html +++ /dev/null @@ -1,124 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
-
- {% csrf_token %} - {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} -

{% trans 'Basic' %}

- {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.ip layout="horizontal" %} - {% bootstrap_field form.port layout="horizontal" %} - {% bootstrap_field form.protocol layout="horizontal" %} - {% bootstrap_field form.domain layout="horizontal" %} - - {% block auth %} -

{% trans 'Auth' %}

-
- {% bootstrap_field form.username layout="horizontal" %} - {% bootstrap_field form.password layout="horizontal" %} - {% bootstrap_field form.private_key layout="horizontal" %} -
- {% endblock %} - -

{% trans 'Other' %}

- {% bootstrap_field form.is_active layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} -
-
- - -
-
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/label_create_update.html b/apps/assets/templates/assets/label_create_update.html deleted file mode 100644 index 62732d6ba..000000000 --- a/apps/assets/templates/assets/label_create_update.html +++ /dev/null @@ -1,56 +0,0 @@ -{% 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" %} - -
-
-
- - -
-
-
-{% include 'assets/_asset_list_modal.html' %} -{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/label_list.html b/apps/assets/templates/assets/label_list.html deleted file mode 100644 index 104e5820b..000000000 --- a/apps/assets/templates/assets/label_list.html +++ /dev/null @@ -1,72 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} -{% block table_container %} - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Value' %}{% trans 'Asset' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - - - diff --git a/apps/assets/templates/assets/platform_create_update.html b/apps/assets/templates/assets/platform_create_update.html deleted file mode 100644 index e130e9e97..000000000 --- a/apps/assets/templates/assets/platform_create_update.html +++ /dev/null @@ -1,79 +0,0 @@ -{% extends '_base_create_update.html' %} {% load static %} {% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
- {% csrf_token %} - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.base layout="horizontal" %} -
- -
- {% bootstrap_field form.comment layout="horizontal" %} - -
-
-
- - -
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/assets/templates/assets/platform_detail.html b/apps/assets/templates/assets/platform_detail.html deleted file mode 100644 index 89d364e0f..000000000 --- a/apps/assets/templates/assets/platform_detail.html +++ /dev/null @@ -1,91 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'Base platform' %}:{{ object.base }}
{% trans 'Charset' %}:{{ object.charset }}
{% trans 'Meta' %}:{{ object.meta }}
{% trans 'Comment' %}:{{ object.comment }}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/platform_list.html b/apps/assets/templates/assets/platform_list.html deleted file mode 100644 index cb1eef5bc..000000000 --- a/apps/assets/templates/assets/platform_list.html +++ /dev/null @@ -1,75 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Base platform' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/system_user_assets.html b/apps/assets/templates/assets/system_user_assets.html deleted file mode 100644 index d48f48201..000000000 --- a/apps/assets/templates/assets/system_user_assets.html +++ /dev/null @@ -1,349 +0,0 @@ -{% extends 'base.html' %} -{% load common_tags %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
-
-
-
-
- -
-
-
-
-
- {{ system_user.name }} {{ paginator.count }} -
- - - - - - - - - - -
-
-
- {% include 'assets/_asset_user_list.html' %} -
-
-
-
-
-
- {% trans 'Quick update' %} -
-
- - - - - - - {% if system_user.auto_push %} - - - - - {% endif %} - -
{% trans 'Test assets connective' %}: - - - -
{% trans 'Push system user now' %}: - - - -
-
-
-
-
- {% trans 'Assets' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
-
-
- {% trans 'Nodes' %} -
-
- - - - - - - - - - -
- -
- -
-
- - -
-
-
-
-
-
-
-
- {% include 'assets/_asset_list_modal.html' %} -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/system_user_create.html b/apps/assets/templates/assets/system_user_create.html deleted file mode 100644 index 7127de993..000000000 --- a/apps/assets/templates/assets/system_user_create.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'assets/_system_user.html' %} -{% load i18n %} -{% load static %} - -{% block auth %} - {{ block.super }} -{% endblock %} diff --git a/apps/assets/templates/assets/system_user_detail.html b/apps/assets/templates/assets/system_user_detail.html deleted file mode 100644 index 01e71fc1c..000000000 --- a/apps/assets/templates/assets/system_user_detail.html +++ /dev/null @@ -1,260 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
-
- -
-
-
-
-
- {{ system_user.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - {% if system_user.username_same_with_user %} - - {% else %} - - {% endif %} - - - - - - - - - - - - - - {% if system_user.shell %} - - - - - {% endif %} - {% if system_user.home %} - - - - - {% endif %} - {% if system_user.uid %} - - - - - {% endif %} - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ system_user.name }}
{% trans 'Username' %}:{% trans 'Username same with user' %}{{ system_user.username }}
{% trans 'Login mode' %}:{{ system_user.get_login_mode_display }}
{% trans 'Protocol' %}:{{ system_user.protocol }}
{% trans 'Sudo' %}:{{ system_user.sudo }}
{% trans 'Shell' %}:{{ system_user.shell }}
{% trans 'Home' %}:{{ system_user.home }}
{% trans 'Uid' %}:{{ system_user.uid }}
{% trans 'Date created' %}:{{ system_user.date_created }}
{% trans 'Created by' %}:{{ system_user.created_by }}
{% trans 'Comment' %}:{{ system_user.comment }}
-
-
-
- -
-
-
- {% trans 'Quick update' %} -
-
- - - - - - - -
{% trans 'Auto push' %}: - -
-
- - -
-
-
-
-
-
-
-
- {% if system_user.is_need_cmd_filter %} -
-
-
- {% trans 'Command filter' %} -
-
- - - - - - - - - - - {% for cf in object.cmd_filters.all %} - - - - - {% endfor %} - -
- -
- -
{{ cf }} - -
-
-
-
- {% endif %} -
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html deleted file mode 100644 index b3bd556ab..000000000 --- a/apps/assets/templates/assets/system_user_list.html +++ /dev/null @@ -1,90 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} - -{% block help_message %} - {% trans 'System user is JumpServer jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%} - {% trans 'In simple terms, users log into JumpServer using their own username, and JumpServer uses system users to log into assets. '%} - {% trans 'When system users are created, if you choose auto push JumpServer to use Ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.' %} -{% endblock %} - -{% block table_search %} - {% include '_csv_import_export.html' %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Username' %}{% trans 'Protocol' %}{% trans 'Login mode' %}{% trans 'Asset' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - - - diff --git a/apps/assets/templates/assets/system_user_update.html b/apps/assets/templates/assets/system_user_update.html deleted file mode 100644 index 0a60d3e70..000000000 --- a/apps/assets/templates/assets/system_user_update.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'assets/_system_user.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} - -{% block auth %} - {% bootstrap_field form.password layout="horizontal" %} - {% bootstrap_field form.private_key layout="horizontal" %} -
- -
- {{ form.auto_push}} -
-
-{% endblock %} - -{% block formUrl %} - var the_url = '{% url 'api-assets:system-user-detail' pk=object.pk %}'; - var redirect_to = '{% url "assets:system-user-list" %}'; - var method = "PUT"; -{% endblock %} - diff --git a/apps/assets/templates/assets/system_user_users.html b/apps/assets/templates/assets/system_user_users.html deleted file mode 100644 index 59d5e0653..000000000 --- a/apps/assets/templates/assets/system_user_users.html +++ /dev/null @@ -1,212 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
-
-
-
-
- -
-
-
-
-
- {{ system_user.name }} {{ paginator.count }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - {% trans 'User' %}{% trans 'Action' %}
-
-
-
-
-
-
- {% trans 'Users' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html deleted file mode 100644 index 2a8c8cf64..000000000 --- a/apps/assets/templates/assets/user_asset_list.html +++ /dev/null @@ -1,132 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - - -{% endblock %} - -{% block content %} -
-
- {% include 'users/_granted_assets.html' %} -
-
- -{% include 'assets/_user_asset_detail_modal.html' %} -{% endblock %} - - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templatetags/__init__.py b/apps/assets/templatetags/__init__.py deleted file mode 100644 index ec51c5a2b..000000000 --- a/apps/assets/templatetags/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -# diff --git a/apps/assets/templatetags/asset_tags.py b/apps/assets/templatetags/asset_tags.py deleted file mode 100644 index 15605f835..000000000 --- a/apps/assets/templatetags/asset_tags.py +++ /dev/null @@ -1,12 +0,0 @@ -from collections import defaultdict - -from django import template -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 25b6deefd..d9832e041 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -1,66 +1,6 @@ # coding:utf-8 -from django.urls import path -from .. import views app_name = 'assets' urlpatterns = [ - # Resource asset url - path('', views.AssetListView.as_view(), name='asset-index'), - path('asset/', views.AssetListView.as_view(), name='asset-list'), - path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'), - path('asset//', views.AssetDetailView.as_view(), name='asset-detail'), - path('asset//update/', views.AssetUpdateView.as_view(), name='asset-update'), - path('asset//delete/', views.AssetDeleteView.as_view(), name='asset-delete'), - path('asset/update/', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'), - # Asset user view - path('asset//asset-user/', views.AssetUserListView.as_view(), name='asset-user-list'), - - path('platform/', views.PlatformListView.as_view(), name='platform-list'), - path('platform/create/', views.PlatformCreateView.as_view(), name='platform-create'), - path('platform//', views.PlatformDetailView.as_view(), name='platform-detail'), - path('platform//update/', views.PlatformUpdateView.as_view(), name='platform-update'), - - # User asset view - path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'), - - # Resource admin user url - path('admin-user/', views.AdminUserListView.as_view(), name='admin-user-list'), - path('admin-user/create/', views.AdminUserCreateView.as_view(), name='admin-user-create'), - path('admin-user//', views.AdminUserDetailView.as_view(), name='admin-user-detail'), - path('admin-user//update/', views.AdminUserUpdateView.as_view(), name='admin-user-update'), - path('admin-user//delete/', views.AdminUserDeleteView.as_view(), name='admin-user-delete'), - path('admin-user//assets/', views.AdminUserAssetsView.as_view(), name='admin-user-assets'), - - # Resource system user url - path('system-user/', views.SystemUserListView.as_view(), name='system-user-list'), - path('system-user/create/', views.SystemUserCreateView.as_view(), name='system-user-create'), - path('system-user//', views.SystemUserDetailView.as_view(), name='system-user-detail'), - path('system-user//update/', views.SystemUserUpdateView.as_view(), name='system-user-update'), - path('system-user//delete/', views.SystemUserDeleteView.as_view(), name='system-user-delete'), - path('system-user//asset/', views.SystemUserAssetView.as_view(), name='system-user-asset'), - path('system-user//user/', views.SystemUserUserView.as_view(), name='system-user-user'), - - path('label/', views.LabelListView.as_view(), name='label-list'), - path('label/create/', views.LabelCreateView.as_view(), name='label-create'), - path('label//update/', views.LabelUpdateView.as_view(), name='label-update'), - path('label//delete/', views.LabelDeleteView.as_view(), name='label-delete'), - - path('domain/', views.DomainListView.as_view(), name='domain-list'), - path('domain/create/', views.DomainCreateView.as_view(), name='domain-create'), - path('domain//', views.DomainDetailView.as_view(), name='domain-detail'), - path('domain//update/', views.DomainUpdateView.as_view(), name='domain-update'), - path('domain//delete/', views.DomainDeleteView.as_view(), name='domain-delete'), - path('domain//gateway/', views.DomainGatewayListView.as_view(), name='domain-gateway-list'), - - path('domain//gateway/create/', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'), - path('domain/gateway//update/', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'), - - path('cmd-filter/', views.CommandFilterListView.as_view(), name='cmd-filter-list'), - path('cmd-filter/create/', views.CommandFilterCreateView.as_view(), name='cmd-filter-create'), - path('cmd-filter//update/', views.CommandFilterUpdateView.as_view(), name='cmd-filter-update'), - path('cmd-filter//', views.CommandFilterDetailView.as_view(), name='cmd-filter-detail'), - path('cmd-filter//rule/', views.CommandFilterRuleListView.as_view(), name='cmd-filter-rule-list'), - path('cmd-filter//rule/create/', views.CommandFilterRuleCreateView.as_view(), name='cmd-filter-rule-create'), - path('cmd-filter//rule//update/', views.CommandFilterRuleUpdateView.as_view(), name='cmd-filter-rule-update'), ] diff --git a/apps/assets/views/__init__.py b/apps/assets/views/__init__.py deleted file mode 100644 index 74055a76c..000000000 --- a/apps/assets/views/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# coding:utf-8 -from .asset import * -from .platform import * -from .system_user import * -from .admin_user import * -from .label import * -from .domain import * -from .cmd_filter import * diff --git a/apps/assets/views/admin_user.py b/apps/assets/views/admin_user.py deleted file mode 100644 index 611b03d7d..000000000 --- a/apps/assets/views/admin_user.py +++ /dev/null @@ -1,121 +0,0 @@ -# coding:utf-8 -from __future__ import absolute_import, unicode_literals -from django.utils.translation import ugettext as _ -from django.conf import settings -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 AdminUser, Node -from common.permissions import PermissionsMixin, IsOrgAdmin - -__all__ = [ - 'AdminUserCreateView', 'AdminUserDetailView', - 'AdminUserDeleteView', 'AdminUserListView', - 'AdminUserUpdateView', 'AdminUserAssetsView', -] - - -class AdminUserListView(PermissionsMixin, TemplateView): - model = AdminUser - template_name = 'assets/admin_user_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Admin user list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdminUserCreateView(PermissionsMixin, - SuccessMessageMixin, - CreateView): - model = AdminUser - 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 - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create admin user'), - "type": "create" - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdminUserUpdateView(PermissionsMixin, 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 - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update admin user'), - "type": "update" - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdminUserDetailView(PermissionsMixin, DetailView): - model = AdminUser - template_name = 'assets/admin_user_detail.html' - context_object_name = 'admin_user' - object = None - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Admin user detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdminUserAssetsView(PermissionsMixin, SingleObjectMixin, ListView): - paginate_by = settings.DISPLAY_PER_PAGE - template_name = 'assets/admin_user_assets.html' - context_object_name = 'admin_user' - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=AdminUser.objects.all()) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - self.queryset = self.object.assets.all() - return self.queryset - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Admin user assets'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdminUserDeleteView(PermissionsMixin, DeleteView): - model = AdminUser - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:admin-user-list') - permission_classes = [IsOrgAdmin] - - diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py deleted file mode 100644 index 63179f4f8..000000000 --- a/apps/assets/views/asset.py +++ /dev/null @@ -1,203 +0,0 @@ -# coding:utf-8 -from __future__ import absolute_import, unicode_literals - -from django.contrib import messages -from django.utils.translation import ugettext_lazy as _ -from django.views.generic import TemplateView, ListView -from django.views.generic.edit import FormMixin -from django.views.generic.edit import DeleteView, UpdateView -from django.urls import reverse_lazy -from django.views.generic.detail import DetailView -from django.core.cache import cache -from django.shortcuts import redirect -from django.forms.formsets import formset_factory - -from common.utils import get_object_or_none, get_logger -from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser -from common.const import KEY_CACHE_RESOURCES_ID -from .. import forms -from ..models import Asset, Label, Node - - -__all__ = [ - 'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView', - 'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView', - 'AssetDeleteView', -] -logger = get_logger(__file__) - - -class AssetListView(PermissionsMixin, TemplateView): - template_name = 'assets/asset_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - Node.org_root() - context = { - 'app': _('Assets'), - 'action': _('Asset list'), - 'labels': Label.objects.all().order_by('name'), - 'nodes': Node.objects.all().order_by('-key'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetUserListView(PermissionsMixin, DetailView): - model = Asset - context_object_name = 'asset' - template_name = 'assets/asset_asset_user_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Asset user list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserAssetListView(PermissionsMixin, TemplateView): - template_name = 'assets/user_asset_list.html' - permission_classes = [IsValidUser] - - def get_context_data(self, **kwargs): - context = { - 'action': _('My assets'), - 'labels': Label.objects.all().order_by('name'), - 'show_actions': True - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetCreateView(PermissionsMixin, FormMixin, TemplateView): - model = Asset - form_class = forms.AssetCreateUpdateForm - template_name = 'assets/asset_create.html' - success_url = reverse_lazy('assets:asset-list') - permission_classes = [IsOrgAdmin] - - def get_form(self, form_class=None): - form = super().get_form(form_class=form_class) - node_id = self.request.GET.get("node_id") - if node_id: - node = get_object_or_none(Node, id=node_id) - else: - node = Node.org_root() - form.add_nodes_initial(node) - return form - - def get_protocol_formset(self): - ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5) - if self.request.method == "POST": - formset = ProtocolFormset(self.request.POST) - else: - formset = ProtocolFormset() - return formset - - def get_context_data(self, **kwargs): - formset = self.get_protocol_formset() - context = { - 'app': _('Assets'), - 'action': _('Create asset'), - 'formset': formset, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetUpdateView(PermissionsMixin, UpdateView): - model = Asset - form_class = forms.AssetCreateUpdateForm - template_name = 'assets/asset_update.html' - success_url = reverse_lazy('assets:asset-list') - permission_classes = [IsOrgAdmin] - - def get_protocol_formset(self): - ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5) - if self.request.method == "POST": - formset = ProtocolFormset(self.request.POST) - else: - initial_data = self.object.protocols_as_json - formset = ProtocolFormset(initial=initial_data) - return formset - - def get_context_data(self, **kwargs): - formset = self.get_protocol_formset() - context = { - 'app': _('Assets'), - 'action': _('Update asset'), - 'formset': formset, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetBulkUpdateView(PermissionsMixin, ListView): - model = Asset - form_class = forms.AssetBulkUpdateForm - template_name = 'assets/asset_bulk_update.html' - success_url = reverse_lazy('assets:asset-list') - success_message = _("Bulk update asset success") - id_list = None - form = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - spm = request.GET.get('spm', '') - assets_id = cache.get(KEY_CACHE_RESOURCES_ID.format(spm)) - if kwargs.get('form'): - self.form = kwargs['form'] - elif assets_id: - self.form = self.form_class(initial={'assets': assets_id}) - else: - self.form = self.form_class() - return super().get(request, *args, **kwargs) - - def post(self, request, *args, **kwargs): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - messages.success(request, self.success_message) - return redirect(self.success_url) - else: - return self.get(request, form=form, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Bulk update asset'), - 'form': self.form, - 'assets_selected': self.id_list, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetDeleteView(PermissionsMixin, DeleteView): - model = Asset - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:asset-list') - permission_classes = [IsOrgAdmin] - - -class AssetDetailView(PermissionsMixin, DetailView): - model = Asset - context_object_name = 'asset' - template_name = 'assets/asset_detail.html' - permission_classes = [IsValidUser] - - def get_queryset(self): - return super().get_queryset().prefetch_related( - "nodes", "labels", - ).select_related('admin_user', 'domain') - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Asset detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/assets/views/cmd_filter.py b/apps/assets/views/cmd_filter.py deleted file mode 100644 index 530f4193b..000000000 --- a/apps/assets/views/cmd_filter.py +++ /dev/null @@ -1,180 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.views.generic import TemplateView, CreateView, \ - UpdateView, DeleteView, DetailView -from django.views.generic.detail import SingleObjectMixin -from django.utils.translation import ugettext_lazy as _ -from django.urls import reverse_lazy -from django.shortcuts import get_object_or_404, reverse - -from common.permissions import PermissionsMixin, IsOrgAdmin -from common.const import create_success_msg, update_success_msg -from ..models import CommandFilter, CommandFilterRule, SystemUser -from ..forms import CommandFilterForm, CommandFilterRuleForm - - -__all__ = ( - "CommandFilterListView", "CommandFilterCreateView", - "CommandFilterUpdateView", - "CommandFilterRuleListView", "CommandFilterRuleCreateView", - "CommandFilterRuleUpdateView", "CommandFilterDetailView", -) - - -class CommandFilterListView(PermissionsMixin, TemplateView): - template_name = 'assets/cmd_filter_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Command filter list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterCreateView(PermissionsMixin, CreateView): - model = CommandFilter - template_name = 'assets/cmd_filter_create_update.html' - form_class = CommandFilterForm - success_url = reverse_lazy('assets:cmd-filter-list') - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create command filter'), - 'type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterUpdateView(PermissionsMixin, UpdateView): - model = CommandFilter - template_name = 'assets/cmd_filter_create_update.html' - form_class = CommandFilterForm - success_url = reverse_lazy('assets:cmd-filter-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update command filter'), - 'type': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterDetailView(PermissionsMixin, DetailView): - model = CommandFilter - template_name = 'assets/cmd_filter_detail.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - system_users_remain = SystemUser.objects\ - .exclude(cmd_filters=self.object)\ - .exclude(protocol='rdp') - context = { - 'app': _('Assets'), - 'action': _('Command filter detail'), - 'system_users_remain': system_users_remain - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterRuleListView(PermissionsMixin, SingleObjectMixin, TemplateView): - template_name = 'assets/cmd_filter_rule_list.html' - model = CommandFilter - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=self.model.objects.all()) - return super().get(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Command filter rule list'), - 'object': self.get_object() - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterRuleCreateView(PermissionsMixin, CreateView): - template_name = 'assets/cmd_filter_rule_create_update.html' - model = CommandFilterRule - form_class = CommandFilterRuleForm - success_message = create_success_msg - cmd_filter = None - permission_classes = [IsOrgAdmin] - - def get_success_url(self): - return reverse('assets:cmd-filter-rule-list', kwargs={ - 'pk': self.cmd_filter.id - }) - - def get_form(self, form_class=None): - form = super().get_form(form_class=form_class) - form['filter'].initial = self.cmd_filter - form['filter'].field.widget.attrs['readonly'] = 1 - return form - - def dispatch(self, request, *args, **kwargs): - filter_pk = self.kwargs.get('filter_pk') - self.cmd_filter = get_object_or_404(CommandFilter, pk=filter_pk) - return super().dispatch(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create command filter rule'), - 'object': self.cmd_filter, - 'request_type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterRuleUpdateView(PermissionsMixin, UpdateView): - template_name = 'assets/cmd_filter_rule_create_update.html' - model = CommandFilterRule - form_class = CommandFilterRuleForm - success_message = create_success_msg - cmd_filter = None - permission_classes = [IsOrgAdmin] - - def get_success_url(self): - return reverse('assets:cmd-filter-rule-list', kwargs={ - 'pk': self.cmd_filter.id - }) - - def get_form(self, form_class=None): - form = super().get_form(form_class=form_class) - form['filter'].initial = self.cmd_filter - form['filter'].field.widget.attrs['readonly'] = 1 - return form - - def dispatch(self, request, *args, **kwargs): - filter_pk = self.kwargs.get('filter_pk') - self.cmd_filter = get_object_or_404(CommandFilter, pk=filter_pk) - return super().dispatch(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update command filter rule'), - 'object': self.cmd_filter, - 'rule': self.get_object(), - 'request_type': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) \ No newline at end of file diff --git a/apps/assets/views/domain.py b/apps/assets/views/domain.py deleted file mode 100644 index ad7fad1b6..000000000 --- a/apps/assets/views/domain.py +++ /dev/null @@ -1,162 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.views.generic import ( - TemplateView, CreateView, UpdateView, DeleteView, DetailView -) -from django.views.generic.detail import SingleObjectMixin -from django.utils.translation import ugettext_lazy as _ -from django.urls import reverse_lazy, reverse - -from common.permissions import PermissionsMixin, IsOrgAdmin -from common.const import create_success_msg, update_success_msg -from common.utils import get_object_or_none -from ..models import Domain, Gateway -from ..forms import DomainForm, GatewayForm - - -__all__ = ( - "DomainListView", "DomainCreateView", "DomainUpdateView", - "DomainDetailView", "DomainDeleteView", "DomainGatewayListView", - "DomainGatewayCreateView", 'DomainGatewayUpdateView', -) - - -class DomainListView(PermissionsMixin, TemplateView): - template_name = 'assets/domain_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Domain list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainCreateView(PermissionsMixin, CreateView): - model = Domain - template_name = 'assets/domain_create_update.html' - form_class = DomainForm - success_url = reverse_lazy('assets:domain-list') - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create domain'), - 'type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainUpdateView(PermissionsMixin, UpdateView): - model = Domain - template_name = 'assets/domain_create_update.html' - form_class = DomainForm - success_url = reverse_lazy('assets:domain-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update domain'), - 'type': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainDetailView(PermissionsMixin, DetailView): - model = Domain - template_name = 'assets/domain_detail.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Domain detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainDeleteView(PermissionsMixin, DeleteView): - model = Domain - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:domain-list') - permission_classes = [IsOrgAdmin] - - -class DomainGatewayListView(PermissionsMixin, SingleObjectMixin, TemplateView): - template_name = 'assets/domain_gateway_list.html' - model = Domain - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=self.model.objects.all()) - return super().get(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Domain gateway list'), - 'object': self.get_object() - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainGatewayCreateView(PermissionsMixin, CreateView): - model = Gateway - template_name = 'assets/gateway_create_update.html' - form_class = GatewayForm - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_success_url(self): - domain = self.object.domain - return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id}) - - def get_form(self, form_class=None): - form = super().get_form(form_class=form_class) - domain_id = self.kwargs.get("pk") - domain = get_object_or_none(Domain, id=domain_id) - if domain: - form['domain'].initial = domain - return form - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create gateway'), - 'type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainGatewayUpdateView(PermissionsMixin, UpdateView): - model = Gateway - template_name = 'assets/gateway_create_update.html' - form_class = GatewayForm - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_success_url(self): - domain = self.object.domain - return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id}) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update gateway'), - "type": "update" - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/assets/views/label.py b/apps/assets/views/label.py deleted file mode 100644 index 522962ce3..000000000 --- a/apps/assets/views/label.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -# - -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.permissions import PermissionsMixin, IsOrgAdmin -from common.const import create_success_msg, update_success_msg -from ..models import Label -from ..forms import LabelForm - - -__all__ = ( - "LabelListView", "LabelCreateView", "LabelUpdateView", - "LabelDetailView", "LabelDeleteView", -) - - -class LabelListView(PermissionsMixin, TemplateView): - template_name = 'assets/label_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Label list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class LabelCreateView(PermissionsMixin, CreateView): - model = Label - template_name = 'assets/label_create_update.html' - form_class = LabelForm - success_url = reverse_lazy('assets:label-list') - success_message = create_success_msg - disable_name = ['draw', 'search', 'limit', 'offset', '_'] - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create label'), - 'type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def form_valid(self, form): - name = form.cleaned_data.get('name') - if name in self.disable_name: - msg = _( - 'Tips: Avoid using label names reserved internally: {}' - ).format(', '.join(self.disable_name)) - form.add_error("name", msg) - return self.form_invalid(form) - return super().form_valid(form) - - -class LabelUpdateView(PermissionsMixin, UpdateView): - model = Label - template_name = 'assets/label_create_update.html' - form_class = LabelForm - success_url = reverse_lazy('assets:label-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update label'), - 'type': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class LabelDetailView(PermissionsMixin, DetailView): - pass - - -class LabelDeleteView(PermissionsMixin, DeleteView): - model = Label - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:label-list') - permission_classes = [IsOrgAdmin] diff --git a/apps/assets/views/platform.py b/apps/assets/views/platform.py deleted file mode 100644 index 8c74da138..000000000 --- a/apps/assets/views/platform.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -from django.views import generic -from django.utils.translation import ugettext as _ - -from common.permissions import PermissionsMixin, IsSuperUser -from ..models import Platform -from ..forms import PlatformForm, PlatformMetaForm - -__all__ = [ - 'PlatformListView', 'PlatformUpdateView', 'PlatformCreateView', - 'PlatformDetailView', -] - - -class PlatformListView(PermissionsMixin, generic.TemplateView): - template_name = 'assets/platform_list.html' - permission_classes = (IsSuperUser,) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'app': _('Assets'), - 'action': _("Platform list"), - }) - return context - - -class PlatformCreateView(PermissionsMixin, generic.CreateView): - form_class = PlatformForm - permission_classes = (IsSuperUser,) - template_name = 'assets/platform_create_update.html' - model = Platform - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - meta_form = PlatformMetaForm() - context.update({ - 'app': _('Assets'), - 'action': _("Create platform"), - 'meta_form': meta_form, - }) - return context - - -class PlatformUpdateView(generic.UpdateView): - form_class = PlatformForm - permission_classes = (IsSuperUser,) - model = Platform - template_name = 'assets/platform_create_update.html' - - def post(self, *args, **kwargs): - pass - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - meta_form = PlatformMetaForm(initial=self.object.meta) - context.update({ - 'app': _('Assets'), - 'action': _("Update platform"), - 'type': 'update', - 'meta_form': meta_form, - }) - return context - - -class PlatformDetailView(generic.DetailView): - permission_classes = (IsSuperUser,) - model = Platform - template_name = 'assets/platform_detail.html' - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'app': _('Assets'), - 'action': _("Platform detail"), - }) - return context diff --git a/apps/assets/views/system_user.py b/apps/assets/views/system_user.py deleted file mode 100644 index 5aa1e8431..000000000 --- a/apps/assets/views/system_user.py +++ /dev/null @@ -1,122 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from django.utils.translation import ugettext as _ -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 - -from common.const import create_success_msg, update_success_msg -from ..forms import SystemUserForm -from ..models import SystemUser, Node, CommandFilter -from common.permissions import PermissionsMixin, IsOrgAdmin - - -__all__ = [ - 'SystemUserCreateView', 'SystemUserUpdateView', - 'SystemUserDetailView', 'SystemUserDeleteView', - 'SystemUserAssetView', 'SystemUserListView', - 'SystemUserUserView', -] - - -class SystemUserListView(PermissionsMixin, TemplateView): - template_name = 'assets/system_user_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('System user list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SystemUserCreateView(PermissionsMixin, SuccessMessageMixin, CreateView): - model = SystemUser - form_class = SystemUserForm - template_name = 'assets/system_user_create.html' - success_url = reverse_lazy('assets:system-user-list') - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create system user'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SystemUserUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView): - model = SystemUser - form_class = SystemUserForm - template_name = 'assets/system_user_update.html' - success_url = reverse_lazy('assets:system-user-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update system user') - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SystemUserDetailView(PermissionsMixin, DetailView): - template_name = 'assets/system_user_detail.html' - context_object_name = 'system_user' - model = SystemUser - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - cmd_filters_remain = CommandFilter.objects.exclude(system_users=self.object) - context = { - 'app': _('Assets'), - 'action': _('System user detail'), - 'cmd_filters_remain': cmd_filters_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SystemUserDeleteView(PermissionsMixin, DeleteView): - model = SystemUser - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:system-user-list') - permission_classes = [IsOrgAdmin] - - -class SystemUserAssetView(PermissionsMixin, DetailView): - model = SystemUser - template_name = 'assets/system_user_assets.html' - context_object_name = 'system_user' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('assets'), - 'action': _('System user assets'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SystemUserUserView(PermissionsMixin, DetailView): - model = SystemUser - template_name = 'assets/system_user_users.html' - context_object_name = 'system_user' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('assets'), - 'action': _('System user users'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/audits/api.py b/apps/audits/api.py index 3677a8e8e..9d6d0339e 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -1,14 +1,124 @@ # -*- coding: utf-8 -*- # +from rest_framework.mixins import ListModelMixin, CreateModelMixin +from django.db.models import F, Value +from django.db.models.functions import Concat -from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor -from orgs.mixins.api import OrgModelViewSet -from .models import FTPLog -from .serializers import FTPLogSerializer +from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin +from common.drf.filters import DatetimeRangeFilter +from common.api import CommonGenericViewSet +from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin +from orgs.utils import current_org +from ops.models import CommandExecution +from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog +from .serializers import FTPLogSerializer, UserLoginLogSerializer, CommandExecutionSerializer +from .serializers import OperateLogSerializer, PasswordChangeLogSerializer, CommandExecutionHostsRelationSerializer -class FTPLogViewSet(OrgModelViewSet): +class FTPLogViewSet(CreateModelMixin, + ListModelMixin, + OrgGenericViewSet): model = FTPLog serializer_class = FTPLogSerializer permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) - http_method_names = ['get', 'post', 'head', 'options'] + extra_filter_backends = [DatetimeRangeFilter] + date_range_filter_fields = [ + ('date_start', ('date_from', 'date_to')) + ] + filter_fields = ['user', 'asset', 'system_user', 'filename'] + search_fields = filter_fields + + +class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet): + queryset = UserLoginLog.objects.all() + permission_classes = [IsOrgAdmin | IsOrgAuditor] + serializer_class = UserLoginLogSerializer + extra_filter_backends = [DatetimeRangeFilter] + date_range_filter_fields = [ + ('datetime', ('date_from', 'date_to')) + ] + filter_fields = ['username', 'ip', 'city', 'type', 'status', 'mfa'] + search_fields =['username', 'ip', 'city'] + + @staticmethod + def get_org_members(): + users = current_org.get_org_members().values_list('username', flat=True) + return users + + def get_queryset(self): + queryset = super().get_queryset() + if not current_org.is_default(): + users = self.get_org_members() + queryset = queryset.filter(username__in=users) + return queryset + + +class OperateLogViewSet(ListModelMixin, OrgGenericViewSet): + model = OperateLog + serializer_class = OperateLogSerializer + permission_classes = [IsOrgAdmin | IsOrgAuditor] + extra_filter_backends = [DatetimeRangeFilter] + date_range_filter_fields = [ + ('datetime', ('date_from', 'date_to')) + ] + filter_fields = ['user', 'action', 'resource_type', 'resource', 'remote_addr'] + search_fields = ['resource'] + ordering = ['-datetime'] + + +class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet): + queryset = PasswordChangeLog.objects.all() + permission_classes = [IsOrgAdmin | IsOrgAuditor] + serializer_class = PasswordChangeLogSerializer + extra_filter_backends = [DatetimeRangeFilter] + date_range_filter_fields = [ + ('datetime', ('date_from', 'date_to')) + ] + filter_fields = ['user', 'change_by', 'remote_addr'] + ordering = ['-datetime'] + + def get_queryset(self): + users = current_org.get_org_members() + queryset = super().get_queryset().filter( + user__in=[user.__str__() for user in users] + ) + return queryset + + +class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): + model = CommandExecution + serializer_class = CommandExecutionSerializer + permission_classes = [IsOrgAdmin | IsOrgAuditor] + extra_filter_backends = [DatetimeRangeFilter] + date_range_filter_fields = [ + ('date_start', ('date_from', 'date_to')) + ] + filter_fields = ['user__name', 'command', 'run_as__name', 'is_finished'] + search_fields = ['command', 'user__name', 'run_as__name'] + ordering = ['-date_created'] + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.filter(run_as__org_id=current_org.org_id()) + return queryset + + +class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): + serializer_class = CommandExecutionHostsRelationSerializer + m2m_field = CommandExecution.hosts.field + permission_classes = (IsOrgAdmin,) + filter_fields = [ + 'id', 'asset', 'commandexecution' + ] + search_fields = ('asset__hostname', ) + http_method_names = ['options', 'get'] + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.annotate( + asset_display=Concat( + F('asset__hostname'), Value('('), + F('asset__ip'), Value(')') + ) + ) + return queryset diff --git a/apps/audits/filters.py b/apps/audits/filters.py new file mode 100644 index 000000000..6db2d9b21 --- /dev/null +++ b/apps/audits/filters.py @@ -0,0 +1,32 @@ +from rest_framework import filters +from rest_framework.compat import coreapi, coreschema + +from orgs.utils import current_org + + +__all__ = ['CurrentOrgMembersFilter'] + + +class CurrentOrgMembersFilter(filters.BaseFilterBackend): + def get_schema_fields(self, view): + return [ + coreapi.Field( + name='user', location='query', required=False, type='string', + schema=coreschema.String( + title='user', + description='user' + ) + ) + ] + + def _get_user_list(self): + users = current_org.get_org_members(exclude=('Auditor',)) + return users + + def filter_queryset(self, request, queryset, view): + user_id = request.GET.get('user') + if user_id: + queryset = queryset.filter(user=user_id) + else: + queryset = queryset.filter(user__in=self._get_user_list()) + return queryset diff --git a/apps/audits/migrations/0008_auto_20200508_2105.py b/apps/audits/migrations/0008_auto_20200508_2105.py new file mode 100644 index 000000000..bde6687ba --- /dev/null +++ b/apps/audits/migrations/0008_auto_20200508_2105.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.10 on 2020-05-08 13:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audits', '0007_auto_20191202_1010'), + ] + + operations = [ + migrations.AlterField( + model_name='ftplog', + name='date_start', + field=models.DateTimeField(auto_now_add=True, verbose_name='Date start'), + ), + migrations.AlterField( + model_name='operatelog', + name='datetime', + field=models.DateTimeField(auto_now=True, verbose_name='Datetime'), + ), + migrations.AlterField( + model_name='passwordchangelog', + name='datetime', + field=models.DateTimeField(auto_now=True, verbose_name='Datetime'), + ), + ] diff --git a/apps/audits/models.py b/apps/audits/models.py index 81866bb1d..406d0aa44 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -22,7 +22,7 @@ class FTPLog(OrgModelMixin): operate = models.CharField(max_length=16, verbose_name=_("Operate")) filename = models.CharField(max_length=1024, verbose_name=_("Filename")) is_success = models.BooleanField(default=True, verbose_name=_("Success")) - date_start = models.DateTimeField(auto_now_add=True) + date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) class OperateLog(OrgModelMixin): @@ -40,7 +40,7 @@ class OperateLog(OrgModelMixin): resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type")) resource = models.CharField(max_length=128, verbose_name=_("Resource")) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) - datetime = models.DateTimeField(auto_now=True) + datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime')) def __str__(self): return "<{}> {} <{}>".format(self.user, self.action, self.resource) @@ -51,7 +51,7 @@ class PasswordChangeLog(models.Model): user = models.CharField(max_length=128, verbose_name=_('User')) change_by = models.CharField(max_length=128, verbose_name=_("Change by")) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) - datetime = models.DateTimeField(auto_now=True) + datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime')) def __str__(self): return "{} change {}'s password".format(self.change_by, self.user) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 7dcd94d73..d4213f243 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -1,9 +1,13 @@ # -*- coding: utf-8 -*- # - +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from django.db.models import F +from common.mixins import BulkSerializerMixin +from common.serializers import AdaptedBulkListSerializer from terminal.models import Session +from ops.models import CommandExecution from . import models @@ -11,25 +15,40 @@ class FTPLogSerializer(serializers.ModelSerializer): class Meta: model = models.FTPLog - fields = '__all__' + fields = ( + 'id', 'user', 'remote_addr', 'asset', 'system_user', + 'operate', 'filename', 'is_success', 'date_start' + ) -class LoginLogSerializer(serializers.ModelSerializer): +class UserLoginLogSerializer(serializers.ModelSerializer): + type_display = serializers.ReadOnlyField(source='get_type_display') + status_display = serializers.ReadOnlyField(source='get_status_display') + mfa_display = serializers.ReadOnlyField(source='get_mfa_display') + class Meta: model = models.UserLoginLog - fields = '__all__' + fields = ( + 'id', 'username', 'type', 'type_display', 'ip', 'city', 'user_agent', + 'mfa', 'reason', 'status', 'status_display', 'datetime', 'mfa_display' + ) class OperateLogSerializer(serializers.ModelSerializer): class Meta: model = models.OperateLog - fields = '__all__' + fields = ( + 'id', 'user', 'action', 'resource_type', 'resource', + 'remote_addr', 'datetime' + ) class PasswordChangeLogSerializer(serializers.ModelSerializer): class Meta: model = models.PasswordChangeLog - fields = '__all__' + fields = ( + 'id', 'user', 'change_by', 'remote_addr', 'datetime' + ) class SessionAuditSerializer(serializers.ModelSerializer): @@ -37,3 +56,41 @@ class SessionAuditSerializer(serializers.ModelSerializer): model = Session fields = '__all__' + +class CommandExecutionSerializer(serializers.ModelSerializer): + is_success = serializers.BooleanField(read_only=True, label=_('Is success')) + + class Meta: + model = CommandExecution + fields_mini = ['id'] + fields_small = fields_mini + [ + 'run_as', 'command', 'user', 'is_finished', + 'date_start', 'result', 'is_success' + ] + fields = fields_small + ['hosts', 'run_as_display', 'user_display'] + extra_kwargs = { + 'result': {'label': _('Result')}, # model 上的方法,只能在这修改 + 'is_success': {'label': _('Is success')}, + 'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改 + 'run_as': {'label': _('Run as')}, + 'user': {'label': _('User')}, + } + + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.annotate(user_display=F('user__name'))\ + .annotate(run_as_display=F('run_as__name')) + return queryset + + +class CommandExecutionHostsRelationSerializer(BulkSerializerMixin, serializers.ModelSerializer): + asset_display = serializers.ReadOnlyField() + commandexecution_display = serializers.ReadOnlyField() + + class Meta: + list_serializer_class = AdaptedBulkListSerializer + model = CommandExecution.hosts.through + fields = [ + 'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display' + ] diff --git a/apps/audits/templates/audits/ftp_log_list.html b/apps/audits/templates/audits/ftp_log_list.html deleted file mode 100644 index e0399ca73..000000000 --- a/apps/audits/templates/audits/ftp_log_list.html +++ /dev/null @@ -1,138 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load terminal_tags %} -{% load common_tags %} -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content_left_head %} -{% endblock %} - - -{% block table_search %} -
-
-
- - - to - -
-
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
-
-{% endblock %} - -{% block table_head %} -{# {% trans 'ID' %}#} - {% trans 'User' %} - {% trans 'Asset' %} - {% trans 'System user' %} - {% trans 'Remote addr' %} - {% trans 'Operate' %} - {% trans 'Filename' %} - {% trans 'Success' %} - {% trans 'Date start' %} -{# {% trans 'Action' %}#} -{% endblock %} - -{% block table_body %} - {% for object in object_list %} - -{# #} -{# {{ forloop.counter }}#} -{# #} - {{ object.user }} - {{ object.asset }} - {{ object.system_user }} - {{ object.remote_addr|default:"" }} - {{ object.operate }} - {{ object.filename }} - - {% if object.is_success %} - - {% else %} - - {% endif %} - - {{ object.date_start }} - - {% endfor %} -{% endblock %} - -{% block content_bottom_left %} -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} - diff --git a/apps/audits/templates/audits/login_log_list.html b/apps/audits/templates/audits/login_log_list.html deleted file mode 100644 index 1ac74d311..000000000 --- a/apps/audits/templates/audits/login_log_list.html +++ /dev/null @@ -1,148 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load common_tags %} -{% block content_left_head %} - - -{% endblock %} - - -{% block table_search %} -
-
-
- - -{# #} - to - -
-
-
- -
-
- -
-
-
- -
-
-
-{% endblock %} -{% block table_container %} - - - - - - - - - - - - - - - - - - {% for login_log in object_list %} - - - - - - - - - - - - - {% endfor %} - -
{% trans 'ID' %}{% trans 'Username' %}{% trans 'Type' %}{% trans 'UA' %}{% trans 'IP' %}{% trans 'City' %}{% trans 'MFA' %}{% trans 'Reason' %}{% trans 'Status' %}{% trans 'Date' %}
{{ forloop.counter }}{{ login_log.username }}{{ login_log.get_type_display }} - {{ login_log.user_agent | truncatechars:20 }} - {{ login_log.ip }}{{ login_log.city }}{{ login_log.get_mfa_display }}{{ login_log.reason_display }}{{ login_log.get_status_display }}{{ login_log.datetime }}
-
-
- -
- -
-
-
- -{% endblock %} - - -{% block custom_foot_js %} - - -{% endblock %} - diff --git a/apps/audits/templates/audits/operate_log_list.html b/apps/audits/templates/audits/operate_log_list.html deleted file mode 100644 index 0dd8c8ff4..000000000 --- a/apps/audits/templates/audits/operate_log_list.html +++ /dev/null @@ -1,123 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load terminal_tags %} -{% load common_tags %} -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content_left_head %} -{% endblock %} - - -{% block table_search %} -
-
-
- - - to - -
-
-
- -
-
- -
-
- -
-
-
- -
-
-
-{% endblock %} - -{% block table_head %} - {% trans 'Handlers' %} - {% trans 'Action' %} - {% trans 'Resource Type' %} - {% trans 'Resource' %} - {% trans 'Remote addr' %} - {% trans 'Datetime' %} -{% endblock %} - -{% block table_body %} - {% for object in object_list %} - -{# #} -{# {{ forloop.counter }}#} -{# #} - {{ object.user }} - {{ object.get_action_display }} - {{ object.resource_type }} - {{ object.resource }} - {{ object.remote_addr }} - {{ object.datetime }} - - {% endfor %} -{% endblock %} - -{% block content_bottom_left %} -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} - diff --git a/apps/audits/templates/audits/password_change_log_list.html b/apps/audits/templates/audits/password_change_log_list.html deleted file mode 100644 index 506366cbf..000000000 --- a/apps/audits/templates/audits/password_change_log_list.html +++ /dev/null @@ -1,100 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load terminal_tags %} -{% load common_tags %} -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content_left_head %} -{% endblock %} - - -{% block table_search %} -
-
-
- - - to - -
-
-
- -
-
-
- -
-
-
-{% endblock %} - -{% block table_head %} - {% trans 'User' %} - {% trans 'Change by' %} - {% trans 'Remote addr' %} - {% trans 'Datetime' %} -{% endblock %} - -{% block table_body %} - {% for object in object_list %} - - {{ object.user }} - {{ object.change_by }} - {{ object.remote_addr }} - {{ object.datetime }} - - {% endfor %} -{% endblock %} - -{% block content_bottom_left %} -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} - diff --git a/apps/audits/urls/api_urls.py b/apps/audits/urls/api_urls.py index 249843f24..8cc822706 100644 --- a/apps/audits/urls/api_urls.py +++ b/apps/audits/urls/api_urls.py @@ -12,6 +12,12 @@ app_name = "audits" router = DefaultRouter() router.register(r'ftp-logs', api.FTPLogViewSet, 'ftp-log') +router.register(r'login-logs', api.UserLoginLogViewSet, 'login-log') +router.register(r'operate-logs', api.OperateLogViewSet, 'operate-log') +router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password-change-log') +router.register(r'command-execution-logs', api.CommandExecutionViewSet, 'command-execution-log') +router.register(r'command-executions-hosts-relations', api.CommandExecutionHostRelationViewSet, 'command-executions-hosts-relation') + urlpatterns = [ ] diff --git a/apps/audits/urls/view_urls.py b/apps/audits/urls/view_urls.py index ef400cb99..2066db771 100644 --- a/apps/audits/urls/view_urls.py +++ b/apps/audits/urls/view_urls.py @@ -2,17 +2,10 @@ from __future__ import unicode_literals from django.urls import path -from .. import views __all__ = ["urlpatterns"] app_name = "audits" urlpatterns = [ - path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'), - path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'), - path('operate-log/', views.OperateLogListView.as_view(), name='operate-log-list'), - path('password-change-log/', views.PasswordChangeLogList.as_view(), name='password-change-log-list'), - path('command-execution-log/', views.CommandExecutionListView.as_view(), name='command-execution-log-list'), - path('login-log/export/', views.LoginLogExportView.as_view(), name='login-log-export'), ] diff --git a/apps/audits/views.py b/apps/audits/views.py deleted file mode 100644 index 6ac0b1b99..000000000 --- a/apps/audits/views.py +++ /dev/null @@ -1,292 +0,0 @@ -import csv -import json -import uuid -import codecs - - -from django.conf import settings -from django.urls import reverse -from django.utils import timezone -from django.core.cache import cache -from django.http import HttpResponse, JsonResponse -from django.utils.decorators import method_decorator -from django.views import View -from django.views.decorators.csrf import csrf_exempt -from django.views.generic import ListView -from django.utils.translation import ugettext as _ -from django.db.models import Q - -from audits.utils import get_excel_response, write_content_to_excel -from common.mixins import DatetimeSearchMixin -from common.permissions import ( - PermissionsMixin, IsOrgAdmin, IsValidUser, IsOrgAuditor -) -from orgs.utils import current_org -from ops.views import CommandExecutionListView as UserCommandExecutionListView -from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog - - -def get_resource_type_list(): - from users.models import User, UserGroup - from assets.models import ( - Asset, Node, AdminUser, SystemUser, Domain, Gateway, CommandFilter, - CommandFilterRule, - ) - from orgs.models import Organization - from perms.models import AssetPermission - - models = [ - User, UserGroup, Asset, Node, AdminUser, SystemUser, Domain, - Gateway, Organization, AssetPermission, CommandFilter, CommandFilterRule - ] - return [model._meta.verbose_name for model in models] - - -class FTPLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): - model = FTPLog - template_name = 'audits/ftp_log_list.html' - paginate_by = settings.DISPLAY_PER_PAGE - user = asset = system_user = filename = '' - date_from = date_to = None - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get_queryset(self): - self.queryset = super().get_queryset() - self.user = self.request.GET.get('user') - self.asset = self.request.GET.get('asset') - self.system_user = self.request.GET.get('system_user') - self.filename = self.request.GET.get('filename', '') - - filter_kwargs = dict() - filter_kwargs['date_start__gt'] = self.date_from - filter_kwargs['date_start__lt'] = self.date_to - if self.user: - filter_kwargs['user'] = self.user - if self.asset: - filter_kwargs['asset'] = self.asset - if self.system_user: - filter_kwargs['system_user'] = self.system_user - if self.filename: - filter_kwargs['filename__contains'] = self.filename - if filter_kwargs: - self.queryset = self.queryset.filter(**filter_kwargs).order_by('-date_start') - return self.queryset - - def get_context_data(self, **kwargs): - context = { - 'user_list': FTPLog.objects.values_list('user', flat=True).distinct(), - 'asset_list': FTPLog.objects.values_list('asset', flat=True).distinct(), - 'system_user_list': FTPLog.objects.values_list('system_user', flat=True).distinct(), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user': self.user, - 'asset': self.asset, - 'system_user': self.system_user, - 'filename': self.filename, - "app": _("Audits"), - "action": _("FTP log"), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class OperateLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): - model = OperateLog - template_name = 'audits/operate_log_list.html' - paginate_by = settings.DISPLAY_PER_PAGE - user = action = resource_type = '' - date_from = date_to = None - actions_dict = dict(OperateLog.ACTION_CHOICES) - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get_queryset(self): - self.queryset = super().get_queryset() - self.user = self.request.GET.get('user') - self.action = self.request.GET.get('action') - self.resource_type = self.request.GET.get('resource_type') - - filter_kwargs = dict() - filter_kwargs['datetime__gt'] = self.date_from - filter_kwargs['datetime__lt'] = self.date_to - if self.user: - filter_kwargs['user'] = self.user - if self.action: - filter_kwargs['action'] = self.action - if self.resource_type: - filter_kwargs['resource_type'] = self.resource_type - if filter_kwargs: - self.queryset = self.queryset.filter(**filter_kwargs).order_by('-datetime') - return self.queryset - - def get_context_data(self, **kwargs): - context = { - 'user_list': [str(user) for user in current_org.get_org_members()], - 'actions': self.actions_dict, - 'search_action': self.action, - 'resource_type_list': get_resource_type_list(), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user': self.user, - 'resource_type': self.resource_type, - "app": _("Audits"), - "action": _("Operate log"), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class PasswordChangeLogList(PermissionsMixin, DatetimeSearchMixin, ListView): - model = PasswordChangeLog - template_name = 'audits/password_change_log_list.html' - paginate_by = settings.DISPLAY_PER_PAGE - user = '' - date_from = date_to = None - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get_queryset(self): - users = current_org.get_org_members() - self.queryset = super().get_queryset().filter( - user__in=[user.__str__() for user in users] - ) - self.user = self.request.GET.get('user') - - filter_kwargs = dict() - filter_kwargs['datetime__gt'] = self.date_from - filter_kwargs['datetime__lt'] = self.date_to - if self.user: - filter_kwargs['user'] = self.user - if filter_kwargs: - self.queryset = self.queryset.filter(**filter_kwargs).order_by('-datetime') - return self.queryset - - def get_context_data(self, **kwargs): - context = { - 'user_list': [str(user) for user in current_org.get_org_members()], - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user': self.user, - "app": _("Audits"), - "action": _("Password change log"), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class LoginLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): - template_name = 'audits/login_log_list.html' - model = UserLoginLog - paginate_by = settings.DISPLAY_PER_PAGE - user = keyword = "" - date_to = date_from = None - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - @staticmethod - def get_org_members(): - users = current_org.get_org_members().values_list('username', flat=True) - return users - - def get_queryset(self): - if current_org.is_default(): - queryset = super().get_queryset() - else: - users = self.get_org_members() - queryset = super().get_queryset().filter(username__in=users) - - self.user = self.request.GET.get('user', '') - self.keyword = self.request.GET.get("keyword", '') - - queryset = queryset.filter( - datetime__gt=self.date_from, datetime__lt=self.date_to - ) - if self.user: - queryset = queryset.filter(username=self.user) - if self.keyword: - queryset = queryset.filter( - Q(ip__contains=self.keyword) | - Q(city__contains=self.keyword) | - Q(username__contains=self.keyword) - ) - return queryset - - def get_context_data(self, **kwargs): - context = { - 'app': _('Audits'), - 'action': _('Login log'), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user': self.user, - 'keyword': self.keyword, - 'user_list': self.get_org_members(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandExecutionListView(UserCommandExecutionListView): - user_id = None - - def get_user_list(self): - users = current_org.get_org_members(exclude=('Auditor',)) - return users - - def get_queryset(self): - queryset = self._get_queryset() - self.user_id = self.request.GET.get('user') - org_users = self.get_user_list() - if self.user_id: - queryset = queryset.filter(user=self.user_id) - else: - queryset = queryset.filter(user__in=org_users) - return queryset - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'app': _('Audits'), - 'action': _('Command execution log'), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user_list': [(str(user.id), user) for user in self.get_user_list()], - 'keyword': self.keyword, - 'user_id': self.user_id, - }) - return context - - -@method_decorator(csrf_exempt, name='dispatch') -class LoginLogExportView(PermissionsMixin, View): - permission_classes = [IsValidUser] - - def get(self, request): - fields = [ - field for field in UserLoginLog._meta.fields - ] - filename = 'login-logs-{}.csv'.format( - timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S') - ) - excel_response = get_excel_response(filename) - header = [field.verbose_name for field in fields] - login_logs = cache.get(request.GET.get('spm', ''), []) - - response = write_content_to_excel( - excel_response, login_logs=login_logs, header=header, fields=fields - ) - return response - - def post(self, request): - try: - date_from = json.loads(request.body).get('date_from', []) - date_to = json.loads(request.body).get('date_to', []) - user = json.loads(request.body).get('user', []) - keyword = json.loads(request.body).get('keyword', []) - - login_logs = UserLoginLog.get_login_logs( - date_from=date_from, date_to=date_to, user=user, - keyword=keyword, - ) - except ValueError: - return HttpResponse('Json object not valid', status=400) - spm = uuid.uuid4().hex - cache.set(spm, login_logs, 300) - url = reverse('audits:login-log-export') + '?spm=%s' % spm - return JsonResponse({'redirect': url}) \ No newline at end of file diff --git a/apps/authentication/api/mfa.py b/apps/authentication/api/mfa.py index cc4f2ab6b..f95593bbe 100644 --- a/apps/authentication/api/mfa.py +++ b/apps/authentication/api/mfa.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # import time +from django.utils.translation import ugettext as _ from rest_framework.permissions import AllowAny from rest_framework.generics import CreateAPIView from rest_framework.serializers import ValidationError @@ -56,4 +57,4 @@ class UserOtpVerifyApi(CreateAPIView): request.session["MFA_VERIFY_TIME"] = int(time.time()) return Response({"ok": "1"}) else: - return Response({"error": "Code not valid"}, status=400) + return Response({"error": _("Code is invalid")}, status=400) diff --git a/apps/authentication/backends/api.py b/apps/authentication/backends/api.py index 599351d0a..b61798695 100644 --- a/apps/authentication/backends/api.py +++ b/apps/authentication/backends/api.py @@ -106,6 +106,9 @@ class AccessKeyAuthentication(authentication.BaseAuthentication): raise exceptions.AuthenticationFailed(_('User disabled.')) return access_key.user, None + def authenticate_header(self, request): + return 'Sign access_key_id:Signature' + class AccessTokenAuthentication(authentication.BaseAuthentication): keyword = 'Bearer' @@ -143,6 +146,9 @@ class AccessTokenAuthentication(authentication.BaseAuthentication): raise exceptions.AuthenticationFailed(msg) return user, None + def authenticate_header(self, request): + return self.keyword + class PrivateTokenAuthentication(authentication.TokenAuthentication): model = PrivateToken diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html index 9cacdf4ff..8812f582b 100644 --- a/apps/authentication/templates/authentication/login.html +++ b/apps/authentication/templates/authentication/login.html @@ -46,7 +46,7 @@
diff --git a/apps/authentication/templates/authentication/login_wait_confirm.html b/apps/authentication/templates/authentication/login_wait_confirm.html index 8a67a501e..7ee7632f0 100644 --- a/apps/authentication/templates/authentication/login_wait_confirm.html +++ b/apps/authentication/templates/authentication/login_wait_confirm.html @@ -10,7 +10,6 @@ {{ title }} {% include '_head_css_js.html' %} - diff --git a/apps/authentication/templates/authentication/xpack_login.html b/apps/authentication/templates/authentication/xpack_login.html index ba3f92308..8c1cb24f8 100644 --- a/apps/authentication/templates/authentication/xpack_login.html +++ b/apps/authentication/templates/authentication/xpack_login.html @@ -112,7 +112,7 @@
diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index bee5f8517..12e1fea84 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -4,6 +4,7 @@ from django.urls import path, include from .. import views +from users import views as users_view app_name = 'authentication' @@ -15,7 +16,28 @@ urlpatterns = [ path('login/guard/', views.UserLoginGuardView.as_view(), name='login-guard'), path('logout/', views.UserLogoutView.as_view(), name='logout'), + # 原来在users中的 + path('password/forgot/', users_view.UserForgotPasswordView.as_view(), name='forgot-password'), + path('password/forgot/sendmail-success/', users_view.UserForgotPasswordSendmailSuccessView.as_view(), + name='forgot-password-sendmail-success'), + path('password/reset/', users_view.UserResetPasswordView.as_view(), name='reset-password'), + path('password/reset/success/', users_view.UserResetPasswordSuccessView.as_view(), name='reset-password-success'), + path('password/verify/', users_view.UserVerifyPasswordView.as_view(), name='user-verify-password'), + + # Profile + path('profile/pubkey/generate/', users_view.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'), + path('profile/otp/enable/start/', users_view.UserOtpEnableStartView.as_view(), name='user-otp-enable-start'), + path('profile/otp/enable/install-app/', users_view.UserOtpEnableInstallAppView.as_view(), + name='user-otp-enable-install-app'), + path('profile/otp/enable/bind/', users_view.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'), + path('profile/otp/disable/authentication/', users_view.UserDisableMFAView.as_view(), + name='user-otp-disable-authentication'), + path('profile/otp/update/', users_view.UserOtpUpdateView.as_view(), name='user-otp-update'), + path('profile/otp/settings-success/', users_view.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'), + path('first-login/', users_view.UserFirstLoginView.as_view(), name='user-first-login'), + # openid path('cas/', include(('authentication.backends.cas.urls', 'authentication'), namespace='cas')), path('openid/', include(('jms_oidc_rp.urls', 'authentication'), namespace='openid')), + path('captcha/', include('captcha.urls')), ] diff --git a/apps/common/api.py b/apps/common/api.py index d69540cfd..06c162539 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -9,13 +9,16 @@ from django.views.decorators.csrf import csrf_exempt from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import generics, serializers +from rest_framework.viewsets import GenericViewSet +from common.permissions import IsValidUser from .http import HttpResponseTemporaryRedirect from .const import KEY_CACHE_RESOURCES_ID from .utils import get_logger +from .mixins import CommonApiMixin __all__ = [ - 'LogTailApi', 'ResourcesIDCacheApi', + 'LogTailApi', 'ResourcesIDCacheApi', 'CommonGenericViewSet' ] logger = get_logger(__file__) @@ -28,7 +31,7 @@ class OutputSerializer(serializers.Serializer): class LogTailApi(generics.RetrieveAPIView): - permission_classes = () + permission_classes = (IsValidUser,) buff_size = 1024 * 10 serializer_class = OutputSerializer end = False @@ -84,12 +87,14 @@ class LogTailApi(generics.RetrieveAPIView): class ResourcesIDCacheApi(APIView): + permission_classes = (IsValidUser,) + def post(self, request, *args, **kwargs): spm = str(uuid.uuid4()) - resources_id = request.data.get('resources') - if resources_id: + resources = request.data.get('resources') + if resources is not None: cache_key = KEY_CACHE_RESOURCES_ID.format(spm) - cache.set(cache_key, resources_id, 300) + cache.set(cache_key, resources, 300) return Response({'spm': spm}) @@ -100,3 +105,7 @@ def redirect_plural_name_api(request, *args, **kwargs): full_path = org_full_path.replace(resource, resource+"s", 1) logger.debug("Redirect {} => {}".format(org_full_path, full_path)) return HttpResponseTemporaryRedirect(full_path) + + +class CommonGenericViewSet(CommonApiMixin, GenericViewSet): + pass diff --git a/apps/common/const.py b/apps/common/const/__init__.py similarity index 100% rename from apps/common/const.py rename to apps/common/const/__init__.py diff --git a/apps/common/const/http.py b/apps/common/const/http.py new file mode 100644 index 000000000..4717d38c9 --- /dev/null +++ b/apps/common/const/http.py @@ -0,0 +1,7 @@ + +GET = 'GET' +POST = 'POST' +PUT = 'PUT' +PATCH = 'PATCH' +DELETE = 'DELETE' +OPTIONS = 'OPTIONS' diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py index c11a7864d..7740cadba 100644 --- a/apps/common/drf/filters.py +++ b/apps/common/drf/filters.py @@ -1,27 +1,61 @@ # -*- coding: utf-8 -*- # -import coreapi from rest_framework import filters from rest_framework.fields import DateTimeField from rest_framework.serializers import ValidationError +from rest_framework.compat import coreapi, coreschema from django.core.cache import cache +from django.core.exceptions import ImproperlyConfigured import logging from common import const -__all__ = ["DatetimeRangeFilter", "IDSpmFilter", "CustomFilter"] +__all__ = ["DatetimeRangeFilter", "IDSpmFilter", 'IDInFilter', "CustomFilter"] class DatetimeRangeFilter(filters.BaseFilterBackend): - def filter_queryset(self, request, queryset, view): + def get_schema_fields(self, view): + ret = [] + fields = self._get_date_range_filter_fields(view) + + for attr, date_range_keyword in fields.items(): + if len(date_range_keyword) != 2: + continue + for v in date_range_keyword: + ret.append( + coreapi.Field( + name=v, location='query', required=False, type='string', + schema=coreschema.String( + title=v, + description='%s %s' % (attr, v) + ) + ) + ) + + return ret + + def _get_date_range_filter_fields(self, view): if not hasattr(view, 'date_range_filter_fields'): - return queryset + return {} try: - fields = dict(view.date_range_filter_fields) + return dict(view.date_range_filter_fields) except ValueError: - msg = "View {} datetime_filter_fields set is error".format(view.name) + msg = """ + View {} `date_range_filter_fields` set is improperly. + For example: + ``` + class ExampleView: + date_range_filter_fields = [ + ('db column', ('query param date from', 'query param date to')) + ] + ``` + """.format(view.name) logging.error(msg) - return queryset + raise ImproperlyConfigured(msg) + + def filter_queryset(self, request, queryset, view): + fields = self._get_date_range_filter_fields(view) + kwargs = {} for attr, date_range_keyword in fields.items(): if len(date_range_keyword) != 2: @@ -62,12 +96,31 @@ class IDSpmFilter(filters.BaseFilterBackend): return queryset cache_key = const.KEY_CACHE_RESOURCES_ID.format(spm) resources_id = cache.get(cache_key) - if not resources_id or not isinstance(resources_id, list): + if resources_id is None or not isinstance(resources_id, list): return queryset queryset = queryset.filter(id__in=resources_id) return queryset +class IDInFilter(filters.BaseFilterBackend): + def get_schema_fields(self, view): + return [ + coreapi.Field( + name='ids', location='query', required=False, + type='string', example='/api/v1/users/users?ids=1,2,3', + description='Filter by id set' + ) + ] + + def filter_queryset(self, request, queryset, view): + ids = request.query_params.get('ids') + if not ids: + return queryset + id_list = [i.strip() for i in ids.split(',')] + queryset = queryset.filter(id__in=id_list) + return queryset + + class CustomFilter(filters.BaseFilterBackend): def get_schema_fields(self, view): @@ -92,3 +145,10 @@ class CustomFilter(filters.BaseFilterBackend): def filter_queryset(self, request, queryset, view): return queryset + + +def current_user_filter(user_field='user'): + class CurrentUserFilter(filters.BaseFilterBackend): + def filter_queryset(self, request, queryset, view): + return queryset.filter(**{user_field: request.user}) + return CurrentUserFilter diff --git a/apps/common/drf/parsers/csv.py b/apps/common/drf/parsers/csv.py index 254c73375..95a9ec732 100644 --- a/apps/common/drf/parsers/csv.py +++ b/apps/common/drf/parsers/csv.py @@ -58,6 +58,12 @@ class JMSCSVParser(BaseParser): col = col.replace("“", '"').replace("”", '"').\ replace("‘", '"').replace('’', '"').replace("'", '"') col = json.loads(col) + # 字典转换 + if isinstance(col, str) and col.find("{") != -1 and col.find("}") != -1: + # 替换中文格式引号 + col = col.replace("“", '"').replace("”", '"'). \ + replace("‘", '"').replace('’', '"').replace("'", '"') + col = json.loads(col) _row.append(col) return _row @@ -68,7 +74,7 @@ class JMSCSVParser(BaseParser): """ _row_data = {} for k, v in row_data.items(): - if isinstance(v, list) \ + if isinstance(v, list) or isinstance(v, dict)\ or isinstance(v, str) and k.strip() and v.strip(): _row_data[k] = v return _row_data diff --git a/apps/common/drf/renders/__init__.py b/apps/common/drf/renders/__init__.py index 671c86586..f99b13586 100644 --- a/apps/common/drf/renders/__init__.py +++ b/apps/common/drf/renders/__init__.py @@ -1 +1,13 @@ -from .csv import * \ No newline at end of file +from rest_framework import renderers + +from .csv import * + + +class PassthroughRenderer(renderers.BaseRenderer): + """ + Return data as-is. View should supply a Response. + """ + media_type = '' + format = '' + def render(self, data, accepted_media_type=None, renderer_context=None): + return data diff --git a/apps/common/drf/renders/csv.py b/apps/common/drf/renders/csv.py index d4ae9e6b8..0d90af359 100644 --- a/apps/common/drf/renders/csv.py +++ b/apps/common/drf/renders/csv.py @@ -21,13 +21,16 @@ class JMSCSVRender(BaseRenderer): @staticmethod def _get_show_fields(fields, template): - if template in ('import', 'update'): + if template == 'import': + return [v for k, v in fields.items() if not v.read_only and k != "org_id" and k != 'id'] + elif template == 'update': return [v for k, v in fields.items() if not v.read_only and k != "org_id"] else: return [v for k, v in fields.items() if not v.write_only and k != "org_id"] @staticmethod def _gen_table(data, fields): + data = data[:100] yield ['*{}'.format(f.label) if f.required else f.label for f in fields] for item in data: diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py index ddb61f7c0..945a98ea4 100644 --- a/apps/common/fields/model.py +++ b/apps/common/fields/model.py @@ -3,8 +3,9 @@ import json from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import force_text -from ..utils import signer +from ..utils import signer, aes_crypto __all__ = [ @@ -111,22 +112,45 @@ class EncryptMixin: """ EncryptMixin要放在最前面 """ + + def decrypt_from_signer(self, value): + return signer.unsign(value) or '' + + def decrypt_from_aes(self, value): + try: + return aes_crypto.decrypt(value) + except (TypeError, ValueError): + pass + def from_db_value(self, value, expression, connection, context): if value is None: return value - value = signer.unsign(value) + value = force_text(value) + + # 优先采用 aes 解密 + plain_value = self.decrypt_from_aes(value) + + # 如果没有解开,使用原来的signer解密 + if not plain_value: + plain_value = self.decrypt_from_signer(value) + + # 可能和Json mix,所以要先解密,再json sp = super() if hasattr(sp, 'from_db_value'): - return sp.from_db_value(value, expression, connection, context) - return value + plain_value = sp.from_db_value(plain_value, expression, connection, context) + return plain_value def get_prep_value(self, value): if value is None: return value + + # 先 json 再解密 sp = super() if hasattr(sp, 'get_prep_value'): value = sp.get_prep_value(value) - return signer.sign(value) + value = force_text(value) + # 替换新的加密方式 + return aes_crypto.encrypt(value) class EncryptTextField(EncryptMixin, models.TextField): diff --git a/apps/common/local.py b/apps/common/local.py index 37a2ccb0b..e075d29ff 100644 --- a/apps/common/local.py +++ b/apps/common/local.py @@ -1,9 +1,40 @@ # -*- coding: utf-8 -*- # -from werkzeug.local import Local +from jumpserver.const import DYNAMIC +from werkzeug.local import Local, LocalProxy thread_local = Local() def _find(attr): return getattr(thread_local, attr, None) + + +class _Settings: + pass + + +def get_dynamic_cfg_from_thread_local(): + KEY = 'dynamic_config' + + try: + cfg = getattr(thread_local, KEY) + except AttributeError: + cfg = _Settings() + setattr(thread_local, KEY, cfg) + + return cfg + + +class DynamicDefaultLocalProxy(LocalProxy): + def __getattr__(self, item): + try: + value = super().__getattr__(item) + except AttributeError: + value = getattr(DYNAMIC, item)() + setattr(self, item, value) + + return value + + +LOCAL_DYNAMIC_SETTINGS = DynamicDefaultLocalProxy(get_dynamic_cfg_from_thread_local) diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py index 6440cdd10..0b7b5aed6 100644 --- a/apps/common/mixins/api.py +++ b/apps/common/mixins/api.py @@ -3,18 +3,20 @@ import time from hashlib import md5 from threading import Thread +from collections import defaultdict +from django.db.models.signals import m2m_changed from django.core.cache import cache from django.http import JsonResponse from rest_framework.response import Response from rest_framework.settings import api_settings -from common.drf.filters import IDSpmFilter, CustomFilter +from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter from ..utils import lazyproperty __all__ = [ "JSONResponseMixin", "CommonApiMixin", - "IDSpmFilterMixin", 'AsyncApiMixin', + 'AsyncApiMixin', 'RelationMixin' ] @@ -25,19 +27,11 @@ class JSONResponseMixin(object): return JsonResponse(context) -class IDSpmFilterMixin: - def get_filter_backends(self): - backends = super().get_filter_backends() - backends.append(IDSpmFilter) - return backends - - class SerializerMixin: def get_serializer_class(self): serializer_class = None - if hasattr(self, 'serializer_classes') and \ - isinstance(self.serializer_classes, dict): - if self.action == 'list' and self.request.query_params.get('draw'): + if hasattr(self, 'serializer_classes') and isinstance(self.serializer_classes, dict): + if self.action in ['list', 'metadata'] and self.request.query_params.get('draw'): serializer_class = self.serializer_classes.get('display') if serializer_class is None: serializer_class = self.serializer_classes.get( @@ -49,7 +43,10 @@ class SerializerMixin: class ExtraFilterFieldsMixin: - default_added_filters = [CustomFilter, IDSpmFilter] + """ + 额外的 api filter + """ + default_added_filters = [CustomFilter, IDSpmFilter, IDInFilter] filter_backends = api_settings.DEFAULT_FILTER_BACKENDS extra_filter_fields = [] extra_filter_backends = [] @@ -57,9 +54,10 @@ class ExtraFilterFieldsMixin: def get_filter_backends(self): if self.filter_backends != self.__class__.filter_backends: return self.filter_backends - return list(self.filter_backends) + \ - self.default_added_filters + \ - list(self.extra_filter_backends) + backends = list(self.filter_backends) + \ + list(self.default_added_filters) + \ + list(self.extra_filter_backends) + return backends def filter_queryset(self, queryset): for backend in self.get_filter_backends(): @@ -72,6 +70,9 @@ class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin): class InterceptMixin: + """ + Hack默认的dispatch, 让用户可以实现 self.do + """ def dispatch(self, request, *args, **kwargs): self.args = args self.kwargs = kwargs @@ -188,3 +189,47 @@ class AsyncApiMixin(InterceptMixin): data["error"] = str(e) data["status"] = "error" cache.set(key, data, 600) + + +class RelationMixin: + m2m_field = None + from_field = None + to_field = None + to_model = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + assert self.m2m_field is not None, ''' + `m2m_field` should not be `None` + ''' + + self.from_field = self.m2m_field.m2m_field_name() + self.to_field = self.m2m_field.m2m_reverse_field_name() + self.to_model = self.m2m_field.related_model + self.through = getattr(self.m2m_field.model, self.m2m_field.attname).through + + def get_queryset(self): + queryset = self.through.objects.all() + return queryset + + def send_post_add_signal(self, instances): + if not isinstance(instances, list): + instances = [instances] + + from_to_mapper = defaultdict(list) + + for i in instances: + to_id = getattr(i, self.to_field).id + from_obj = getattr(i, self.from_field) + from_to_mapper[from_obj].append(to_id) + + for from_obj, to_ids in from_to_mapper.items(): + m2m_changed.send( + sender=self.through, instance=from_obj, action='post_add', + reverse=False, model=self.to_model, pk_set=to_ids + ) + + def perform_create(self, serializer): + instance = serializer.save() + self.send_post_add_signal(instance) diff --git a/apps/common/mixins/serializers.py b/apps/common/mixins/serializers.py index 492a7cf98..5c3a243cf 100644 --- a/apps/common/mixins/serializers.py +++ b/apps/common/mixins/serializers.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- # +from collections import Iterable +from django.db.models import Prefetch, F from django.core.exceptions import ObjectDoesNotExist from rest_framework.utils import html from rest_framework.settings import api_settings from rest_framework.exceptions import ValidationError from rest_framework.fields import SkipField, empty -__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin'] +__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin', 'CommonSerializerMixin', 'CommonBulkSerializerMixin'] class BulkSerializerMixin(object): @@ -113,3 +115,126 @@ class BulkListSerializerMixin(object): raise ValidationError(errors) return ret + + +class BaseDynamicFieldsPlugin: + def __init__(self, serializer): + self.serializer = serializer + + def can_dynamic(self): + try: + request = self.serializer.context['request'] + method = request.method + except (AttributeError, TypeError, KeyError): + # The serializer was not initialized with request context. + return False + + if method != 'GET': + return False + return True + + def get_request(self): + return self.serializer.context['request'] + + def get_query_params(self): + request = self.get_request() + try: + query_params = request.query_params + except AttributeError: + # DRF 2 + query_params = getattr(request, 'QUERY_PARAMS', request.GET) + return query_params + + def get_exclude_field_names(self): + return set() + + +class QueryFieldsMixin(BaseDynamicFieldsPlugin): + # https://github.com/wimglenn/djangorestframework-queryfields/ + + # If using Django filters in the API, these labels mustn't conflict with any model field names. + include_arg_name = 'fields' + exclude_arg_name = 'fields!' + + # Split field names by this string. It doesn't necessarily have to be a single character. + # Avoid RFC 1738 reserved characters i.e. ';', '/', '?', ':', '@', '=' and '&' + delimiter = ',' + + def get_exclude_field_names(self): + query_params = self.get_query_params() + includes = query_params.getlist(self.include_arg_name) + include_field_names = {name for names in includes for name in names.split(self.delimiter) if name} + + excludes = query_params.getlist(self.exclude_arg_name) + exclude_field_names = {name for names in excludes for name in names.split(self.delimiter) if name} + + if not include_field_names and not exclude_field_names: + # No user fields filtering was requested, we have nothing to do here. + return [] + + serializer_field_names = set(self.serializer.fields) + fields_to_drop = serializer_field_names & exclude_field_names + + if include_field_names: + fields_to_drop |= serializer_field_names - include_field_names + return fields_to_drop + + +class SizedModelFieldsMixin(BaseDynamicFieldsPlugin): + arg_name = 'fields_size' + + def can_dynamic(self): + if not hasattr(self.serializer, 'Meta'): + return False + can = super().can_dynamic() + return can + + def get_exclude_field_names(self): + query_params = self.get_query_params() + size = query_params.get(self.arg_name) + if not size: + return [] + if size not in ['mini', 'small']: + return [] + size_fields = getattr(self.serializer.Meta, 'fields_{}'.format(size), None) + if not size_fields or not isinstance(size_fields, Iterable): + return [] + serializer_field_names = set(self.serializer.fields) + fields_to_drop = serializer_field_names - set(size_fields) + return fields_to_drop + + +class DynamicFieldsMixin: + dynamic_fields_plugins = [QueryFieldsMixin, SizedModelFieldsMixin] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + exclude_field_names = set() + for cls in self.dynamic_fields_plugins: + plugin = cls(self) + if not plugin.can_dynamic(): + continue + exclude_field_names |= set(plugin.get_exclude_field_names()) + + for field in exclude_field_names or []: + self.fields.pop(field, None) + + +class EagerLoadQuerySetFields: + def setup_eager_loading(self, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.prefetch_related( + Prefetch('nodes'), + Prefetch('labels'), + ).select_related('admin_user', 'domain', 'platform') \ + .annotate(platform_base=F('platform__base')) + return queryset + + +class CommonSerializerMixin(DynamicFieldsMixin): + pass + + +class CommonBulkSerializerMixin(BulkSerializerMixin, CommonSerializerMixin): + pass diff --git a/apps/common/mixins/views.py b/apps/common/mixins/views.py index a00535b5b..b6685def5 100644 --- a/apps/common/mixins/views.py +++ b/apps/common/mixins/views.py @@ -36,5 +36,3 @@ class DatetimeSearchMixin: def get(self, request, *args, **kwargs): self.get_date_range() return super().get(request, *args, **kwargs) - - diff --git a/apps/common/utils/__init__.py b/apps/common/utils/__init__.py index 802943072..01850f0cf 100644 --- a/apps/common/utils/__init__.py +++ b/apps/common/utils/__init__.py @@ -6,3 +6,4 @@ from .django import * from .encode import * from .http import * from .ipip import * +from .crypto import * diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py new file mode 100644 index 000000000..ea3590d6c --- /dev/null +++ b/apps/common/utils/crypto.py @@ -0,0 +1,54 @@ +import base64 +from Crypto.Cipher import AES + +from django.conf import settings + + +class AESCrypto: + """ + AES + 除了MODE_SIV模式key长度为:32, 48, or 64, + 其余key长度为16, 24 or 32 + 详细见AES内部文档 + CBC模式传入iv参数 + 本例使用常用的ECB模式 + """ + + def __init__(self, key): + if len(key) > 32: + key = key[:32] + self.key = self.to_16(key) + + @staticmethod + def to_16(key): + """ + 转为16倍数的bytes数据 + :param key: + :return: + """ + key = bytes(key, encoding="utf8") + while len(key) % 16 != 0: + key += b'\0' + return key # 返回bytes + + def aes(self): + return AES.new(self.key, AES.MODE_ECB) # 初始化加密器 + + def encrypt(self, text): + aes = self.aes() + return str(base64.encodebytes(aes.encrypt(self.to_16(text))), + encoding='utf8').replace('\n', '') # 加密 + + def decrypt(self, text): + aes = self.aes() + return str(aes.decrypt(base64.decodebytes(bytes(text, encoding='utf8'))).rstrip(b'\0').decode("utf8")) # 解密 + + +def get_aes_crypto(key=None): + if key is None: + key = settings.SECRET_KEY + a = AESCrypto(key) + return a + + +aes_crypto = get_aes_crypto() diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py index 097e28292..cd130e7fe 100644 --- a/apps/common/utils/encode.py +++ b/apps/common/utils/encode.py @@ -225,3 +225,4 @@ def model_to_json(instance, sort_keys=True, indent=2, cls=None): if cls is None: cls = DjangoJSONEncoder return json.dumps(data, sort_keys=sort_keys, indent=indent, cls=cls) + diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index fd580093e..9558a92df 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -10,33 +10,42 @@ from users.models import User from assets.models import Asset from terminal.models import Session from orgs.utils import current_org -from common.permissions import IsOrgAdmin +from common.permissions import IsOrgAdmin, IsOrgAuditor from common.utils import lazyproperty __all__ = ['IndexApi'] -class MonthLoginMetricMixin: +class DatesLoginMetricMixin: + @lazyproperty + def days(self): + query_params = self.request.query_params + if query_params.get('monthly'): + return 30 + return 7 @lazyproperty - def session_month(self): - month_ago = timezone.now() - timezone.timedelta(days=30) - session_month = Session.objects.filter(date_start__gt=month_ago) - return session_month + def sessions_queryset(self): + days = timezone.now() - timezone.timedelta(days=self.days) + sessions_queryset = Session.objects.filter(date_start__gt=days) + return sessions_queryset @lazyproperty - def session_month_dates(self): - dates = self.session_month.dates('date_start', 'day') + def session_dates_list(self): + now = timezone.now() + dates = [(now - timezone.timedelta(days=i)).date() for i in range(self.days)] + dates.reverse() + # dates = self.sessions_queryset.dates('date_start', 'day') return dates - def get_month_metrics_date(self): - month_metrics_date = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0'] - return month_metrics_date + def get_dates_metrics_date(self): + dates_metrics_date = [d.strftime('%m-%d') for d in self.session_dates_list] or ['0'] + return dates_metrics_date @staticmethod def get_cache_key(date, tp): date_str = date.strftime("%Y%m%d") - key = "SESSION_MONTH_{}_{}_{}".format(current_org.id, tp, date_str) + key = "SESSION_DATE_{}_{}_{}".format(current_org.id, tp, date_str) return key def __get_data_from_cache(self, date, tp): @@ -69,9 +78,9 @@ class MonthLoginMetricMixin: self.__set_data_to_cache(date, tp, count) return count - def get_month_metrics_total_count_login(self): + def get_dates_metrics_total_count_login(self): data = [] - for d in self.session_month_dates: + for d in self.session_dates_list: count = self.get_date_login_count(d) data.append(count) if len(data) == 0: @@ -88,9 +97,9 @@ class MonthLoginMetricMixin: self.__set_data_to_cache(date, tp, count) return count - def get_month_metrics_total_count_active_users(self): + def get_dates_metrics_total_count_active_users(self): data = [] - for d in self.session_month_dates: + for d in self.session_dates_list: count = self.get_date_user_count(d) data.append(count) return data @@ -105,90 +114,81 @@ class MonthLoginMetricMixin: self.__set_data_to_cache(date, tp, count) return count - def get_month_metrics_total_count_active_assets(self): + def get_dates_metrics_total_count_active_assets(self): data = [] - for d in self.session_month_dates: + for d in self.session_dates_list: count = self.get_date_asset_count(d) data.append(count) return data @lazyproperty - def month_total_count_active_users(self): - count = len(set(self.session_month.values_list('user', flat=True))) + def dates_total_count_active_users(self): + count = len(set(self.sessions_queryset.values_list('user', flat=True))) return count @lazyproperty - def month_total_count_inactive_users(self): + def dates_total_count_inactive_users(self): total = current_org.get_org_members().count() - active = self.month_total_count_active_users + active = self.dates_total_count_active_users count = total - active if count < 0: count = 0 return count @lazyproperty - def month_total_count_disabled_users(self): + def dates_total_count_disabled_users(self): return current_org.get_org_members().filter(is_active=False).count() @lazyproperty - def month_total_count_active_assets(self): - return len(set(self.session_month.values_list('asset', flat=True))) + def dates_total_count_active_assets(self): + return len(set(self.sessions_queryset.values_list('asset', flat=True))) @lazyproperty - def month_total_count_inactive_assets(self): + def dates_total_count_inactive_assets(self): total = Asset.objects.all().count() - active = self.month_total_count_active_assets + active = self.dates_total_count_active_assets count = total - active if count < 0: count = 0 return count @lazyproperty - def month_total_count_disabled_assets(self): + def dates_total_count_disabled_assets(self): return Asset.objects.filter(is_active=False).count() - - -class WeekSessionMetricMixin: - session_week = None - - @lazyproperty - def session_week(self): - week_ago = timezone.now() - timezone.timedelta(weeks=1) - session_week = Session.objects.filter(date_start__gt=week_ago) - return session_week - - def get_week_login_times_top5_users(self): - users = self.session_week.values_list('user', flat=True) + + # 以下是从week中而来 + def get_dates_login_times_top5_users(self): + users = self.sessions_queryset.values_list('user', flat=True) users = [ {'user': user, 'total': total} for user, total in Counter(users).most_common(5) ] return users - def get_week_total_count_login_users(self): - return len(set(self.session_week.values_list('user', flat=True))) + def get_dates_total_count_login_users(self): + return len(set(self.sessions_queryset.values_list('user', flat=True))) - def get_week_total_count_login_times(self): - return self.session_week.count() + def get_dates_total_count_login_times(self): + return self.sessions_queryset.count() - def get_week_login_times_top10_assets(self): - assets = self.session_week.values("asset")\ - .annotate(total=Count("asset"))\ - .annotate(last=Max("date_start")).order_by("-total")[:10] + def get_dates_login_times_top10_assets(self): + assets = self.sessions_queryset.values("asset") \ + .annotate(total=Count("asset")) \ + .annotate(last=Max("date_start")).order_by("-total")[:10] for asset in assets: asset['last'] = str(asset['last']) return list(assets) - def get_week_login_times_top10_users(self): - users = self.session_week.values("user") \ - .annotate(total=Count("user")) \ - .annotate(last=Max("date_start")).order_by("-total")[:10] + def get_dates_login_times_top10_users(self): + users = self.sessions_queryset.values("user") \ + .annotate(total=Count("user")) \ + .annotate(last=Max("date_start")).order_by("-total")[:10] for user in users: user['last'] = str(user['last']) return list(users) - def get_week_login_record_top10_sessions(self): - sessions = self.session_week.order_by('-date_start')[:10] + def get_dates_login_record_top10_sessions(self): + sessions = self.sessions_queryset.order_by('-date_start')[:10] for session in sessions: session.avatar_url = User.get_avatar_url("") sessions = [ @@ -223,8 +223,8 @@ class TotalCountMixin: return Session.objects.filter(is_finished=False).count() -class IndexApi(TotalCountMixin, WeekSessionMetricMixin, MonthLoginMetricMixin, APIView): - permission_classes = (IsOrgAdmin,) +class IndexApi(TotalCountMixin, DatesLoginMetricMixin, APIView): + permission_classes = (IsOrgAdmin | IsOrgAuditor,) http_method_names = ['get'] def get(self, request, *args, **kwargs): @@ -234,60 +234,72 @@ class IndexApi(TotalCountMixin, WeekSessionMetricMixin, MonthLoginMetricMixin, A _all = query_params.get('all') - if _all or query_params.get('total_count'): + if _all or query_params.get('total_count') or query_params.get('total_count_users'): + data.update({ + 'total_count_users': self.get_total_count_users(), + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_assets'): data.update({ 'total_count_assets': self.get_total_count_assets(), - 'total_count_users': self.get_total_count_users(), + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_online_users'): + data.update({ 'total_count_online_users': self.get_total_count_online_users(), + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_online_sessions'): + data.update({ 'total_count_online_sessions': self.get_total_count_online_sessions(), }) - if _all or query_params.get('month_metrics'): + if _all or query_params.get('dates_metrics'): data.update({ - 'month_metrics_date': self.get_month_metrics_date(), - 'month_metrics_total_count_login': self.get_month_metrics_total_count_login(), - 'month_metrics_total_count_active_users': self.get_month_metrics_total_count_active_users(), - 'month_metrics_total_count_active_assets': self.get_month_metrics_total_count_active_assets(), + 'dates_metrics_date': self.get_dates_metrics_date(), + 'dates_metrics_total_count_login': self.get_dates_metrics_total_count_login(), + 'dates_metrics_total_count_active_users': self.get_dates_metrics_total_count_active_users(), + 'dates_metrics_total_count_active_assets': self.get_dates_metrics_total_count_active_assets(), }) - if _all or query_params.get('month_total_count_users'): + if _all or query_params.get('dates_total_count_users'): data.update({ - 'month_total_count_active_users': self.month_total_count_active_users, - 'month_total_count_inactive_users': self.month_total_count_inactive_users, - 'month_total_count_disabled_users': self.month_total_count_disabled_users, + 'dates_total_count_active_users': self.dates_total_count_active_users, + 'dates_total_count_inactive_users': self.dates_total_count_inactive_users, + 'dates_total_count_disabled_users': self.dates_total_count_disabled_users, }) - if _all or query_params.get('month_total_count_assets'): + if _all or query_params.get('dates_total_count_assets'): data.update({ - 'month_total_count_active_assets': self.month_total_count_active_assets, - 'month_total_count_inactive_assets': self.month_total_count_inactive_assets, - 'month_total_count_disabled_assets': self.month_total_count_disabled_assets, + 'dates_total_count_active_assets': self.dates_total_count_active_assets, + 'dates_total_count_inactive_assets': self.dates_total_count_inactive_assets, + 'dates_total_count_disabled_assets': self.dates_total_count_disabled_assets, }) - if _all or query_params.get('week_total_count'): + if _all or query_params.get('dates_total_count'): data.update({ - 'week_total_count_login_users': self.get_week_total_count_login_users(), - 'week_total_count_login_times': self.get_week_total_count_login_times(), + 'dates_total_count_login_users': self.get_dates_total_count_login_users(), + 'dates_total_count_login_times': self.get_dates_total_count_login_times(), }) - if _all or query_params.get('week_login_times_top5_users'): + if _all or query_params.get('dates_login_times_top5_users'): data.update({ - 'week_login_times_top5_users': self.get_week_login_times_top5_users(), + 'dates_login_times_top5_users': self.get_dates_login_times_top5_users(), }) - if _all or query_params.get('week_login_times_top10_assets'): + if _all or query_params.get('dates_login_times_top10_assets'): data.update({ - 'week_login_times_top10_assets': self.get_week_login_times_top10_assets(), + 'dates_login_times_top10_assets': self.get_dates_login_times_top10_assets(), }) - if _all or query_params.get('week_login_times_top10_users'): + if _all or query_params.get('dates_login_times_top10_users'): data.update({ - 'week_login_times_top10_users': self.get_week_login_times_top10_users(), + 'dates_login_times_top10_users': self.get_dates_login_times_top10_users(), }) - if _all or query_params.get('week_login_record_top10_sessions'): + if _all or query_params.get('dates_login_record_top10_sessions'): data.update({ - 'week_login_record_top10_sessions': self.get_week_login_record_top10_sessions() + 'dates_login_record_top10_sessions': self.get_dates_login_record_top10_sessions() }) return JsonResponse(data, status=200) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index ef1439255..2b4a5b65f 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -16,6 +16,7 @@ import json import yaml from importlib import import_module from django.urls import reverse_lazy +from django.contrib.staticfiles.templatetags.staticfiles import static from urllib.parse import urljoin, urlparse BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -123,7 +124,7 @@ class Config(dict): # Django Config, Must set before start 'SECRET_KEY': '', 'BOOTSTRAP_TOKEN': '', - 'DEBUG': True, + 'DEBUG': False, 'LOG_LEVEL': 'DEBUG', 'LOG_DIR': os.path.join(PROJECT_DIR, 'logs'), 'DB_ENGINE': 'mysql', @@ -257,6 +258,8 @@ class Config(dict): 'LOGIN_CONFIRM_ENABLE': False, 'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False, 'ORG_CHANGE_TO_URL': '', + 'LANGUAGE_CODE': 'zh', + 'TIME_ZONE': 'Asia/Shanghai', 'CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED': True } @@ -436,6 +439,38 @@ class DynamicConfig: backends.insert(0, 'authentication.backends.radius.RadiusBackend') return backends + def XPACK_LICENSE_IS_VALID(self): + if not HAS_XPACK: + return False + try: + from xpack.plugins.license.models import License + return License.has_valid_license() + except: + return False + + def LOGO_URLS(self): + logo_urls = {'logo_logout': static('img/logo.png'), + 'logo_index': static('img/logo_text.png'), + 'login_image': static('img/login_image.png'), + 'favicon': static('img/facio.ico')} + if not HAS_XPACK: + return logo_urls + try: + from xpack.plugins.interface.models import Interface + obj = Interface.interface() + if obj: + if obj.logo_logout: + logo_urls.update({'logo_logout': obj.logo_logout.url}) + if obj.logo_index: + logo_urls.update({'logo_index': obj.logo_index.url}) + if obj.login_image: + logo_urls.update({'login_image': obj.login_image.url}) + if obj.favicon: + logo_urls.update({'favicon': obj.favicon.url}) + except: + pass + return logo_urls + def get_from_db(self, item): if self.db_setting is not None: value = self.db_setting.get(item) @@ -631,4 +666,3 @@ class ConfigManager: @classmethod def get_dynamic_config(cls, config): return DynamicConfig(config) - diff --git a/apps/jumpserver/const.py b/apps/jumpserver/const.py index b7caa6114..c2889870e 100644 --- a/apps/jumpserver/const.py +++ b/apps/jumpserver/const.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- # import os + from .conf import ConfigManager __all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG', 'DYNAMIC'] BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(BASE_DIR) -VERSION = '1.5.9' +VERSION = '2.0.0' CONFIG = ConfigManager.load_user_config() DYNAMIC = ConfigManager.get_dynamic_config(CONFIG) + + diff --git a/apps/jumpserver/middleware.py b/apps/jumpserver/middleware.py index 775041b9d..277d8492a 100644 --- a/apps/jumpserver/middleware.py +++ b/apps/jumpserver/middleware.py @@ -15,11 +15,14 @@ class TimezoneMiddleware: self.get_response = get_response def __call__(self, request): - tzname = request.META.get('TZ') - if tzname: - timezone.activate(pytz.timezone(tzname)) - else: - timezone.deactivate() + tzname = request.META.get('HTTP_X_TZ') + if not tzname or tzname == 'undefined': + return self.get_response(request) + try: + tz = pytz.timezone(tzname) + timezone.activate(tz) + except pytz.UnknownTimeZoneError: + pass response = self.get_response(request) return response diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index d1fbb5a36..0f8cf98a8 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -175,9 +175,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/1.10/topics/i18n/ # LANGUAGE_CODE = 'en' -LANGUAGE_CODE = 'zh' +LANGUAGE_CODE = CONFIG.LANGUAGE_CODE -TIME_ZONE = 'Asia/Shanghai' +TIME_ZONE = CONFIG.TIME_ZONE USE_I18N = True diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index 2c187b60d..1e552345b 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -86,6 +86,11 @@ TASK_LOG_KEEP_DAYS = CONFIG.TASK_LOG_KEEP_DAYS ORG_CHANGE_TO_URL = CONFIG.ORG_CHANGE_TO_URL WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD -CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED - AUTH_EXPIRED_SECONDS = 60 * 5 + +# XPACK +XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID + +LOGO_URLS = DYNAMIC.LOGO_URLS + +CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index 706fb3647..eb07e299d 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -40,7 +40,9 @@ REST_FRAMEWORK = { 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z', 'DATETIME_INPUT_FORMATS': ['iso-8601', '%Y-%m-%d %H:%M:%S %z'], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', - # 'PAGE_SIZE': 15 + # 'PAGE_SIZE': 100, + # 'MAX_PAGE_SIZE': 5000 + } SWAGGER_SETTINGS = { diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index ade854397..60c04676f 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -4,8 +4,6 @@ from __future__ import unicode_literals from django.urls import path, include, re_path from django.conf import settings from django.conf.urls.static import static -from django.conf.urls.i18n import i18n_patterns -from django.views.i18n import JavaScriptCatalog from . import views, api @@ -32,16 +30,8 @@ api_v2 = [ app_view_patterns = [ - path('users/', include('users.urls.views_urls', namespace='users')), - path('assets/', include('assets.urls.views_urls', namespace='assets')), - path('perms/', include('perms.urls.views_urls', namespace='perms')), - path('terminal/', include('terminal.urls.views_urls', namespace='terminal')), - path('ops/', include('ops.urls.view_urls', namespace='ops')), - path('audits/', include('audits.urls.view_urls', namespace='audits')), - path('orgs/', include('orgs.urls.views_urls', namespace='orgs')), path('auth/', include('authentication.urls.view_urls'), name='auth'), - path('applications/', include('applications.urls.views_urls', namespace='applications')), - path('tickets/', include('tickets.urls.views_urls', namespace='tickets')), + path('ops/', include('ops.urls.view_urls'), name='ops'), re_path(r'flower/(?P.*)', views.celery_flower_view, name='flower-view'), ] @@ -54,9 +44,12 @@ if settings.XPACK_ENABLED: path('xpack/', include('xpack.urls.api_urls', namespace='api-xpack')) ) -js_i18n_patterns = i18n_patterns( - path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'), -) + +apps = [ + 'users', 'assets', 'perms', 'terminal', 'ops', 'audits', 'orgs', 'auth', + 'applications', 'tickets', 'settings', 'xpack' + 'flower', 'luna', 'koko', 'ws', 'docs', 'redocs', +] urlpatterns = [ @@ -65,34 +58,36 @@ urlpatterns = [ path('api/v2/', include(api_v2)), re_path('api/(?P\w+)/(?Pv\d)/.*', views.redirect_format_api), path('api/health/', views.HealthCheckView.as_view(), name="health"), - re_path('luna/.*', views.LunaView.as_view(), name='luna-view'), - re_path('koko/.*', views.KokoView.as_view(), name='koko-view'), - re_path('ws/.*', views.WsView.as_view(), name='ws-view'), - path('i18n//', views.I18NView.as_view(), name='i18n-switch'), - path('settings/', include('settings.urls.view_urls', namespace='settings')), - # External apps url - path('captcha/', include('captcha.urls')), + path('core/auth/captcha/', include('captcha.urls')), + path('core/', include(app_view_patterns)), + path('ui/', views.UIView.as_view()) ] -urlpatterns += app_view_patterns urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) -urlpatterns += js_i18n_patterns +# urlpatterns += js_i18n_patterns handler404 = 'jumpserver.views.handler404' handler500 = 'jumpserver.views.handler500' if settings.DEBUG: urlpatterns += [ - re_path('^swagger(?P\.json|\.yaml)$', + re_path('^api/swagger(?P\.json|\.yaml)$', views.get_swagger_view().without_ui(cache_timeout=1), name='schema-json'), - path('docs/', views.get_swagger_view().with_ui('swagger', cache_timeout=1), name="docs"), - path('redoc/', views.get_swagger_view().with_ui('redoc', cache_timeout=1), name='redoc'), + path('api/docs/', views.get_swagger_view().with_ui('swagger', cache_timeout=1), name="docs"), + path('api/redoc/', views.get_swagger_view().with_ui('redoc', cache_timeout=1), name='redoc'), - re_path('^v2/swagger(?P\.json|\.yaml)$', + re_path('^api/v2/swagger(?P\.json|\.yaml)$', views.get_swagger_view().without_ui(cache_timeout=1), name='schema-json'), - path('docs/v2/', views.get_swagger_view("v2").with_ui('swagger', cache_timeout=1), name="docs"), - path('redoc/v2/', views.get_swagger_view("v2").with_ui('redoc', cache_timeout=1), name='redoc'), + path('api/docs/v2/', views.get_swagger_view("v2").with_ui('swagger', cache_timeout=1), name="docs"), + path('api/redoc/v2/', views.get_swagger_view("v2").with_ui('redoc', cache_timeout=1), name='redoc'), ] + +# 兼容之前的 +old_app_pattern = '|'.join(apps) +old_app_pattern = r'^{}'.format(old_app_pattern) +urlpatterns += [re_path(old_app_pattern, views.redirect_old_apps_view)] + + diff --git a/apps/jumpserver/views/index.py b/apps/jumpserver/views/index.py index ffcb10493..19380466f 100644 --- a/apps/jumpserver/views/index.py +++ b/apps/jumpserver/views/index.py @@ -10,6 +10,9 @@ class IndexView(PermissionsMixin, TemplateView): template_name = 'index.html' permission_classes = [IsValidUser] + def get(self, request, *args, **kwargs): + return redirect('/ui/') + def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() diff --git a/apps/jumpserver/views/other.py b/apps/jumpserver/views/other.py index a1db94e77..a0b386383 100644 --- a/apps/jumpserver/views/other.py +++ b/apps/jumpserver/views/other.py @@ -3,9 +3,10 @@ import re import time -from django.http import HttpResponseRedirect, JsonResponse +from django.http import HttpResponseRedirect, JsonResponse, Http404 from django.conf import settings from django.views.generic import View +from django.shortcuts import redirect from django.utils.translation import ugettext_lazy as _ from rest_framework.views import APIView from django.views.decorators.csrf import csrf_exempt @@ -16,7 +17,7 @@ from common.http import HttpResponseTemporaryRedirect __all__ = [ 'LunaView', 'I18NView', 'KokoView', 'WsView', 'HealthCheckView', - 'redirect_format_api' + 'redirect_format_api', 'redirect_old_apps_view', 'UIView' ] @@ -51,6 +52,16 @@ def redirect_format_api(request, *args, **kwargs): return JsonResponse({"msg": "Redirect url failed: {}".format(_path)}, status=404) +def redirect_old_apps_view(request, *args, **kwargs): + path = request.get_full_path() + if path.find('/core') != -1: + raise Http404() + if path in ['/docs/', '/docs', '/core/docs/', '/core/docs']: + return redirect('/api/docs/') + new_path = '/core{}'.format(path) + return HttpResponseTemporaryRedirect(new_path) + + class HealthCheckView(APIView): permission_classes = () @@ -67,6 +78,12 @@ class WsView(APIView): return JsonResponse({"msg": msg}) +class UIView(View): + def get(self, request): + msg = "如果你能看到这个页面,证明你的配置是有问题的,请参考文档设置好nginx, UI由Lina项目提供" + return HttpResponse(msg) + + class KokoView(View): def get(self, request): msg = _( diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 296ee0808..1fa128c6a 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index d1b270063..d2612f98a 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: 2020-06-15 13:54+0800\n" +"POT-Creation-Date: 2020-06-16 11:02+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -21,133 +21,14 @@ msgstr "" msgid "Custom" msgstr "自定义" -#: applications/forms/remote_app.py:44 applications/models/remote_app.py:23 -#: applications/templates/applications/remote_app_detail.html:52 -#: applications/templates/applications/remote_app_list.html:27 -#: applications/templates/applications/user_remote_app_list.html:18 -#: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:352 assets/models/authbook.py:27 -#: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32 -#: assets/serializers/asset_user.py:47 assets/serializers/asset_user.py:84 -#: assets/serializers/system_user.py:44 assets/serializers/system_user.py:176 -#: assets/templates/assets/admin_user_list.html:23 -#: assets/templates/assets/asset_list.html:170 -#: assets/templates/assets/domain_detail.html:55 -#: assets/templates/assets/domain_list.html:22 -#: assets/templates/assets/label_list.html:16 -#: assets/templates/assets/system_user_list.html:28 audits/models.py:20 -#: audits/templates/audits/ftp_log_list.html:45 -#: audits/templates/audits/ftp_log_list.html:75 -#: perms/forms/asset_permission.py:89 perms/models/asset_permission.py:80 -#: perms/templates/perms/asset_permission_asset.html:53 -#: perms/templates/perms/asset_permission_create_update.html:57 -#: perms/templates/perms/asset_permission_list.html:35 -#: perms/templates/perms/asset_permission_list.html:87 templates/index.html:82 -#: terminal/backends/command/models.py:19 terminal/models.py:187 -#: terminal/templates/terminal/command_list.html:31 -#: terminal/templates/terminal/command_list.html:106 -#: terminal/templates/terminal/session_detail.html:52 -#: terminal/templates/terminal/session_list.html:26 -#: terminal/templates/terminal/session_list.html:70 -#: users/templates/users/user_asset_permission.html:40 -#: users/templates/users/user_asset_permission.html:70 -#: users/templates/users/user_granted_remote_app.html:36 -#: xpack/plugins/change_auth_plan/forms.py:74 -#: xpack/plugins/change_auth_plan/models.py:265 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:40 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14 -#: xpack/plugins/cloud/models.py:269 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:37 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:47 -#: xpack/plugins/orgs/templates/orgs/org_list.html:17 -#: xpack/plugins/vault/forms.py:13 xpack/plugins/vault/forms.py:15 -msgid "Asset" -msgstr "资产" - -#: applications/forms/remote_app.py:55 -msgid "Target URL" -msgstr "目标URL" - -#: applications/forms/remote_app.py:58 applications/forms/remote_app.py:97 -#: applications/forms/remote_app.py:114 -msgid "Login username" -msgstr "登录账号" - -#: applications/forms/remote_app.py:62 applications/forms/remote_app.py:101 -#: applications/forms/remote_app.py:118 -msgid "Login password" -msgstr "登录密码" - -#: applications/forms/remote_app.py:73 -msgid "Database IP" -msgstr "数据库IP" - -#: applications/forms/remote_app.py:76 -msgid "Database name" -msgstr "数据库名" - -#: applications/forms/remote_app.py:79 -msgid "Database username" -msgstr "数据库账号" - -#: applications/forms/remote_app.py:83 -msgid "Database password" -msgstr "数据库密码" - -#: applications/forms/remote_app.py:94 applications/forms/remote_app.py:111 -msgid "Target address" -msgstr "目标地址" - -#: applications/forms/remote_app.py:108 -msgid "Operating parameter" -msgstr "运行参数" - #: applications/models/database_app.py:18 applications/models/remote_app.py:21 -#: applications/templates/applications/database_app_detail.html:48 -#: applications/templates/applications/database_app_list.html:23 -#: applications/templates/applications/remote_app_detail.html:48 -#: applications/templates/applications/remote_app_list.html:25 -#: applications/templates/applications/user_database_app_list.html:16 -#: applications/templates/applications/user_remote_app_list.html:16 -#: assets/forms/asset.py:21 assets/forms/domain.py:77 assets/forms/user.py:74 -#: assets/forms/user.py:96 assets/models/asset.py:145 assets/models/base.py:232 +#: assets/models/asset.py:145 assets/models/base.py:232 #: assets/models/cluster.py:18 assets/models/cmd_filter.py:21 #: assets/models/domain.py:20 assets/models/group.py:20 -#: assets/models/label.py:18 assets/templates/assets/_node_detail_modal.html:27 -#: assets/templates/assets/admin_user_detail.html:51 -#: assets/templates/assets/admin_user_list.html:21 -#: assets/templates/assets/cmd_filter_detail.html:56 -#: assets/templates/assets/cmd_filter_list.html:22 -#: assets/templates/assets/domain_detail.html:51 -#: assets/templates/assets/domain_gateway_list.html:62 -#: assets/templates/assets/domain_list.html:21 -#: assets/templates/assets/label_list.html:14 -#: assets/templates/assets/platform_detail.html:48 -#: assets/templates/assets/platform_list.html:16 -#: assets/templates/assets/system_user_detail.html:62 -#: assets/templates/assets/system_user_list.html:24 ops/mixin.py:24 -#: ops/templates/ops/task_detail.html:58 ops/templates/ops/task_list.html:11 -#: orgs/models.py:12 perms/models/base.py:48 -#: perms/templates/perms/asset_permission_detail.html:57 -#: perms/templates/perms/asset_permission_list.html:32 -#: perms/templates/perms/asset_permission_list.html:183 -#: perms/templates/perms/asset_permission_user.html:53 -#: perms/templates/perms/database_app_permission_detail.html:57 -#: perms/templates/perms/database_app_permission_list.html:14 -#: perms/templates/perms/database_app_permission_user.html:53 -#: perms/templates/perms/remote_app_permission_detail.html:57 -#: perms/templates/perms/remote_app_permission_list.html:14 -#: perms/templates/perms/remote_app_permission_remote_app.html:49 -#: perms/templates/perms/remote_app_permission_user.html:49 -#: settings/models.py:26 -#: settings/templates/settings/_ldap_list_users_modal.html:32 -#: terminal/models.py:26 terminal/models.py:342 terminal/models.py:374 -#: terminal/models.py:411 terminal/templates/terminal/base_storage_list.html:31 -#: terminal/templates/terminal/terminal_detail.html:43 -#: terminal/templates/terminal/terminal_list.html:30 users/forms/profile.py:20 -#: users/models/group.py:15 users/models/user.py:440 +#: assets/models/label.py:18 ops/mixin.py:24 orgs/models.py:12 +#: perms/models/base.py:48 settings/models.py:27 terminal/models.py:26 +#: terminal/models.py:342 terminal/models.py:374 terminal/models.py:411 +#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:466 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -161,59 +42,27 @@ msgstr "运行参数" #: users/templates/users/user_profile.html:51 #: users/templates/users/user_pubkey_update.html:57 #: users/templates/users/user_remote_app_permission.html:36 -#: xpack/plugins/change_auth_plan/forms.py:57 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:59 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 #: xpack/plugins/cloud/models.py:35 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:47 -#: xpack/plugins/cloud/templates/cloud/account_list.html:12 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:16 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:51 -#: xpack/plugins/orgs/templates/orgs/org_list.html:12 -#: xpack/plugins/orgs/templates/orgs/org_users.html:46 msgid "Name" msgstr "名称" -#: applications/models/database_app.py:22 -#: applications/templates/applications/database_app_detail.html:52 -#: applications/templates/applications/database_app_list.html:24 -#: applications/templates/applications/user_database_app_list.html:17 -#: assets/models/cmd_filter.py:51 -#: assets/templates/assets/cmd_filter_rule_list.html:53 -#: audits/templates/audits/login_log_list.html:58 -#: perms/templates/perms/remote_app_permission_remote_app.html:50 -#: terminal/models.py:376 terminal/models.py:413 -#: terminal/templates/terminal/base_storage_list.html:32 -#: tickets/models/ticket.py:43 tickets/templates/tickets/ticket_detail.html:33 -#: tickets/templates/tickets/ticket_list.html:35 +#: applications/models/database_app.py:22 assets/models/cmd_filter.py:51 +#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:43 #: users/templates/users/user_granted_database_app.html:35 msgid "Type" msgstr "类型" -#: applications/models/database_app.py:25 -#: applications/templates/applications/database_app_detail.html:56 -#: applications/templates/applications/database_app_list.html:25 -#: applications/templates/applications/user_database_app_list.html:18 -#: ops/models/adhoc.py:146 +#: applications/models/database_app.py:25 ops/models/adhoc.py:146 #: users/templates/users/user_granted_database_app.html:36 msgid "Host" msgstr "主机" -#: applications/models/database_app.py:27 -#: applications/templates/applications/database_app_detail.html:60 -#: applications/templates/applications/database_app_list.html:26 -#: assets/forms/asset.py:25 assets/models/asset.py:191 +#: applications/models/database_app.py:27 assets/models/asset.py:191 #: assets/models/domain.py:50 -#: assets/templates/assets/domain_gateway_list.html:64 msgid "Port" msgstr "端口" #: applications/models/database_app.py:29 -#: applications/templates/applications/database_app_detail.html:64 -#: applications/templates/applications/database_app_list.html:27 -#: applications/templates/applications/user_database_app_list.html:19 #: users/templates/users/user_granted_database_app.html:37 msgid "Database" msgstr "数据库" @@ -221,68 +70,28 @@ msgstr "数据库" # msgid "Date created" # msgstr "创建日期" #: applications/models/database_app.py:33 applications/models/remote_app.py:45 -#: applications/templates/applications/database_app_detail.html:76 -#: applications/templates/applications/database_app_list.html:28 -#: applications/templates/applications/remote_app_detail.html:72 -#: applications/templates/applications/remote_app_list.html:28 -#: applications/templates/applications/user_database_app_list.html:20 -#: applications/templates/applications/user_remote_app_list.html:19 #: assets/models/asset.py:150 assets/models/asset.py:226 #: assets/models/base.py:237 assets/models/cluster.py:29 #: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:56 #: assets/models/domain.py:21 assets/models/domain.py:53 -#: assets/models/group.py:23 assets/models/label.py:23 -#: assets/templates/assets/admin_user_detail.html:67 -#: assets/templates/assets/admin_user_list.html:24 -#: assets/templates/assets/asset_detail.html:128 -#: assets/templates/assets/cmd_filter_detail.html:60 -#: assets/templates/assets/cmd_filter_list.html:25 -#: assets/templates/assets/cmd_filter_rule_list.html:57 -#: assets/templates/assets/domain_detail.html:67 -#: assets/templates/assets/domain_gateway_list.html:67 -#: assets/templates/assets/domain_list.html:24 -#: assets/templates/assets/platform_detail.html:64 -#: assets/templates/assets/platform_list.html:18 -#: assets/templates/assets/system_user_detail.html:112 -#: assets/templates/assets/system_user_list.html:29 -#: assets/templates/assets/user_asset_list.html:81 ops/models/adhoc.py:37 -#: orgs/models.py:18 perms/models/base.py:56 -#: perms/templates/perms/asset_permission_detail.html:97 -#: perms/templates/perms/database_app_permission_detail.html:93 -#: perms/templates/perms/remote_app_permission_detail.html:89 -#: settings/models.py:31 terminal/models.py:36 terminal/models.py:381 -#: terminal/models.py:418 terminal/templates/terminal/base_storage_list.html:33 -#: terminal/templates/terminal/terminal_detail.html:63 -#: tickets/templates/tickets/ticket_detail.html:104 users/models/group.py:16 -#: users/models/user.py:473 users/templates/users/user_detail.html:115 +#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37 +#: orgs/models.py:18 perms/models/base.py:56 settings/models.py:32 +#: terminal/models.py:36 terminal/models.py:381 terminal/models.py:418 +#: users/models/group.py:16 users/models/user.py:499 +#: users/templates/users/user_detail.html:115 #: users/templates/users/user_granted_database_app.html:38 #: users/templates/users/user_granted_remote_app.html:37 #: users/templates/users/user_group_detail.html:62 #: users/templates/users/user_group_list.html:16 #: users/templates/users/user_profile.html:138 -#: xpack/plugins/change_auth_plan/models.py:75 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:115 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 -#: xpack/plugins/cloud/models.py:53 xpack/plugins/cloud/models.py:139 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:67 -#: xpack/plugins/cloud/templates/cloud/account_list.html:15 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:128 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18 -#: xpack/plugins/gathered_user/models.py:26 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:63 -#: xpack/plugins/orgs/templates/orgs/org_list.html:23 +#: xpack/plugins/change_auth_plan/models.py:76 xpack/plugins/cloud/models.py:53 +#: xpack/plugins/cloud/models.py:139 xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" #: applications/models/database_app.py:41 #: perms/forms/database_app_permission.py:44 -#: perms/models/database_app_permission.py:17 -#: perms/templates/perms/database_app_permission_create_update.html:46 -#: perms/templates/perms/database_app_permission_database_app.html:23 -#: perms/templates/perms/database_app_permission_database_app.html:53 -#: perms/templates/perms/database_app_permission_detail.html:22 -#: perms/templates/perms/database_app_permission_list.html:17 -#: perms/templates/perms/database_app_permission_user.html:23 +#: perms/models/database_app_permission.py:18 #: perms/utils/database_app_permission.py:77 templates/_nav.html:66 #: templates/_nav.html:86 templates/_nav_user.html:22 #: users/templates/users/user_database_app_permission.html:39 @@ -290,16 +99,27 @@ msgstr "备注" msgid "DatabaseApp" msgstr "数据库应用" +#: applications/models/remote_app.py:23 assets/models/asset.py:356 +#: assets/models/authbook.py:27 assets/models/gathered_user.py:14 +#: assets/serializers/admin_user.py:32 assets/serializers/asset_user.py:47 +#: assets/serializers/asset_user.py:84 assets/serializers/system_user.py:44 +#: assets/serializers/system_user.py:176 audits/models.py:20 +#: perms/forms/asset_permission.py:89 perms/models/asset_permission.py:80 +#: templates/index.html:82 terminal/backends/command/models.py:19 +#: terminal/models.py:187 users/templates/users/user_asset_permission.html:40 +#: users/templates/users/user_asset_permission.html:70 +#: users/templates/users/user_granted_remote_app.html:36 +#: xpack/plugins/change_auth_plan/models.py:282 +#: xpack/plugins/cloud/models.py:269 +msgid "Asset" +msgstr "资产" + #: applications/models/remote_app.py:28 -#: applications/templates/applications/remote_app_detail.html:56 -#: applications/templates/applications/remote_app_list.html:26 -#: applications/templates/applications/user_remote_app_list.html:17 #: users/templates/users/user_granted_remote_app.html:35 msgid "App type" msgstr "应用类型" #: applications/models/remote_app.py:32 -#: applications/templates/applications/remote_app_detail.html:60 msgid "App path" msgstr "应用路径" @@ -307,66 +127,32 @@ msgstr "应用路径" msgid "Parameters" msgstr "参数" -#: applications/models/remote_app.py:39 -#: applications/templates/applications/database_app_detail.html:72 -#: applications/templates/applications/remote_app_detail.html:68 -#: assets/models/asset.py:224 assets/models/base.py:240 -#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 -#: assets/models/cmd_filter.py:59 assets/models/group.py:21 -#: assets/templates/assets/admin_user_detail.html:63 -#: assets/templates/assets/asset_detail.html:120 -#: assets/templates/assets/cmd_filter_detail.html:72 -#: assets/templates/assets/system_user_detail.html:108 -#: common/mixins/models.py:49 ops/templates/ops/adhoc_detail.html:84 -#: orgs/models.py:16 perms/models/base.py:54 -#: perms/templates/perms/asset_permission_detail.html:93 -#: perms/templates/perms/database_app_permission_detail.html:89 -#: perms/templates/perms/remote_app_permission_detail.html:85 -#: users/models/user.py:481 users/serializers/group.py:32 -#: users/templates/users/user_detail.html:97 -#: xpack/plugins/change_auth_plan/models.py:79 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:111 -#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:145 -#: xpack/plugins/gathered_user/models.py:30 +#: applications/models/remote_app.py:39 assets/models/asset.py:224 +#: assets/models/base.py:240 assets/models/cluster.py:28 +#: assets/models/cmd_filter.py:26 assets/models/cmd_filter.py:59 +#: assets/models/group.py:21 common/mixins/models.py:49 orgs/models.py:16 +#: perms/models/base.py:54 users/models/user.py:507 +#: users/serializers/group.py:35 users/templates/users/user_detail.html:97 +#: xpack/plugins/change_auth_plan/models.py:80 xpack/plugins/cloud/models.py:56 +#: xpack/plugins/cloud/models.py:145 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" # msgid "Created by" # msgstr "创建者" -#: applications/models/remote_app.py:42 -#: applications/templates/applications/database_app_detail.html:68 -#: applications/templates/applications/remote_app_detail.html:64 -#: assets/models/asset.py:225 assets/models/base.py:238 -#: assets/models/cluster.py:26 assets/models/domain.py:23 -#: assets/models/gathered_user.py:19 assets/models/group.py:22 -#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:59 -#: assets/templates/assets/cmd_filter_detail.html:64 -#: assets/templates/assets/domain_detail.html:63 -#: assets/templates/assets/system_user_detail.html:104 -#: common/mixins/models.py:50 ops/models/adhoc.py:38 -#: ops/templates/ops/adhoc_detail.html:88 ops/templates/ops/task_detail.html:62 -#: orgs/models.py:17 perms/models/base.py:55 -#: perms/templates/perms/asset_permission_detail.html:89 -#: perms/templates/perms/database_app_permission_detail.html:85 -#: perms/templates/perms/remote_app_permission_detail.html:81 -#: terminal/templates/terminal/terminal_detail.html:59 -#: tickets/templates/tickets/ticket_detail.html:52 users/models/group.py:18 +#: applications/models/remote_app.py:42 assets/models/asset.py:225 +#: assets/models/base.py:238 assets/models/cluster.py:26 +#: assets/models/domain.py:23 assets/models/gathered_user.py:19 +#: assets/models/group.py:22 assets/models/label.py:25 +#: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27 +#: orgs/models.py:17 perms/models/base.py:55 users/models/group.py:18 #: users/templates/users/user_group_detail.html:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:103 #: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:148 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:63 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:108 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:59 msgid "Date created" msgstr "创建日期" #: applications/models/remote_app.py:49 perms/forms/remote_app_permission.py:46 #: perms/models/remote_app_permission.py:15 -#: perms/templates/perms/remote_app_permission_create_update.html:46 -#: perms/templates/perms/remote_app_permission_detail.html:22 -#: perms/templates/perms/remote_app_permission_list.html:17 -#: perms/templates/perms/remote_app_permission_remote_app.html:22 -#: perms/templates/perms/remote_app_permission_user.html:22 #: perms/utils/remote_app_permission.py:76 templates/_nav.html:64 #: templates/_nav.html:82 templates/_nav_user.html:16 #: users/templates/users/user_remote_app_permission.html:39 @@ -374,351 +160,7 @@ msgstr "创建日期" msgid "RemoteApp" msgstr "远程应用" -#: applications/templates/applications/database_app_create_update.html:12 -#: applications/templates/applications/remote_app_create_update.html:12 -#: assets/templates/assets/_system_user.html:73 -#: assets/templates/assets/admin_user_create_update.html:41 -#: assets/templates/assets/asset_bulk_update.html:23 -#: assets/templates/assets/asset_create.html:81 -#: assets/templates/assets/cmd_filter_create_update.html:15 -#: assets/templates/assets/cmd_filter_rule_create_update.html:36 -#: assets/templates/assets/domain_create_update.html:16 -#: assets/templates/assets/gateway_create_update.html:54 -#: assets/templates/assets/label_create_update.html:18 -#: assets/templates/assets/platform_create_update.html:20 -#: perms/templates/perms/asset_permission_create_update.html:127 -#: perms/templates/perms/database_app_permission_create_update.html:82 -#: perms/templates/perms/remote_app_permission_create_update.html:82 -#: settings/templates/settings/basic_setting.html:45 -#: settings/templates/settings/email_content_setting.html:35 -#: settings/templates/settings/email_setting.html:46 -#: settings/templates/settings/ldap_setting.html:45 -#: settings/templates/settings/security_setting.html:54 -#: settings/templates/settings/terminal_setting.html:53 -#: terminal/templates/terminal/base_storage_create_update.html:12 -#: terminal/templates/terminal/terminal_update.html:43 -#: users/templates/users/_user.html:51 -#: users/templates/users/user_bulk_update.html:23 -#: users/templates/users/user_detail.html:168 -#: users/templates/users/user_group_create_update.html:27 -#: users/templates/users/user_password_update.html:75 -#: users/templates/users/user_profile.html:209 -#: users/templates/users/user_profile_update.html:67 -#: users/templates/users/user_pubkey_update.html:74 -#: users/templates/users/user_pubkey_update.html:80 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:65 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:29 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:52 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:40 -#: xpack/plugins/interface/templates/interface/interface.html:72 -#: xpack/plugins/orgs/templates/orgs/org_create_update.html:29 -#: xpack/plugins/vault/templates/vault/vault_create.html:41 -msgid "Reset" -msgstr "重置" - -#: applications/templates/applications/database_app_create_update.html:13 -#: applications/templates/applications/remote_app_create_update.html:14 -#: assets/templates/assets/_system_user.html:74 -#: assets/templates/assets/admin_user_create_update.html:42 -#: assets/templates/assets/asset_bulk_update.html:24 -#: assets/templates/assets/asset_create.html:82 -#: assets/templates/assets/asset_list.html:45 -#: assets/templates/assets/cmd_filter_create_update.html:16 -#: assets/templates/assets/cmd_filter_rule_create_update.html:37 -#: assets/templates/assets/domain_create_update.html:17 -#: assets/templates/assets/gateway_create_update.html:55 -#: assets/templates/assets/label_create_update.html:19 -#: assets/templates/assets/platform_create_update.html:21 -#: audits/templates/audits/login_log_list.html:95 -#: perms/templates/perms/asset_permission_create_update.html:128 -#: perms/templates/perms/database_app_permission_create_update.html:83 -#: perms/templates/perms/remote_app_permission_create_update.html:83 -#: settings/templates/settings/basic_setting.html:46 -#: settings/templates/settings/email_content_setting.html:36 -#: settings/templates/settings/email_setting.html:47 -#: settings/templates/settings/ldap_setting.html:49 -#: settings/templates/settings/security_setting.html:55 -#: settings/templates/settings/terminal_setting.html:55 -#: terminal/templates/terminal/base_storage_create_update.html:13 -#: terminal/templates/terminal/command_list.html:48 -#: terminal/templates/terminal/session_list.html:50 -#: terminal/templates/terminal/terminal_update.html:44 -#: users/templates/users/_user.html:52 -#: users/templates/users/forgot_password.html:24 -#: users/templates/users/user_bulk_update.html:24 -#: users/templates/users/user_list.html:40 -#: users/templates/users/user_password_update.html:76 -#: users/templates/users/user_profile_update.html:68 -#: users/templates/users/user_pubkey_update.html:81 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:66 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 -#: xpack/plugins/interface/templates/interface/interface.html:74 -#: xpack/plugins/vault/templates/vault/vault_create.html:42 -msgid "Submit" -msgstr "提交" - -#: applications/templates/applications/database_app_detail.html:13 -#: applications/templates/applications/remote_app_detail.html:13 -#: assets/templates/assets/admin_user_assets.html:13 -#: assets/templates/assets/admin_user_detail.html:13 -#: assets/templates/assets/cmd_filter_detail.html:14 -#: assets/templates/assets/cmd_filter_rule_list.html:14 -#: assets/templates/assets/domain_detail.html:13 -#: assets/templates/assets/domain_gateway_list.html:15 -#: assets/templates/assets/platform_detail.html:13 -#: assets/templates/assets/system_user_assets.html:22 -#: assets/templates/assets/system_user_detail.html:13 -#: assets/templates/assets/system_user_users.html:21 -#: ops/templates/ops/adhoc_history.html:128 -#: ops/templates/ops/task_adhoc.html:114 -#: ops/templates/ops/task_history.html:135 -#: perms/templates/perms/asset_permission_asset.html:14 -#: perms/templates/perms/asset_permission_detail.html:13 -#: perms/templates/perms/asset_permission_user.html:14 -#: perms/templates/perms/database_app_permission_database_app.html:14 -#: perms/templates/perms/database_app_permission_detail.html:13 -#: perms/templates/perms/database_app_permission_user.html:14 -#: perms/templates/perms/remote_app_permission_detail.html:13 -#: perms/templates/perms/remote_app_permission_remote_app.html:13 -#: perms/templates/perms/remote_app_permission_user.html:13 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:13 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:18 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:17 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:106 -#: xpack/plugins/change_auth_plan/views.py:91 -msgid "Detail" -msgstr "详情" - -#: applications/templates/applications/database_app_detail.html:16 -#: applications/templates/applications/database_app_list.html:53 -#: applications/templates/applications/remote_app_detail.html:16 -#: applications/templates/applications/remote_app_list.html:59 -#: assets/templates/assets/_asset_user_list.html:67 -#: assets/templates/assets/admin_user_detail.html:19 -#: assets/templates/assets/admin_user_list.html:46 -#: assets/templates/assets/asset_detail.html:24 -#: assets/templates/assets/asset_list.html:89 -#: assets/templates/assets/cmd_filter_detail.html:24 -#: assets/templates/assets/cmd_filter_list.html:56 -#: assets/templates/assets/cmd_filter_rule_list.html:81 -#: assets/templates/assets/domain_detail.html:19 -#: assets/templates/assets/domain_detail.html:94 -#: assets/templates/assets/domain_gateway_list.html:92 -#: assets/templates/assets/domain_list.html:50 -#: assets/templates/assets/label_list.html:39 -#: assets/templates/assets/platform_detail.html:16 -#: assets/templates/assets/platform_list.html:40 -#: assets/templates/assets/system_user_detail.html:30 -#: assets/templates/assets/system_user_list.html:55 audits/models.py:34 -#: perms/templates/perms/asset_permission_detail.html:25 -#: perms/templates/perms/asset_permission_list.html:144 -#: perms/templates/perms/database_app_permission_detail.html:25 -#: perms/templates/perms/database_app_permission_list.html:64 -#: perms/templates/perms/remote_app_permission_detail.html:25 -#: perms/templates/perms/remote_app_permission_list.html:64 -#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6 -#: terminal/templates/terminal/base_storage_list.html:63 -#: terminal/templates/terminal/base_storage_list.html:70 -#: terminal/templates/terminal/terminal_detail.html:16 -#: terminal/templates/terminal/terminal_list.html:74 -#: users/templates/users/user_asset_permission.html:127 -#: users/templates/users/user_database_app_permission.html:110 -#: users/templates/users/user_detail.html:12 -#: users/templates/users/user_group_detail.html:23 -#: users/templates/users/user_group_list.html:51 -#: users/templates/users/user_list.html:84 -#: users/templates/users/user_list.html:87 -#: users/templates/users/user_profile.html:181 -#: users/templates/users/user_profile.html:191 -#: users/templates/users/user_profile.html:201 -#: users/templates/users/user_remote_app_permission.html:110 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:27 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:56 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:20 -#: xpack/plugins/cloud/templates/cloud/account_list.html:40 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:60 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:46 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:24 -#: xpack/plugins/orgs/templates/orgs/org_list.html:93 -msgid "Update" -msgstr "更新" - -#: applications/templates/applications/database_app_detail.html:20 -#: applications/templates/applications/database_app_list.html:54 -#: applications/templates/applications/remote_app_detail.html:20 -#: applications/templates/applications/remote_app_list.html:60 -#: assets/templates/assets/_asset_user_list.html:70 -#: assets/templates/assets/admin_user_detail.html:23 -#: assets/templates/assets/admin_user_list.html:47 -#: assets/templates/assets/asset_detail.html:28 -#: assets/templates/assets/asset_list.html:90 -#: assets/templates/assets/cmd_filter_detail.html:28 -#: assets/templates/assets/cmd_filter_list.html:57 -#: assets/templates/assets/cmd_filter_rule_list.html:82 -#: assets/templates/assets/domain_detail.html:23 -#: assets/templates/assets/domain_detail.html:95 -#: assets/templates/assets/domain_gateway_list.html:93 -#: assets/templates/assets/domain_list.html:51 -#: assets/templates/assets/label_list.html:40 -#: assets/templates/assets/platform_detail.html:20 -#: assets/templates/assets/platform_list.html:41 -#: assets/templates/assets/system_user_detail.html:34 -#: assets/templates/assets/system_user_list.html:56 audits/models.py:35 -#: authentication/templates/authentication/_access_key_modal.html:65 -#: ops/templates/ops/task_list.html:74 -#: perms/templates/perms/asset_permission_detail.html:29 -#: perms/templates/perms/asset_permission_list.html:145 -#: perms/templates/perms/database_app_permission_detail.html:29 -#: perms/templates/perms/database_app_permission_list.html:65 -#: perms/templates/perms/remote_app_permission_detail.html:29 -#: perms/templates/perms/remote_app_permission_list.html:65 -#: terminal/templates/terminal/base_storage_list.html:60 -#: terminal/templates/terminal/base_storage_list.html:67 -#: terminal/templates/terminal/terminal_list.html:76 -#: users/templates/users/user_asset_permission.html:128 -#: users/templates/users/user_database_app_permission.html:111 -#: users/templates/users/user_detail.html:16 -#: users/templates/users/user_group_detail.html:27 -#: users/templates/users/user_group_list.html:53 -#: users/templates/users/user_list.html:94 -#: users/templates/users/user_list.html:98 -#: users/templates/users/user_remote_app_permission.html:111 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:31 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:58 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:24 -#: xpack/plugins/cloud/templates/cloud/account_list.html:42 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:61 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:47 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:28 -#: xpack/plugins/orgs/templates/orgs/org_list.html:95 -msgid "Delete" -msgstr "删除" - -#: applications/templates/applications/database_app_list.html:9 -#: applications/views/database_app.py:69 applications/views/database_app.py:84 -msgid "Create DatabaseApp" -msgstr "创建数据库应用" - -#: applications/templates/applications/database_app_list.html:29 -#: applications/templates/applications/remote_app_list.html:29 -#: applications/templates/applications/user_database_app_list.html:21 -#: applications/templates/applications/user_remote_app_list.html:20 -#: assets/models/cmd_filter.py:55 -#: assets/templates/assets/_asset_user_list.html:25 -#: assets/templates/assets/admin_user_list.html:25 -#: assets/templates/assets/asset_list.html:28 -#: assets/templates/assets/cmd_filter_list.html:26 -#: assets/templates/assets/cmd_filter_rule_list.html:58 -#: assets/templates/assets/domain_gateway_list.html:68 -#: assets/templates/assets/domain_list.html:25 -#: assets/templates/assets/label_list.html:17 -#: assets/templates/assets/platform_list.html:19 -#: assets/templates/assets/system_user_list.html:30 -#: assets/templates/assets/system_user_users.html:64 audits/models.py:39 -#: audits/templates/audits/operate_log_list.html:45 -#: audits/templates/audits/operate_log_list.html:71 -#: authentication/templates/authentication/_access_key_modal.html:34 -#: ops/templates/ops/adhoc_history.html:57 ops/templates/ops/task_adhoc.html:62 -#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:17 -#: perms/forms/asset_permission.py:20 -#: perms/templates/perms/asset_permission_asset.html:54 -#: perms/templates/perms/asset_permission_create_update.html:63 -#: perms/templates/perms/asset_permission_list.html:39 -#: perms/templates/perms/asset_permission_list.html:96 -#: perms/templates/perms/asset_permission_user.html:54 -#: perms/templates/perms/database_app_permission_database_app.html:54 -#: perms/templates/perms/database_app_permission_list.html:20 -#: perms/templates/perms/database_app_permission_user.html:54 -#: perms/templates/perms/remote_app_permission_list.html:20 -#: terminal/templates/terminal/base_storage_list.html:34 -#: terminal/templates/terminal/session_list.html:34 -#: terminal/templates/terminal/terminal_list.html:37 -#: tickets/templates/tickets/ticket_list.html:108 -#: users/templates/users/_granted_assets.html:29 -#: users/templates/users/user_asset_permission.html:44 -#: users/templates/users/user_asset_permission.html:79 -#: users/templates/users/user_database_app_permission.html:42 -#: users/templates/users/user_group_list.html:17 -#: users/templates/users/user_list.html:20 -#: users/templates/users/user_remote_app_permission.html:42 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20 -#: xpack/plugins/cloud/templates/cloud/account_list.html:16 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:19 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:20 -#: xpack/plugins/orgs/templates/orgs/org_list.html:24 -#: xpack/plugins/orgs/templates/orgs/org_users.html:47 -msgid "Action" -msgstr "动作" - -#: applications/templates/applications/remote_app_list.html:4 -msgid "" -"Before using this feature, make sure that the application loader has been " -"uploaded to the application server and successfully published as a RemoteApp " -"application" -msgstr "" -"使用此功能前,请确保已将应用加载器上传到应用服务器并成功发布为一个 RemoteApp " -"应用" - -#: applications/templates/applications/remote_app_list.html:5 -msgid "Download application loader" -msgstr "下载应用加载器" - -#: applications/templates/applications/remote_app_list.html:11 -#: applications/views/remote_app.py:69 -msgid "Create RemoteApp" -msgstr "创建远程应用" - -#: applications/templates/applications/user_database_app_list.html:61 -#: applications/templates/applications/user_remote_app_list.html:52 -#: perms/models/asset_permission.py:32 -msgid "Connect" -msgstr "连接" - -#: applications/views/database_app.py:26 users/models/user.py:144 -msgid "Application" -msgstr "应用程序" - -#: applications/views/database_app.py:27 -msgid "DatabaseApp list" -msgstr "数据库应用列表" - -#: applications/views/database_app.py:68 applications/views/database_app.py:83 -#: applications/views/database_app.py:99 applications/views/remote_app.py:28 -#: applications/views/remote_app.py:68 applications/views/remote_app.py:96 -#: applications/views/remote_app.py:112 templates/_nav.html:60 -msgid "Applications" -msgstr "应用管理" - -#: applications/views/database_app.py:100 -msgid "DatabaseApp detail" -msgstr "数据库应用详情" - -#: applications/views/database_app.py:112 -msgid "My DatabaseApp" -msgstr "我的数据库应用" - -#: applications/views/remote_app.py:29 -msgid "RemoteApp list" -msgstr "远程应用列表" - -#: applications/views/remote_app.py:97 -msgid "Update RemoteApp" -msgstr "更新远程应用" - -#: applications/views/remote_app.py:113 -msgid "RemoteApp detail" -msgstr "远程应用详情" - -#: applications/views/remote_app.py:125 -msgid "My RemoteApp" -msgstr "我的远程应用" - -#: assets/api/admin_user.py:59 +#: assets/api/admin_user.py:46 msgid "Deleted failed, There are related assets" msgstr "删除失败,存在关联资产" @@ -738,259 +180,15 @@ msgstr "不能移除资产的管理用户账号" msgid "Latest version could not be delete" msgstr "最新版本的不能被删除" -#: assets/forms/asset.py:83 assets/models/asset.py:195 -#: assets/models/user.py:109 assets/templates/assets/asset_detail.html:186 -#: assets/templates/assets/asset_detail.html:194 -#: assets/templates/assets/system_user_assets.html:118 -#: perms/models/asset_permission.py:81 -#: xpack/plugins/change_auth_plan/models.py:54 -#: xpack/plugins/gathered_user/models.py:24 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17 -msgid "Nodes" -msgstr "节点" - -#: assets/forms/asset.py:86 assets/models/asset.py:199 -#: assets/models/cluster.py:19 assets/models/user.py:65 -#: assets/templates/assets/admin_user_list.html:62 -#: assets/templates/assets/asset_detail.html:72 templates/_nav.html:44 -#: xpack/plugins/cloud/models.py:133 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65 -#: xpack/plugins/orgs/templates/orgs/org_list.html:19 -msgid "Admin user" -msgstr "管理用户" - -#: assets/forms/asset.py:89 assets/forms/asset.py:131 -#: assets/templates/assets/asset_create.html:48 -#: assets/templates/assets/asset_create.html:50 -#: assets/templates/assets/asset_list.html:13 -#: xpack/plugins/orgs/templates/orgs/org_list.html:21 -msgid "Label" -msgstr "标签" - -#: assets/forms/asset.py:92 assets/models/asset.py:194 -#: assets/models/domain.py:26 assets/models/domain.py:52 -#: assets/templates/assets/asset_detail.html:76 -#: assets/templates/assets/user_asset_list.html:80 -#: xpack/plugins/orgs/templates/orgs/org_list.html:18 -msgid "Domain" -msgstr "网域" - -#: assets/forms/asset.py:95 assets/models/asset.py:169 -#: assets/models/asset.py:193 assets/serializers/asset.py:67 -#: assets/templates/assets/asset_detail.html:100 -#: assets/templates/assets/user_asset_list.html:78 -msgid "Platform" -msgstr "系统平台" - -#: assets/forms/asset.py:99 assets/forms/asset.py:134 assets/models/node.py:497 -#: assets/serializers/system_user.py:43 assets/serializers/system_user.py:175 -#: assets/templates/assets/asset_create.html:42 -#: perms/forms/asset_permission.py:92 perms/forms/asset_permission.py:99 -#: perms/templates/perms/asset_permission_list.html:36 -#: perms/templates/perms/asset_permission_list.html:90 -#: perms/templates/perms/asset_permission_list.html:189 -#: users/templates/users/user_asset_permission.html:41 -#: users/templates/users/user_asset_permission.html:73 -#: users/templates/users/user_asset_permission.html:158 -#: xpack/plugins/change_auth_plan/forms.py:75 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15 -#: xpack/plugins/cloud/models.py:129 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:61 -msgid "Node" -msgstr "节点" - -#: assets/forms/asset.py:103 -msgid "" -"root or other NOPASSWD sudo privilege user existed in asset,If asset is " -"windows or other set any one, more see admin user left menu" -msgstr "" -"root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置一" -"个, 更多信息查看左侧 `管理用户` 菜单" - -#: assets/forms/asset.py:106 -msgid "Windows 2016 RDP protocol is different, If is window 2016, set it" -msgstr "Windows 2016的RDP协议与之前不同,如果是请设置" - -#: assets/forms/asset.py:107 -msgid "" -"If your have some network not connect with each other, you can set domain" -msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录" - -#: assets/forms/asset.py:114 assets/forms/asset.py:118 -#: assets/forms/domain.py:17 assets/forms/label.py:15 -#: assets/templates/assets/system_user_assets.html:102 -#: perms/templates/perms/asset_permission_asset.html:74 -#: xpack/plugins/change_auth_plan/forms.py:65 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:70 -msgid "Select assets" -msgstr "选择资产" - -#: assets/forms/cmd_filter.py:38 -msgid "Content should not be contain: {}" -msgstr "内容不能包含: {}" - -#: assets/forms/domain.py:55 assets/models/domain.py:67 -msgid "Password should not contain special characters" -msgstr "不能包含特殊字符" - -#: assets/forms/domain.py:74 -msgid "SSH gateway support proxy SSH,RDP,VNC" -msgstr "SSH网关,支持代理SSH,RDP和VNC" - -#: assets/forms/domain.py:78 assets/forms/user.py:75 assets/forms/user.py:97 -#: assets/models/base.py:233 assets/models/gathered_user.py:15 -#: assets/templates/assets/_asset_user_auth_update_modal.html:15 -#: assets/templates/assets/_asset_user_auth_view_modal.html:21 -#: assets/templates/assets/_asset_user_list.html:21 -#: assets/templates/assets/admin_user_detail.html:55 -#: assets/templates/assets/admin_user_list.html:22 -#: assets/templates/assets/domain_gateway_list.html:66 -#: assets/templates/assets/system_user_detail.html:66 -#: assets/templates/assets/system_user_list.html:25 audits/models.py:81 -#: audits/templates/audits/login_log_list.html:57 authentication/forms.py:10 -#: authentication/templates/authentication/login.html:21 -#: authentication/templates/authentication/xpack_login.html:93 -#: ops/models/adhoc.py:148 perms/templates/perms/asset_permission_list.html:185 -#: perms/templates/perms/remote_app_permission_user.html:50 -#: settings/templates/settings/_ldap_list_users_modal.html:31 -#: settings/templates/settings/_ldap_test_user_login_modal.html:10 -#: users/forms/profile.py:19 users/models/user.py:438 -#: users/templates/users/_select_user_modal.html:14 -#: users/templates/users/user_detail.html:53 -#: users/templates/users/user_list.html:15 -#: users/templates/users/user_profile.html:47 -#: xpack/plugins/change_auth_plan/forms.py:59 -#: xpack/plugins/change_auth_plan/models.py:45 -#: xpack/plugins/change_auth_plan/models.py:261 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:63 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:13 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:25 -msgid "Username" -msgstr "用户名" - -#: assets/forms/platform.py:20 ops/templates/ops/task_detail.html:85 -#: ops/templates/ops/task_detail.html:95 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:82 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:72 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:82 -msgid "Yes" -msgstr "是" - -#: assets/forms/platform.py:21 ops/templates/ops/task_detail.html:87 -#: ops/templates/ops/task_detail.html:97 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:84 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:74 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:84 -msgid "No" -msgstr "否" - -#: assets/forms/platform.py:24 -msgid "RDP security" -msgstr "" - -#: assets/forms/platform.py:28 -msgid "RDP console" -msgstr "" - -#: assets/forms/platform.py:40 assets/templates/assets/platform_detail.html:52 -#: assets/templates/assets/platform_list.html:17 -msgid "Base platform" -msgstr "基础平台" - -#: assets/forms/user.py:25 -msgid "Password or private key passphrase" -msgstr "密码或密钥密码" - -#: assets/forms/user.py:26 assets/models/base.py:234 -#: assets/serializers/asset_user.py:71 -#: assets/templates/assets/_asset_user_auth_update_modal.html:21 -#: assets/templates/assets/_asset_user_auth_view_modal.html:27 -#: authentication/forms.py:12 -#: authentication/templates/authentication/login.html:29 -#: authentication/templates/authentication/xpack_login.html:101 -#: settings/forms/ldap.py:22 -#: settings/templates/settings/_ldap_test_user_login_modal.html:16 -#: users/forms/user.py:22 users/forms/user.py:193 -#: users/templates/users/user_otp_check_password.html:13 -#: users/templates/users/user_password_update.html:44 -#: users/templates/users/user_password_verify.html:18 -#: users/templates/users/user_profile_update.html:41 -#: users/templates/users/user_pubkey_update.html:41 -#: users/templates/users/user_update.html:20 -#: xpack/plugins/change_auth_plan/models.py:66 -#: xpack/plugins/change_auth_plan/models.py:181 -#: xpack/plugins/change_auth_plan/models.py:268 -msgid "Password" -msgstr "密码" - -#: assets/forms/user.py:29 assets/serializers/asset_user.py:79 -#: assets/templates/assets/_asset_user_auth_update_modal.html:27 -#: users/models/user.py:467 -msgid "Private key" -msgstr "ssh私钥" - -#: assets/forms/user.py:41 -msgid "Invalid private key, Only support RSA/DSA format key" -msgstr "不合法的密钥,仅支持RSA/DSA格式的密钥" - -#: assets/forms/user.py:52 -msgid "Password and private key file must be input one" -msgstr "密码和私钥, 必须输入一个" - -#: assets/forms/user.py:99 assets/models/cmd_filter.py:32 -#: assets/models/user.py:119 assets/templates/assets/_system_user.html:63 -#: assets/templates/assets/system_user_detail.html:155 -msgid "Command filter" -msgstr "命令过滤器" - -#: assets/forms/user.py:103 assets/models/user.py:108 -#: assets/templates/assets/system_user_detail.html:68 -msgid "Username same with user" -msgstr "用户名与用户相同" - -#: assets/forms/user.py:106 -msgid "Auto push system user to asset" -msgstr "自动推送系统用户到资产" - -#: assets/forms/user.py:107 -msgid "" -"1-100, High level will be using login asset as default, if user was granted " -"more than 2 system user" -msgstr "" -"1-100, 1最低优先级,100最高优先级。授权多个用户时,高优先级的系统用户将会作为" -"默认登录用户" - -#: assets/forms/user.py:109 -msgid "" -"If you choose manual login mode, you do not need to fill in the username and " -"password." -msgstr "如果选择手动登录模式,用户名和密码可以不填写" - -#: assets/forms/user.py:111 -msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig" -msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" - -#: assets/forms/user.py:112 -msgid "SFTP root dir, tmp, home or custom" -msgstr "SFTP的起始路径,tmp目录, 用户home目录或者自定义" - -#: assets/forms/user.py:113 -msgid "Username is dynamic, When connect asset, using current user's username" -msgstr "用户名是动态的,登录资产时使用当前用户的用户名登录" - #: assets/models/asset.py:146 xpack/plugins/cloud/providers/base.py:16 msgid "Base" msgstr "基础" -#: assets/models/asset.py:147 assets/templates/assets/platform_detail.html:56 +#: assets/models/asset.py:147 msgid "Charset" msgstr "编码" -#: assets/models/asset.py:148 assets/templates/assets/platform_detail.html:60 -#: tickets/models/ticket.py:38 +#: assets/models/asset.py:148 tickets/models/ticket.py:38 msgid "Meta" msgstr "元数据" @@ -998,82 +196,76 @@ msgstr "元数据" msgid "Internal" msgstr "内部的" +#: assets/models/asset.py:169 assets/models/asset.py:193 +#: assets/serializers/asset.py:67 +msgid "Platform" +msgstr "系统平台" + #: assets/models/asset.py:186 assets/models/domain.py:49 -#: assets/serializers/asset_user.py:46 -#: assets/templates/assets/_asset_list_modal.html:47 -#: assets/templates/assets/_asset_user_list.html:20 -#: assets/templates/assets/asset_detail.html:60 -#: assets/templates/assets/asset_list.html:25 -#: assets/templates/assets/domain_gateway_list.html:63 -#: assets/templates/assets/user_asset_list.html:76 -#: audits/templates/audits/login_log_list.html:60 -#: perms/templates/perms/asset_permission_list.html:187 -#: settings/forms/terminal.py:16 users/templates/users/_granted_assets.html:26 +#: assets/serializers/asset_user.py:46 settings/serializers/settings.py:52 +#: users/templates/users/_granted_assets.html:26 #: users/templates/users/user_asset_permission.html:156 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:50 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:24 msgid "IP" msgstr "IP" #: assets/models/asset.py:187 assets/serializers/asset_user.py:45 -#: assets/serializers/gathered_user.py:20 -#: assets/templates/assets/_asset_list_modal.html:46 -#: assets/templates/assets/_asset_user_auth_update_modal.html:9 -#: assets/templates/assets/_asset_user_auth_view_modal.html:15 -#: assets/templates/assets/_asset_user_list.html:19 -#: assets/templates/assets/asset_detail.html:56 -#: assets/templates/assets/asset_list.html:24 -#: assets/templates/assets/user_asset_list.html:75 -#: perms/templates/perms/asset_permission_list.html:188 -#: settings/forms/terminal.py:15 users/templates/users/_granted_assets.html:25 +#: assets/serializers/gathered_user.py:20 settings/serializers/settings.py:51 +#: users/templates/users/_granted_assets.html:25 #: users/templates/users/user_asset_permission.html:157 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:49 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:23 msgid "Hostname" msgstr "主机名" #: assets/models/asset.py:190 assets/models/domain.py:51 -#: assets/models/user.py:114 assets/templates/assets/asset_detail.html:68 -#: assets/templates/assets/domain_gateway_list.html:65 -#: assets/templates/assets/system_user_detail.html:78 -#: assets/templates/assets/system_user_list.html:26 -#: terminal/forms/storage.py:145 -#: terminal/templates/terminal/session_detail.html:60 -#: terminal/templates/terminal/session_list.html:29 -#: terminal/templates/terminal/session_list.html:73 +#: assets/models/user.py:114 terminal/serializers/session.py:29 msgid "Protocol" msgstr "协议" #: assets/models/asset.py:192 assets/serializers/asset.py:69 -#: assets/templates/assets/asset_create.html:24 -#: assets/templates/assets/user_asset_list.html:77 #: perms/serializers/user_permission.py:60 msgid "Protocols" msgstr "协议组" +#: assets/models/asset.py:194 assets/models/domain.py:26 +#: assets/models/domain.py:52 +msgid "Domain" +msgstr "网域" + +#: assets/models/asset.py:195 assets/models/user.py:109 +#: perms/models/asset_permission.py:81 +#: xpack/plugins/change_auth_plan/models.py:55 +#: xpack/plugins/gathered_user/models.py:24 +msgid "Nodes" +msgstr "节点" + #: assets/models/asset.py:196 assets/models/cmd_filter.py:22 #: assets/models/domain.py:54 assets/models/label.py:22 -#: assets/templates/assets/asset_detail.html:108 authentication/models.py:45 +#: authentication/models.py:45 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:202 assets/templates/assets/asset_detail.html:64 +#: assets/models/asset.py:199 assets/models/cluster.py:19 +#: assets/models/user.py:65 templates/_nav.html:44 +#: xpack/plugins/cloud/models.py:133 +msgid "Admin user" +msgstr "管理用户" + +#: assets/models/asset.py:202 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:116 +#: assets/models/asset.py:203 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:206 assets/templates/assets/asset_detail.html:80 +#: assets/models/asset.py:206 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:207 assets/templates/assets/asset_detail.html:84 +#: assets/models/asset.py:207 msgid "Model" msgstr "型号" -#: assets/models/asset.py:208 assets/templates/assets/asset_detail.html:112 +#: assets/models/asset.py:208 msgid "Serial number" msgstr "序列号" @@ -1093,7 +285,7 @@ msgstr "CPU核数" msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:214 assets/templates/assets/asset_detail.html:92 +#: assets/models/asset.py:214 msgid "Memory" msgstr "内存" @@ -1105,7 +297,7 @@ msgstr "硬盘大小" msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:218 assets/templates/assets/asset_detail.html:104 +#: assets/models/asset.py:218 msgid "OS" msgstr "操作系统" @@ -1121,8 +313,7 @@ msgstr "系统架构" msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:223 assets/templates/assets/asset_create.html:46 -#: assets/templates/assets/asset_detail.html:220 templates/_nav.html:46 +#: assets/models/asset.py:223 templates/_nav.html:46 msgid "Labels" msgstr "标签管理" @@ -1130,15 +321,11 @@ msgstr "标签管理" msgid "Bulk delete deny" msgstr "拒绝批量删除" -#: assets/models/authbook.py:28 ops/templates/ops/task_detail.html:70 +#: assets/models/authbook.py:28 msgid "Latest version" msgstr "最新版本" #: assets/models/authbook.py:29 -#: assets/templates/assets/_asset_user_list.html:22 -#: ops/templates/ops/adhoc_history.html:56 -#: ops/templates/ops/adhoc_history_detail.html:55 -#: ops/templates/ops/task_adhoc.html:56 ops/templates/ops/task_history.html:62 msgid "Version" msgstr "版本" @@ -1146,23 +333,51 @@ msgstr "版本" msgid "AuthBook" msgstr "" -#: assets/models/base.py:235 xpack/plugins/change_auth_plan/models.py:70 -#: xpack/plugins/change_auth_plan/models.py:188 -#: xpack/plugins/change_auth_plan/models.py:275 -msgid "SSH private key" -msgstr "ssh密钥" +#: assets/models/base.py:233 assets/models/gathered_user.py:15 +#: audits/models.py:81 authentication/forms.py:10 +#: authentication/templates/authentication/login.html:21 +#: authentication/templates/authentication/xpack_login.html:93 +#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:464 +#: users/templates/users/_select_user_modal.html:14 +#: users/templates/users/user_detail.html:53 +#: users/templates/users/user_list.html:15 +#: users/templates/users/user_profile.html:47 +#: xpack/plugins/change_auth_plan/models.py:46 +#: xpack/plugins/change_auth_plan/models.py:278 +msgid "Username" +msgstr "用户名" -#: assets/models/base.py:236 xpack/plugins/change_auth_plan/models.py:73 -#: xpack/plugins/change_auth_plan/models.py:184 -#: xpack/plugins/change_auth_plan/models.py:271 +#: assets/models/base.py:234 assets/serializers/asset_user.py:71 +#: authentication/forms.py:12 +#: authentication/templates/authentication/login.html:29 +#: authentication/templates/authentication/xpack_login.html:101 +#: users/forms/user.py:22 users/forms/user.py:193 +#: users/templates/users/user_otp_check_password.html:13 +#: users/templates/users/user_password_update.html:43 +#: users/templates/users/user_password_verify.html:18 +#: users/templates/users/user_profile_update.html:41 +#: users/templates/users/user_pubkey_update.html:41 +#: users/templates/users/user_update.html:20 +#: xpack/plugins/change_auth_plan/models.py:67 +#: xpack/plugins/change_auth_plan/models.py:190 +#: xpack/plugins/change_auth_plan/models.py:285 +msgid "Password" +msgstr "密码" + +#: assets/models/base.py:235 xpack/plugins/change_auth_plan/models.py:71 +#: xpack/plugins/change_auth_plan/models.py:197 +#: xpack/plugins/change_auth_plan/models.py:292 +msgid "SSH private key" +msgstr "SSH密钥" + +#: assets/models/base.py:236 xpack/plugins/change_auth_plan/models.py:74 +#: xpack/plugins/change_auth_plan/models.py:193 +#: xpack/plugins/change_auth_plan/models.py:288 msgid "SSH public key" -msgstr "ssh公钥" +msgstr "SSH公钥" #: assets/models/base.py:239 assets/models/gathered_user.py:20 -#: assets/templates/assets/cmd_filter_detail.html:68 common/mixins/models.py:51 -#: ops/models/adhoc.py:39 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:107 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:29 +#: common/mixins/models.py:51 ops/models/adhoc.py:39 msgid "Date updated" msgstr "更新日期" @@ -1174,7 +389,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:459 +#: assets/models/cluster.py:22 users/models/user.py:485 #: users/templates/users/user_detail.html:62 msgid "Phone" msgstr "手机" @@ -1200,7 +415,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:600 +#: users/models/user.py:626 msgid "System" msgstr "系统" @@ -1224,16 +439,16 @@ msgstr "北京电信" msgid "BGP full netcom" msgstr "BGP全网通" +#: assets/models/cmd_filter.py:32 assets/models/user.py:119 +msgid "Command filter" +msgstr "命令过滤器" + #: assets/models/cmd_filter.py:39 msgid "Regex" msgstr "正则表达式" #: assets/models/cmd_filter.py:40 ops/models/command.py:23 -#: ops/templates/ops/command_execution_list.html:67 terminal/models.py:196 -#: terminal/templates/terminal/command_list.html:28 -#: terminal/templates/terminal/command_list.html:108 -#: terminal/templates/terminal/session_commands.html:49 -#: terminal/templates/terminal/session_list.html:31 +#: terminal/models.py:196 msgid "Command" msgstr "命令" @@ -1250,7 +465,6 @@ msgid "Filter" msgstr "过滤器" #: assets/models/cmd_filter.py:52 assets/models/user.py:113 -#: assets/templates/assets/cmd_filter_rule_list.html:55 msgid "Priority" msgstr "优先级" @@ -1258,9 +472,7 @@ msgstr "优先级" msgid "1-100, the higher will be match first" msgstr "优先级可选范围为1-100,1最低优先级,100最高优先级" -#: assets/models/cmd_filter.py:54 -#: assets/templates/assets/cmd_filter_rule_list.html:54 -#: xpack/plugins/license/models.py:29 +#: assets/models/cmd_filter.py:54 xpack/plugins/license/models.py:29 msgid "Content" msgstr "内容" @@ -1268,29 +480,40 @@ msgstr "内容" msgid "One line one command" msgstr "每行一个命令" +#: assets/models/cmd_filter.py:55 audits/models.py:39 +#: authentication/templates/authentication/_access_key_modal.html:34 +#: perms/forms/asset_permission.py:20 tickets/serializers/ticket.py:26 +#: users/templates/users/_granted_assets.html:29 +#: users/templates/users/user_asset_permission.html:44 +#: users/templates/users/user_asset_permission.html:79 +#: users/templates/users/user_database_app_permission.html:42 +#: users/templates/users/user_group_list.html:17 +#: users/templates/users/user_list.html:20 +#: users/templates/users/user_remote_app_permission.html:42 +msgid "Action" +msgstr "动作" + #: assets/models/cmd_filter.py:63 msgid "Command filter rule" msgstr "命令过滤规则" -#: assets/models/domain.py:61 assets/templates/assets/domain_detail.html:16 -#: assets/templates/assets/domain_detail.html:59 -#: assets/templates/assets/domain_gateway_list.html:21 -#: assets/templates/assets/domain_list.html:23 +#: assets/models/domain.py:61 msgid "Gateway" msgstr "网关" +#: assets/models/domain.py:67 +msgid "Password should not contain special characters" +msgstr "不能包含特殊字符" + #: assets/models/gathered_user.py:16 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:28 msgid "Present" msgstr "存在" #: assets/models/gathered_user.py:17 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:26 msgid "Date last login" msgstr "最后登录日期" #: assets/models/gathered_user.py:18 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:27 msgid "IP last login" msgstr "最后登录IP" @@ -1306,34 +529,14 @@ msgstr "资产组" msgid "Default asset group" msgstr "默认资产组" -#: assets/models/label.py:15 assets/templates/assets/system_user_users.html:63 -#: audits/models.py:18 audits/models.py:38 audits/models.py:51 -#: audits/templates/audits/ftp_log_list.html:37 -#: audits/templates/audits/ftp_log_list.html:74 -#: audits/templates/audits/operate_log_list.html:37 -#: audits/templates/audits/password_change_log_list.html:37 -#: audits/templates/audits/password_change_log_list.html:54 -#: authentication/models.py:43 ops/templates/ops/command_execution_list.html:41 -#: ops/templates/ops/command_execution_list.html:66 +#: assets/models/label.py:15 audits/models.py:18 audits/models.py:38 +#: audits/models.py:51 audits/serializers.py:76 authentication/models.py:43 #: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38 #: perms/forms/remote_app_permission.py:40 perms/models/base.py:49 -#: perms/templates/perms/asset_permission_create_update.html:52 -#: perms/templates/perms/asset_permission_list.html:33 -#: perms/templates/perms/asset_permission_list.html:81 -#: perms/templates/perms/database_app_permission_create_update.html:41 -#: perms/templates/perms/database_app_permission_list.html:15 -#: perms/templates/perms/remote_app_permission_create_update.html:41 -#: perms/templates/perms/remote_app_permission_list.html:15 #: templates/index.html:78 terminal/backends/command/models.py:18 -#: terminal/models.py:185 terminal/templates/terminal/command_list.html:30 -#: terminal/templates/terminal/command_list.html:105 -#: terminal/templates/terminal/session_detail.html:48 -#: terminal/templates/terminal/session_list.html:25 -#: terminal/templates/terminal/session_list.html:69 tickets/models/ticket.py:33 -#: tickets/models/ticket.py:128 tickets/templates/tickets/ticket_detail.html:32 -#: tickets/templates/tickets/ticket_list.html:34 -#: tickets/templates/tickets/ticket_list.html:103 users/forms/group.py:15 -#: users/models/user.py:143 users/models/user.py:159 users/models/user.py:588 +#: terminal/models.py:185 tickets/models/ticket.py:33 +#: tickets/models/ticket.py:128 users/forms/group.py:15 +#: users/models/user.py:159 users/models/user.py:175 users/models/user.py:614 #: users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 @@ -1341,15 +544,13 @@ msgstr "默认资产组" #: users/templates/users/user_database_app_permission.html:58 #: users/templates/users/user_group_detail.html:73 #: users/templates/users/user_group_list.html:15 +#: users/templates/users/user_list.html:135 #: users/templates/users/user_remote_app_permission.html:37 #: users/templates/users/user_remote_app_permission.html:58 -#: users/views/profile/base.py:46 xpack/plugins/orgs/forms.py:27 -#: xpack/plugins/orgs/templates/orgs/org_list.html:15 msgid "User" msgstr "用户" -#: assets/models/label.py:19 assets/models/node.py:488 -#: assets/templates/assets/label_list.html:15 settings/models.py:27 +#: assets/models/label.py:19 assets/models/node.py:488 settings/models.py:28 msgid "Value" msgstr "值" @@ -1373,10 +574,20 @@ msgstr "空" msgid "favorite" msgstr "收藏夹" -#: assets/models/node.py:487 assets/templates/assets/_node_detail_modal.html:39 +#: assets/models/node.py:487 msgid "Key" msgstr "键" +#: assets/models/node.py:497 assets/serializers/system_user.py:43 +#: assets/serializers/system_user.py:175 perms/forms/asset_permission.py:92 +#: perms/forms/asset_permission.py:99 +#: users/templates/users/user_asset_permission.html:41 +#: users/templates/users/user_asset_permission.html:73 +#: users/templates/users/user_asset_permission.html:158 +#: xpack/plugins/cloud/models.py:129 +msgid "Node" +msgstr "节点" + #: assets/models/user.py:105 msgid "Automatic login" msgstr "自动登录" @@ -1385,63 +596,38 @@ msgstr "自动登录" msgid "Manually login" msgstr "手动登录" -#: assets/models/user.py:110 -#: assets/templates/assets/_asset_group_bulk_update_modal.html:11 -#: assets/templates/assets/system_user_assets.html:94 -#: assets/views/admin_user.py:30 assets/views/admin_user.py:49 -#: assets/views/admin_user.py:67 assets/views/admin_user.py:84 -#: assets/views/admin_user.py:108 assets/views/asset.py:37 -#: assets/views/asset.py:54 assets/views/asset.py:103 assets/views/asset.py:130 -#: assets/views/asset.py:170 assets/views/asset.py:199 -#: assets/views/cmd_filter.py:31 assets/views/cmd_filter.py:48 -#: assets/views/cmd_filter.py:66 assets/views/cmd_filter.py:84 -#: assets/views/cmd_filter.py:104 assets/views/cmd_filter.py:138 -#: assets/views/cmd_filter.py:173 assets/views/domain.py:31 -#: assets/views/domain.py:48 assets/views/domain.py:66 -#: assets/views/domain.py:81 assets/views/domain.py:107 -#: assets/views/domain.py:136 assets/views/domain.py:157 -#: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:73 -#: assets/views/platform.py:22 assets/views/platform.py:38 -#: assets/views/platform.py:58 assets/views/platform.py:74 -#: assets/views/system_user.py:30 assets/views/system_user.py:47 -#: assets/views/system_user.py:64 assets/views/system_user.py:80 -#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:50 +#: assets/models/user.py:108 +msgid "Username same with user" +msgstr "用户名与用户相同" + +#: assets/models/user.py:110 templates/_nav.html:39 +#: xpack/plugins/change_auth_plan/models.py:51 msgid "Assets" msgstr "资产管理" -#: assets/models/user.py:111 assets/templates/assets/system_user_users.html:76 -#: templates/_nav.html:17 users/views/group.py:28 users/views/group.py:45 -#: users/views/group.py:63 users/views/group.py:82 users/views/group.py:99 -#: users/views/login.py:163 users/views/profile/password.py:40 -#: users/views/profile/pubkey.py:36 users/views/user.py:50 -#: users/views/user.py:67 users/views/user.py:111 users/views/user.py:178 -#: users/views/user.py:206 users/views/user.py:220 users/views/user.py:234 -#: users/views/user.py:248 users/views/user.py:262 users/views/user.py:276 +#: assets/models/user.py:111 templates/_nav.html:17 +#: users/views/profile/password.py:40 users/views/profile/pubkey.py:36 msgid "Users" msgstr "用户管理" #: assets/models/user.py:112 users/templates/users/user_group_list.html:90 -#: users/templates/users/user_list.html:135 #: users/templates/users/user_profile.html:124 msgid "User groups" msgstr "用户组" -#: assets/models/user.py:115 assets/templates/assets/_system_user.html:56 -#: assets/templates/assets/system_user_detail.html:130 -#: assets/templates/assets/system_user_update.html:10 +#: assets/models/user.py:115 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:116 assets/templates/assets/system_user_detail.html:82 +#: assets/models/user.py:116 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:117 assets/templates/assets/system_user_detail.html:87 +#: assets/models/user.py:117 msgid "Shell" msgstr "Shell" -#: assets/models/user.py:118 assets/templates/assets/system_user_detail.html:74 -#: assets/templates/assets/system_user_list.html:27 +#: assets/models/user.py:118 msgid "Login mode" msgstr "登录模式" @@ -1449,27 +635,12 @@ msgstr "登录模式" msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:195 assets/templates/assets/system_user_list.html:73 -#: audits/models.py:21 audits/templates/audits/ftp_log_list.html:53 -#: audits/templates/audits/ftp_log_list.html:76 +#: assets/models/user.py:195 audits/models.py:21 #: perms/forms/asset_permission.py:95 perms/forms/remote_app_permission.py:49 #: perms/models/asset_permission.py:82 -#: perms/models/database_app_permission.py:21 -#: perms/models/remote_app_permission.py:16 -#: perms/templates/perms/asset_permission_asset.html:124 -#: perms/templates/perms/asset_permission_list.html:37 -#: perms/templates/perms/asset_permission_list.html:93 -#: perms/templates/perms/asset_permission_list.html:190 -#: perms/templates/perms/database_app_permission_database_app.html:94 -#: perms/templates/perms/database_app_permission_list.html:18 -#: perms/templates/perms/remote_app_permission_detail.html:126 -#: perms/templates/perms/remote_app_permission_list.html:18 -#: templates/_nav.html:45 terminal/backends/command/models.py:20 -#: terminal/models.py:189 terminal/templates/terminal/command_list.html:32 -#: terminal/templates/terminal/command_list.html:107 -#: terminal/templates/terminal/session_detail.html:56 -#: terminal/templates/terminal/session_list.html:27 -#: terminal/templates/terminal/session_list.html:71 +#: perms/models/database_app_permission.py:22 +#: perms/models/remote_app_permission.py:16 templates/_nav.html:45 +#: terminal/backends/command/models.py:20 terminal/models.py:189 #: users/templates/users/_granted_assets.html:27 #: users/templates/users/user_asset_permission.html:42 #: users/templates/users/user_asset_permission.html:76 @@ -1478,7 +649,6 @@ msgstr "SFTP根路径" #: users/templates/users/user_database_app_permission.html:67 #: users/templates/users/user_remote_app_permission.html:40 #: users/templates/users/user_remote_app_permission.html:67 -#: xpack/plugins/orgs/templates/orgs/org_list.html:20 msgid "System user" msgstr "系统用户" @@ -1492,7 +662,6 @@ msgid "Unreachable" msgstr "不可达" #: assets/models/utils.py:44 assets/tasks/const.py:50 -#: assets/templates/assets/asset_list.html:27 msgid "Reachable" msgstr "可连接" @@ -1508,28 +677,20 @@ msgstr "协议格式 {}/{}" msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset.py:94 +#: assets/serializers/asset.py:108 msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:95 orgs/mixins/serializers.py:27 +#: assets/serializers/asset.py:109 orgs/mixins/serializers.py:27 msgid "Org name" msgstr "组织名称" -#: assets/serializers/asset.py:134 assets/serializers/asset.py:171 +#: assets/serializers/asset.py:144 assets/serializers/asset.py:181 msgid "Connectivity" msgstr "连接" #: assets/serializers/asset_user.py:44 -#: assets/templates/assets/_node_detail_modal.html:18 -#: audits/templates/audits/login_log_list.html:56 #: authentication/templates/authentication/_access_key_modal.html:30 -#: ops/templates/ops/adhoc_detail.html:47 -#: ops/templates/ops/adhoc_history_detail.html:47 -#: ops/templates/ops/task_detail.html:54 -#: terminal/templates/terminal/session_list.html:24 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:45 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:44 msgid "ID" msgstr "ID" @@ -1538,13 +699,16 @@ msgid "Backend" msgstr "后端" #: assets/serializers/asset_user.py:75 users/forms/profile.py:148 -#: users/models/user.py:470 users/templates/users/first_login.html:42 -#: users/templates/users/user_password_update.html:49 +#: users/models/user.py:496 users/templates/users/user_password_update.html:48 #: users/templates/users/user_profile.html:69 #: users/templates/users/user_profile_update.html:46 #: users/templates/users/user_pubkey_update.html:46 msgid "Public key" -msgstr "ssh公钥" +msgstr "SSH公钥" + +#: assets/serializers/asset_user.py:79 users/models/user.py:493 +msgid "Private key" +msgstr "ssh私钥" #: assets/serializers/base.py:45 msgid "" @@ -1691,834 +855,78 @@ msgstr "为了安全,禁止推送用户 {}" msgid "No assets matched, stop task" msgstr "没有匹配到资产,结束任务" -#: assets/templates/assets/_asset_group_bulk_update_modal.html:5 -msgid "Update asset group" -msgstr "更新用户组" - -#: assets/templates/assets/_asset_group_bulk_update_modal.html:8 -msgid "Hint: only change the field you want to update." -msgstr "仅修改你需要更新的字段" - -#: assets/templates/assets/_asset_group_bulk_update_modal.html:13 -msgid "Select Asset" -msgstr "选择资产" - -#: assets/templates/assets/_asset_group_bulk_update_modal.html:21 -#: assets/templates/assets/cmd_filter_detail.html:84 -#: assets/templates/assets/cmd_filter_list.html:24 -#: perms/forms/database_app_permission.py:47 -msgid "System users" -msgstr "系统用户" - -#: assets/templates/assets/_asset_group_bulk_update_modal.html:23 -msgid "Select System Users" -msgstr "选择系统用户" - -#: assets/templates/assets/_asset_group_bulk_update_modal.html:34 -msgid "Enable-MFA" -msgstr "启用多因子认证" - -#: assets/templates/assets/_asset_list_modal.html:7 -#: assets/templates/assets/system_user_assets.html:26 -#: assets/templates/assets/system_user_detail.html:18 -#: assets/templates/assets/system_user_users.html:25 assets/views/asset.py:38 -#: templates/_nav.html:42 xpack/plugins/change_auth_plan/views.py:118 -msgid "Asset list" -msgstr "资产列表" - -#: assets/templates/assets/_asset_list_modal.html:33 -#: assets/templates/assets/_node_tree.html:39 -#: ops/templates/ops/command_execution_create.html:62 -#: ops/templates/ops/command_execution_create.html:112 -#: settings/templates/settings/_ldap_list_users_modal.html:41 -#: users/templates/users/_granted_assets.html:7 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:65 -msgid "Loading" -msgstr "加载中" - -#: assets/templates/assets/_asset_user_auth_update_modal.html:4 -msgid "Update asset user auth" -msgstr "更新资产用户认证信息" - -#: assets/templates/assets/_asset_user_auth_update_modal.html:23 -#: settings/templates/settings/_ldap_test_user_login_modal.html:18 -#: xpack/plugins/change_auth_plan/forms.py:61 -msgid "Please input password" -msgstr "请输入密码" - -#: assets/templates/assets/_asset_user_auth_update_modal.html:68 -#: assets/templates/assets/asset_detail.html:300 -#: users/templates/users/user_detail.html:356 -#: users/templates/users/user_detail.html:383 -#: xpack/plugins/interface/views.py:35 -msgid "Update successfully!" -msgstr "更新成功" - -#: assets/templates/assets/_asset_user_auth_view_modal.html:5 -msgid "Asset user auth" -msgstr "资产用户信息" - -#: assets/templates/assets/_asset_user_auth_view_modal.html:54 -#: assets/templates/assets/_node_detail_modal.html:56 -#: authentication/templates/authentication/login_wait_confirm.html:114 -msgid "Copy success" -msgstr "复制成功" - -#: assets/templates/assets/_asset_user_auth_view_modal.html:68 -msgid "Get auth info error" -msgstr "获取认证信息错误" - -#: assets/templates/assets/_asset_user_auth_view_modal.html:101 -#: assets/templates/assets/_node_detail_modal.html:67 -#: assets/templates/assets/_user_asset_detail_modal.html:23 -#: authentication/templates/authentication/_access_key_modal.html:155 -#: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: settings/templates/settings/_ldap_list_users_modal.html:171 -#: templates/_modal.html:22 tickets/models/ticket.py:68 -#: tickets/templates/tickets/ticket_detail.html:103 -msgid "Close" -msgstr "关闭" - -#: assets/templates/assets/_asset_user_list.html:24 -#: audits/templates/audits/operate_log_list.html:75 -#: audits/templates/audits/password_change_log_list.html:57 -#: ops/templates/ops/task_adhoc.html:61 -#: terminal/templates/terminal/command_list.html:34 -#: terminal/templates/terminal/session_commands.html:51 -#: tickets/templates/tickets/ticket_list.html:37 -msgid "Datetime" -msgstr "日期" - -#: assets/templates/assets/_asset_user_list.html:41 -#: assets/templates/assets/asset_list.html:59 -msgid "Test datetime: " -msgstr "测试日期: " - -#: assets/templates/assets/_asset_user_list.html:44 -msgid "Only latest version" -msgstr "仅最新版本" - -#: assets/templates/assets/_asset_user_list.html:66 -msgid "View" -msgstr "查看" - -#: assets/templates/assets/_asset_user_list.html:68 -#: assets/templates/assets/admin_user_assets.html:56 -#: assets/templates/assets/asset_asset_user_list.html:57 -#: assets/templates/assets/asset_detail.html:174 -#: assets/templates/assets/system_user_assets.html:74 -#: terminal/templates/terminal/base_storage_list.html:72 -msgid "Test" -msgstr "测试" - -#: assets/templates/assets/_asset_user_list.html:69 -#: assets/templates/assets/system_user_assets.html:83 -msgid "Push" -msgstr "推送" - -#: assets/templates/assets/_asset_user_list.html:167 -#: authentication/templates/authentication/_access_key_modal.html:147 -msgid "Delete success" -msgstr "删除成功" - -#: assets/templates/assets/_gateway_test_modal.html:4 -msgid "Test gateway test connection" -msgstr "测试连接网关" - -#: assets/templates/assets/_gateway_test_modal.html:10 terminal/models.py:28 -msgid "SSH Port" -msgstr "SSH端口" - -#: assets/templates/assets/_gateway_test_modal.html:13 -msgid "If use nat, set the ssh real port" -msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口" - -#: assets/templates/assets/_node_detail_modal.html:11 -#: assets/templates/assets/asset_list.html:124 -msgid "Node detail" -msgstr "节点详情" - -#: assets/templates/assets/_node_detail_modal.html:33 -msgid "Full name" -msgstr "全称" - -#: assets/templates/assets/_node_tree.html:49 -msgid "Add node" -msgstr "新建节点" - -#: assets/templates/assets/_node_tree.html:50 -msgid "Rename node" -msgstr "重命名节点" - -#: assets/templates/assets/_node_tree.html:51 -msgid "Delete node" -msgstr "删除节点" - -#: assets/templates/assets/_node_tree.html:166 -msgid "Create node failed" -msgstr "创建节点失败" - -#: assets/templates/assets/_node_tree.html:250 -msgid "Rename success" -msgstr "重命名成功" - -#: assets/templates/assets/_system_user.html:33 -#: assets/templates/assets/asset_create.html:16 -#: assets/templates/assets/gateway_create_update.html:33 -#: perms/templates/perms/asset_permission_create_update.html:48 -#: perms/templates/perms/database_app_permission_create_update.html:37 -#: perms/templates/perms/remote_app_permission_create_update.html:37 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:37 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:23 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:23 -msgid "Basic" -msgstr "基本" - -#: assets/templates/assets/_system_user.html:41 -#: assets/templates/assets/asset_create.html:38 -#: assets/templates/assets/gateway_create_update.html:41 -#: users/templates/users/_user.html:21 -msgid "Auth" -msgstr "认证" - -#: assets/templates/assets/_system_user.html:45 -msgid "Auto generate key" -msgstr "自动生成密钥" - -#: assets/templates/assets/_system_user.html:66 -#: assets/templates/assets/asset_create.html:74 -#: assets/templates/assets/gateway_create_update.html:49 -#: perms/templates/perms/asset_permission_create_update.html:97 -#: perms/templates/perms/database_app_permission_create_update.html:51 -#: perms/templates/perms/remote_app_permission_create_update.html:51 -#: terminal/templates/terminal/terminal_update.html:38 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:61 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:47 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:35 -msgid "Other" -msgstr "其它" - -#: assets/templates/assets/_user_asset_detail_modal.html:11 -#: assets/templates/assets/asset_asset_user_list.html:13 -#: assets/templates/assets/asset_detail.html:18 assets/views/asset.py:200 -msgid "Asset detail" -msgstr "资产详情" - -#: assets/templates/assets/admin_user_assets.html:16 -#: assets/templates/assets/admin_user_detail.html:16 -msgid "Assets list" -msgstr "资产列表" - -#: assets/templates/assets/admin_user_assets.html:24 -#: perms/templates/perms/asset_permission_asset.html:31 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:27 -msgid "Asset list of " -msgstr "资产列表" - -#: assets/templates/assets/admin_user_assets.html:47 -#: assets/templates/assets/system_user_assets.html:65 -#: assets/templates/assets/system_user_detail.html:124 -#: perms/templates/perms/asset_permission_detail.html:109 -#: perms/templates/perms/database_app_permission_detail.html:105 -#: perms/templates/perms/remote_app_permission_detail.html:101 -msgid "Quick update" -msgstr "快速更新" - -#: assets/templates/assets/admin_user_assets.html:53 -#: assets/templates/assets/asset_asset_user_list.html:54 -#: assets/templates/assets/asset_detail.html:171 -msgid "Test connective" -msgstr "测试可连接性" - -#: assets/templates/assets/admin_user_detail.html:78 -msgid "Replace node assets admin user with this" -msgstr "替换资产的管理员" - -#: assets/templates/assets/admin_user_detail.html:86 -#: assets/templates/assets/system_user_assets.html:126 -#: perms/templates/perms/asset_permission_asset.html:99 -#: xpack/plugins/change_auth_plan/forms.py:69 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:95 -#: xpack/plugins/gathered_user/forms.py:33 -msgid "Select nodes" -msgstr "选择节点" - -#: assets/templates/assets/admin_user_detail.html:95 -#: assets/templates/assets/asset_detail.html:200 -#: assets/templates/assets/asset_list.html:258 -#: assets/templates/assets/cmd_filter_detail.html:101 -#: assets/templates/assets/system_user_assets.html:108 -#: assets/templates/assets/system_user_assets.html:132 -#: assets/templates/assets/system_user_detail.html:172 -#: assets/templates/assets/system_user_users.html:90 -#: authentication/templates/authentication/_mfa_confirm_modal.html:20 -#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:122 -#: users/templates/users/user_detail.html:264 -#: users/templates/users/user_detail.html:417 -#: users/templates/users/user_detail.html:443 -#: users/templates/users/user_detail.html:466 -#: users/templates/users/user_detail.html:511 -#: users/templates/users/user_group_create_update.html:28 -#: users/templates/users/user_list.html:184 -#: users/templates/users/user_password_verify.html:20 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:30 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:53 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:41 -#: xpack/plugins/interface/templates/interface/interface.html:103 -#: xpack/plugins/orgs/templates/orgs/org_create_update.html:30 -msgid "Confirm" -msgstr "确认" - -#: assets/templates/assets/admin_user_list.html:4 -msgid "" -"Admin users are asset (charged server) on the root, or have NOPASSWD: ALL " -"sudo permissions users, " -msgstr "" -"管理用户是资产(被控服务器)上的 root,或拥有 NOPASSWD: ALL sudo 权限的用户," - -#: assets/templates/assets/admin_user_list.html:5 -msgid "" -"JumpServer users of the system using the user to `push system user`, `get " -"assets hardware information`, etc. " -msgstr "JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等。" - -#: assets/templates/assets/admin_user_list.html:13 -#: assets/views/admin_user.py:50 -msgid "Create admin user" -msgstr "创建管理用户" - -#: assets/templates/assets/asset_asset_user_list.html:16 -#: assets/templates/assets/asset_detail.html:21 assets/views/asset.py:55 -msgid "Asset user list" -msgstr "资产用户列表" - -#: assets/templates/assets/asset_asset_user_list.html:24 -msgid "Asset users of" -msgstr "资产用户" - -#: assets/templates/assets/asset_asset_user_list.html:47 -#: assets/templates/assets/asset_detail.html:140 -#: terminal/templates/terminal/session_detail.html:87 -#: users/templates/users/user_detail.html:126 -#: users/templates/users/user_profile.html:150 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:126 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:139 -#: xpack/plugins/license/templates/license/license_detail.html:80 -msgid "Quick modify" -msgstr "快速修改" - -#: assets/templates/assets/asset_bulk_update.html:8 -#: users/templates/users/user_bulk_update.html:8 -msgid "Select properties that need to be modified" -msgstr "选择需要修改属性" - -#: assets/templates/assets/asset_bulk_update.html:10 -#: users/templates/users/user_bulk_update.html:10 -msgid "Select all" -msgstr "全选" - -#: assets/templates/assets/asset_detail.html:88 -msgid "CPU" -msgstr "CPU" - -#: assets/templates/assets/asset_detail.html:96 -msgid "Disk" -msgstr "硬盘" - -#: assets/templates/assets/asset_detail.html:124 -#: users/templates/users/user_detail.html:101 -#: users/templates/users/user_profile.html:106 -msgid "Date joined" -msgstr "创建日期" - -#: assets/templates/assets/asset_detail.html:146 authentication/models.py:19 -#: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/base.py:51 -#: perms/templates/perms/asset_permission_create_update.html:99 -#: perms/templates/perms/asset_permission_detail.html:115 -#: perms/templates/perms/database_app_permission_create_update.html:53 -#: perms/templates/perms/database_app_permission_detail.html:111 -#: perms/templates/perms/remote_app_permission_create_update.html:53 -#: perms/templates/perms/remote_app_permission_detail.html:107 -#: terminal/templates/terminal/terminal_list.html:35 -#: users/templates/users/_select_user_modal.html:18 -#: users/templates/users/user_detail.html:132 -#: users/templates/users/user_profile.html:63 -msgid "Active" -msgstr "激活中" - -#: assets/templates/assets/asset_detail.html:163 -msgid "Refresh hardware" -msgstr "更新硬件信息" - -#: assets/templates/assets/asset_detail.html:166 -#: authentication/templates/authentication/login_wait_confirm.html:42 -msgid "Refresh" -msgstr "刷新" - -#: assets/templates/assets/asset_list.html:6 -msgid "" -"The left side is the asset tree, right click to create, delete, and change " -"the tree node, authorization asset is also organized as a node, and the " -"right side is the asset under that node" -msgstr "" -"左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的," -"右侧是属于该节点下的资产" - -#: assets/templates/assets/asset_list.html:10 assets/views/asset.py:104 -msgid "Create asset" -msgstr "创建资产" - -#: assets/templates/assets/asset_list.html:26 -msgid "Hardware" -msgstr "硬件" - -#: assets/templates/assets/asset_list.html:37 -#: users/templates/users/user_list.html:30 -msgid "Delete selected" -msgstr "批量删除" - -#: assets/templates/assets/asset_list.html:38 -#: users/templates/users/user_list.html:34 -msgid "Update selected" -msgstr "批量更新" - -#: assets/templates/assets/asset_list.html:39 -msgid "Remove from this node" -msgstr "从节点移除" - -#: assets/templates/assets/asset_list.html:40 -#: users/templates/users/user_list.html:35 -msgid "Deactive selected" -msgstr "禁用所选" - -#: assets/templates/assets/asset_list.html:41 -#: users/templates/users/user_list.html:36 -msgid "Active selected" -msgstr "激活所选" - -#: assets/templates/assets/asset_list.html:115 -msgid "Add assets to node" -msgstr "添加资产到节点" - -#: assets/templates/assets/asset_list.html:116 -msgid "Move assets to node" -msgstr "移动资产到节点" - -#: assets/templates/assets/asset_list.html:118 -msgid "Refresh node hardware info" -msgstr "更新节点资产硬件信息" - -#: assets/templates/assets/asset_list.html:119 -msgid "Test node connective" -msgstr "测试节点资产可连接性" - -#: assets/templates/assets/asset_list.html:121 -msgid "Display only current node assets" -msgstr "仅显示当前节点资产" - -#: assets/templates/assets/asset_list.html:122 -msgid "Displays all child node assets" -msgstr "显示所有子节点资产" - -#: assets/templates/assets/asset_list.html:252 -#: users/templates/users/user_detail.html:411 -#: users/templates/users/user_detail.html:437 -#: users/templates/users/user_detail.html:505 -#: users/templates/users/user_list.html:178 -#: xpack/plugins/interface/templates/interface/interface.html:97 -msgid "Are you sure?" -msgstr "你确认吗?" - -#: assets/templates/assets/asset_list.html:253 -msgid "This will delete the selected assets !!!" -msgstr "删除选择资产" - -#: assets/templates/assets/asset_list.html:256 -#: users/templates/users/user_detail.html:415 -#: users/templates/users/user_detail.html:441 -#: users/templates/users/user_detail.html:509 -#: users/templates/users/user_list.html:182 -#: xpack/plugins/interface/templates/interface/interface.html:101 -msgid "Cancel" -msgstr "取消" - -#: assets/templates/assets/asset_list.html:262 -msgid "Asset Deleting failed." -msgstr "删除失败" - -#: assets/templates/assets/asset_list.html:263 -#: assets/templates/assets/asset_list.html:272 -msgid "Asset Delete" -msgstr "删除" - -#: assets/templates/assets/asset_list.html:271 -msgid "Asset Deleted." -msgstr "已被删除" - -#: assets/templates/assets/asset_list.html:310 -msgid "Please select node" -msgstr "请选择节点" - -#: assets/templates/assets/asset_update.html:7 -msgid "Configuration" -msgstr "配置" - -#: assets/templates/assets/cmd_filter_detail.html:20 -#: assets/templates/assets/cmd_filter_list.html:23 -#: assets/templates/assets/cmd_filter_rule_list.html:18 -msgid "Rules" -msgstr "规则" - -#: assets/templates/assets/cmd_filter_detail.html:92 -msgid "Binding to system user" -msgstr "绑定到系统用户" - -#: assets/templates/assets/cmd_filter_list.html:5 -msgid "" -"System user bound some command filter, each command filter has some rules," -msgstr "系统用户可以绑定一些命令过滤器,一个过滤器可以定义一些规则" - -#: assets/templates/assets/cmd_filter_list.html:6 -msgid "When user login asset with this system user, then run a command," -msgstr "当用户使用这个系统用户登录资产,然后执行一个命令" - -#: assets/templates/assets/cmd_filter_list.html:7 -msgid "The command will be filter by rules, higher priority rule run first," -msgstr "这个命令需要被绑定过滤器的所有规则匹配,高优先级先被匹配," - -#: assets/templates/assets/cmd_filter_list.html:8 -msgid "" -"When a rule matched, if rule action is allow, then allow command execute," -msgstr "当一个规则匹配到了,如果规则的动作是允许,这个命令会被放行," - -#: assets/templates/assets/cmd_filter_list.html:9 -msgid "else if action is deny, then command with be deny," -msgstr "如果规则的动作是禁止,命令将会被禁止执行," - -#: assets/templates/assets/cmd_filter_list.html:10 -msgid "else match next rule, if none matched, allowed" -msgstr "否则就匹配下一个规则,如果最后没有匹配到规则,则允许执行" - -#: assets/templates/assets/cmd_filter_list.html:14 -#: assets/views/cmd_filter.py:49 -msgid "Create command filter" -msgstr "创建命令过滤器" - -#: assets/templates/assets/cmd_filter_rule_list.html:28 -#: assets/views/cmd_filter.py:105 -msgid "Command filter rule list" -msgstr "命令过滤器规则列表" - -#: assets/templates/assets/cmd_filter_rule_list.html:45 -msgid "Create rule" -msgstr "创建规则" - -#: assets/templates/assets/cmd_filter_rule_list.html:56 -msgid "Strategy" -msgstr "策略" - -#: assets/templates/assets/delete_confirm.html:6 -#: perms/templates/perms/delete_confirm.html:6 templates/delete_confirm.html:6 -msgid "Confirm delete" -msgstr "确认删除" - -#: assets/templates/assets/delete_confirm.html:11 -#: templates/delete_confirm.html:11 -msgid "Are you sure delete" -msgstr "您确定删除吗?" - -#: assets/templates/assets/domain_gateway_list.html:32 -msgid "Gateway list" -msgstr "网关列表" - -#: assets/templates/assets/domain_gateway_list.html:51 -#: assets/views/domain.py:137 -msgid "Create gateway" -msgstr "创建网关" - -#: assets/templates/assets/domain_gateway_list.html:94 -#: assets/templates/assets/domain_gateway_list.html:96 -#: settings/templates/settings/email_setting.html:45 -#: settings/templates/settings/ldap_setting.html:46 -msgid "Test connection" -msgstr "测试连接" - -#: assets/templates/assets/domain_gateway_list.html:136 -msgid "Can be connected" -msgstr "可连接" - -#: assets/templates/assets/domain_list.html:6 -msgid "" -"The domain function is added to address the fact that some environments " -"(such as the hybrid cloud) cannot be connected directly by jumping on the " -"gateway server." -msgstr "" -"网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过" -"网关服务器进行跳转登录。" - -#: assets/templates/assets/domain_list.html:8 -msgid "JMS => Domain gateway => Target assets" -msgstr "JMS => 网域网关 => 目标资产" - -#: assets/templates/assets/domain_list.html:13 assets/views/domain.py:49 -msgid "Create domain" -msgstr "创建网域" - -#: assets/templates/assets/label_list.html:6 assets/views/label.py:46 -msgid "Create label" -msgstr "创建标签" - -#: assets/templates/assets/platform_list.html:8 assets/views/platform.py:39 -msgid "Create platform" -msgstr "创建系统平台" - -#: assets/templates/assets/system_user_assets.html:32 -#: assets/templates/assets/system_user_detail.html:25 -#: assets/templates/assets/system_user_users.html:31 templates/_nav.html:20 -#: users/views/user.py:51 -msgid "User list" -msgstr "用户列表" - -#: assets/templates/assets/system_user_assets.html:71 -msgid "Test assets connective" -msgstr "测试资产可连接性" - -#: assets/templates/assets/system_user_assets.html:80 -msgid "Push system user now" -msgstr "立刻推送系统" - -#: assets/templates/assets/system_user_assets.html:279 -#: assets/templates/assets/system_user_users.html:205 -msgid "Have existed: " -msgstr "已经存在: " - -#: assets/templates/assets/system_user_detail.html:93 -msgid "Home" -msgstr "家目录" - -#: assets/templates/assets/system_user_detail.html:99 -msgid "Uid" -msgstr "Uid" - -#: assets/templates/assets/system_user_detail.html:163 -msgid "Binding command filters" -msgstr "绑定命令过滤器" - -#: assets/templates/assets/system_user_list.html:5 -msgid "" -"System user is JumpServer jump login assets used by the users, can be " -"understood as the user login assets, such as web, sa, the dba (` ssh " -"web@some-host `), rather than using a user the username login server jump (` " -"ssh xiaoming@some-host `); " -msgstr "" -"系统用户是 JumpServer 跳转登录资产时使用的用户,可以理解为登录资产用户,如 " -"web,sa,dba(`ssh web@some-host`),而不是使用某个用户的用户名跳转登录服务器" -"(`ssh xiaoming@some-host`);" - -#: assets/templates/assets/system_user_list.html:6 -msgid "" -"In simple terms, users log into JumpServer using their own username, and " -"JumpServer uses system users to log into assets. " -msgstr "" -"简单来说是用户使用自己的用户名登录 JumpServer,JumpServer 使用系统用户登录资" -"产。" - -#: assets/templates/assets/system_user_list.html:7 -msgid "" -"When system users are created, if you choose auto push JumpServer to use " -"Ansible push system users into the asset, if the asset (Switch) does not " -"support ansible, please manually fill in the account password." -msgstr "" -"系统用户创建时,如果选择了自动推送,JumpServer 会使用 Ansible 自动推送系统用" -"户到资产中,如果资产(交换机)不支持 Ansible,请手动填写账号密码。" - -#: assets/templates/assets/system_user_list.html:16 -#: assets/views/system_user.py:48 -msgid "Create system user" -msgstr "创建系统用户" - -#: assets/templates/assets/system_user_users.html:84 users/forms/group.py:19 -#: users/forms/user.py:143 users/forms/user.py:148 -#: xpack/plugins/orgs/forms.py:17 -msgid "Select users" -msgstr "选择用户" - -#: assets/templates/assets/system_user_users.html:118 -#: users/templates/users/user_list.html:106 -#: users/templates/users/user_list.html:110 -msgid "Remove" -msgstr "移除" - -#: assets/templates/assets/system_user_users.html:176 -msgid "Remove success" -msgstr "移除成功" - -#: assets/views/admin_user.py:31 -msgid "Admin user list" -msgstr "管理用户列表" - -#: assets/views/admin_user.py:68 -msgid "Update admin user" -msgstr "更新管理用户" - -#: assets/views/admin_user.py:85 -msgid "Admin user detail" -msgstr "管理用户详情" - -#: assets/views/admin_user.py:109 -msgid "Admin user assets" -msgstr "管理用户关联资产" - -#: assets/views/asset.py:67 templates/_nav_user.html:4 -msgid "My assets" -msgstr "我的资产" - -#: assets/views/asset.py:131 -msgid "Update asset" -msgstr "更新资产" - -#: assets/views/asset.py:143 -msgid "Bulk update asset success" -msgstr "批量更新资产成功" - -#: assets/views/asset.py:171 -msgid "Bulk update asset" -msgstr "批量更新资产" - -#: assets/views/cmd_filter.py:32 -msgid "Command filter list" -msgstr "命令过滤器列表" - -#: assets/views/cmd_filter.py:67 -msgid "Update command filter" -msgstr "更新命令过滤器" - -#: assets/views/cmd_filter.py:85 -msgid "Command filter detail" -msgstr "命令过滤器详情" - -#: assets/views/cmd_filter.py:139 -msgid "Create command filter rule" -msgstr "创建命令过滤器规则" - -#: assets/views/cmd_filter.py:174 -msgid "Update command filter rule" -msgstr "更新命令过滤器规则" - -#: assets/views/domain.py:32 templates/_nav.html:43 -msgid "Domain list" -msgstr "网域列表" - -#: assets/views/domain.py:67 -msgid "Update domain" -msgstr "更新网域" - -#: assets/views/domain.py:82 -msgid "Domain detail" -msgstr "网域详情" - -#: assets/views/domain.py:108 -msgid "Domain gateway list" -msgstr "域网关列表" - -#: assets/views/domain.py:158 -msgid "Update gateway" -msgstr "创建网关" - -#: assets/views/label.py:28 -msgid "Label list" -msgstr "标签列表" - -#: assets/views/label.py:56 -msgid "Tips: Avoid using label names reserved internally: {}" -msgstr "提示: 请避免使用内部预留标签名: {}" - -#: assets/views/label.py:74 -msgid "Update label" -msgstr "更新标签" - -#: assets/views/platform.py:23 templates/_nav.html:49 -msgid "Platform list" -msgstr "平台列表" - -#: assets/views/platform.py:59 -msgid "Update platform" -msgstr "更新系统平台" - -#: assets/views/platform.py:75 -msgid "Platform detail" -msgstr "平台详情" - -#: assets/views/system_user.py:31 -msgid "System user list" -msgstr "系统用户列表" - -#: assets/views/system_user.py:65 -msgid "Update system user" -msgstr "更新系统用户" - -#: assets/views/system_user.py:81 -msgid "System user detail" -msgstr "系统用户详情" - -#: assets/views/system_user.py:103 assets/views/system_user.py:118 -msgid "assets" -msgstr "资产管理" - -#: assets/views/system_user.py:104 -msgid "System user assets" -msgstr "系统用户关联资产" - -#: assets/views/system_user.py:119 -msgid "System user users" -msgstr "系统用户关联用户" - #: audits/models.py:19 audits/models.py:42 audits/models.py:53 -#: audits/templates/audits/ftp_log_list.html:77 -#: audits/templates/audits/operate_log_list.html:74 -#: audits/templates/audits/password_change_log_list.html:56 -#: terminal/models.py:192 terminal/templates/terminal/session_detail.html:68 -#: terminal/templates/terminal/session_list.html:28 -#: terminal/templates/terminal/session_list.html:72 -#: terminal/templates/terminal/terminal_detail.html:47 +#: terminal/models.py:192 msgid "Remote addr" msgstr "远端地址" -#: audits/models.py:22 audits/templates/audits/ftp_log_list.html:78 +#: audits/models.py:22 msgid "Operate" msgstr "操作" -#: audits/models.py:23 audits/templates/audits/ftp_log_list.html:60 -#: audits/templates/audits/ftp_log_list.html:79 +#: audits/models.py:23 msgid "Filename" msgstr "文件名" #: audits/models.py:24 audits/models.py:77 -#: audits/templates/audits/ftp_log_list.html:80 -#: ops/templates/ops/command_execution_list.html:71 -#: ops/templates/ops/task_list.html:14 #: users/templates/users/user_detail.html:487 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:14 msgid "Success" msgstr "成功" +#: audits/models.py:25 ops/models/command.py:28 perms/models/base.py:52 +#: terminal/models.py:199 xpack/plugins/change_auth_plan/models.py:176 +#: xpack/plugins/change_auth_plan/models.py:307 +#: xpack/plugins/gathered_user/models.py:76 +msgid "Date start" +msgstr "开始日期" + #: audits/models.py:33 #: authentication/templates/authentication/_access_key_modal.html:22 -#: xpack/plugins/vault/templates/vault/vault.html:7 msgid "Create" msgstr "创建" -#: audits/models.py:40 audits/templates/audits/operate_log_list.html:53 -#: audits/templates/audits/operate_log_list.html:72 +#: audits/models.py:34 templates/_csv_import_export.html:18 +#: templates/_csv_update_modal.html:6 +#: users/templates/users/user_asset_permission.html:127 +#: users/templates/users/user_database_app_permission.html:110 +#: users/templates/users/user_detail.html:12 +#: users/templates/users/user_group_detail.html:23 +#: users/templates/users/user_group_list.html:51 +#: users/templates/users/user_list.html:84 +#: users/templates/users/user_list.html:87 +#: users/templates/users/user_profile.html:181 +#: users/templates/users/user_profile.html:191 +#: users/templates/users/user_profile.html:201 +#: users/templates/users/user_remote_app_permission.html:110 +msgid "Update" +msgstr "更新" + +#: audits/models.py:35 +#: authentication/templates/authentication/_access_key_modal.html:65 +#: users/templates/users/user_asset_permission.html:128 +#: users/templates/users/user_database_app_permission.html:111 +#: users/templates/users/user_detail.html:16 +#: users/templates/users/user_group_detail.html:27 +#: users/templates/users/user_group_list.html:53 +#: users/templates/users/user_list.html:94 +#: users/templates/users/user_list.html:98 +#: users/templates/users/user_remote_app_permission.html:111 +msgid "Delete" +msgstr "删除" + +#: audits/models.py:40 msgid "Resource Type" msgstr "资源类型" -#: audits/models.py:41 audits/templates/audits/operate_log_list.html:73 +#: audits/models.py:41 msgid "Resource" msgstr "资源" -#: audits/models.py:52 audits/templates/audits/password_change_log_list.html:55 +#: audits/models.py:43 audits/models.py:54 +msgid "Datetime" +msgstr "日期" + +#: audits/models.py:52 msgid "Change by" msgstr "修改者" @@ -2526,7 +934,7 @@ msgstr "修改者" msgid "Disabled" msgstr "禁用" -#: audits/models.py:72 settings/models.py:30 +#: audits/models.py:72 settings/models.py:31 #: users/templates/users/user_detail.html:82 msgid "Enabled" msgstr "启用" @@ -2555,30 +963,22 @@ msgstr "登录城市" msgid "User agent" msgstr "Agent" -#: audits/models.py:86 audits/templates/audits/login_log_list.html:62 +#: audits/models.py:86 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/login_otp.html:6 -#: settings/forms/security.py:16 users/forms/profile.py:52 -#: users/models/user.py:462 users/templates/users/first_login.html:45 -#: users/templates/users/user_detail.html:77 +#: users/forms/profile.py:52 users/models/user.py:488 +#: users/serializers/user.py:216 users/templates/users/user_detail.html:77 #: users/templates/users/user_profile.html:87 msgid "MFA" msgstr "多因子认证" -#: audits/models.py:87 audits/templates/audits/login_log_list.html:63 -#: xpack/plugins/change_auth_plan/models.py:286 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 +#: audits/models.py:87 xpack/plugins/change_auth_plan/models.py:303 #: xpack/plugins/cloud/models.py:217 msgid "Reason" msgstr "原因" -#: audits/models.py:88 audits/templates/audits/login_log_list.html:64 -#: tickets/templates/tickets/ticket_detail.html:34 -#: tickets/templates/tickets/ticket_list.html:36 -#: tickets/templates/tickets/ticket_list.html:104 +#: audits/models.py:88 tickets/serializers/ticket.py:25 #: xpack/plugins/cloud/models.py:214 xpack/plugins/cloud/models.py:272 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:50 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48 msgid "Status" msgstr "状态" @@ -2586,89 +986,26 @@ msgstr "状态" msgid "Date login" msgstr "登录日期" -#: audits/templates/audits/ftp_log_list.html:81 -#: ops/templates/ops/adhoc_history.html:50 -#: ops/templates/ops/adhoc_history_detail.html:59 -#: ops/templates/ops/command_execution_list.html:72 -#: ops/templates/ops/task_history.html:56 perms/models/base.py:52 -#: perms/templates/perms/asset_permission_detail.html:81 -#: perms/templates/perms/database_app_permission_detail.html:77 -#: perms/templates/perms/remote_app_permission_detail.html:73 -#: terminal/models.py:199 terminal/templates/terminal/session_detail.html:72 -#: terminal/templates/terminal/session_list.html:32 -#: xpack/plugins/change_auth_plan/models.py:167 -#: xpack/plugins/change_auth_plan/models.py:290 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17 -#: xpack/plugins/gathered_user/models.py:76 -msgid "Date start" -msgstr "开始日期" +#: audits/serializers.py:61 audits/serializers.py:73 ops/models/adhoc.py:244 +msgid "Is success" +msgstr "是否成功" -#: audits/templates/audits/login_log_list.html:34 -#: perms/templates/perms/asset_permission_user.html:74 -#: perms/templates/perms/database_app_permission_user.html:74 -#: perms/templates/perms/remote_app_permission_user.html:83 -#: xpack/plugins/orgs/templates/orgs/org_users.html:67 -msgid "Select user" -msgstr "选择用户" +#: audits/serializers.py:72 ops/models/command.py:24 +#: xpack/plugins/cloud/models.py:212 +msgid "Result" +msgstr "结果" -#: audits/templates/audits/login_log_list.html:41 -#: audits/templates/audits/login_log_list.html:46 -#: audits/templates/audits/operate_log_list.html:62 -#: audits/templates/audits/password_change_log_list.html:46 -#: ops/templates/ops/command_execution_list.html:49 -#: ops/templates/ops/command_execution_list.html:54 -#: templates/_base_list.html:37 templates/_user_profile.html:23 -msgid "Search" -msgstr "搜索" +#: audits/serializers.py:74 +msgid "Hosts" +msgstr "主机" -#: audits/templates/audits/login_log_list.html:59 -msgid "UA" -msgstr "Agent" +#: audits/serializers.py:75 +msgid "Run as" +msgstr "运行用户" -#: audits/templates/audits/login_log_list.html:61 authentication/models.py:63 -msgid "City" -msgstr "城市" - -#: audits/templates/audits/login_log_list.html:65 -#: authentication/templates/authentication/_access_key_modal.html:33 -#: ops/templates/ops/task_list.html:15 -msgid "Date" -msgstr "日期" - -#: audits/templates/audits/login_log_list.html:91 -#: templates/_csv_import_export.html:8 -msgid "Export" -msgstr "导出" - -#: audits/templates/audits/operate_log_list.html:70 -msgid "Handlers" -msgstr "操作者" - -#: audits/views.py:86 audits/views.py:131 audits/views.py:168 -#: audits/views.py:213 audits/views.py:245 templates/_nav.html:146 -msgid "Audits" -msgstr "日志审计" - -#: audits/views.py:87 templates/_nav.html:150 -msgid "FTP log" -msgstr "FTP日志" - -#: audits/views.py:132 templates/_nav.html:151 -msgid "Operate log" -msgstr "操作日志" - -#: audits/views.py:169 templates/_nav.html:152 -msgid "Password change log" -msgstr "改密日志" - -#: audits/views.py:214 templates/_nav.html:149 -msgid "Login log" -msgstr "登录日志" - -#: audits/views.py:246 -msgid "Command execution log" -msgstr "命令执行" +#: authentication/api/mfa.py:60 +msgid "Code is invalid" +msgstr "Code无效" #: authentication/backends/api.py:53 msgid "Invalid signature header. No credentials provided." @@ -2704,20 +1041,20 @@ msgstr "" msgid "User disabled." msgstr "用户已禁用" -#: authentication/backends/api.py:121 +#: authentication/backends/api.py:124 msgid "Invalid token header. No credentials provided." msgstr "" -#: authentication/backends/api.py:124 +#: authentication/backends/api.py:127 msgid "Invalid token header. Sign string should not contain spaces." msgstr "" -#: authentication/backends/api.py:131 +#: authentication/backends/api.py:134 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "" -#: authentication/backends/api.py:142 +#: authentication/backends/api.py:145 msgid "Invalid token or cache refreshed." msgstr "" @@ -2798,9 +1135,17 @@ msgstr "登录复核 {}" msgid "MFA code" msgstr "多因子认证验证码" +#: authentication/models.py:19 +#: authentication/templates/authentication/_access_key_modal.html:32 +#: perms/models/base.py:51 users/templates/users/_select_user_modal.html:18 +#: users/templates/users/user_detail.html:132 +#: users/templates/users/user_profile.html:63 +msgid "Active" +msgstr "激活中" + #: authentication/models.py:39 msgid "Private Token" -msgstr "ssh密钥" +msgstr "SSH密钥" #: authentication/models.py:44 users/templates/users/user_detail.html:258 msgid "Reviewers" @@ -2811,6 +1156,10 @@ msgstr "审批人" msgid "Login confirm" msgstr "登录复核" +#: authentication/models.py:63 +msgid "City" +msgstr "城市" + #: authentication/templates/authentication/_access_key_modal.html:6 msgid "API key list" msgstr "API Key列表" @@ -2827,13 +1176,18 @@ msgstr "文档" msgid "Secret" msgstr "密文" +#: authentication/templates/authentication/_access_key_modal.html:33 +msgid "Date" +msgstr "日期" + #: authentication/templates/authentication/_access_key_modal.html:48 #: users/templates/users/_granted_assets.html:75 msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: users/models/user.py:360 users/templates/users/user_profile.html:94 +#: users/models/user.py:386 users/serializers/user.py:213 +#: users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166 #: users/templates/users/user_verify_mfa.html:32 @@ -2841,11 +1195,22 @@ msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:361 users/templates/users/user_profile.html:92 +#: users/models/user.py:387 users/serializers/user.py:214 +#: users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:170 msgid "Enable" msgstr "启用" +#: authentication/templates/authentication/_access_key_modal.html:147 +msgid "Delete success" +msgstr "删除成功" + +#: authentication/templates/authentication/_access_key_modal.html:155 +#: authentication/templates/authentication/_mfa_confirm_modal.html:53 +#: templates/_modal.html:22 tickets/models/ticket.py:68 +msgid "Close" +msgstr "关闭" + #: authentication/templates/authentication/_mfa_confirm_modal.html:5 msgid "MFA confirm" msgstr "多因子认证校验" @@ -2854,6 +1219,18 @@ msgstr "多因子认证校验" msgid "Need MFA for view auth" msgstr "需要多因子认证来查看账号信息" +#: authentication/templates/authentication/_mfa_confirm_modal.html:20 +#: templates/_modal.html:23 users/templates/users/user_detail.html:264 +#: users/templates/users/user_detail.html:417 +#: users/templates/users/user_detail.html:443 +#: users/templates/users/user_detail.html:466 +#: users/templates/users/user_detail.html:511 +#: users/templates/users/user_group_create_update.html:28 +#: users/templates/users/user_list.html:184 +#: users/templates/users/user_password_verify.html:20 +msgid "Confirm" +msgstr "确认" + #: authentication/templates/authentication/_mfa_confirm_modal.html:25 msgid "Code error" msgstr "代码错误" @@ -2861,7 +1238,7 @@ msgstr "代码错误" #: authentication/templates/authentication/login.html:6 #: authentication/templates/authentication/login.html:39 #: authentication/templates/authentication/xpack_login.html:112 -#: templates/_base_only_msg_content.html:52 templates/_header_bar.html:83 +#: templates/_base_only_msg_content.html:51 templates/_header_bar.html:83 msgid "Login" msgstr "登录" @@ -2894,7 +1271,6 @@ msgid "Open Google Authenticator and enter the 6-bit dynamic code" msgstr "请打开 Google Authenticator,输入6位动态码" #: authentication/templates/authentication/login_otp.html:26 -#: users/templates/users/first_login.html:100 #: users/templates/users/user_otp_check_password.html:15 #: users/templates/users/user_otp_enable_bind.html:24 #: users/templates/users/user_otp_enable_install_app.html:29 @@ -2906,15 +1282,23 @@ msgstr "下一步" msgid "Can't provide security? Please contact the administrator!" msgstr "如果不能提供多因子认证验证码,请联系管理员!" -#: authentication/templates/authentication/login_wait_confirm.html:47 +#: authentication/templates/authentication/login_wait_confirm.html:41 +msgid "Refresh" +msgstr "刷新" + +#: authentication/templates/authentication/login_wait_confirm.html:46 msgid "Copy link" msgstr "复制链接" -#: authentication/templates/authentication/login_wait_confirm.html:52 +#: authentication/templates/authentication/login_wait_confirm.html:51 #: templates/flash_message_standalone.html:34 msgid "Return" msgstr "返回" +#: authentication/templates/authentication/login_wait_confirm.html:113 +msgid "Copy success" +msgstr "复制成功" + #: authentication/templates/authentication/xpack_login.html:74 msgid "Welcome back, please enter username and password to login" msgstr "欢迎回来,请输入用户名和密码登录" @@ -2943,12 +1327,12 @@ msgstr "退出登录成功" msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: common/const.py:6 +#: common/const/__init__.py:6 #, python-format msgid "%(name)s was created successfully" msgstr "%(name)s 创建成功" -#: common/const.py:7 +#: common/const/__init__.py:7 #, python-format msgid "%(name)s was updated successfully" msgstr "%(name)s 更新成功" @@ -2961,31 +1345,31 @@ msgstr "不是合法json" msgid "Not a string type" msgstr "不是字符类型" -#: common/fields/model.py:79 +#: common/fields/model.py:80 msgid "Marshal dict data to char field" msgstr "" -#: common/fields/model.py:83 +#: common/fields/model.py:84 msgid "Marshal dict data to text field" msgstr "" -#: common/fields/model.py:95 +#: common/fields/model.py:96 msgid "Marshal list data to char field" msgstr "" -#: common/fields/model.py:99 +#: common/fields/model.py:100 msgid "Marshal list data to text field" msgstr "" -#: common/fields/model.py:103 +#: common/fields/model.py:104 msgid "Marshal data to char field" msgstr "" -#: common/fields/model.py:107 +#: common/fields/model.py:108 msgid "Marshal data to text field" msgstr "" -#: common/fields/model.py:133 +#: common/fields/model.py:157 msgid "Encrypt field using Secret Key" msgstr "" @@ -3013,11 +1397,11 @@ msgstr "字段必须唯一" msgid "

Flow service unavailable, check it

" msgstr "" -#: jumpserver/views/index.py:23 templates/_nav.html:7 +#: jumpserver/views/index.py:26 templates/_nav.html:7 msgid "Dashboard" msgstr "仪表盘" -#: jumpserver/views/other.py:25 +#: jumpserver/views/other.py:26 msgid "" "
Luna is a separately deployed program, you need to deploy Luna, koko, " "configure nginx for url distribution,
If you see this page, " @@ -3026,11 +1410,11 @@ msgstr "" "
Luna是单独部署的一个程序,你需要部署luna,koko,
如果你看到了" "这个页面,证明你访问的不是nginx监听的端口,祝你好运
" -#: jumpserver/views/other.py:65 +#: jumpserver/views/other.py:76 msgid "Websocket server run on port: {}, you should proxy it on nginx" msgstr "Websocket 服务运行在端口: {}, 请检查nginx是否代理是否设置" -#: jumpserver/views/other.py:73 +#: jumpserver/views/other.py:90 msgid "" "
Koko is a separately deployed program, you need to deploy Koko, " "configure nginx for url distribution,
If you see this page, " @@ -3049,28 +1433,22 @@ msgid "Not has host {} permission" msgstr "没有该主机 {} 权限" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:98 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:98 msgid "Cycle perform" msgstr "周期执行" #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:111 ops/mixin.py:150 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:90 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:90 msgid "Regularly perform" msgstr "定期执行" #: ops/mixin.py:108 ops/mixin.py:147 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:54 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:79 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:40 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:79 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:28 +#: xpack/plugins/change_auth_plan/serializers.py:53 msgid "Periodic perform" msgstr "定时执行" +#: ops/mixin.py:113 +msgid "Interval" +msgstr "间隔" + #: ops/mixin.py:122 msgid "* Please enter a valid crontab expression" msgstr "* 请输入有效的 crontab 表达式" @@ -3102,16 +1480,15 @@ msgstr "提示:(单位: 时)" msgid "Callback" msgstr "回调" -#: ops/models/adhoc.py:143 ops/templates/ops/adhoc_detail.html:112 +#: ops/models/adhoc.py:143 msgid "Tasks" msgstr "任务" -#: ops/models/adhoc.py:144 ops/templates/ops/adhoc_detail.html:55 -#: ops/templates/ops/task_adhoc.html:58 +#: ops/models/adhoc.py:144 msgid "Pattern" msgstr "模式" -#: ops/models/adhoc.py:145 ops/templates/ops/adhoc_detail.html:59 +#: ops/models/adhoc.py:145 msgid "Options" msgstr "选项" @@ -3119,86 +1496,70 @@ msgstr "选项" msgid "Run as admin" msgstr "再次执行" -#: ops/models/adhoc.py:149 ops/templates/ops/adhoc_detail.html:80 -#: ops/templates/ops/task_adhoc.html:60 +#: ops/models/adhoc.py:149 msgid "Become" msgstr "Become" #: ops/models/adhoc.py:150 users/templates/users/user_group_detail.html:54 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:59 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:55 msgid "Create by" msgstr "创建者" -#: ops/models/adhoc.py:233 +#: ops/models/adhoc.py:237 msgid "Task display" msgstr "任务展示" -#: ops/models/adhoc.py:234 +#: ops/models/adhoc.py:238 msgid "Host amount" msgstr "主机数量" -#: ops/models/adhoc.py:236 +#: ops/models/adhoc.py:240 msgid "Start time" msgstr "开始时间" -#: ops/models/adhoc.py:237 +#: ops/models/adhoc.py:241 msgid "End time" msgstr "完成时间" -#: ops/models/adhoc.py:238 ops/templates/ops/adhoc_history.html:55 -#: ops/templates/ops/task_history.html:61 ops/templates/ops/task_list.html:16 -#: xpack/plugins/change_auth_plan/models.py:170 -#: xpack/plugins/change_auth_plan/models.py:293 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16 +#: ops/models/adhoc.py:242 xpack/plugins/change_auth_plan/models.py:179 +#: xpack/plugins/change_auth_plan/models.py:310 #: xpack/plugins/gathered_user/models.py:79 msgid "Time" msgstr "时间" -#: ops/models/adhoc.py:239 ops/templates/ops/adhoc_detail.html:104 -#: ops/templates/ops/adhoc_history.html:53 -#: ops/templates/ops/adhoc_history_detail.html:67 -#: ops/templates/ops/task_detail.html:82 ops/templates/ops/task_history.html:59 +#: ops/models/adhoc.py:243 ops/models/command.py:26 +#: terminal/serializers/session.py:30 msgid "Is finished" msgstr "是否完成" -#: ops/models/adhoc.py:240 ops/templates/ops/adhoc_history.html:54 -#: ops/templates/ops/task_history.html:60 -msgid "Is success" -msgstr "是否成功" - -#: ops/models/adhoc.py:241 +#: ops/models/adhoc.py:245 msgid "Adhoc raw result" msgstr "结果" -#: ops/models/adhoc.py:242 +#: ops/models/adhoc.py:246 msgid "Adhoc result summary" msgstr "汇总" -#: ops/models/adhoc.py:282 xpack/plugins/change_auth_plan/utils.py:137 +#: ops/models/adhoc.py:286 xpack/plugins/change_auth_plan/utils.py:137 msgid "{} Start task: {}" msgstr "{} 任务开始: {}" -#: ops/models/adhoc.py:291 xpack/plugins/change_auth_plan/utils.py:149 +#: ops/models/adhoc.py:295 xpack/plugins/change_auth_plan/utils.py:149 msgid "{} Task finish" msgstr "{} 任务结束" -#: ops/models/command.py:24 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:56 -#: xpack/plugins/cloud/models.py:212 -msgid "Result" -msgstr "结果" +#: ops/models/command.py:29 +msgid "Date finished" +msgstr "结束日期" -#: ops/models/command.py:64 +#: ops/models/command.py:72 msgid "Task start" msgstr "任务开始" -#: ops/models/command.py:86 +#: ops/models/command.py:94 msgid "Command `{}` is forbidden ........" msgstr "命令 `{}` 不允许被执行 ......." -#: ops/models/command.py:93 +#: ops/models/command.py:101 msgid "Task end" msgstr "任务结束" @@ -3210,210 +1571,10 @@ msgstr "定期清除任务历史" msgid "Clean celery log period" msgstr "定期清除Celery日志" -#: ops/templates/ops/adhoc_detail.html:17 -#: ops/templates/ops/adhoc_history.html:17 -msgid "Version detail" -msgstr "版本详情" - -#: ops/templates/ops/adhoc_detail.html:20 -#: ops/templates/ops/adhoc_history.html:20 ops/views/adhoc.py:106 -msgid "Version run execution" -msgstr "执行历史" - -#: ops/templates/ops/adhoc_detail.html:51 -#: ops/templates/ops/command_execution_list.html:65 -#: ops/templates/ops/task_adhoc.html:57 ops/templates/ops/task_list.html:13 -#: terminal/forms/storage.py:151 -msgid "Hosts" -msgstr "主机" - -#: ops/templates/ops/adhoc_detail.html:70 -#: ops/templates/ops/adhoc_detail.html:75 -#: ops/templates/ops/command_execution_list.html:68 -#: ops/templates/ops/task_adhoc.html:59 -msgid "Run as" -msgstr "运行用户" - -#: ops/templates/ops/adhoc_detail.html:92 ops/templates/ops/task_list.html:12 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:18 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:19 -msgid "Run times" -msgstr "执行次数" - -#: ops/templates/ops/adhoc_detail.html:96 ops/templates/ops/task_detail.html:74 -msgid "Last run" -msgstr "最后运行" - -#: ops/templates/ops/adhoc_detail.html:100 -#: ops/templates/ops/adhoc_history_detail.html:63 -#: ops/templates/ops/task_detail.html:78 -msgid "Time delta" -msgstr "运行时间" - -#: ops/templates/ops/adhoc_detail.html:108 -#: ops/templates/ops/adhoc_history_detail.html:71 -#: ops/templates/ops/task_detail.html:92 -msgid "Is success " -msgstr "成功" - -#: ops/templates/ops/adhoc_detail.html:129 -#: ops/templates/ops/task_detail.html:119 -msgid "Last run failed hosts" -msgstr "最后运行失败主机" - -#: ops/templates/ops/adhoc_detail.html:149 -#: ops/templates/ops/adhoc_detail.html:174 -#: ops/templates/ops/task_detail.html:139 -#: ops/templates/ops/task_detail.html:164 -msgid "No hosts" -msgstr "没有主机" - -#: ops/templates/ops/adhoc_detail.html:159 -#: ops/templates/ops/task_detail.html:149 -msgid "Last run success hosts" -msgstr "最后运行成功主机" - -#: ops/templates/ops/adhoc_history.html:28 -#: ops/templates/ops/task_history.html:34 -msgid "Executions of " -msgstr "执行历史 " - -#: ops/templates/ops/adhoc_history.html:51 -#: ops/templates/ops/task_history.html:57 -msgid "F/S/T" -msgstr "失败/成功/总" - -#: ops/templates/ops/adhoc_history.html:52 -#: ops/templates/ops/task_history.html:58 -msgid "Ratio" -msgstr "比例" - -#: ops/templates/ops/adhoc_history_detail.html:17 ops/views/adhoc.py:120 -msgid "Execution detail" -msgstr "执行历史详情" - -#: ops/templates/ops/adhoc_history_detail.html:20 -#: ops/templates/ops/command_execution_list.html:69 -#: terminal/backends/command/models.py:22 -msgid "Output" -msgstr "输出" - -#: ops/templates/ops/adhoc_history_detail.html:28 -msgid "Execution detail of" -msgstr "执行历史详情" - -#: ops/templates/ops/adhoc_history_detail.html:51 -msgid "Task name" -msgstr "任务名称" - -#: ops/templates/ops/adhoc_history_detail.html:82 -msgid "Failed assets" -msgstr "失败资产" - -#: ops/templates/ops/adhoc_history_detail.html:102 -#: ops/templates/ops/adhoc_history_detail.html:127 -msgid "No assets" -msgstr "没有资产" - -#: ops/templates/ops/adhoc_history_detail.html:112 -msgid "Success assets" -msgstr "成功资产" - #: ops/templates/ops/celery_task_log.html:4 msgid "Task log" msgstr "任务列表" -#: ops/templates/ops/command_execution_create.html:93 -#: terminal/templates/terminal/session_detail.html:97 -#: terminal/templates/terminal/session_detail.html:114 -msgid "Go" -msgstr "" - -#: ops/templates/ops/command_execution_create.html:159 -msgid "Asset configuration does not include the SSH protocol" -msgstr "资产配置不包含 SSH 协议" - -#: ops/templates/ops/command_execution_create.html:183 -msgid "Selected assets" -msgstr "已选择资产" - -#: ops/templates/ops/command_execution_create.html:186 -msgid "In total" -msgstr "总共" - -#: ops/templates/ops/command_execution_create.html:223 -msgid "" -"Select the left asset, select the running system user, execute command in " -"batch" -msgstr "选择左侧资产, 选择运行的系统用户,批量执行命令" - -#: ops/templates/ops/command_execution_create.html:267 -msgid "Unselected assets" -msgstr "没有选中资产" - -#: ops/templates/ops/command_execution_create.html:271 -msgid "No input command" -msgstr "没有输入命令" - -#: ops/templates/ops/command_execution_create.html:275 -msgid "No system user was selected" -msgstr "没有选择系统用户" - -#: ops/templates/ops/command_execution_create.html:285 -msgid "Pending" -msgstr "等待" - -#: ops/templates/ops/command_execution_list.html:70 -#: xpack/plugins/change_auth_plan/models.py:257 -msgid "Finished" -msgstr "结束" - -#: ops/templates/ops/task_adhoc.html:17 ops/templates/ops/task_detail.html:18 -#: ops/templates/ops/task_history.html:17 ops/views/adhoc.py:50 -#: ops/views/adhoc.py:92 -msgid "Task detail" -msgstr "任务详情" - -#: ops/templates/ops/task_adhoc.html:20 ops/templates/ops/task_detail.html:21 -#: ops/templates/ops/task_history.html:20 ops/views/adhoc.py:64 -msgid "Task versions" -msgstr "任务各版本" - -#: ops/templates/ops/task_adhoc.html:23 ops/templates/ops/task_detail.html:24 -#: ops/templates/ops/task_history.html:23 -msgid "Execution" -msgstr "执行历史" - -#: ops/templates/ops/task_adhoc.html:26 ops/templates/ops/task_detail.html:27 -#: ops/templates/ops/task_history.html:26 -msgid "Last execution output" -msgstr "最后执行输出" - -#: ops/templates/ops/task_adhoc.html:34 -msgid "Versions of " -msgstr "版本" - -#: ops/templates/ops/task_detail.html:66 -msgid "Total versions" -msgstr "版本数量" - -#: ops/templates/ops/task_detail.html:102 -msgid "Contents" -msgstr "内容" - -#: ops/templates/ops/task_list.html:73 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:135 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:54 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:148 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:58 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:44 -msgid "Run" -msgstr "执行" - -#: ops/templates/ops/task_list.html:114 -msgid "Task start: " -msgstr "任务开始: " - #: ops/utils.py:60 msgid "Update task content: {}" msgstr "更新任务内容: {}" @@ -3422,29 +1583,6 @@ msgstr "更新任务内容: {}" msgid "Disk used more than 80%: {} => {}" msgstr "磁盘使用率超过 80%: {} => {}" -#: ops/views/adhoc.py:31 ops/views/adhoc.py:49 ops/views/adhoc.py:63 -#: ops/views/adhoc.py:77 ops/views/adhoc.py:91 ops/views/adhoc.py:105 -#: ops/views/adhoc.py:119 ops/views/command.py:48 ops/views/command.py:79 -msgid "Ops" -msgstr "作业中心" - -#: ops/views/adhoc.py:32 templates/_nav.html:124 -#: xpack/plugins/gathered_user/views.py:35 -msgid "Task list" -msgstr "任务列表" - -#: ops/views/adhoc.py:78 -msgid "Task execution list" -msgstr "任务执行列表" - -#: ops/views/command.py:49 -msgid "Command execution list" -msgstr "命令执行列表" - -#: ops/views/command.py:80 templates/_nav_user.html:31 -msgid "Command execution" -msgstr "命令执行" - #: orgs/mixins/models.py:56 orgs/mixins/serializers.py:26 orgs/models.py:31 msgid "Organization" msgstr "组织" @@ -3465,13 +1603,8 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: perms/forms/asset_permission.py:86 perms/forms/database_app_permission.py:41 #: perms/forms/remote_app_permission.py:43 perms/models/base.py:50 -#: perms/templates/perms/asset_permission_list.html:34 -#: perms/templates/perms/asset_permission_list.html:84 -#: perms/templates/perms/asset_permission_list.html:186 -#: perms/templates/perms/database_app_permission_list.html:16 -#: perms/templates/perms/remote_app_permission_list.html:16 #: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31 -#: users/models/user.py:446 users/templates/users/_select_user_modal.html:16 +#: users/models/user.py:472 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 #: users/templates/users/user_database_app_permission.html:38 @@ -3480,7 +1613,6 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: users/templates/users/user_list.html:17 #: users/templates/users/user_remote_app_permission.html:38 #: users/templates/users/user_remote_app_permission.html:61 -#: xpack/plugins/orgs/templates/orgs/org_list.html:16 msgid "User group" msgstr "用户组" @@ -3492,10 +1624,18 @@ msgstr "用户和用户组至少选一个" msgid "Asset or group at least one required" msgstr "资产和节点至少选一个" -#: perms/models/asset_permission.py:31 settings/forms/terminal.py:19 +#: perms/forms/database_app_permission.py:47 +msgid "System users" +msgstr "系统用户" + +#: perms/models/asset_permission.py:31 settings/serializers/settings.py:56 msgid "All" msgstr "全部" +#: perms/models/asset_permission.py:32 +msgid "Connect" +msgstr "连接" + #: perms/models/asset_permission.py:33 msgid "Upload file" msgstr "上传文件" @@ -3513,330 +1653,26 @@ msgid "Actions" msgstr "动作" #: perms/models/asset_permission.py:87 templates/_nav.html:78 -#: tickets/templates/tickets/ticket_list.html:22 #: users/templates/users/_user_detail_nav_header.html:31 -#: users/views/user.py:221 msgid "Asset permission" msgstr "资产授权" -#: perms/models/base.py:53 -#: perms/templates/perms/asset_permission_detail.html:85 -#: perms/templates/perms/database_app_permission_detail.html:81 -#: perms/templates/perms/remote_app_permission_detail.html:77 -#: users/models/user.py:478 users/templates/users/user_detail.html:93 +#: perms/models/base.py:53 users/models/user.py:504 +#: users/templates/users/user_detail.html:93 #: users/templates/users/user_profile.html:120 msgid "Date expired" msgstr "失效日期" -#: perms/models/database_app_permission.py:26 +#: perms/models/database_app_permission.py:27 #: users/templates/users/_user_detail_nav_header.html:61 -#: users/views/user.py:277 msgid "DatabaseApp permission" msgstr "数据库应用授权" #: perms/models/remote_app_permission.py:20 #: users/templates/users/_user_detail_nav_header.html:47 -#: users/views/user.py:249 msgid "RemoteApp permission" msgstr "远程应用授权" -#: perms/templates/perms/asset_permission_asset.html:18 -#: perms/templates/perms/asset_permission_detail.html:17 -#: perms/templates/perms/asset_permission_user.html:18 -#: perms/templates/perms/database_app_permission_database_app.html:18 -#: perms/templates/perms/database_app_permission_detail.html:17 -#: perms/templates/perms/database_app_permission_user.html:18 -#: perms/templates/perms/remote_app_permission_detail.html:17 -#: perms/templates/perms/remote_app_permission_remote_app.html:17 -#: perms/templates/perms/remote_app_permission_user.html:17 -msgid "Users and user groups" -msgstr "用户或用户组" - -#: perms/templates/perms/asset_permission_asset.html:23 -#: perms/templates/perms/asset_permission_detail.html:22 -#: perms/templates/perms/asset_permission_user.html:23 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:16 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:21 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:20 -msgid "Assets and node" -msgstr "资产或节点" - -#: perms/templates/perms/asset_permission_asset.html:66 -msgid "Add asset to this permission" -msgstr "添加资产" - -#: perms/templates/perms/asset_permission_asset.html:80 -#: perms/templates/perms/asset_permission_asset.html:141 -#: perms/templates/perms/asset_permission_user.html:80 -#: perms/templates/perms/asset_permission_user.html:108 -#: perms/templates/perms/database_app_permission_database_app.html:83 -#: perms/templates/perms/database_app_permission_database_app.html:111 -#: perms/templates/perms/database_app_permission_user.html:80 -#: perms/templates/perms/database_app_permission_user.html:108 -#: perms/templates/perms/remote_app_permission_detail.html:143 -#: perms/templates/perms/remote_app_permission_remote_app.html:92 -#: perms/templates/perms/remote_app_permission_user.html:92 -#: perms/templates/perms/remote_app_permission_user.html:120 -#: users/templates/users/user_group_detail.html:87 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:76 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:89 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:123 -#: xpack/plugins/orgs/templates/orgs/org_users.html:73 -msgid "Add" -msgstr "添加" - -#: perms/templates/perms/asset_permission_asset.html:91 -msgid "Add node to this permission" -msgstr "添加节点" - -#: perms/templates/perms/asset_permission_asset.html:105 -#: users/templates/users/user_detail.html:226 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:101 -msgid "Join" -msgstr "加入" - -#: perms/templates/perms/asset_permission_asset.html:132 -#: perms/templates/perms/database_app_permission_database_app.html:102 -#: perms/templates/perms/remote_app_permission_detail.html:134 -msgid "Select system users" -msgstr "选择系统用户" - -#: perms/templates/perms/asset_permission_create_update.html:105 -#: perms/templates/perms/database_app_permission_create_update.html:59 -#: perms/templates/perms/remote_app_permission_create_update.html:59 -msgid "Validity period" -msgstr "有效期" - -#: perms/templates/perms/asset_permission_detail.html:61 -#: perms/templates/perms/database_app_permission_detail.html:61 -#: perms/templates/perms/remote_app_permission_detail.html:61 -msgid "User count" -msgstr "用户数量" - -#: perms/templates/perms/asset_permission_detail.html:65 -#: perms/templates/perms/database_app_permission_detail.html:65 -#: perms/templates/perms/remote_app_permission_detail.html:65 -msgid "User group count" -msgstr "用户组数量" - -#: perms/templates/perms/asset_permission_detail.html:69 -#: xpack/plugins/license/templates/license/license_detail.html:63 -msgid "Asset count" -msgstr "资产数量" - -#: perms/templates/perms/asset_permission_detail.html:73 -msgid "Node count" -msgstr "节点数量" - -#: perms/templates/perms/asset_permission_detail.html:77 -#: perms/templates/perms/database_app_permission_detail.html:73 -msgid "System user count" -msgstr "系统用户数量" - -#: perms/templates/perms/asset_permission_list.html:21 -#: perms/templates/perms/database_app_permission_list.html:6 -#: perms/templates/perms/remote_app_permission_list.html:6 -msgid "Create permission" -msgstr "创建授权规则" - -#: perms/templates/perms/asset_permission_list.html:25 -msgid "Refresh permission cache" -msgstr "刷新授权缓存" - -#: perms/templates/perms/asset_permission_list.html:38 -#: perms/templates/perms/asset_permission_list.html:184 -#: perms/templates/perms/database_app_permission_list.html:19 -#: perms/templates/perms/remote_app_permission_list.html:19 -#: users/templates/users/user_asset_permission.html:43 -#: users/templates/users/user_asset_permission.html:155 -#: users/templates/users/user_database_app_permission.html:41 -#: users/templates/users/user_list.html:19 -#: users/templates/users/user_remote_app_permission.html:41 -#: xpack/plugins/cloud/models.py:50 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:55 -#: xpack/plugins/cloud/templates/cloud/account_list.html:14 -msgid "Validity" -msgstr "有效" - -#: perms/templates/perms/asset_permission_list.html:191 -#: users/templates/users/user_asset_permission.html:160 -msgid "Inherit" -msgstr "继承" - -#: perms/templates/perms/asset_permission_list.html:192 -#: users/templates/users/user_asset_permission.html:161 -msgid "Include" -msgstr "包含" - -#: perms/templates/perms/asset_permission_list.html:193 -#: users/templates/users/user_asset_permission.html:162 -msgid "Exclude" -msgstr "不包含" - -#: perms/templates/perms/asset_permission_list.html:211 -msgid "Refresh success" -msgstr "刷新成功" - -#: perms/templates/perms/asset_permission_user.html:31 -#: perms/templates/perms/database_app_permission_user.html:31 -#: perms/templates/perms/remote_app_permission_user.html:30 -#: xpack/plugins/orgs/templates/orgs/org_users.html:24 -msgid "User list of " -msgstr "用户列表" - -#: perms/templates/perms/asset_permission_user.html:66 -msgid "Add user to asset permission" -msgstr "添加用户" - -#: perms/templates/perms/asset_permission_user.html:91 -msgid "Add user group to asset permission" -msgstr "添加用户组" - -#: perms/templates/perms/asset_permission_user.html:99 -#: perms/templates/perms/database_app_permission_user.html:99 -#: perms/templates/perms/remote_app_permission_user.html:111 -msgid "Select user groups" -msgstr "选择用户组" - -#: perms/templates/perms/database_app_permission_database_app.html:31 -msgid "DatabaseApp list of " -msgstr "数据库应用列表" - -#: perms/templates/perms/database_app_permission_database_app.html:66 -msgid "Add DatabaseApp to this permission" -msgstr "添加数据库应用" - -#: perms/templates/perms/database_app_permission_database_app.html:74 -msgid "Select DatabaseApp" -msgstr "选择数据库应用" - -#: perms/templates/perms/database_app_permission_detail.html:69 -msgid "DatabaseApp count" -msgstr "数据库应用数量" - -#: perms/templates/perms/database_app_permission_user.html:66 -msgid "Add user to permission" -msgstr "添加用户" - -#: perms/templates/perms/database_app_permission_user.html:91 -msgid "Add user group to permission" -msgstr "添加用户组" - -#: perms/templates/perms/remote_app_permission_detail.html:69 -msgid "RemoteApp count" -msgstr "远程应用数量" - -#: perms/templates/perms/remote_app_permission_remote_app.html:30 -msgid "RemoteApp list of " -msgstr "远程应用列表" - -#: perms/templates/perms/remote_app_permission_remote_app.html:75 -msgid "Add RemoteApp to this permission" -msgstr "添加远程应用" - -#: perms/templates/perms/remote_app_permission_remote_app.html:83 -msgid "Select RemoteApp" -msgstr "选择远程应用" - -#: perms/templates/perms/remote_app_permission_user.html:75 -msgid "Add user to this permission" -msgstr "添加用户" - -#: perms/templates/perms/remote_app_permission_user.html:103 -msgid "Add user group to this permission" -msgstr "添加用户组" - -#: perms/views/asset_permission.py:33 perms/views/asset_permission.py:65 -#: perms/views/asset_permission.py:82 perms/views/asset_permission.py:99 -#: perms/views/asset_permission.py:136 perms/views/asset_permission.py:169 -#: perms/views/database_app_permission.py:33 -#: perms/views/database_app_permission.py:48 -#: perms/views/database_app_permission.py:64 -#: perms/views/database_app_permission.py:79 -#: perms/views/database_app_permission.py:108 -#: perms/views/database_app_permission.py:143 -#: perms/views/remote_app_permission.py:33 -#: perms/views/remote_app_permission.py:49 -#: perms/views/remote_app_permission.py:66 -#: perms/views/remote_app_permission.py:84 -#: perms/views/remote_app_permission.py:116 -#: perms/views/remote_app_permission.py:149 templates/_nav.html:75 -#: xpack/plugins/orgs/templates/orgs/org_list.html:22 -msgid "Perms" -msgstr "权限管理" - -#: perms/views/asset_permission.py:34 -msgid "Asset permission list" -msgstr "资产授权列表" - -#: perms/views/asset_permission.py:66 -msgid "Create asset permission" -msgstr "创建权限规则" - -#: perms/views/asset_permission.py:83 -msgid "Update asset permission" -msgstr "更新资产授权" - -#: perms/views/asset_permission.py:100 -msgid "Asset permission detail" -msgstr "资产授权详情" - -#: perms/views/asset_permission.py:137 -msgid "Asset permission user list" -msgstr "资产授权用户列表" - -#: perms/views/asset_permission.py:171 -msgid "Asset permission asset list" -msgstr "资产授权资产列表" - -#: perms/views/database_app_permission.py:34 -msgid "DatabaseApp permission list" -msgstr "数据库应用授权列表" - -#: perms/views/database_app_permission.py:49 -msgid "Create DatabaseApp permission" -msgstr "创建数据库应用授权规则" - -#: perms/views/database_app_permission.py:65 -msgid "Update DatabaseApp permission" -msgstr "更新数据库应用授权规则" - -#: perms/views/database_app_permission.py:80 -msgid "DatabaseApp permission detail" -msgstr "数据库应用授权详情" - -#: perms/views/database_app_permission.py:109 -msgid "DatabaseApp permission user list" -msgstr "数据库应用授权用户列表" - -#: perms/views/database_app_permission.py:149 -msgid "DatabaseApp permission DatabaseApp list" -msgstr "数据库应用授权数据库应用列表" - -#: perms/views/remote_app_permission.py:34 -msgid "RemoteApp permission list" -msgstr "远程应用授权列表" - -#: perms/views/remote_app_permission.py:50 -msgid "Create RemoteApp permission" -msgstr "创建远程应用授权规则" - -#: perms/views/remote_app_permission.py:67 -msgid "Update RemoteApp permission" -msgstr "更新远程应用授权规则" - -#: perms/views/remote_app_permission.py:85 -msgid "RemoteApp permission detail" -msgstr "远程应用授权详情" - -#: perms/views/remote_app_permission.py:117 -msgid "RemoteApp permission user list" -msgstr "远程应用授权用户列表" - -#: perms/views/remote_app_permission.py:150 -msgid "RemoteApp permission RemoteApp list" -msgstr "远程应用授权远程应用列表" - #: settings/api.py:33 msgid "Test mail sent to {}, please check" msgstr "邮件已经发送{}, 请检查" @@ -3849,411 +1685,14 @@ msgstr "获取 LDAP 用户为 None" msgid "Imported {} users successfully" msgstr "导入 {} 个用户成功" -#: settings/forms/basic.py:13 -msgid "Current SITE URL" -msgstr "当前站点URL" - -#: settings/forms/basic.py:17 -msgid "User Guide URL" -msgstr "用户向导URL" - -#: settings/forms/basic.py:18 -msgid "User first login update profile done redirect to it" -msgstr "用户第一次登录,修改profile后重定向到地址" - -#: settings/forms/basic.py:21 -msgid "Email Subject Prefix" -msgstr "Email主题前缀" - -#: settings/forms/basic.py:22 -msgid "Tips: Some word will be intercept by mail provider" -msgstr "提示: 一些关键字可能会被邮件提供商拦截,如 跳板机、JumpServer" - -#: settings/forms/email.py:15 -msgid "SMTP host" -msgstr "SMTP主机" - -#: settings/forms/email.py:17 -msgid "SMTP port" -msgstr "SMTP端口" - -#: settings/forms/email.py:19 -msgid "SMTP user" -msgstr "SMTP账号" - -#: settings/forms/email.py:22 -msgid "SMTP password" -msgstr "SMTP密码" - -#: settings/forms/email.py:24 -msgid "Tips: Some provider use token except password" -msgstr "提示:一些邮件提供商需要输入的是Token" - -#: settings/forms/email.py:27 -msgid "Send user" -msgstr "发送账号" - -#: settings/forms/email.py:29 -msgid "Tips: Send mail account, default SMTP account as the send account" -msgstr "提示:发送邮件账号,默认使用SMTP账号作为发送账号" - -#: settings/forms/email.py:33 -msgid "Test recipient" -msgstr "测试收件人" - -#: settings/forms/email.py:34 -msgid "Tips: Used only as a test mail recipient" -msgstr "提示:仅用来作为测试邮件收件人" - -#: settings/forms/email.py:37 -msgid "Use SSL" -msgstr "使用SSL" - -#: settings/forms/email.py:38 -msgid "If SMTP port is 465, may be select" -msgstr "如果SMTP端口是465,通常需要启用SSL" - -#: settings/forms/email.py:41 -msgid "Use TLS" -msgstr "使用TLS" - -#: settings/forms/email.py:42 -msgid "If SMTP port is 587, may be select" -msgstr "如果SMTP端口是587,通常需要启用TLS" - -#: settings/forms/email.py:48 -msgid "Create user email subject" -msgstr "创建用户邮件的主题" - -#: settings/forms/email.py:49 -msgid "" -"Tips: When creating a user, send the subject of the email (eg:Create account " -"successfully)" -msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" - -#: settings/forms/email.py:53 -msgid "Create user honorific" -msgstr "创建用户邮件的敬语" - -#: settings/forms/email.py:54 -msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" -msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" - -#: settings/forms/email.py:59 -msgid "Create user email content" -msgstr "创建用户邮件的内容" - -#: settings/forms/email.py:60 -msgid "Tips:When creating a user, send the content of the email" -msgstr "提示: 创建用户时,发送设置密码邮件的内容" - -#: settings/forms/email.py:63 -msgid "Signature" -msgstr "署名" - -#: settings/forms/email.py:64 -msgid "Tips: Email signature (eg:jumpserver)" -msgstr "提示: 邮件的署名 (例如: jumpserver)" - -#: settings/forms/ldap.py:16 -msgid "LDAP server" -msgstr "LDAP地址" - -#: settings/forms/ldap.py:19 -msgid "Bind DN" -msgstr "绑定DN" - -#: settings/forms/ldap.py:26 -msgid "User OU" -msgstr "用户OU" - -#: settings/forms/ldap.py:27 -msgid "Use | split User OUs" -msgstr "使用|分隔各OU" - -#: settings/forms/ldap.py:31 -msgid "User search filter" -msgstr "用户过滤器" - -#: settings/forms/ldap.py:32 -#, python-format -msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" -msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" - -#: settings/forms/ldap.py:35 -msgid "User attr map" -msgstr "LDAP属性映射" - -#: settings/forms/ldap.py:37 -msgid "" -"User attr map present how to map LDAP user attr to jumpserver, username,name," -"email is jumpserver attr" -msgstr "" -"用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," -"email 是jumpserver的属性" - -#: settings/forms/ldap.py:46 -msgid "Enable LDAP auth" -msgstr "启用LDAP认证" - -#: settings/forms/security.py:18 -msgid "" -"After opening, all user login must use MFA(valid for all users, including " -"administrators)" -msgstr "开启后,所有用户登录必须使用多因子认证(对所有用户有效,包括管理员)" - -#: settings/forms/security.py:24 -msgid "Batch execute commands" -msgstr "批量命令" - -#: settings/forms/security.py:25 -msgid "Allow user batch execute commands" -msgstr "允许用户批量执行命令" - -#: settings/forms/security.py:28 -msgid "Service account registration" -msgstr "终端注册" - -#: settings/forms/security.py:29 -msgid "" -"Allow using bootstrap token register service account, when terminal setup, " -"can disable it" -msgstr "允许使用bootstrap token注册终端, 当终端注册成功后可以禁止" - -#: settings/forms/security.py:35 -msgid "Limit the number of login failures" -msgstr "限制登录失败次数" - -#: settings/forms/security.py:39 -msgid "No logon interval" -msgstr "禁止登录时间间隔" - -#: settings/forms/security.py:41 -msgid "" -"Tip: (unit/minute) if the user has failed to log in for a limited number of " -"times, no login is allowed during this time interval." -msgstr "" -"提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" - -#: settings/forms/security.py:48 -msgid "Connection max idle time" -msgstr "连接最大空闲时间" - -#: settings/forms/security.py:50 -msgid "If idle time more than it, disconnect connection Unit: minute" -msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" - -#: settings/forms/security.py:56 -msgid "Password expiration time" -msgstr "密码过期时间" - -#: settings/forms/security.py:58 -msgid "" -"Tip: (unit: day) If the user does not update the password during the time, " -"the user password will expire failure;The password expiration reminder mail " -"will be automatic sent to the user by system within 5 days (daily) before " -"the password expires" -msgstr "" -"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" -"提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" - -#: settings/forms/security.py:67 -msgid "Password minimum length" -msgstr "密码最小长度 " - -#: settings/forms/security.py:71 -msgid "Must contain capital letters" -msgstr "必须包含大写字母" - -#: settings/forms/security.py:73 -msgid "" -"After opening, the user password changes and resets must contain uppercase " -"letters" -msgstr "开启后,用户密码修改、重置必须包含大写字母" - -#: settings/forms/security.py:78 -msgid "Must contain lowercase letters" -msgstr "必须包含小写字母" - -#: settings/forms/security.py:79 -msgid "" -"After opening, the user password changes and resets must contain lowercase " -"letters" -msgstr "开启后,用户密码修改、重置必须包含小写字母" - -#: settings/forms/security.py:84 -msgid "Must contain numeric characters" -msgstr "必须包含数字字符" - -#: settings/forms/security.py:85 -msgid "" -"After opening, the user password changes and resets must contain numeric " -"characters" -msgstr "开启后,用户密码修改、重置必须包含数字字符" - -#: settings/forms/security.py:90 -msgid "Must contain special characters" -msgstr "必须包含特殊字符" - -#: settings/forms/security.py:91 -msgid "" -"After opening, the user password changes and resets must contain special " -"characters" -msgstr "开启后,用户密码修改、重置必须包含特殊字符" - -#: settings/forms/terminal.py:20 -msgid "Auto" -msgstr "自动" - -#: settings/forms/terminal.py:27 -msgid "Password auth" -msgstr "密码认证" - -#: settings/forms/terminal.py:30 -msgid "Public key auth" -msgstr "密钥认证" - -#: settings/forms/terminal.py:33 -msgid "Heartbeat interval" -msgstr "心跳间隔" - -#: settings/forms/terminal.py:34 -msgid "Units: seconds" -msgstr "单位: 秒" - -#: settings/forms/terminal.py:37 -msgid "List sort by" -msgstr "资产列表排序" - -#: settings/forms/terminal.py:40 -msgid "List page size" -msgstr "资产分页每页数量" - -#: settings/forms/terminal.py:43 -msgid "Session keep duration" -msgstr "会话保留时长" - -#: settings/forms/terminal.py:44 -msgid "" -"Units: days, Session, record, command will be delete if more than duration, " -"only in database" -msgstr "" -"单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不" -"受影响)" - -#: settings/forms/terminal.py:48 -msgid "Telnet login regex" -msgstr "Telnet 成功正则表达式" - -#: settings/forms/terminal.py:49 -msgid "ex: Last\\s*login|success|成功" -msgstr "" -"登录telnet服务器成功后的提示正则表达式,如: Last\\s*login|success|成功 " - -#: settings/models.py:95 users/templates/users/reset_password.html:29 +#: settings/models.py:96 users/templates/users/reset_password.html:29 #: users/templates/users/user_profile.html:20 msgid "Setting" msgstr "设置" -#: settings/templates/settings/_ldap_list_users_modal.html:7 -msgid "LDAP user list" -msgstr "LDAP 用户列表" - -#: settings/templates/settings/_ldap_list_users_modal.html:9 -msgid "Please submit the LDAP configuration before import" -msgstr "请先提交LDAP配置再进行导入" - -#: settings/templates/settings/_ldap_list_users_modal.html:26 -msgid "Refresh cache" -msgstr "刷新缓存" - -#: settings/templates/settings/_ldap_list_users_modal.html:33 -#: users/forms/profile.py:89 users/models/user.py:442 -#: users/templates/users/user_detail.html:57 -#: users/templates/users/user_profile.html:59 -msgid "Email" -msgstr "邮件" - -#: settings/templates/settings/_ldap_list_users_modal.html:34 -msgid "Existing" -msgstr "已存在" - -#: settings/templates/settings/_ldap_list_users_modal.html:144 -msgid "" -"User is not currently selected, please check the user you want to import" -msgstr "当前无勾选用户,请勾选你想要导入的用户" - -#: settings/templates/settings/_ldap_list_users_modal.html:172 -#: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 -#: xpack/plugins/license/templates/license/license_detail.html:88 -msgid "Import" -msgstr "导入" - -#: settings/templates/settings/_ldap_test_user_login_modal.html:4 -msgid "Test LDAP user login" -msgstr "测试LDAP 用户登录" - -#: settings/templates/settings/_ldap_test_user_login_modal.html:5 -msgid "Save the configuration before testing the login" -msgstr "请先提交LDAP配置再进行测试登录" - -#: settings/templates/settings/_ldap_test_user_login_modal.html:12 -msgid "Please input username" -msgstr "请输入用户名" - -#: settings/templates/settings/_setting_tabs.html:4 -#: settings/templates/settings/terminal_setting.html:31 settings/views.py:20 -msgid "Basic setting" -msgstr "基本设置" - -#: settings/templates/settings/_setting_tabs.html:7 settings/views.py:47 -msgid "Email setting" -msgstr "邮件设置" - -#: settings/templates/settings/_setting_tabs.html:10 settings/views.py:162 -msgid "Email content setting" -msgstr "邮件内容设置" - -#: settings/templates/settings/_setting_tabs.html:13 settings/views.py:74 -msgid "LDAP setting" -msgstr "LDAP设置" - -#: settings/templates/settings/_setting_tabs.html:16 settings/views.py:106 -msgid "Terminal setting" -msgstr "终端设置" - -#: settings/templates/settings/_setting_tabs.html:19 -#: settings/templates/settings/security_setting.html:26 settings/views.py:135 -msgid "Security setting" -msgstr "安全设置" - -#: settings/templates/settings/email_content_setting.html:26 -msgid "Create User setting" -msgstr "创建用户设置" - -#: settings/templates/settings/ldap_setting.html:47 -msgid "Test login" -msgstr "测试登录" - -#: settings/templates/settings/ldap_setting.html:48 -msgid "Bulk import" -msgstr "一键导入" - -#: settings/templates/settings/security_setting.html:30 -msgid "Password check rule" -msgstr "密码校验规则" - -#: settings/templates/settings/terminal_setting.html:7 -msgid "Command and Replay storage configuration migrated to" -msgstr "命令和录像存储配置已迁移到" - -#: settings/templates/settings/terminal_setting.html:8 -msgid "Sessions -> Terminal -> Storage configuration" -msgstr "会话管理 -> 终端管理 -> 存储配置" - -#: settings/templates/settings/terminal_setting.html:9 -msgid "Here" -msgstr "这里" +#: settings/serializers/settings.py:57 +msgid "Auto" +msgstr "自动" #: settings/utils/ldap.py:389 msgid "Host or port is disconnected: {}" @@ -4352,23 +1791,16 @@ msgstr "认证失败: (未知): {}" msgid "Authentication success: {}" msgstr "认证成功: {}" -#: settings/views.py:19 settings/views.py:46 settings/views.py:73 -#: settings/views.py:105 settings/views.py:134 settings/views.py:161 -#: templates/_nav.html:187 -msgid "Settings" -msgstr "系统设置" +#: templates/_base_list.html:37 templates/_user_profile.html:23 +msgid "Search" +msgstr "搜索" -#: settings/views.py:30 settings/views.py:57 settings/views.py:84 -#: settings/views.py:118 settings/views.py:145 settings/views.py:172 -msgid "Update setting successfully" -msgstr "更新设置成功" - -#: templates/_base_only_msg_content.html:28 +#: templates/_base_only_msg_content.html:27 xpack/plugins/interface/api.py:18 #: xpack/plugins/interface/models.py:36 msgid "Welcome to the JumpServer open source fortress" msgstr "欢迎使用JumpServer开源堡垒机" -#: templates/_base_only_msg_content.html:33 +#: templates/_base_only_msg_content.html:32 msgid "" "The world's first fully open source fortress, using the GNU GPL v2.0 open " "source protocol, is a professional operation and maintenance audit system in " @@ -4377,7 +1809,7 @@ msgstr "" "全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计" "系统。" -#: templates/_base_only_msg_content.html:36 +#: templates/_base_only_msg_content.html:35 msgid "" "Developed using Python/Django, following the Web 2.0 specification and " "equipped with industry-leading Web Terminal solutions, with beautiful " @@ -4386,7 +1818,7 @@ msgstr "" "使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web " "Terminal 解决方案,交互界面美观、用户体验好。" -#: templates/_base_only_msg_content.html:39 +#: templates/_base_only_msg_content.html:38 msgid "" "Distributed architecture is adopted to support multi-machine room deployment " "across regions, central node provides API, and each machine room deploys " @@ -4396,10 +1828,18 @@ msgstr "" "采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点," "可横向扩展、无并发访问限制。" -#: templates/_base_only_msg_content.html:42 +#: templates/_base_only_msg_content.html:41 msgid "Changes the world, starting with a little bit." msgstr "改变世界,从一点点开始。" +#: templates/_csv_import_export.html:8 +msgid "Export" +msgstr "导出" + +#: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 +msgid "Import" +msgstr "导入" + #: templates/_csv_import_modal.html:12 msgid "Download the imported template or use the exported CSV file format" msgstr "下载导入的模板或使用导出的csv格式" @@ -4428,7 +1868,7 @@ msgstr "下载更新模版" msgid "Help" msgstr "帮助" -#: templates/_header_bar.html:19 templates/_without_nav_base.html:28 +#: templates/_header_bar.html:19 templates/_without_nav_base.html:27 msgid "Docs" msgstr "文档" @@ -4439,13 +1879,11 @@ msgstr "商业支持" #: templates/_header_bar.html:70 templates/_nav.html:30 #: templates/_nav_user.html:37 users/forms/profile.py:31 #: users/templates/users/_user.html:44 -#: users/templates/users/first_login.html:39 -#: users/templates/users/user_password_update.html:40 +#: users/templates/users/user_password_update.html:39 #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:61 #: users/templates/users/user_pubkey_update.html:37 -#: users/views/profile/base.py:27 msgid "Profile" msgstr "个人信息" @@ -4534,19 +1972,39 @@ msgid "" " " msgstr "" "\n" -" 您的ssh密钥没有设置或已失效,请点击 链接 更新\n" " " +#: templates/_nav.html:20 +msgid "User list" +msgstr "用户列表" + +#: templates/_nav.html:42 +msgid "Asset list" +msgstr "资产列表" + +#: templates/_nav.html:43 +msgid "Domain list" +msgstr "网域列表" + #: templates/_nav.html:47 msgid "Command filters" msgstr "命令过滤" -#: templates/_nav.html:97 terminal/views/command.py:21 -#: terminal/views/session.py:48 terminal/views/session.py:59 -#: terminal/views/session.py:74 terminal/views/session.py:97 -#: terminal/views/terminal.py:32 terminal/views/terminal.py:48 -#: terminal/views/terminal.py:61 +#: templates/_nav.html:49 +msgid "Platform list" +msgstr "平台列表" + +#: templates/_nav.html:60 +msgid "Applications" +msgstr "应用管理" + +#: templates/_nav.html:75 +msgid "Perms" +msgstr "权限管理" + +#: templates/_nav.html:97 msgid "Sessions" msgstr "会话管理" @@ -4554,12 +2012,11 @@ msgstr "会话管理" msgid "Session online" msgstr "在线会话" -#: templates/_nav.html:101 terminal/views/session.py:60 +#: templates/_nav.html:101 msgid "Session offline" msgstr "历史会话" -#: templates/_nav.html:102 terminal/templates/terminal/session_commands.html:21 -#: terminal/templates/terminal/session_detail.html:21 +#: templates/_nav.html:102 msgid "Commands" msgstr "命令记录" @@ -4571,10 +2028,7 @@ msgstr "Web终端" msgid "File manager" msgstr "文件管理" -#: templates/_nav.html:110 terminal/views/storage.py:27 -#: terminal/views/storage.py:42 terminal/views/storage.py:96 -#: terminal/views/storage.py:120 terminal/views/storage.py:149 -#: terminal/views/storage.py:175 +#: templates/_nav.html:110 msgid "Terminal" msgstr "终端管理" @@ -4582,6 +2036,10 @@ msgstr "终端管理" msgid "Job Center" msgstr "作业中心" +#: templates/_nav.html:124 +msgid "Task list" +msgstr "任务列表" + #: templates/_nav.html:125 templates/_nav.html:153 msgid "Batch command" msgstr "批量命令" @@ -4590,15 +2048,35 @@ msgstr "批量命令" msgid "Task monitor" msgstr "任务监控" -#: templates/_nav.html:137 tickets/views.py:19 tickets/views.py:37 +#: templates/_nav.html:137 msgid "Tickets" msgstr "工单管理" +#: templates/_nav.html:146 +msgid "Audits" +msgstr "日志审计" + +#: templates/_nav.html:149 +msgid "Login log" +msgstr "登录日志" + +#: templates/_nav.html:150 +msgid "FTP log" +msgstr "FTP日志" + +#: templates/_nav.html:151 +msgid "Operate log" +msgstr "操作日志" + +#: templates/_nav.html:152 +msgid "Password change log" +msgstr "改密日志" + #: templates/_nav.html:163 msgid "XPack" msgstr "" -#: templates/_nav.html:171 xpack/plugins/cloud/views.py:28 +#: templates/_nav.html:171 msgid "Account list" msgstr "账户列表" @@ -4606,16 +2084,28 @@ msgstr "账户列表" msgid "Sync instance" msgstr "同步实例" +#: templates/_nav.html:187 +msgid "Settings" +msgstr "系统设置" + +#: templates/_nav_user.html:4 +msgid "My assets" +msgstr "我的资产" + #: templates/_nav_user.html:10 msgid "My Applications" msgstr "我的应用" +#: templates/_nav_user.html:31 +msgid "Command execution" +msgstr "命令执行" + #: templates/_pagination.html:59 msgid "" "Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项" -#: templates/_without_nav_base.html:26 +#: templates/_without_nav_base.html:25 msgid "Home page" msgstr "首页" @@ -4627,6 +2117,14 @@ msgstr "语言播放验证码" msgid "Captcha" msgstr "验证码" +#: templates/delete_confirm.html:6 +msgid "Confirm delete" +msgstr "确认删除" + +#: templates/delete_confirm.html:11 +msgid "Are you sure delete" +msgstr "您确定删除吗?" + #: templates/index.html:11 msgid "Total users" msgstr "用户总数" @@ -4777,19 +2275,19 @@ msgstr "登录了" msgid "Filters" msgstr "过滤" -#: terminal/api/session.py:142 +#: terminal/api/session.py:190 msgid "Session does not exist: {}" msgstr "会话不存在: {}" -#: terminal/api/session.py:145 +#: terminal/api/session.py:193 msgid "Session is finished or the protocol not supported" msgstr "会话已经完成或协议不支持" -#: terminal/api/session.py:150 +#: terminal/api/session.py:198 msgid "User does not exist: {}" msgstr "用户不存在: {}" -#: terminal/api/session.py:154 +#: terminal/api/session.py:202 msgid "User does not have permission" msgstr "用户没有权限" @@ -4810,14 +2308,10 @@ msgid "Test failure: Account invalid" msgstr "测试失败: 账户无效" #: terminal/backends/command/models.py:14 -#: terminal/templates/terminal/command_list.html:110 -#: terminal/templates/terminal/command_list.html:205 msgid "Ordinary" msgstr "普通" #: terminal/backends/command/models.py:15 -#: terminal/templates/terminal/command_list.html:111 -#: terminal/templates/terminal/command_list.html:202 msgid "Dangerous" msgstr "危险" @@ -4825,134 +2319,38 @@ msgstr "危险" msgid "Input" msgstr "输入" +#: terminal/backends/command/models.py:22 +msgid "Output" +msgstr "输出" + #: terminal/backends/command/models.py:23 -#: terminal/templates/terminal/command_list.html:33 -#: terminal/templates/terminal/terminal_list.html:34 msgid "Session" msgstr "会话" #: terminal/backends/command/models.py:24 -#: terminal/templates/terminal/command_list.html:29 -#: terminal/templates/terminal/command_list.html:109 msgid "Risk level" msgstr "风险等级" -#: terminal/forms/storage.py:41 -msgid "Container name" -msgstr "容器名称" - -#: terminal/forms/storage.py:44 -msgid "Account name" -msgstr "账户名称" - -#: terminal/forms/storage.py:47 -msgid "Account key" -msgstr "账户密钥" - -#: terminal/forms/storage.py:55 -msgid "Endpoint suffix" -msgstr "端点后缀" - -#: terminal/forms/storage.py:61 terminal/forms/storage.py:84 -#: terminal/forms/storage.py:108 terminal/forms/storage.py:125 -msgid "Bucket" -msgstr "桶名称" - -#: terminal/forms/storage.py:64 terminal/forms/storage.py:87 -#: terminal/forms/storage.py:111 terminal/forms/storage.py:128 -msgid "Access key" -msgstr "" - -#: terminal/forms/storage.py:68 terminal/forms/storage.py:91 -#: terminal/forms/storage.py:115 terminal/forms/storage.py:132 -msgid "Secret key" -msgstr "" - -#: terminal/forms/storage.py:72 terminal/forms/storage.py:95 -#: terminal/forms/storage.py:119 terminal/forms/storage.py:139 -msgid "Endpoint" -msgstr "端点" - -#: terminal/forms/storage.py:74 -#, python-brace-format -msgid "" -"\n" -" OSS: http://{REGION_NAME}.aliyuncs.com
\n" -" Example: http://oss-cn-hangzhou.aliyuncs.com\n" -" " -msgstr "" - -#: terminal/forms/storage.py:97 -#, python-brace-format -msgid "" -"\n" -" S3: http://s3.{REGION_NAME}.amazonaws.com
\n" -" S3(China): http://s3.{REGION_NAME}.amazonaws.com.cn
\n" -" Example: http://s3.cn-north-1.amazonaws.com.cn\n" -" " -msgstr "" - -#: terminal/forms/storage.py:136 xpack/plugins/cloud/models.py:266 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:29 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:112 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:46 -msgid "Region" -msgstr "地域" - -#: terminal/forms/storage.py:153 -msgid "" -"\n" -" Tips: If there are multiple hosts, separate them with a comma " -"(,) \n" -"
\n" -" eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com\n" -" " -msgstr "" -"\n" -" 提示: 如果有多台主机,请使用逗号 ( , ) 进行分割\n" -"
\n" -" eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com\n" -" " - -#: terminal/forms/storage.py:161 -msgid "Index" -msgstr "索引" - -#: terminal/forms/storage.py:164 -msgid "Doc type" -msgstr "文档类型" - -#: terminal/forms/terminal.py:25 terminal/models.py:30 -#: terminal/templates/terminal/base_storage_list.html:10 -msgid "Command storage" -msgstr "命令存储" - -#: terminal/forms/terminal.py:26 -msgid "Command can store in server db or ES, default to server, more see docs" -msgstr "" -"命令支持存储到服务器端数据库、ES中,默认存储的服务器端数据库,更多查看文档" - -#: terminal/forms/terminal.py:30 terminal/models.py:31 -#: terminal/templates/terminal/base_storage_list.html:9 -msgid "Replay storage" -msgstr "录像存储" - -#: terminal/forms/terminal.py:31 -msgid "" -"Replay file can store in server disk, AWS S3, Aliyun OSS, default to server, " -"more see docs" -msgstr "" -"录像文件支持存储到服务器端硬盘、AWS S3、 阿里云 OSS 中,默认存储到服务器端硬" -"盘, 更多查看文档" - #: terminal/models.py:27 msgid "Remote Address" msgstr "远端地址" +#: terminal/models.py:28 +msgid "SSH Port" +msgstr "SSH端口" + #: terminal/models.py:29 msgid "HTTP Port" msgstr "HTTP端口" +#: terminal/models.py:30 +msgid "Command storage" +msgstr "命令存储" + +#: terminal/models.py:31 +msgid "Replay storage" +msgstr "录像存储" + #: terminal/models.py:154 msgid "Session Online" msgstr "在线会话" @@ -4977,11 +2375,11 @@ msgstr "线程数" msgid "Boot Time" msgstr "运行时间" -#: terminal/models.py:195 terminal/templates/terminal/session_list.html:135 +#: terminal/models.py:195 msgid "Replay" msgstr "回放" -#: terminal/models.py:200 terminal/templates/terminal/session_detail.html:76 +#: terminal/models.py:200 msgid "Date end" msgstr "结束日期" @@ -4989,191 +2387,11 @@ msgstr "结束日期" msgid "Args" msgstr "参数" -#: terminal/templates/terminal/command_list.html:44 -msgid "Export command" -msgstr "导出命令" - -#: terminal/templates/terminal/command_list.html:210 -msgid "Goto" -msgstr "转到" - -#: terminal/templates/terminal/command_storage_list.html:5 -#: terminal/views/storage.py:150 -msgid "Create command storage" -msgstr "创建命令存储" - -#: terminal/templates/terminal/replay_storage_list.html:5 -#: terminal/views/storage.py:97 -msgid "Create replay storage" -msgstr "创建录像存储" - -#: terminal/templates/terminal/session_commands.html:18 -#: terminal/templates/terminal/session_detail.html:18 -#: terminal/views/session.py:75 terminal/views/session.py:98 -msgid "Session detail" -msgstr "会话详情" - -#: terminal/templates/terminal/session_commands.html:29 -#: terminal/views/command.py:22 -msgid "Command list" -msgstr "命令记录列表" - -#: terminal/templates/terminal/session_commands.html:68 -msgid "There is no command about this session" -msgstr "该会话没有命令记录" - -#: terminal/templates/terminal/session_detail.html:64 -#: terminal/templates/terminal/session_list.html:30 -msgid "Login from" -msgstr "登录来源" - -#: terminal/templates/terminal/session_detail.html:94 -msgid "Replay session" -msgstr "回放会话" - -#: terminal/templates/terminal/session_detail.html:102 -msgid "Download replay" -msgstr "下载录像" - -#: terminal/templates/terminal/session_detail.html:105 -#: terminal/templates/terminal/session_list.html:137 -msgid "Download" -msgstr "下载" - -#: terminal/templates/terminal/session_detail.html:111 -msgid "Monitor session" -msgstr "监控" - -#: terminal/templates/terminal/session_detail.html:119 -msgid "Terminate session" -msgstr "终止会话" - -#: terminal/templates/terminal/session_detail.html:161 -msgid "Terminate success" -msgstr "终断成功" - -#: terminal/templates/terminal/session_list.html:33 -msgid "Duration" -msgstr "时长" - -#: terminal/templates/terminal/session_list.html:45 -msgid "Terminate selected" -msgstr "终断所选" - -#: terminal/templates/terminal/session_list.html:46 -msgid "Confirm finished" -msgstr "确认已完成" - -#: terminal/templates/terminal/session_list.html:90 -msgid "Terminate task send, waiting ..." -msgstr "终断任务已发送,请等待" - -#: terminal/templates/terminal/session_list.html:143 -msgid "Terminate" -msgstr "终断" - -#: terminal/templates/terminal/session_list.html:149 -msgid "Monitoring" -msgstr "监控" - -#: terminal/templates/terminal/session_list.html:179 -msgid "Finish session success" -msgstr "标记会话完成成功" - -#: terminal/templates/terminal/session_list.html:247 -msgid "Visit doc for replay play offline: " -msgstr "访问文档查看如何离线播放: " - -#: terminal/templates/terminal/terminal_detail.html:13 -#: terminal/views/terminal.py:62 -msgid "Terminal detail" -msgstr "终端详情" - -#: terminal/templates/terminal/terminal_detail.html:51 -msgid "SSH port" -msgstr "SSH端口" - -#: terminal/templates/terminal/terminal_detail.html:55 -msgid "Http port" -msgstr "HTTP端口" - -#: terminal/templates/terminal/terminal_list.html:21 -msgid "Storage configuration" -msgstr "存储配置" - -#: terminal/templates/terminal/terminal_list.html:31 -msgid "Addr" -msgstr "地址" - -#: terminal/templates/terminal/terminal_list.html:36 -msgid "Alive" -msgstr "在线" - -#: terminal/templates/terminal/terminal_list.html:79 -msgid "Accept" -msgstr "接受" - -#: terminal/templates/terminal/terminal_list.html:81 -#: tickets/models/ticket.py:31 tickets/templates/tickets/ticket_detail.html:101 -#: tickets/templates/tickets/ticket_list.html:110 -msgid "Reject" -msgstr "拒绝" - -#: terminal/templates/terminal/terminal_modal_accept.html:5 -msgid "Accept terminal registration" -msgstr "接受终端注册" - -#: terminal/templates/terminal/terminal_update.html:31 -msgid "Info" -msgstr "信息" - -#: terminal/views/session.py:49 -msgid "Session online list" -msgstr "在线会话" - -#: terminal/views/storage.py:28 -msgid "Replay storage list" -msgstr "录像存储列表" - -#: terminal/views/storage.py:43 -msgid "Command storage list" -msgstr "命令存储列表" - -#: terminal/views/storage.py:121 -msgid "Update replay storage" -msgstr "更新录像存储" - -#: terminal/views/storage.py:176 -msgid "Update command storage" -msgstr "更新命令存储" - -#: terminal/views/terminal.py:33 -msgid "Terminal list" -msgstr "终端列表" - -#: terminal/views/terminal.py:48 -msgid "Update terminal" -msgstr "更新终端" - -#: terminal/views/terminal.py:111 terminal/views/terminal.py:112 -msgid "Redirect to web terminal" -msgstr "重定向到web terminal" - -#: terminal/views/terminal.py:119 -msgid "Connect ssh terminal" -msgstr "连接ssh终端" - -#: terminal/views/terminal.py:120 -msgid "" -"You should use your ssh client tools connect terminal: {}

{}" -msgstr "你可以使用ssh客户端工具连接终端" - #: tickets/models/ticket.py:18 tickets/models/ticket.py:70 -#: tickets/templates/tickets/ticket_list.html:105 msgid "Open" msgstr "开启" -#: tickets/models/ticket.py:19 tickets/templates/tickets/ticket_list.html:106 +#: tickets/models/ticket.py:19 msgid "Closed" msgstr "关闭" @@ -5181,17 +2399,19 @@ msgstr "关闭" msgid "General" msgstr "一般" -#: tickets/models/ticket.py:30 tickets/templates/tickets/ticket_detail.html:100 -#: tickets/templates/tickets/ticket_list.html:109 +#: tickets/models/ticket.py:30 msgid "Approve" msgstr "同意" +#: tickets/models/ticket.py:31 +msgid "Reject" +msgstr "拒绝" + #: tickets/models/ticket.py:34 tickets/models/ticket.py:129 msgid "User display name" msgstr "用户显示名称" -#: tickets/models/ticket.py:36 tickets/templates/tickets/ticket_list.html:33 -#: tickets/templates/tickets/ticket_list.html:102 +#: tickets/models/ticket.py:36 msgid "Title" msgstr "标题" @@ -5199,7 +2419,7 @@ msgstr "标题" msgid "Body" msgstr "内容" -#: tickets/models/ticket.py:39 tickets/templates/tickets/ticket_detail.html:51 +#: tickets/models/ticket.py:39 msgid "Assignee" msgstr "处理人" @@ -5207,7 +2427,7 @@ msgstr "处理人" msgid "Assignee display name" msgstr "处理人名称" -#: tickets/models/ticket.py:41 tickets/templates/tickets/ticket_detail.html:50 +#: tickets/models/ticket.py:41 msgid "Assignees" msgstr "待处理人" @@ -5223,23 +2443,6 @@ msgstr "{} {} 这个工单" msgid "this ticket" msgstr "这个工单" -#: tickets/templates/tickets/ticket_detail.html:66 -#: tickets/templates/tickets/ticket_detail.html:80 -msgid "ago" -msgstr "前" - -#: tickets/templates/tickets/ticket_list.html:9 -msgid "My tickets" -msgstr "我的工单" - -#: tickets/templates/tickets/ticket_list.html:10 -msgid "Assigned me" -msgstr "待处理" - -#: tickets/templates/tickets/ticket_list.html:19 -msgid "Create ticket" -msgstr "提交工单" - #: tickets/utils.py:18 msgid "New ticket" msgstr "新工单" @@ -5304,18 +2507,14 @@ msgstr "" " \n" " " -#: tickets/views.py:20 -msgid "Ticket list" -msgstr "工单列表" - -#: tickets/views.py:38 -msgid "Ticket detail" -msgstr "工单详情" - -#: users/api/user.py:116 +#: users/api/user.py:117 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置" +#: users/forms/group.py:19 users/forms/user.py:143 users/forms/user.py:148 +msgid "Select users" +msgstr "选择用户" + #: users/forms/profile.py:37 msgid "" "When enabled, you will enter the MFA binding process the next time you log " @@ -5338,9 +2537,7 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,并启用多因子认证)" -#: users/forms/profile.py:64 users/templates/users/first_login.html:48 -#: users/templates/users/first_login.html:102 -#: users/templates/users/first_login.html:128 +#: users/forms/profile.py:64 msgid "Finish" msgstr "完成" @@ -5356,6 +2553,12 @@ msgstr "确认密码" msgid "Password does not match" msgstr "密码不一致" +#: users/forms/profile.py:89 users/models/user.py:468 +#: users/templates/users/user_detail.html:57 +#: users/templates/users/user_profile.html:59 +msgid "Email" +msgstr "邮件" + #: users/forms/profile.py:96 msgid "Old password" msgstr "原来密码" @@ -5371,7 +2574,7 @@ msgstr "自动配置并下载SSH密钥" #: users/forms/profile.py:118 users/forms/user.py:34 #: users/templates/users/user_update.html:30 msgid "ssh public key" -msgstr "ssh公钥" +msgstr "SSH公钥" #: users/forms/profile.py:119 users/forms/user.py:35 msgid "ssh-rsa AAAA..." @@ -5386,11 +2589,12 @@ msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" #: users/forms/profile.py:137 users/forms/user.py:90 -#: users/serializers/user.py:138 +#: users/serializers/user.py:177 users/serializers/user.py:258 +#: users/serializers/user.py:316 msgid "Not a valid ssh public key" -msgstr "ssh密钥不合法" +msgstr "SSH密钥不合法" -#: users/forms/user.py:27 users/models/user.py:450 +#: users/forms/user.py:27 users/models/user.py:476 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:73 #: users/templates/users/user_list.html:16 @@ -5398,7 +2602,7 @@ msgstr "ssh密钥不合法" msgid "Role" msgstr "角色" -#: users/forms/user.py:31 users/models/user.py:485 +#: users/forms/user.py:31 users/models/user.py:511 #: users/templates/users/user_detail.html:89 #: users/templates/users/user_list.html:18 #: users/templates/users/user_profile.html:102 @@ -5413,109 +2617,117 @@ msgstr "复制用户公钥到这里" msgid "Join user groups" msgstr "添加到用户组" -#: users/forms/user.py:103 users/views/login.py:123 -#: users/views/profile/password.py:57 +#: users/forms/user.py:103 users/views/profile/password.py:57 +#: users/views/profile/reset.py:123 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/forms/user.py:124 +#: users/forms/user.py:124 users/serializers/user.py:30 msgid "Reset link will be generated and sent to the user" msgstr "生成重置密码链接,通过邮件发送给用户" -#: users/forms/user.py:125 +#: users/forms/user.py:125 users/serializers/user.py:31 msgid "Set password" msgstr "设置密码" -#: users/forms/user.py:132 xpack/plugins/change_auth_plan/models.py:59 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:45 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:67 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:16 +#: users/forms/user.py:132 users/serializers/user.py:38 +#: xpack/plugins/change_auth_plan/models.py:60 +#: xpack/plugins/change_auth_plan/serializers.py:30 msgid "Password strategy" msgstr "密码策略" -#: users/models/user.py:142 users/models/user.py:596 +#: users/models/user.py:158 users/models/user.py:622 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:145 xpack/plugins/orgs/forms.py:29 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:109 -#: xpack/plugins/orgs/templates/orgs/org_list.html:14 +#: users/models/user.py:160 +msgid "Application" +msgstr "应用程序" + +#: users/models/user.py:161 msgid "Auditor" msgstr "审计员" -#: users/models/user.py:155 +#: users/models/user.py:171 msgid "Org admin" msgstr "组织管理员" -#: users/models/user.py:157 +#: users/models/user.py:173 msgid "Org auditor" msgstr "组织审计员" -#: users/models/user.py:362 users/templates/users/user_profile.html:90 +#: users/models/user.py:388 users/templates/users/user_profile.html:90 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:429 +#: users/models/user.py:455 msgid "Local" msgstr "数据库" -#: users/models/user.py:453 +#: users/models/user.py:479 msgid "Avatar" msgstr "头像" -#: users/models/user.py:456 users/templates/users/user_detail.html:68 +#: users/models/user.py:482 users/templates/users/user_detail.html:68 msgid "Wechat" msgstr "微信" -#: users/models/user.py:489 +#: users/models/user.py:515 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:599 +#: users/models/user.py:625 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/serializers/group.py:46 +#: users/serializers/group.py:50 msgid "Auditors cannot be join in the user group" msgstr "审计员不能被加入到用户组" -#: users/serializers/user.py:42 +#: users/serializers/user.py:69 users/serializers/user.py:229 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:43 +#: users/serializers/user.py:70 msgid "Is valid" msgstr "账户是否有效" -#: users/serializers/user.py:44 +#: users/serializers/user.py:71 msgid "Is expired" msgstr " 是否过期" -#: users/serializers/user.py:45 +#: users/serializers/user.py:72 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:53 -msgid "Role limit to {}" -msgstr "角色只能为 {}" - -#: users/serializers/user.py:65 -msgid "Password does not match security rules" -msgstr "密码不满足安全规则" - -#: users/serializers/user.py:123 +#: users/serializers/user.py:76 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:124 +#: users/serializers/user.py:77 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:125 +#: users/serializers/user.py:78 msgid "Role name" msgstr "角色名" +#: users/serializers/user.py:97 +msgid "Role limit to {}" +msgstr "角色只能为 {}" + +#: users/serializers/user.py:109 users/serializers/user.py:282 +msgid "Password does not match security rules" +msgstr "密码不满足安全规则" + +#: users/serializers/user.py:274 +msgid "The old password is incorrect" +msgstr "旧密码错误" + +#: users/serializers/user.py:288 +msgid "The newly set password is inconsistent" +msgstr "两次密码不一致" + #: users/serializers_v2/user.py:36 msgid "name not unique" msgstr "名称重复" @@ -5527,8 +2739,6 @@ msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:14 users/templates/users/_user.html:13 #: users/templates/users/user_profile_update.html:55 #: xpack/plugins/cloud/models.py:119 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:57 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:13 msgid "Account" msgstr "账户" @@ -5536,6 +2746,10 @@ msgstr "账户" msgid "Follow these steps to complete the binding operation" msgstr "请按照以下步骤完成绑定操作" +#: users/templates/users/_granted_assets.html:7 +msgid "Loading" +msgstr "加载中" + #: users/templates/users/_select_user_modal.html:5 msgid "Please Select User" msgstr "选择用户" @@ -5544,12 +2758,37 @@ msgstr "选择用户" msgid "Asset num" msgstr "资产数量" +#: users/templates/users/_user.html:21 +msgid "Auth" +msgstr "认证" + #: users/templates/users/_user.html:27 msgid "Security and Role" msgstr "角色安全" +#: users/templates/users/_user.html:51 +#: users/templates/users/user_bulk_update.html:23 +#: users/templates/users/user_detail.html:168 +#: users/templates/users/user_group_create_update.html:27 +#: users/templates/users/user_password_update.html:74 +#: users/templates/users/user_profile.html:209 +#: users/templates/users/user_profile_update.html:67 +#: users/templates/users/user_pubkey_update.html:74 +#: users/templates/users/user_pubkey_update.html:80 +msgid "Reset" +msgstr "重置" + +#: users/templates/users/_user.html:52 +#: users/templates/users/forgot_password.html:24 +#: users/templates/users/user_bulk_update.html:24 +#: users/templates/users/user_list.html:40 +#: users/templates/users/user_password_update.html:75 +#: users/templates/users/user_profile_update.html:68 +#: users/templates/users/user_pubkey_update.html:81 +msgid "Submit" +msgstr "提交" + #: users/templates/users/_user_detail_nav_header.html:11 -#: users/views/user.py:179 msgid "User detail" msgstr "用户详情" @@ -5573,30 +2812,13 @@ msgstr "授权的数据库应用" #: users/templates/users/_user_update_pk_modal.html:4 msgid "Update User SSH Public Key" -msgstr "更新ssh密钥" +msgstr "更新SSH密钥" -#: users/templates/users/first_login.html:19 +#: users/templates/users/first_login.html:6 #: users/templates/users/first_login_done.html:19 msgid "First Login" msgstr "首次登录" -#: users/templates/users/first_login.html:65 -msgid "I agree with the terms and conditions." -msgstr "我同意条款和条件" - -#: users/templates/users/first_login.html:66 -msgid "Please choose the terms and conditions." -msgstr "请选择同意条款和条件" - -#: users/templates/users/first_login.html:70 -#: users/templates/users/user_update.html:32 -msgid "User auth from {}, ssh key login is not supported" -msgstr "用户认证源来自 {}, 不支持使用 SSH Key 登录" - -#: users/templates/users/first_login.html:96 -msgid "Previous" -msgstr "上一步" - #: users/templates/users/first_login_done.html:31 msgid "Welcome to use jumpserver, visit " msgstr "欢迎使用 JumpServer 堡垒机" @@ -5621,62 +2843,91 @@ msgstr "重置密码" #: users/templates/users/reset_password.html:23 #: users/templates/users/user_create.html:13 -#: users/templates/users/user_password_update.html:65 +#: users/templates/users/user_password_update.html:64 #: users/templates/users/user_update.html:13 msgid "Your password must satisfy" msgstr "您的密码必须满足:" #: users/templates/users/reset_password.html:24 #: users/templates/users/user_create.html:14 -#: users/templates/users/user_password_update.html:66 +#: users/templates/users/user_password_update.html:65 #: users/templates/users/user_update.html:14 msgid "Password strength" msgstr "密码强度:" #: users/templates/users/reset_password.html:48 #: users/templates/users/user_create.html:33 -#: users/templates/users/user_password_update.html:103 +#: users/templates/users/user_password_update.html:102 #: users/templates/users/user_update.html:55 msgid "Very weak" msgstr "很弱" #: users/templates/users/reset_password.html:49 #: users/templates/users/user_create.html:34 -#: users/templates/users/user_password_update.html:104 +#: users/templates/users/user_password_update.html:103 #: users/templates/users/user_update.html:56 msgid "Weak" msgstr "弱" #: users/templates/users/reset_password.html:50 #: users/templates/users/user_create.html:35 -#: users/templates/users/user_password_update.html:105 +#: users/templates/users/user_password_update.html:104 #: users/templates/users/user_update.html:57 msgid "Normal" msgstr "正常" #: users/templates/users/reset_password.html:51 #: users/templates/users/user_create.html:36 -#: users/templates/users/user_password_update.html:106 +#: users/templates/users/user_password_update.html:105 #: users/templates/users/user_update.html:58 msgid "Medium" msgstr "一般" #: users/templates/users/reset_password.html:52 #: users/templates/users/user_create.html:37 -#: users/templates/users/user_password_update.html:107 +#: users/templates/users/user_password_update.html:106 #: users/templates/users/user_update.html:59 msgid "Strong" msgstr "强" #: users/templates/users/reset_password.html:53 #: users/templates/users/user_create.html:38 -#: users/templates/users/user_password_update.html:108 +#: users/templates/users/user_password_update.html:107 #: users/templates/users/user_update.html:60 msgid "Very strong" msgstr "很强" +#: users/templates/users/user_asset_permission.html:43 +#: users/templates/users/user_asset_permission.html:155 +#: users/templates/users/user_database_app_permission.html:41 +#: users/templates/users/user_list.html:19 +#: users/templates/users/user_remote_app_permission.html:41 +#: xpack/plugins/cloud/models.py:50 xpack/plugins/cloud/serializers.py:32 +msgid "Validity" +msgstr "有效" + +#: users/templates/users/user_asset_permission.html:160 +msgid "Inherit" +msgstr "继承" + +#: users/templates/users/user_asset_permission.html:161 +msgid "Include" +msgstr "包含" + +#: users/templates/users/user_asset_permission.html:162 +msgid "Exclude" +msgstr "不包含" + +#: users/templates/users/user_bulk_update.html:8 +msgid "Select properties that need to be modified" +msgstr "选择需要修改属性" + +#: users/templates/users/user_bulk_update.html:10 +msgid "Select all" +msgstr "全选" + #: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:7 users/views/user.py:68 +#: users/templates/users/user_list.html:7 msgid "Create user" msgstr "创建用户" @@ -5684,6 +2935,11 @@ msgstr "创建用户" msgid "Force enabled" msgstr "强制启用" +#: users/templates/users/user_detail.html:101 +#: users/templates/users/user_profile.html:106 +msgid "Date joined" +msgstr "创建日期" + #: users/templates/users/user_detail.html:105 #: users/templates/users/user_profile.html:110 msgid "Last login" @@ -5694,6 +2950,11 @@ msgstr "最后登录" msgid "Last password updated" msgstr "最后更新密码" +#: users/templates/users/user_detail.html:126 +#: users/templates/users/user_profile.html:150 +msgid "Quick modify" +msgstr "快速修改" + #: users/templates/users/user_detail.html:148 msgid "Force enabled MFA" msgstr "强制启用多因子认证" @@ -5724,6 +2985,15 @@ msgstr "解除登录限制" msgid "Unblock" msgstr "解除" +#: users/templates/users/user_detail.html:226 +msgid "Join" +msgstr "加入" + +#: users/templates/users/user_detail.html:356 +#: users/templates/users/user_detail.html:383 +msgid "Update successfully!" +msgstr "更新成功" + #: users/templates/users/user_detail.html:365 msgid "Goto profile page enable MFA" msgstr "请去个人信息页面启用自己的多因子认证" @@ -5732,10 +3002,24 @@ msgstr "请去个人信息页面启用自己的多因子认证" msgid "An e-mail has been sent to the user`s mailbox." msgstr "已发送邮件到用户邮箱" +#: users/templates/users/user_detail.html:411 +#: users/templates/users/user_detail.html:437 +#: users/templates/users/user_detail.html:505 +#: users/templates/users/user_list.html:178 +msgid "Are you sure?" +msgstr "你确认吗?" + #: users/templates/users/user_detail.html:412 msgid "This will reset the user password and send a reset mail" msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱" +#: users/templates/users/user_detail.html:415 +#: users/templates/users/user_detail.html:441 +#: users/templates/users/user_detail.html:509 +#: users/templates/users/user_list.html:182 +msgid "Cancel" +msgstr "取消" + #: users/templates/users/user_detail.html:427 msgid "" "The reset-ssh-public-key E-mail has been sent successfully. Please inform " @@ -5752,12 +3036,12 @@ msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱" #: users/templates/users/user_detail.html:456 msgid "Successfully updated the SSH public key." -msgstr "更新ssh密钥成功" +msgstr "更新SSH密钥成功" #: users/templates/users/user_detail.html:457 #: users/templates/users/user_detail.html:461 msgid "User SSH public key update" -msgstr "ssh密钥" +msgstr "SSH密钥" #: users/templates/users/user_detail.html:506 msgid "After unlocking the user, the user can log in normally." @@ -5769,7 +3053,6 @@ msgstr "重置用户多因子认证成功" #: users/templates/users/user_group_detail.html:17 #: users/templates/users/user_group_granted_asset.html:18 -#: users/views/group.py:83 msgid "User group detail" msgstr "用户组详情" @@ -5777,14 +3060,39 @@ msgstr "用户组详情" msgid "Add user" msgstr "添加用户" -#: users/templates/users/user_group_list.html:7 users/views/group.py:46 +#: users/templates/users/user_group_detail.html:87 +msgid "Add" +msgstr "添加" + +#: users/templates/users/user_group_list.html:7 msgid "Create user group" msgstr "创建用户组" +#: users/templates/users/user_list.html:30 +msgid "Delete selected" +msgstr "批量删除" + #: users/templates/users/user_list.html:32 msgid "Remove selected" msgstr "批量移除" +#: users/templates/users/user_list.html:34 +msgid "Update selected" +msgstr "批量更新" + +#: users/templates/users/user_list.html:35 +msgid "Deactive selected" +msgstr "禁用所选" + +#: users/templates/users/user_list.html:36 +msgid "Active selected" +msgstr "激活所选" + +#: users/templates/users/user_list.html:106 +#: users/templates/users/user_list.html:110 +msgid "Remove" +msgstr "移除" + #: users/templates/users/user_list.html:179 msgid "This will delete the selected users !!!" msgstr "删除选中用户 !!!" @@ -5900,7 +3208,7 @@ msgstr "重置并下载SSH密钥" #: users/templates/users/user_pubkey_update.html:55 msgid "Old public key" -msgstr "原来ssh密钥" +msgstr "原来SSH密钥" #: users/templates/users/user_pubkey_update.html:63 msgid "Fingerprint" @@ -5920,15 +3228,19 @@ msgid "" "corresponding private key." msgstr "新的公钥已设置成功,请下载对应的私钥" -#: users/templates/users/user_update.html:4 users/views/user.py:112 +#: users/templates/users/user_update.html:4 msgid "Update user" msgstr "更新用户" -#: users/templates/users/user_update.html:22 users/views/login.py:49 -#: users/views/login.py:116 +#: users/templates/users/user_update.html:22 users/views/profile/reset.py:49 +#: users/views/profile/reset.py:116 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" +#: users/templates/users/user_update.html:32 +msgid "User auth from {}, ssh key login is not supported" +msgstr "用户认证源来自 {}, 不支持使用 SSH Key 登录" + #: users/templates/users/user_verify_mfa.html:11 msgid "" "The account protection has been opened, please complete the following " @@ -6114,7 +3426,7 @@ msgstr "" #: users/utils.py:175 msgid "SSH Key Reset" -msgstr "重置ssh密钥" +msgstr "重置SSH密钥" #: users/utils.py:177 #, python-format @@ -6141,51 +3453,6 @@ msgstr "" "
\n" " " -#: users/views/group.py:29 -msgid "User group list" -msgstr "用户组列表" - -#: users/views/group.py:64 -msgid "Update user group" -msgstr "更新用户组" - -#: users/views/group.py:100 -msgid "User group granted asset" -msgstr "用户组授权资产" - -#: users/views/login.py:45 -msgid "Email address invalid, please input again" -msgstr "邮箱地址错误,重新输入" - -#: users/views/login.py:62 -msgid "Send reset password message" -msgstr "发送重置密码邮件" - -#: users/views/login.py:63 -msgid "Send reset password mail success, login your mail box and follow it " -msgstr "" -"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" - -#: users/views/login.py:76 -msgid "Reset password success" -msgstr "重置密码成功" - -#: users/views/login.py:77 -msgid "Reset password success, return to login page" -msgstr "重置密码成功,返回到登录页面" - -#: users/views/login.py:101 users/views/login.py:111 -msgid "Token invalid or expired" -msgstr "Token错误或失效" - -#: users/views/login.py:163 -msgid "First login" -msgstr "首次登录" - -#: users/views/profile/base.py:47 -msgid "Profile setting" -msgstr "个人信息设置" - #: users/views/profile/otp.py:145 msgid "MFA enable success" msgstr "多因子认证启用成功" @@ -6214,174 +3481,110 @@ msgstr "用户名或密码无效" msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:130 -msgid "Bulk update user success" -msgstr "批量更新用户成功" +#: users/views/profile/reset.py:45 +msgid "Email address invalid, please input again" +msgstr "邮箱地址错误,重新输入" -#: users/views/user.py:158 -msgid "Bulk update user" -msgstr "批量更新用户" +#: users/views/profile/reset.py:62 +msgid "Send reset password message" +msgstr "发送重置密码邮件" -#: users/views/user.py:207 -msgid "User granted assets" -msgstr "用户授权资产" - -#: users/views/user.py:235 -msgid "User granted RemoteApp" -msgstr "用户授权远程应用" - -#: users/views/user.py:263 -msgid "User granted DatabaseApp" -msgstr "用户授权数据库应用" - -#: xpack/plugins/change_auth_plan/forms.py:21 -msgid "Password length" -msgstr "密码长度" - -#: xpack/plugins/change_auth_plan/forms.py:79 -msgid "" -"Tips: The username of the user on the asset to be modified. if the user " -"exists, change the password; If the user does not exist, create the user." +#: users/views/profile/reset.py:63 +msgid "Send reset password mail success, login your mail box and follow it " msgstr "" -"提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果" -"用户不存在,则创建用户。" +"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" + +#: users/views/profile/reset.py:76 +msgid "Reset password success" +msgstr "重置密码成功" + +#: users/views/profile/reset.py:77 +msgid "Reset password success, return to login page" +msgstr "重置密码成功,返回到登录页面" + +#: users/views/profile/reset.py:101 users/views/profile/reset.py:111 +msgid "Token invalid or expired" +msgstr "Token错误或失效" #: xpack/plugins/change_auth_plan/meta.py:9 -#: xpack/plugins/change_auth_plan/models.py:87 -#: xpack/plugins/change_auth_plan/models.py:174 -#: xpack/plugins/change_auth_plan/views.py:33 -#: xpack/plugins/change_auth_plan/views.py:50 -#: xpack/plugins/change_auth_plan/views.py:74 -#: xpack/plugins/change_auth_plan/views.py:90 -#: xpack/plugins/change_auth_plan/views.py:117 -#: xpack/plugins/change_auth_plan/views.py:132 -#: xpack/plugins/change_auth_plan/views.py:147 +#: xpack/plugins/change_auth_plan/models.py:88 +#: xpack/plugins/change_auth_plan/models.py:183 msgid "Change auth plan" msgstr "改密计划" -#: xpack/plugins/change_auth_plan/models.py:39 +#: xpack/plugins/change_auth_plan/models.py:40 msgid "Custom password" msgstr "自定义密码" -#: xpack/plugins/change_auth_plan/models.py:40 +#: xpack/plugins/change_auth_plan/models.py:41 msgid "All assets use the same random password" msgstr "所有资产使用相同的随机密码" -#: xpack/plugins/change_auth_plan/models.py:41 +#: xpack/plugins/change_auth_plan/models.py:42 msgid "All assets use different random password" msgstr "所有资产使用不同的随机密码" -#: xpack/plugins/change_auth_plan/models.py:63 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:72 +#: xpack/plugins/change_auth_plan/models.py:64 msgid "Password rules" msgstr "密码规则" -#: xpack/plugins/change_auth_plan/models.py:178 +#: xpack/plugins/change_auth_plan/models.py:187 msgid "Change auth plan snapshot" msgstr "改密计划快照" -#: xpack/plugins/change_auth_plan/models.py:193 -#: xpack/plugins/change_auth_plan/models.py:279 +#: xpack/plugins/change_auth_plan/models.py:202 +#: xpack/plugins/change_auth_plan/models.py:296 msgid "Change auth plan execution" msgstr "改密计划执行" -#: xpack/plugins/change_auth_plan/models.py:252 +#: xpack/plugins/change_auth_plan/models.py:269 msgid "Ready" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:253 +#: xpack/plugins/change_auth_plan/models.py:270 msgid "Preflight check" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:254 +#: xpack/plugins/change_auth_plan/models.py:271 msgid "Change auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:255 +#: xpack/plugins/change_auth_plan/models.py:272 msgid "Verify auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:256 +#: xpack/plugins/change_auth_plan/models.py:273 msgid "Keep auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:283 +#: xpack/plugins/change_auth_plan/models.py:274 +msgid "Finished" +msgstr "结束" + +#: xpack/plugins/change_auth_plan/models.py:300 msgid "Step" msgstr "步骤" -#: xpack/plugins/change_auth_plan/models.py:300 +#: xpack/plugins/change_auth_plan/models.py:317 msgid "Change auth plan task" msgstr "改密计划任务" -#: xpack/plugins/change_auth_plan/serializers.py:68 +#: xpack/plugins/change_auth_plan/serializers.py:54 +msgid "Run times" +msgstr "执行次数" + +#: xpack/plugins/change_auth_plan/serializers.py:70 msgid "* Please enter custom password" msgstr "* 请输入自定义密码" -#: xpack/plugins/change_auth_plan/serializers.py:78 +#: xpack/plugins/change_auth_plan/serializers.py:80 msgid "* Please enter the correct password length" msgstr "* 请输入正确的密码长度" -#: xpack/plugins/change_auth_plan/serializers.py:81 +#: xpack/plugins/change_auth_plan/serializers.py:83 msgid "* Password length range 6-30 bits" msgstr "* 密码长度范围 6-30 位" -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:19 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:24 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:23 -#: xpack/plugins/change_auth_plan/views.py:133 -msgid "Plan execution list" -msgstr "执行列表" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:62 -msgid "Add asset to this plan" -msgstr "添加资产" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:87 -msgid "Add node to this plan" -msgstr "添加节点" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:7 -msgid "" -"When the user password on the asset is changed, the action is performed " -"using the admin user associated with the asset" -msgstr "更改资产上的用户密码时,将会使用与该资产关联的管理用户进行操作" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74 -msgid "Length" -msgstr "长度" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:132 -msgid "Run plan manually" -msgstr "手动执行计划" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:176 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:102 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:90 -msgid "Execute failed" -msgstr "执行失败" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:31 -msgid "Execution list of plan" -msgstr "执行历史列表" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:104 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:106 -msgid "Log" -msgstr "日志" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:61 -msgid "Retry" -msgstr "重试" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:95 -msgid "Run failed" -msgstr "执行失败" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:5 -#: xpack/plugins/change_auth_plan/views.py:51 -msgid "Create plan" -msgstr "创建计划" - #: xpack/plugins/change_auth_plan/utils.py:437 msgid "Invalid/incorrect password" msgstr "无效/错误 密码" @@ -6394,56 +3597,7 @@ msgstr "连接主机失败" msgid "Data could not be sent to remote" msgstr "无法将数据发送到远程" -#: xpack/plugins/change_auth_plan/views.py:34 -msgid "Plan list" -msgstr "计划列表" - -#: xpack/plugins/change_auth_plan/views.py:75 -msgid "Update plan" -msgstr "更新计划" - -#: xpack/plugins/change_auth_plan/views.py:148 -msgid "Plan execution task list" -msgstr "执行任务列表" - -#: xpack/plugins/cloud/forms.py:15 -msgid "Access Key" -msgstr "" - -#: xpack/plugins/cloud/forms.py:19 -msgid "Secret Key" -msgstr "" - -#: xpack/plugins/cloud/forms.py:56 -msgid "Select account" -msgstr "选择账户" - -#: xpack/plugins/cloud/forms.py:62 -msgid "Select regions" -msgstr "选择地域" - -#: xpack/plugins/cloud/forms.py:68 -msgid "Select instances" -msgstr "选择实例" - -#: xpack/plugins/cloud/forms.py:74 -msgid "Select node" -msgstr "选择节点" - -#: xpack/plugins/cloud/forms.py:80 xpack/plugins/orgs/forms.py:20 -msgid "Select admins" -msgstr "选择管理员" - -#: xpack/plugins/cloud/forms.py:85 -msgid "Tips: The asset information is always covered" -msgstr "提示:资产信息总是被覆盖" - -#: xpack/plugins/cloud/meta.py:9 xpack/plugins/cloud/views.py:27 -#: xpack/plugins/cloud/views.py:44 xpack/plugins/cloud/views.py:62 -#: xpack/plugins/cloud/views.py:78 xpack/plugins/cloud/views.py:92 -#: xpack/plugins/cloud/views.py:109 xpack/plugins/cloud/views.py:127 -#: xpack/plugins/cloud/views.py:143 xpack/plugins/cloud/views.py:158 -#: xpack/plugins/cloud/views.py:172 +#: xpack/plugins/cloud/meta.py:9 msgid "Cloud center" msgstr "云管中心" @@ -6455,9 +3609,7 @@ msgstr "有效" msgid "Unavailable" msgstr "无效" -#: xpack/plugins/cloud/models.py:39 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:51 -#: xpack/plugins/cloud/templates/cloud/account_list.html:13 +#: xpack/plugins/cloud/models.py:39 xpack/plugins/cloud/serializers.py:31 msgid "Provider" msgstr "云服务商" @@ -6470,11 +3622,10 @@ msgid "Access key secret" msgstr "" #: xpack/plugins/cloud/models.py:64 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:26 msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:122 +#: xpack/plugins/cloud/models.py:122 xpack/plugins/cloud/serializers.py:55 msgid "Regions" msgstr "地域" @@ -6482,14 +3633,11 @@ msgstr "地域" msgid "Instances" msgstr "实例" -#: xpack/plugins/cloud/models.py:136 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 +#: xpack/plugins/cloud/models.py:136 xpack/plugins/cloud/serializers.py:77 msgid "Covered always" -msgstr "总是覆盖" +msgstr "总是被覆盖" #: xpack/plugins/cloud/models.py:142 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:104 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:17 msgid "Date last sync" msgstr "最后同步日期" @@ -6502,8 +3650,6 @@ msgid "Succeed" msgstr "成功" #: xpack/plugins/cloud/models.py:220 xpack/plugins/cloud/models.py:275 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:51 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:49 msgid "Date sync" msgstr "同步日期" @@ -6528,10 +3674,13 @@ msgid "Sync instance task history" msgstr "同步实例任务历史" #: xpack/plugins/cloud/models.py:263 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:45 msgid "Instance" msgstr "实例" +#: xpack/plugins/cloud/models.py:266 +msgid "Region" +msgstr "地域" + #: xpack/plugins/cloud/providers/aliyun.py:19 msgid "Alibaba Cloud" msgstr "阿里云" @@ -6582,7 +3731,7 @@ msgstr "华北-北京4" #: xpack/plugins/cloud/providers/huaweicloud.py:28 msgid "CN Northeast-Dalian" -msgstr "东北-大连" +msgstr "华北-大连" #: xpack/plugins/cloud/providers/huaweicloud.py:29 msgid "CN South-Guangzhou" @@ -6590,7 +3739,7 @@ msgstr "华南-广州" #: xpack/plugins/cloud/providers/huaweicloud.py:30 msgid "CN Southwest-Guiyang1" -msgstr "西南-贵阳一" +msgstr "西南-贵阳1" #: xpack/plugins/cloud/providers/huaweicloud.py:31 msgid "EU-Paris" @@ -6604,116 +3753,32 @@ msgstr "拉美-圣地亚哥" msgid "Tencent Cloud" msgstr "腾讯云" -#: xpack/plugins/cloud/templates/cloud/account_detail.html:17 -#: xpack/plugins/cloud/views.py:79 -msgid "Account detail" -msgstr "账户详情" +#: xpack/plugins/cloud/serializers.py:53 +msgid "History count" +msgstr "用户数量" -#: xpack/plugins/cloud/templates/cloud/account_list.html:5 -#: xpack/plugins/cloud/views.py:45 -msgid "Create account" -msgstr "创建账户" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:33 -msgid "Node & AdminUser" -msgstr "节点 & 管理用户" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66 -msgid "Load failed" -msgstr "加载失败" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:17 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:19 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:18 -#: xpack/plugins/cloud/views.py:144 -msgid "Sync task detail" -msgstr "同步任务详情" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:20 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:22 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21 -#: xpack/plugins/cloud/views.py:159 -msgid "Sync task history" -msgstr "同步历史列表" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:25 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24 -#: xpack/plugins/cloud/views.py:173 -msgid "Sync instance list" -msgstr "同步实例列表" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:145 -msgid "Run task manually" -msgstr "手动执行任务" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:188 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:102 -msgid "Sync success" -msgstr "同步成功" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:46 -msgid "New count" -msgstr "新增" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:47 -msgid "Unsync count" -msgstr "未同步" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:48 -msgid "Synced count" -msgstr "已同步" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:49 -msgid "Released count" -msgstr "已释放" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 -msgid "Delete released assets" -msgstr "删除已释放的资产" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:5 -msgid "Create sync instance task" -msgstr "创建同步实例任务" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:14 -msgid "Run count" -msgstr "执行次数" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:15 +#: xpack/plugins/cloud/serializers.py:54 msgid "Instance count" msgstr "实例个数" +#: xpack/plugins/cloud/serializers.py:75 +msgid "Account name" +msgstr "账户名称" + +#: xpack/plugins/cloud/serializers.py:76 +#: xpack/plugins/gathered_user/serializers.py:20 +msgid "Periodic display" +msgstr "定时执行" + #: xpack/plugins/cloud/utils.py:38 msgid "Account unavailable" msgstr "账户无效" -#: xpack/plugins/cloud/views.py:63 -msgid "Update account" -msgstr "更新账户" - -#: xpack/plugins/cloud/views.py:93 -msgid "Sync instance task list" -msgstr "同步实例任务列表" - -#: xpack/plugins/cloud/views.py:110 -msgid "Create sync Instance task" -msgstr "创建同步实例任务" - -#: xpack/plugins/cloud/views.py:128 -msgid "Update sync Instance task" -msgstr "更新同步实例任务" - #: xpack/plugins/gathered_user/meta.py:11 -#: xpack/plugins/gathered_user/views.py:21 -#: xpack/plugins/gathered_user/views.py:34 -#: xpack/plugins/gathered_user/views.py:49 -#: xpack/plugins/gathered_user/views.py:69 msgid "Gathered user" msgstr "收集用户" #: xpack/plugins/gathered_user/models.py:39 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:13 msgid "Gather user task" msgstr "收集用户任务" @@ -6729,111 +3794,51 @@ msgstr "收集用户执行" msgid "Assets is empty, please change nodes" msgstr "资产为空,请更改节点" -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:170 -msgid "Asset user" -msgstr "资产用户" +#: xpack/plugins/gathered_user/serializers.py:21 +msgid "Executed times" +msgstr "执行次数" -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:7 -#: xpack/plugins/gathered_user/views.py:50 -msgid "Create task" -msgstr "创建任务" +#: xpack/plugins/interface/api.py:68 +msgid "It is already in the default setting state!" +msgstr "当前已经是初始化状态!" -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:18 -msgid "Periodic" -msgstr "定时执行" - -#: xpack/plugins/gathered_user/views.py:22 -msgid "Gathered user list" -msgstr "收集用户列表" - -#: xpack/plugins/gathered_user/views.py:70 -msgid "Update task" -msgstr "更新任务" - -#: xpack/plugins/interface/forms.py:17 xpack/plugins/interface/models.py:15 -msgid "Title of login page" -msgstr "登录页面标题" - -#: xpack/plugins/interface/forms.py:19 -msgid "" -"Tips: This will be displayed on the enterprise user login page. (eg: Welcome " -"to the JumpServer open source fortress)" -msgstr "提示:将会显示在企业版用户登录页面(eg: 欢迎使用JumpServer开源堡垒机)" - -#: xpack/plugins/interface/forms.py:25 xpack/plugins/interface/models.py:19 -msgid "Image of login page" -msgstr "登录页面图片" - -#: xpack/plugins/interface/forms.py:27 -msgid "" -"Tips: This will be displayed on the enterprise user login page. (suggest " -"image size: 492px*472px)" -msgstr "提示:将会显示在企业版用户登录页面(建议图片大小为: 492*472px)" - -#: xpack/plugins/interface/forms.py:33 xpack/plugins/interface/models.py:23 -msgid "Website icon" -msgstr "网站图标" - -#: xpack/plugins/interface/forms.py:35 -msgid "Tips: website icon. (suggest image size: 16px*16px)" -msgstr "提示:网站图标(建议图片大小为: 16px*16px)" - -#: xpack/plugins/interface/forms.py:40 xpack/plugins/interface/models.py:27 -msgid "Logo of management page" -msgstr "管理页面logo" - -#: xpack/plugins/interface/forms.py:42 -msgid "" -"Tips: This will appear at the top left of the administration page. (suggest " -"image size: 185px*55px)" -msgstr "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px)" - -#: xpack/plugins/interface/forms.py:48 xpack/plugins/interface/models.py:31 -msgid "Logo of logout page" -msgstr "退出页面logo" - -#: xpack/plugins/interface/forms.py:50 -msgid "" -"Tips: This will be displayed on the enterprise user logout page. (suggest " -"image size: 82px*82px)" -msgstr "提示:将会显示在企业版用户退出页面(建议图片大小为:82px*82px)" +#: xpack/plugins/interface/api.py:72 +msgid "Restore default successfully." +msgstr "恢复默认成功!" #: xpack/plugins/interface/meta.py:10 msgid "Interface settings" msgstr "界面设置" -#: xpack/plugins/interface/templates/interface/interface.html:15 -#: xpack/plugins/interface/views.py:24 xpack/plugins/interface/views.py:25 -msgid "Interface setting" -msgstr "界面设置" +#: xpack/plugins/interface/models.py:15 +msgid "Title of login page" +msgstr "登录页面标题" -#: xpack/plugins/interface/templates/interface/interface.html:73 -#: xpack/plugins/interface/templates/interface/interface.html:108 -#: xpack/plugins/interface/templates/interface/interface.html:115 -msgid "Restore Default" -msgstr "恢复默认" +#: xpack/plugins/interface/models.py:19 +msgid "Image of login page" +msgstr "登录页面图片" -#: xpack/plugins/interface/templates/interface/interface.html:98 -msgid "This will restore default Settings of the interface !!!" -msgstr "您确定要恢复默认初始化吗?" +#: xpack/plugins/interface/models.py:23 +msgid "Website icon" +msgstr "网站图标" -#: xpack/plugins/interface/templates/interface/interface.html:107 -#: xpack/plugins/interface/views.py:55 -msgid "Restore default successfully." -msgstr "恢复默认成功!" +#: xpack/plugins/interface/models.py:27 +msgid "Logo of management page" +msgstr "管理页面logo" -#: xpack/plugins/interface/templates/interface/interface.html:114 -msgid "Restore default failed." -msgstr "恢复默认失败!" +#: xpack/plugins/interface/models.py:31 +msgid "Logo of logout page" +msgstr "退出页面logo" -#: xpack/plugins/interface/views.py:51 -msgid "It is already in the default setting state!" -msgstr "当前已经是初始化状态!" +#: xpack/plugins/license/api.py:46 +msgid "License import successfully" +msgstr "许可证导入成功" + +#: xpack/plugins/license/api.py:47 +msgid "License is invalid" +msgstr "无效的许可证" #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 -#: xpack/plugins/license/templates/license/license_detail.html:41 -#: xpack/plugins/license/templates/license/license_detail.html:46 -#: xpack/plugins/license/views.py:32 msgid "License" msgstr "许可证" @@ -6849,152 +3854,1601 @@ msgstr "企业版" msgid "Ultimate edition" msgstr "旗舰版" -#: xpack/plugins/license/templates/license/_license_import_modal.html:4 -#: xpack/plugins/license/templates/license/license_detail.html:86 -msgid "Import license" -msgstr "导入许可证" +#~ msgid "Target URL" +#~ msgstr "目标URL" -#: xpack/plugins/license/templates/license/_license_import_modal.html:9 -msgid "License file" -msgstr "许可证文件" +#~ msgid "Login username" +#~ msgstr "登录账号" -#: xpack/plugins/license/templates/license/license_detail.html:11 -msgid "Please Import License" -msgstr "请导入许可证" +#~ msgid "Login password" +#~ msgstr "登录密码" -#: xpack/plugins/license/templates/license/license_detail.html:13 -#: xpack/plugins/license/templates/license/license_detail.html:47 -msgid "License has expired" -msgstr "许可证已经过期" +#~ msgid "Database IP" +#~ msgstr "数据库IP" -#: xpack/plugins/license/templates/license/license_detail.html:15 -msgid "The license will at " -msgstr "许可证将在 " +#~ msgid "Database name" +#~ msgstr "数据库名" -#: xpack/plugins/license/templates/license/license_detail.html:15 -msgid " expired." -msgstr " 过期。" +#~ msgid "Database username" +#~ msgstr "数据库账号" -#: xpack/plugins/license/templates/license/license_detail.html:28 -#: xpack/plugins/license/views.py:33 -msgid "License detail" -msgstr "许可证详情" +#~ msgid "Database password" +#~ msgstr "数据库密码" -#: xpack/plugins/license/templates/license/license_detail.html:42 -msgid "No license" -msgstr "暂无许可证" +#~ msgid "Target address" +#~ msgstr "目标地址" -#: xpack/plugins/license/templates/license/license_detail.html:51 -msgid "Subscription ID" -msgstr "订阅授权ID" +#~ msgid "Operating parameter" +#~ msgstr "运行参数" -#: xpack/plugins/license/templates/license/license_detail.html:55 -msgid "Corporation" -msgstr "公司" +#~ msgid "Detail" +#~ msgstr "详情" -#: xpack/plugins/license/templates/license/license_detail.html:59 -msgid "Expired" -msgstr "过期时间" +#~ msgid "Create DatabaseApp" +#~ msgstr "创建数据库应用" -#: xpack/plugins/license/templates/license/license_detail.html:67 -msgid "Edition" -msgstr "版本" +#~ msgid "" +#~ "Before using this feature, make sure that the application loader has been " +#~ "uploaded to the application server and successfully published as a " +#~ "RemoteApp application" +#~ msgstr "" +#~ "使用此功能前,请确保已将应用加载器上传到应用服务器并成功发布为一个 " +#~ "RemoteApp 应用" -#: xpack/plugins/license/templates/license/license_detail.html:93 -msgid "Technology consulting" -msgstr "技术咨询" +#~ msgid "Download application loader" +#~ msgstr "下载应用加载器" -#: xpack/plugins/license/templates/license/license_detail.html:96 -msgid "Consult" -msgstr "咨询" +#~ msgid "Create RemoteApp" +#~ msgstr "创建远程应用" -#: xpack/plugins/license/views.py:47 -msgid "License import successfully" -msgstr "许可证导入成功" +#~ msgid "DatabaseApp list" +#~ msgstr "数据库应用列表" -#: xpack/plugins/license/views.py:49 -msgid "License is invalid" -msgstr "无效的许可证" +#~ msgid "DatabaseApp detail" +#~ msgstr "数据库应用详情" -#: xpack/plugins/orgs/forms.py:23 -msgid "Select auditor" -msgstr "选择审计员" +#~ msgid "My DatabaseApp" +#~ msgstr "我的数据库应用" -#: xpack/plugins/orgs/forms.py:28 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:75 -#: xpack/plugins/orgs/templates/orgs/org_list.html:13 -msgid "Admin" -msgstr "管理员" +#~ msgid "RemoteApp list" +#~ msgstr "远程应用列表" -#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:27 -#: xpack/plugins/orgs/views.py:44 xpack/plugins/orgs/views.py:62 -#: xpack/plugins/orgs/views.py:85 xpack/plugins/orgs/views.py:116 -msgid "Organizations" -msgstr "组织管理" +#~ msgid "Update RemoteApp" +#~ msgstr "更新远程应用" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:17 -#: xpack/plugins/orgs/templates/orgs/org_users.html:13 -#: xpack/plugins/orgs/views.py:86 -msgid "Org detail" -msgstr "组织详情" +#~ msgid "RemoteApp detail" +#~ msgstr "远程应用详情" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:20 -#: xpack/plugins/orgs/templates/orgs/org_users.html:16 -msgid "Org users" -msgstr "组织用户" +#~ msgid "My RemoteApp" +#~ msgstr "我的远程应用" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:83 -msgid "Add admin" -msgstr "添加管理员" +#~ msgid "Label" +#~ msgstr "标签" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:117 -msgid "Add auditor" -msgstr "添加审计员" +#~ msgid "" +#~ "root or other NOPASSWD sudo privilege user existed in asset,If asset is " +#~ "windows or other set any one, more see admin user left menu" +#~ msgstr "" +#~ "root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置" +#~ "一个, 更多信息查看左侧 `管理用户` 菜单" -#: xpack/plugins/orgs/templates/orgs/org_list.html:5 -msgid "Create organization " -msgstr "创建组织" +#~ msgid "Windows 2016 RDP protocol is different, If is window 2016, set it" +#~ msgstr "Windows 2016的RDP协议与之前不同,如果是请设置" -#: xpack/plugins/orgs/templates/orgs/org_users.html:59 -msgid "Add user to organization" -msgstr "添加用户" +#~ msgid "" +#~ "If your have some network not connect with each other, you can set domain" +#~ msgstr "" +#~ "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录" -#: xpack/plugins/orgs/views.py:28 -msgid "Org list" -msgstr "组织列表" +#~ msgid "Select assets" +#~ msgstr "选择资产" -#: xpack/plugins/orgs/views.py:45 -msgid "Create org" -msgstr "创建组织" +#~ msgid "Content should not be contain: {}" +#~ msgstr "内容不能包含: {}" -#: xpack/plugins/orgs/views.py:63 -msgid "Update org" -msgstr "更新组织" +#~ msgid "SSH gateway support proxy SSH,RDP,VNC" +#~ msgstr "SSH网关,支持代理SSH,RDP和VNC" -#: xpack/plugins/orgs/views.py:117 -msgid "Org user list" -msgstr "组织用户列表" +#~ msgid "Yes" +#~ msgstr "是" -#: xpack/plugins/vault/meta.py:11 xpack/plugins/vault/views.py:23 -#: xpack/plugins/vault/views.py:38 -msgid "Vault" -msgstr "密码匣子" +#~ msgid "No" +#~ msgstr "否" -#: xpack/plugins/vault/templates/vault/_xpack_import_modal.html:4 -msgid "Import vault" -msgstr "导入密码" +#~ msgid "Base platform" +#~ msgstr "基础平台" -#: xpack/plugins/vault/templates/vault/vault.html:64 -msgid "vault" -msgstr "密码匣子" +#~ msgid "Password or private key passphrase" +#~ msgstr "密码或密钥密码" -#: xpack/plugins/vault/views.py:24 -msgid "vault list" -msgstr "密码匣子" +#~ msgid "Invalid private key, Only support RSA/DSA format key" +#~ msgstr "不合法的密钥,仅支持RSA/DSA格式的密钥" -#: xpack/plugins/vault/views.py:39 -msgid "vault create" -msgstr "创建" +#~ msgid "Password and private key file must be input one" +#~ msgstr "密码和私钥, 必须输入一个" + +#~ msgid "Auto push system user to asset" +#~ msgstr "自动推送系统用户到资产" + +#~ msgid "" +#~ "1-100, High level will be using login asset as default, if user was " +#~ "granted more than 2 system user" +#~ msgstr "" +#~ "1-100, 1最低优先级,100最高优先级。授权多个用户时,高优先级的系统用户将会" +#~ "作为默认登录用户" + +#~ msgid "" +#~ "If you choose manual login mode, you do not need to fill in the username " +#~ "and password." +#~ msgstr "如果选择手动登录模式,用户名和密码可以不填写" + +#~ msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig" +#~ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" + +#~ msgid "SFTP root dir, tmp, home or custom" +#~ msgstr "SFTP的起始路径,tmp目录, 用户home目录或者自定义" + +#~ msgid "" +#~ "Username is dynamic, When connect asset, using current user's username" +#~ msgstr "用户名是动态的,登录资产时使用当前用户的用户名登录" + +#~ msgid "Update asset group" +#~ msgstr "更新用户组" + +#~ msgid "Hint: only change the field you want to update." +#~ msgstr "仅修改你需要更新的字段" + +#~ msgid "Select Asset" +#~ msgstr "选择资产" + +#~ msgid "Select System Users" +#~ msgstr "选择系统用户" + +#~ msgid "Enable-MFA" +#~ msgstr "启用多因子认证" + +#~ msgid "Update asset user auth" +#~ msgstr "更新资产用户认证信息" + +#~ msgid "Please input password" +#~ msgstr "请输入密码" + +#~ msgid "Asset user auth" +#~ msgstr "资产用户信息" + +#~ msgid "Get auth info error" +#~ msgstr "获取认证信息错误" + +#~ msgid "Test datetime: " +#~ msgstr "测试日期: " + +#~ msgid "Only latest version" +#~ msgstr "仅最新版本" + +#~ msgid "View" +#~ msgstr "查看" + +#~ msgid "Test" +#~ msgstr "测试" + +#~ msgid "Push" +#~ msgstr "推送" + +#~ msgid "Test gateway test connection" +#~ msgstr "测试连接网关" + +#~ msgid "If use nat, set the ssh real port" +#~ msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口" + +#~ msgid "Node detail" +#~ msgstr "节点详情" + +#~ msgid "Full name" +#~ msgstr "全称" + +#~ msgid "Add node" +#~ msgstr "新建节点" + +#~ msgid "Rename node" +#~ msgstr "重命名节点" + +#~ msgid "Delete node" +#~ msgstr "删除节点" + +#~ msgid "Create node failed" +#~ msgstr "创建节点失败" + +#~ msgid "Rename success" +#~ msgstr "重命名成功" + +#~ msgid "Basic" +#~ msgstr "基本" + +#~ msgid "Auto generate key" +#~ msgstr "自动生成密钥" + +#~ msgid "Other" +#~ msgstr "其它" + +#~ msgid "Asset detail" +#~ msgstr "资产详情" + +#~ msgid "Assets list" +#~ msgstr "资产列表" + +#~ msgid "Asset list of " +#~ msgstr "资产列表" + +#~ msgid "Quick update" +#~ msgstr "快速更新" + +#~ msgid "Test connective" +#~ msgstr "测试可连接性" + +#~ msgid "Replace node assets admin user with this" +#~ msgstr "替换资产的管理员" + +#~ msgid "Select nodes" +#~ msgstr "选择节点" + +#~ msgid "" +#~ "Admin users are asset (charged server) on the root, or have NOPASSWD: ALL " +#~ "sudo permissions users, " +#~ msgstr "" +#~ "管理用户是资产(被控服务器)上的 root,或拥有 NOPASSWD: ALL sudo 权限的用" +#~ "户," + +#~ msgid "" +#~ "JumpServer users of the system using the user to `push system user`, " +#~ "`get assets hardware information`, etc. " +#~ msgstr "JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等。" + +#~ msgid "Create admin user" +#~ msgstr "创建管理用户" + +#~ msgid "Asset user list" +#~ msgstr "资产用户列表" + +#~ msgid "Asset users of" +#~ msgstr "资产用户" + +#~ msgid "CPU" +#~ msgstr "CPU" + +#~ msgid "Disk" +#~ msgstr "硬盘" + +#~ msgid "Refresh hardware" +#~ msgstr "更新硬件信息" + +#~ msgid "" +#~ "The left side is the asset tree, right click to create, delete, and " +#~ "change the tree node, authorization asset is also organized as a node, " +#~ "and the right side is the asset under that node" +#~ msgstr "" +#~ "左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织" +#~ "的,右侧是属于该节点下的资产" + +#~ msgid "Create asset" +#~ msgstr "创建资产" + +#~ msgid "Hardware" +#~ msgstr "硬件" + +#~ msgid "Remove from this node" +#~ msgstr "从节点移除" + +#~ msgid "Add assets to node" +#~ msgstr "添加资产到节点" + +#~ msgid "Move assets to node" +#~ msgstr "移动资产到节点" + +#~ msgid "Refresh node hardware info" +#~ msgstr "更新节点资产硬件信息" + +#~ msgid "Test node connective" +#~ msgstr "测试节点资产可连接性" + +#~ msgid "Display only current node assets" +#~ msgstr "仅显示当前节点资产" + +#~ msgid "Displays all child node assets" +#~ msgstr "显示所有子节点资产" + +#~ msgid "This will delete the selected assets !!!" +#~ msgstr "删除选择资产" + +#~ msgid "Asset Deleting failed." +#~ msgstr "删除失败" + +#~ msgid "Asset Delete" +#~ msgstr "删除" + +#~ msgid "Asset Deleted." +#~ msgstr "已被删除" + +#~ msgid "Please select node" +#~ msgstr "请选择节点" + +#~ msgid "Configuration" +#~ msgstr "配置" + +#~ msgid "Rules" +#~ msgstr "规则" + +#~ msgid "Binding to system user" +#~ msgstr "绑定到系统用户" + +#~ msgid "" +#~ "System user bound some command filter, each command filter has some rules," +#~ msgstr "系统用户可以绑定一些命令过滤器,一个过滤器可以定义一些规则" + +#~ msgid "When user login asset with this system user, then run a command," +#~ msgstr "当用户使用这个系统用户登录资产,然后执行一个命令" + +#~ msgid "The command will be filter by rules, higher priority rule run first," +#~ msgstr "这个命令需要被绑定过滤器的所有规则匹配,高优先级先被匹配," + +#~ msgid "" +#~ "When a rule matched, if rule action is allow, then allow command execute," +#~ msgstr "当一个规则匹配到了,如果规则的动作是允许,这个命令会被放行," + +#~ msgid "else if action is deny, then command with be deny," +#~ msgstr "如果规则的动作是禁止,命令将会被禁止执行," + +#~ msgid "else match next rule, if none matched, allowed" +#~ msgstr "否则就匹配下一个规则,如果最后没有匹配到规则,则允许执行" + +#~ msgid "Create command filter" +#~ msgstr "创建命令过滤器" + +#~ msgid "Command filter rule list" +#~ msgstr "命令过滤器规则列表" + +#~ msgid "Create rule" +#~ msgstr "创建规则" + +#~ msgid "Strategy" +#~ msgstr "策略" + +#~ msgid "Gateway list" +#~ msgstr "网关列表" + +#~ msgid "Create gateway" +#~ msgstr "创建网关" + +#~ msgid "Test connection" +#~ msgstr "测试连接" + +#~ msgid "Can be connected" +#~ msgstr "可连接" + +#~ msgid "" +#~ "The domain function is added to address the fact that some environments " +#~ "(such as the hybrid cloud) cannot be connected directly by jumping on the " +#~ "gateway server." +#~ msgstr "" +#~ "网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通" +#~ "过网关服务器进行跳转登录。" + +#~ msgid "JMS => Domain gateway => Target assets" +#~ msgstr "JMS => 网域网关 => 目标资产" + +#~ msgid "Create domain" +#~ msgstr "创建网域" + +#~ msgid "Create label" +#~ msgstr "创建标签" + +#~ msgid "Create platform" +#~ msgstr "创建系统平台" + +#~ msgid "Test assets connective" +#~ msgstr "测试资产可连接性" + +#~ msgid "Push system user now" +#~ msgstr "立刻推送系统" + +#~ msgid "Have existed: " +#~ msgstr "已经存在: " + +#~ msgid "Home" +#~ msgstr "家目录" + +#~ msgid "Uid" +#~ msgstr "Uid" + +#~ msgid "Binding command filters" +#~ msgstr "绑定命令过滤器" + +#~ msgid "" +#~ "System user is JumpServer jump login assets used by the users, can be " +#~ "understood as the user login assets, such as web, sa, the dba (` ssh " +#~ "web@some-host `), rather than using a user the username login server jump " +#~ "(` ssh xiaoming@some-host `); " +#~ msgstr "" +#~ "系统用户是 JumpServer 跳转登录资产时使用的用户,可以理解为登录资产用户," +#~ "如 web,sa,dba(`ssh web@some-host`),而不是使用某个用户的用户名跳转登录" +#~ "服务器(`ssh xiaoming@some-host`);" + +#~ msgid "" +#~ "In simple terms, users log into JumpServer using their own username, and " +#~ "JumpServer uses system users to log into assets. " +#~ msgstr "" +#~ "简单来说是用户使用自己的用户名登录 JumpServer,JumpServer 使用系统用户登录" +#~ "资产。" + +#~ msgid "" +#~ "When system users are created, if you choose auto push JumpServer to use " +#~ "Ansible push system users into the asset, if the asset (Switch) does not " +#~ "support ansible, please manually fill in the account password." +#~ msgstr "" +#~ "系统用户创建时,如果选择了自动推送,JumpServer 会使用 Ansible 自动推送系统" +#~ "用户到资产中,如果资产(交换机)不支持 Ansible,请手动填写账号密码。" + +#~ msgid "Create system user" +#~ msgstr "创建系统用户" + +#~ msgid "Remove success" +#~ msgstr "移除成功" + +#~ msgid "Admin user list" +#~ msgstr "管理用户列表" + +#~ msgid "Update admin user" +#~ msgstr "更新管理用户" + +#~ msgid "Admin user detail" +#~ msgstr "管理用户详情" + +#~ msgid "Admin user assets" +#~ msgstr "管理用户关联资产" + +#~ msgid "Update asset" +#~ msgstr "更新资产" + +#~ msgid "Bulk update asset success" +#~ msgstr "批量更新资产成功" + +#~ msgid "Bulk update asset" +#~ msgstr "批量更新资产" + +#~ msgid "Command filter list" +#~ msgstr "命令过滤器列表" + +#~ msgid "Update command filter" +#~ msgstr "更新命令过滤器" + +#~ msgid "Command filter detail" +#~ msgstr "命令过滤器详情" + +#~ msgid "Create command filter rule" +#~ msgstr "创建命令过滤器规则" + +#~ msgid "Update command filter rule" +#~ msgstr "更新命令过滤器规则" + +#~ msgid "Update domain" +#~ msgstr "更新网域" + +#~ msgid "Domain detail" +#~ msgstr "网域详情" + +#~ msgid "Domain gateway list" +#~ msgstr "域网关列表" + +#~ msgid "Update gateway" +#~ msgstr "创建网关" + +#~ msgid "Label list" +#~ msgstr "标签列表" + +#~ msgid "Tips: Avoid using label names reserved internally: {}" +#~ msgstr "提示: 请避免使用内部预留标签名: {}" + +#~ msgid "Update label" +#~ msgstr "更新标签" + +#~ msgid "Update platform" +#~ msgstr "更新系统平台" + +#~ msgid "Platform detail" +#~ msgstr "平台详情" + +#~ msgid "System user list" +#~ msgstr "系统用户列表" + +#~ msgid "Update system user" +#~ msgstr "更新系统用户" + +#~ msgid "System user detail" +#~ msgstr "系统用户详情" + +#~ msgid "assets" +#~ msgstr "资产管理" + +#~ msgid "System user assets" +#~ msgstr "系统用户关联资产" + +#~ msgid "System user users" +#~ msgstr "系统用户关联用户" + +#~ msgid "Select user" +#~ msgstr "选择用户" + +#~ msgid "UA" +#~ msgstr "Agent" + +#~ msgid "Handlers" +#~ msgstr "操作者" + +#~ msgid "Command execution log" +#~ msgstr "命令执行" + +#~ msgid "Version detail" +#~ msgstr "版本详情" + +#~ msgid "Version run execution" +#~ msgstr "执行历史" + +#~ msgid "Last run" +#~ msgstr "最后运行" + +#~ msgid "Time delta" +#~ msgstr "运行时间" + +#~ msgid "Is success " +#~ msgstr "成功" + +#~ msgid "Last run failed hosts" +#~ msgstr "最后运行失败主机" + +#~ msgid "No hosts" +#~ msgstr "没有主机" + +#~ msgid "Last run success hosts" +#~ msgstr "最后运行成功主机" + +#~ msgid "Executions of " +#~ msgstr "执行历史 " + +#~ msgid "F/S/T" +#~ msgstr "失败/成功/总" + +#~ msgid "Ratio" +#~ msgstr "比例" + +#~ msgid "Execution detail" +#~ msgstr "执行历史详情" + +#~ msgid "Execution detail of" +#~ msgstr "执行历史详情" + +#~ msgid "Task name" +#~ msgstr "任务名称" + +#~ msgid "Failed assets" +#~ msgstr "失败资产" + +#~ msgid "No assets" +#~ msgstr "没有资产" + +#~ msgid "Success assets" +#~ msgstr "成功资产" + +#~ msgid "Asset configuration does not include the SSH protocol" +#~ msgstr "资产配置不包含 SSH 协议" + +#~ msgid "Selected assets" +#~ msgstr "已选择资产" + +#~ msgid "In total" +#~ msgstr "总共" + +#~ msgid "" +#~ "Select the left asset, select the running system user, execute command in " +#~ "batch" +#~ msgstr "选择左侧资产, 选择运行的系统用户,批量执行命令" + +#~ msgid "Unselected assets" +#~ msgstr "没有选中资产" + +#~ msgid "No input command" +#~ msgstr "没有输入命令" + +#~ msgid "No system user was selected" +#~ msgstr "没有选择系统用户" + +#~ msgid "Pending" +#~ msgstr "等待" + +#~ msgid "Task detail" +#~ msgstr "任务详情" + +#~ msgid "Task versions" +#~ msgstr "任务各版本" + +#~ msgid "Execution" +#~ msgstr "执行历史" + +#~ msgid "Last execution output" +#~ msgstr "最后执行输出" + +#~ msgid "Versions of " +#~ msgstr "版本" + +#~ msgid "Total versions" +#~ msgstr "版本数量" + +#~ msgid "Contents" +#~ msgstr "内容" + +#~ msgid "Run" +#~ msgstr "执行" + +#~ msgid "Task start: " +#~ msgstr "任务开始: " + +#~ msgid "Ops" +#~ msgstr "作业中心" + +#~ msgid "Task execution list" +#~ msgstr "任务执行列表" + +#~ msgid "Command execution list" +#~ msgstr "命令执行列表" + +#~ msgid "Users and user groups" +#~ msgstr "用户或用户组" + +#~ msgid "Assets and node" +#~ msgstr "资产或节点" + +#~ msgid "Add asset to this permission" +#~ msgstr "添加资产" + +#~ msgid "Add node to this permission" +#~ msgstr "添加节点" + +#~ msgid "Select system users" +#~ msgstr "选择系统用户" + +#~ msgid "Validity period" +#~ msgstr "有效期" + +#~ msgid "User count" +#~ msgstr "用户数量" + +#~ msgid "User group count" +#~ msgstr "用户组数量" + +#~ msgid "Asset count" +#~ msgstr "资产数量" + +#~ msgid "Node count" +#~ msgstr "节点数量" + +#~ msgid "System user count" +#~ msgstr "系统用户数量" + +#~ msgid "Create permission" +#~ msgstr "创建授权规则" + +#~ msgid "Refresh permission cache" +#~ msgstr "刷新授权缓存" + +#~ msgid "Refresh success" +#~ msgstr "刷新成功" + +#~ msgid "User list of " +#~ msgstr "用户列表" + +#~ msgid "Add user to asset permission" +#~ msgstr "添加用户" + +#~ msgid "Add user group to asset permission" +#~ msgstr "添加用户组" + +#~ msgid "Select user groups" +#~ msgstr "选择用户组" + +#~ msgid "DatabaseApp list of " +#~ msgstr "数据库应用列表" + +#~ msgid "Add DatabaseApp to this permission" +#~ msgstr "添加数据库应用" + +#~ msgid "Select DatabaseApp" +#~ msgstr "选择数据库应用" + +#~ msgid "DatabaseApp count" +#~ msgstr "数据库应用数量" + +#~ msgid "Add user to permission" +#~ msgstr "添加用户" + +#~ msgid "Add user group to permission" +#~ msgstr "添加用户组" + +#~ msgid "RemoteApp count" +#~ msgstr "远程应用数量" + +#~ msgid "RemoteApp list of " +#~ msgstr "远程应用列表" + +#~ msgid "Add RemoteApp to this permission" +#~ msgstr "添加远程应用" + +#~ msgid "Select RemoteApp" +#~ msgstr "选择远程应用" + +#~ msgid "Add user to this permission" +#~ msgstr "添加用户" + +#~ msgid "Add user group to this permission" +#~ msgstr "添加用户组" + +#~ msgid "Asset permission list" +#~ msgstr "资产授权列表" + +#~ msgid "Create asset permission" +#~ msgstr "创建权限规则" + +#~ msgid "Update asset permission" +#~ msgstr "更新资产授权" + +#~ msgid "Asset permission detail" +#~ msgstr "资产授权详情" + +#~ msgid "Asset permission user list" +#~ msgstr "资产授权用户列表" + +#~ msgid "Asset permission asset list" +#~ msgstr "资产授权资产列表" + +#~ msgid "DatabaseApp permission list" +#~ msgstr "数据库应用授权列表" + +#~ msgid "Create DatabaseApp permission" +#~ msgstr "创建数据库应用授权规则" + +#~ msgid "Update DatabaseApp permission" +#~ msgstr "更新数据库应用授权规则" + +#~ msgid "DatabaseApp permission detail" +#~ msgstr "数据库应用授权详情" + +#~ msgid "DatabaseApp permission user list" +#~ msgstr "数据库应用授权用户列表" + +#~ msgid "DatabaseApp permission DatabaseApp list" +#~ msgstr "数据库应用授权数据库应用列表" + +#~ msgid "RemoteApp permission list" +#~ msgstr "远程应用授权列表" + +#~ msgid "Create RemoteApp permission" +#~ msgstr "创建远程应用授权规则" + +#~ msgid "Update RemoteApp permission" +#~ msgstr "更新远程应用授权规则" + +#~ msgid "RemoteApp permission detail" +#~ msgstr "远程应用授权详情" + +#~ msgid "RemoteApp permission user list" +#~ msgstr "远程应用授权用户列表" + +#~ msgid "RemoteApp permission RemoteApp list" +#~ msgstr "远程应用授权远程应用列表" + +#~ msgid "Current SITE URL" +#~ msgstr "当前站点URL" + +#~ msgid "User Guide URL" +#~ msgstr "用户向导URL" + +#~ msgid "User first login update profile done redirect to it" +#~ msgstr "用户第一次登录,修改profile后重定向到地址" + +#~ msgid "Email Subject Prefix" +#~ msgstr "Email主题前缀" + +#~ msgid "Tips: Some word will be intercept by mail provider" +#~ msgstr "提示: 一些关键字可能会被邮件提供商拦截,如 跳板机、JumpServer" + +#~ msgid "SMTP host" +#~ msgstr "SMTP主机" + +#~ msgid "SMTP port" +#~ msgstr "SMTP端口" + +#~ msgid "SMTP user" +#~ msgstr "SMTP账号" + +#~ msgid "SMTP password" +#~ msgstr "SMTP密码" + +#~ msgid "Tips: Some provider use token except password" +#~ msgstr "提示:一些邮件提供商需要输入的是Token" + +#~ msgid "Send user" +#~ msgstr "发送账号" + +#~ msgid "Tips: Send mail account, default SMTP account as the send account" +#~ msgstr "提示:发送邮件账号,默认使用SMTP账号作为发送账号" + +#~ msgid "Test recipient" +#~ msgstr "测试收件人" + +#~ msgid "Tips: Used only as a test mail recipient" +#~ msgstr "提示:仅用来作为测试邮件收件人" + +#~ msgid "Use SSL" +#~ msgstr "使用SSL" + +#~ msgid "If SMTP port is 465, may be select" +#~ msgstr "如果SMTP端口是465,通常需要启用SSL" + +#~ msgid "Use TLS" +#~ msgstr "使用TLS" + +#~ msgid "If SMTP port is 587, may be select" +#~ msgstr "如果SMTP端口是587,通常需要启用TLS" + +#~ msgid "Create user email subject" +#~ msgstr "创建用户邮件的主题" + +#~ msgid "" +#~ "Tips: When creating a user, send the subject of the email (eg:Create " +#~ "account successfully)" +#~ msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" + +#~ msgid "Create user honorific" +#~ msgstr "创建用户邮件的敬语" + +#~ msgid "" +#~ "Tips: When creating a user, send the honorific of the email (eg:Hello)" +#~ msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" + +#~ msgid "Create user email content" +#~ msgstr "创建用户邮件的内容" + +#~ msgid "Tips:When creating a user, send the content of the email" +#~ msgstr "提示: 创建用户时,发送设置密码邮件的内容" + +#~ msgid "Signature" +#~ msgstr "署名" + +#~ msgid "Tips: Email signature (eg:jumpserver)" +#~ msgstr "提示: 邮件的署名 (例如: jumpserver)" + +#~ msgid "LDAP server" +#~ msgstr "LDAP地址" + +#~ msgid "Bind DN" +#~ msgstr "绑定DN" + +#~ msgid "User OU" +#~ msgstr "用户OU" + +#~ msgid "Use | split User OUs" +#~ msgstr "使用|分隔各OU" + +#~ msgid "User search filter" +#~ msgstr "用户过滤器" + +#, python-format +#~ msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" +#~ msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" + +#~ msgid "User attr map" +#~ msgstr "LDAP属性映射" + +#~ msgid "" +#~ "User attr map present how to map LDAP user attr to jumpserver, username," +#~ "name,email is jumpserver attr" +#~ msgstr "" +#~ "用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, " +#~ "name,email 是jumpserver的属性" + +#~ msgid "Enable LDAP auth" +#~ msgstr "启用LDAP认证" + +#~ msgid "" +#~ "After opening, all user login must use MFA(valid for all users, including " +#~ "administrators)" +#~ msgstr "" +#~ "开启后,所有用户登录必须使用多因子认证(对所有用户有效,包括管理员)" + +#~ msgid "Batch execute commands" +#~ msgstr "批量命令" + +#~ msgid "Allow user batch execute commands" +#~ msgstr "允许用户批量执行命令" + +#~ msgid "Service account registration" +#~ msgstr "终端注册" + +#~ msgid "" +#~ "Allow using bootstrap token register service account, when terminal " +#~ "setup, can disable it" +#~ msgstr "允许使用bootstrap token注册终端, 当终端注册成功后可以禁止" + +#~ msgid "Limit the number of login failures" +#~ msgstr "限制登录失败次数" + +#~ msgid "No logon interval" +#~ msgstr "禁止登录时间间隔" + +#~ msgid "" +#~ "Tip: (unit/minute) if the user has failed to log in for a limited number " +#~ "of times, no login is allowed during this time interval." +#~ msgstr "" +#~ "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" + +#~ msgid "Connection max idle time" +#~ msgstr "连接最大空闲时间" + +#~ msgid "If idle time more than it, disconnect connection Unit: minute" +#~ msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" + +#~ msgid "Password expiration time" +#~ msgstr "密码过期时间" + +#~ msgid "" +#~ "Tip: (unit: day) If the user does not update the password during the " +#~ "time, the user password will expire failure;The password expiration " +#~ "reminder mail will be automatic sent to the user by system within 5 days " +#~ "(daily) before the password expires" +#~ msgstr "" +#~ "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码" +#~ "过期提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" + +#~ msgid "Password minimum length" +#~ msgstr "密码最小长度 " + +#~ msgid "Must contain capital letters" +#~ msgstr "必须包含大写字母" + +#~ msgid "" +#~ "After opening, the user password changes and resets must contain " +#~ "uppercase letters" +#~ msgstr "开启后,用户密码修改、重置必须包含大写字母" + +#~ msgid "Must contain lowercase letters" +#~ msgstr "必须包含小写字母" + +#~ msgid "" +#~ "After opening, the user password changes and resets must contain " +#~ "lowercase letters" +#~ msgstr "开启后,用户密码修改、重置必须包含小写字母" + +#~ msgid "Must contain numeric characters" +#~ msgstr "必须包含数字字符" + +#~ msgid "" +#~ "After opening, the user password changes and resets must contain numeric " +#~ "characters" +#~ msgstr "开启后,用户密码修改、重置必须包含数字字符" + +#~ msgid "Must contain special characters" +#~ msgstr "必须包含特殊字符" + +#~ msgid "" +#~ "After opening, the user password changes and resets must contain special " +#~ "characters" +#~ msgstr "开启后,用户密码修改、重置必须包含特殊字符" + +#~ msgid "Password auth" +#~ msgstr "密码认证" + +#~ msgid "Public key auth" +#~ msgstr "密钥认证" + +#~ msgid "Heartbeat interval" +#~ msgstr "心跳间隔" + +#~ msgid "Units: seconds" +#~ msgstr "单位: 秒" + +#~ msgid "List sort by" +#~ msgstr "资产列表排序" + +#~ msgid "List page size" +#~ msgstr "资产分页每页数量" + +#~ msgid "Session keep duration" +#~ msgstr "会话保留时长" + +#~ msgid "" +#~ "Units: days, Session, record, command will be delete if more than " +#~ "duration, only in database" +#~ msgstr "" +#~ "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss" +#~ "等不受影响)" + +#~ msgid "Telnet login regex" +#~ msgstr "Telnet 成功正则表达式" + +#~ msgid "ex: Last\\s*login|success|成功" +#~ msgstr "" +#~ "登录telnet服务器成功后的提示正则表达式,如: Last\\s*login|success|成功 " + +#~ msgid "LDAP user list" +#~ msgstr "LDAP 用户列表" + +#~ msgid "Please submit the LDAP configuration before import" +#~ msgstr "请先提交LDAP配置再进行导入" + +#~ msgid "Refresh cache" +#~ msgstr "刷新缓存" + +#~ msgid "Existing" +#~ msgstr "已存在" + +#~ msgid "" +#~ "User is not currently selected, please check the user you want to import" +#~ msgstr "当前无勾选用户,请勾选你想要导入的用户" + +#~ msgid "Test LDAP user login" +#~ msgstr "测试LDAP 用户登录" + +#~ msgid "Save the configuration before testing the login" +#~ msgstr "请先提交LDAP配置再进行测试登录" + +#~ msgid "Please input username" +#~ msgstr "请输入用户名" + +#~ msgid "Basic setting" +#~ msgstr "基本设置" + +#~ msgid "Email setting" +#~ msgstr "邮件设置" + +#~ msgid "Email content setting" +#~ msgstr "邮件内容设置" + +#~ msgid "LDAP setting" +#~ msgstr "LDAP设置" + +#~ msgid "Terminal setting" +#~ msgstr "终端设置" + +#~ msgid "Security setting" +#~ msgstr "安全设置" + +#~ msgid "Create User setting" +#~ msgstr "创建用户设置" + +#~ msgid "Test login" +#~ msgstr "测试登录" + +#~ msgid "Bulk import" +#~ msgstr "一键导入" + +#~ msgid "Password check rule" +#~ msgstr "密码校验规则" + +#~ msgid "Command and Replay storage configuration migrated to" +#~ msgstr "命令和录像存储配置已迁移到" + +#~ msgid "Sessions -> Terminal -> Storage configuration" +#~ msgstr "会话管理 -> 终端管理 -> 存储配置" + +#~ msgid "Here" +#~ msgstr "这里" + +#~ msgid "Update setting successfully" +#~ msgstr "更新设置成功" + +#~ msgid "Container name" +#~ msgstr "容器名称" + +#~ msgid "Account key" +#~ msgstr "账户密钥" + +#~ msgid "Endpoint suffix" +#~ msgstr "端点后缀" + +#~ msgid "Bucket" +#~ msgstr "桶名称" + +#~ msgid "Endpoint" +#~ msgstr "端点" + +#~ msgid "" +#~ "\n" +#~ " Tips: If there are multiple hosts, separate them with a comma " +#~ "(,) \n" +#~ "
\n" +#~ " eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com\n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ " 提示: 如果有多台主机,请使用逗号 ( , ) 进行分割\n" +#~ "
\n" +#~ " eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com\n" +#~ " " + +#~ msgid "Index" +#~ msgstr "索引" + +#~ msgid "Doc type" +#~ msgstr "文档类型" + +#~ msgid "" +#~ "Command can store in server db or ES, default to server, more see docs" +#~ msgstr "" +#~ "命令支持存储到服务器端数据库、ES中,默认存储的服务器端数据库,更多查看文档" + +#~ msgid "" +#~ "Replay file can store in server disk, AWS S3, Aliyun OSS, default to " +#~ "server, more see docs" +#~ msgstr "" +#~ "录像文件支持存储到服务器端硬盘、AWS S3、 阿里云 OSS 中,默认存储到服务器端" +#~ "硬盘, 更多查看文档" + +#~ msgid "Export command" +#~ msgstr "导出命令" + +#~ msgid "Goto" +#~ msgstr "转到" + +#~ msgid "Create command storage" +#~ msgstr "创建命令存储" + +#~ msgid "Create replay storage" +#~ msgstr "创建录像存储" + +#~ msgid "Session detail" +#~ msgstr "会话详情" + +#~ msgid "Command list" +#~ msgstr "命令记录列表" + +#~ msgid "There is no command about this session" +#~ msgstr "该会话没有命令记录" + +#~ msgid "Login from" +#~ msgstr "登录来源" + +#~ msgid "Replay session" +#~ msgstr "回放会话" + +#~ msgid "Download replay" +#~ msgstr "下载录像" + +#~ msgid "Download" +#~ msgstr "下载" + +#~ msgid "Monitor session" +#~ msgstr "监控" + +#~ msgid "Terminate session" +#~ msgstr "终止会话" + +#~ msgid "Terminate success" +#~ msgstr "终断成功" + +#~ msgid "Duration" +#~ msgstr "时长" + +#~ msgid "Terminate selected" +#~ msgstr "终断所选" + +#~ msgid "Confirm finished" +#~ msgstr "确认已完成" + +#~ msgid "Terminate task send, waiting ..." +#~ msgstr "终断任务已发送,请等待" + +#~ msgid "Terminate" +#~ msgstr "终断" + +#~ msgid "Monitoring" +#~ msgstr "监控" + +#~ msgid "Finish session success" +#~ msgstr "标记会话完成成功" + +#~ msgid "Visit doc for replay play offline: " +#~ msgstr "访问文档查看如何离线播放: " + +#~ msgid "Terminal detail" +#~ msgstr "终端详情" + +#~ msgid "SSH port" +#~ msgstr "SSH端口" + +#~ msgid "Http port" +#~ msgstr "HTTP端口" + +#~ msgid "Storage configuration" +#~ msgstr "存储配置" + +#~ msgid "Addr" +#~ msgstr "地址" + +#~ msgid "Alive" +#~ msgstr "在线" + +#~ msgid "Accept" +#~ msgstr "接受" + +#~ msgid "Accept terminal registration" +#~ msgstr "接受终端注册" + +#~ msgid "Info" +#~ msgstr "信息" + +#~ msgid "Session online list" +#~ msgstr "在线会话" + +#~ msgid "Replay storage list" +#~ msgstr "录像存储列表" + +#~ msgid "Command storage list" +#~ msgstr "命令存储列表" + +#~ msgid "Update replay storage" +#~ msgstr "更新录像存储" + +#~ msgid "Update command storage" +#~ msgstr "更新命令存储" + +#~ msgid "Terminal list" +#~ msgstr "终端列表" + +#~ msgid "Update terminal" +#~ msgstr "更新终端" + +#~ msgid "Redirect to web terminal" +#~ msgstr "重定向到web terminal" + +#~ msgid "Connect ssh terminal" +#~ msgstr "连接ssh终端" + +#~ msgid "" +#~ "You should use your ssh client tools connect terminal: {}

{}" +#~ msgstr "你可以使用ssh客户端工具连接终端" + +#~ msgid "ago" +#~ msgstr "前" + +#~ msgid "My tickets" +#~ msgstr "我的工单" + +#~ msgid "Assigned me" +#~ msgstr "待处理" + +#~ msgid "Create ticket" +#~ msgstr "提交工单" + +#~ msgid "Ticket list" +#~ msgstr "工单列表" + +#~ msgid "Ticket detail" +#~ msgstr "工单详情" + +#~ msgid "I agree with the terms and conditions." +#~ msgstr "我同意条款和条件" + +#~ msgid "Please choose the terms and conditions." +#~ msgstr "请选择同意条款和条件" + +#~ msgid "Previous" +#~ msgstr "上一步" + +#~ msgid "User group list" +#~ msgstr "用户组列表" + +#~ msgid "Update user group" +#~ msgstr "更新用户组" + +#~ msgid "User group granted asset" +#~ msgstr "用户组授权资产" + +#~ msgid "First login" +#~ msgstr "首次登录" + +#~ msgid "Profile setting" +#~ msgstr "个人信息设置" + +#~ msgid "Bulk update user success" +#~ msgstr "批量更新用户成功" + +#~ msgid "Bulk update user" +#~ msgstr "批量更新用户" + +#~ msgid "User granted assets" +#~ msgstr "用户授权资产" + +#~ msgid "User granted RemoteApp" +#~ msgstr "用户授权远程应用" + +#~ msgid "User granted DatabaseApp" +#~ msgstr "用户授权数据库应用" + +#~ msgid "Password length" +#~ msgstr "密码长度" + +#~ msgid "" +#~ "Tips: The username of the user on the asset to be modified. if the user " +#~ "exists, change the password; If the user does not exist, create the user." +#~ msgstr "" +#~ "提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如" +#~ "果用户不存在,则创建用户。" + +#~ msgid "Plan execution list" +#~ msgstr "执行列表" + +#~ msgid "Add asset to this plan" +#~ msgstr "添加资产" + +#~ msgid "Add node to this plan" +#~ msgstr "添加节点" + +#~ msgid "" +#~ "When the user password on the asset is changed, the action is performed " +#~ "using the admin user associated with the asset" +#~ msgstr "更改资产上的用户密码时,将会使用与该资产关联的管理用户进行操作" + +#~ msgid "Length" +#~ msgstr "长度" + +#~ msgid "Run plan manually" +#~ msgstr "手动执行计划" + +#~ msgid "Execute failed" +#~ msgstr "执行失败" + +#~ msgid "Execution list of plan" +#~ msgstr "执行历史列表" + +#~ msgid "Log" +#~ msgstr "日志" + +#~ msgid "Retry" +#~ msgstr "重试" + +#~ msgid "Run failed" +#~ msgstr "执行失败" + +#~ msgid "Create plan" +#~ msgstr "创建计划" + +#~ msgid "Plan list" +#~ msgstr "计划列表" + +#~ msgid "Update plan" +#~ msgstr "更新计划" + +#~ msgid "Plan execution task list" +#~ msgstr "执行任务列表" + +#~ msgid "Select account" +#~ msgstr "选择账户" + +#~ msgid "Select regions" +#~ msgstr "选择地域" + +#~ msgid "Select instances" +#~ msgstr "选择实例" + +#~ msgid "Select node" +#~ msgstr "选择节点" + +#~ msgid "Select admins" +#~ msgstr "选择管理员" + +#~ msgid "Account detail" +#~ msgstr "账户详情" + +#~ msgid "Create account" +#~ msgstr "创建账户" + +#~ msgid "Node & AdminUser" +#~ msgstr "节点 & 管理用户" + +#~ msgid "Load failed" +#~ msgstr "加载失败" + +#~ msgid "Sync task detail" +#~ msgstr "同步任务详情" + +#~ msgid "Sync task history" +#~ msgstr "同步历史列表" + +#~ msgid "Sync instance list" +#~ msgstr "同步实例列表" + +#~ msgid "Run task manually" +#~ msgstr "手动执行任务" + +#~ msgid "Sync success" +#~ msgstr "同步成功" + +#~ msgid "New count" +#~ msgstr "新增" + +#~ msgid "Unsync count" +#~ msgstr "未同步" + +#~ msgid "Synced count" +#~ msgstr "已同步" + +#~ msgid "Released count" +#~ msgstr "已释放" + +#~ msgid "Delete released assets" +#~ msgstr "删除已释放的资产" + +#~ msgid "Create sync instance task" +#~ msgstr "创建同步实例任务" + +#~ msgid "Run count" +#~ msgstr "执行次数" + +#~ msgid "Update account" +#~ msgstr "更新账户" + +#~ msgid "Sync instance task list" +#~ msgstr "同步实例任务列表" + +#~ msgid "Create sync Instance task" +#~ msgstr "创建同步实例任务" + +#~ msgid "Update sync Instance task" +#~ msgstr "更新同步实例任务" + +#~ msgid "Asset user" +#~ msgstr "资产用户" + +#~ msgid "Create task" +#~ msgstr "创建任务" + +#~ msgid "Periodic" +#~ msgstr "定时执行" + +#~ msgid "Gathered user list" +#~ msgstr "收集用户列表" + +#~ msgid "Update task" +#~ msgstr "更新任务" + +#~ msgid "" +#~ "Tips: This will be displayed on the enterprise user login page. (eg: " +#~ "Welcome to the JumpServer open source fortress)" +#~ msgstr "" +#~ "提示:将会显示在企业版用户登录页面(eg: 欢迎使用JumpServer开源堡垒机)" + +#~ msgid "" +#~ "Tips: This will be displayed on the enterprise user login page. (suggest " +#~ "image size: 492px*472px)" +#~ msgstr "提示:将会显示在企业版用户登录页面(建议图片大小为: 492*472px)" + +#~ msgid "Tips: website icon. (suggest image size: 16px*16px)" +#~ msgstr "提示:网站图标(建议图片大小为: 16px*16px)" + +#~ msgid "" +#~ "Tips: This will appear at the top left of the administration page. " +#~ "(suggest image size: 185px*55px)" +#~ msgstr "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px)" + +#~ msgid "" +#~ "Tips: This will be displayed on the enterprise user logout page. (suggest " +#~ "image size: 82px*82px)" +#~ msgstr "提示:将会显示在企业版用户退出页面(建议图片大小为:82px*82px)" + +#~ msgid "Interface setting" +#~ msgstr "界面设置" + +#~ msgid "Restore Default" +#~ msgstr "恢复默认" + +#~ msgid "This will restore default Settings of the interface !!!" +#~ msgstr "您确定要恢复默认初始化吗?" + +#~ msgid "Restore default failed." +#~ msgstr "恢复默认失败!" + +#~ msgid "Import license" +#~ msgstr "导入许可证" + +#~ msgid "License file" +#~ msgstr "许可证文件" + +#~ msgid "Please Import License" +#~ msgstr "请导入许可证" + +#~ msgid "License has expired" +#~ msgstr "许可证已经过期" + +#~ msgid "The license will at " +#~ msgstr "许可证将在 " + +#~ msgid " expired." +#~ msgstr " 过期。" + +#~ msgid "License detail" +#~ msgstr "许可证详情" + +#~ msgid "No license" +#~ msgstr "暂无许可证" + +#~ msgid "Subscription ID" +#~ msgstr "订阅授权ID" + +#~ msgid "Corporation" +#~ msgstr "公司" + +#~ msgid "Expired" +#~ msgstr "过期时间" + +#~ msgid "Edition" +#~ msgstr "版本" + +#~ msgid "Technology consulting" +#~ msgstr "技术咨询" + +#~ msgid "Consult" +#~ msgstr "咨询" + +#~ msgid "Select auditor" +#~ msgstr "选择审计员" + +#~ msgid "Admin" +#~ msgstr "管理员" + +#~ msgid "Organizations" +#~ msgstr "组织管理" + +#~ msgid "Org detail" +#~ msgstr "组织详情" + +#~ msgid "Add admin" +#~ msgstr "添加管理员" + +#~ msgid "Create organization " +#~ msgstr "创建组织" + +#~ msgid "Org list" +#~ msgstr "组织列表" + +#~ msgid "Create org" +#~ msgstr "创建组织" + +#~ msgid "Update org" +#~ msgstr "更新组织" + +#~ msgid "Vault" +#~ msgstr "密码匣子" + +#~ msgid "Import vault" +#~ msgstr "导入密码" + +#~ msgid "vault" +#~ msgstr "密码匣子" + +#~ msgid "vault list" +#~ msgstr "密码匣子" + +#~ msgid "vault create" +#~ msgstr "创建" + +#~ msgid "Org users" +#~ msgstr "组织用户" + +#~ msgid "Add auditor" +#~ msgstr "添加审计员" + +#~ msgid "Add user to organization" +#~ msgstr "添加用户" + +#~ msgid "Org user list" +#~ msgstr "组织用户列表" #~ msgid "Total hosts" #~ msgstr "主机总数" @@ -7008,9 +5462,6 @@ msgstr "创建" #~ msgid "Region & Instance" #~ msgstr "地域 & 实例" -#~ msgid "Interval" -#~ msgstr "间隔" - #~ msgid "Crontab" #~ msgstr "Crontab" @@ -7026,9 +5477,6 @@ msgstr "创建" #~ msgid "History detail of" #~ msgstr "执行历史详情" -#~ msgid "History of " -#~ msgstr "执行历史" - #~ msgid "Assets count: {}" #~ msgstr "资产数量" @@ -7468,7 +5916,7 @@ msgstr "创建" #~ msgstr "导入 {} 个用户成功; 导入 {} 这些用户失败,因为对象没有属性'keys'" #~ msgid "Invalid private key" -#~ msgstr "ssh密钥不合法" +#~ msgstr "SSH密钥不合法" #~ msgid "Login JumpServer" #~ msgstr "登录 JumpServer" @@ -7488,9 +5936,6 @@ msgstr "创建" #~ msgid "Update assets hardware info period" #~ msgstr "定期更新资产硬件信息" -#~ msgid "Date finished" -#~ msgstr "结束日期" - #~ msgid "User id" #~ msgstr "用户" diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index 5f7c8318e..aef4c9b06 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -4,14 +4,17 @@ from django.shortcuts import get_object_or_404 from rest_framework import viewsets, generics from rest_framework.views import Response -from django.db.models import Count, Q from common.permissions import IsOrgAdmin from common.serializers import CeleryTaskSerializer -from orgs.utils import current_org from ..models import Task, AdHoc, AdHocExecution -from ..serializers import TaskSerializer, AdHocSerializer, \ - AdHocExecutionSerializer +from ..serializers import ( + TaskSerializer, + AdHocSerializer, + AdHocExecutionSerializer, + TaskDetailSerializer, + AdHocDetailSerializer, +) from ..tasks import run_ansible_task __all__ = [ @@ -26,6 +29,11 @@ class TaskViewSet(viewsets.ModelViewSet): serializer_class = TaskSerializer permission_classes = (IsOrgAdmin,) + def get_serializer_class(self): + if self.action == 'retrieve': + return TaskDetailSerializer + return super().get_serializer_class() + def get_queryset(self): queryset = super().get_queryset() queryset = queryset.select_related('latest_execution') @@ -48,6 +56,11 @@ class AdHocViewSet(viewsets.ModelViewSet): serializer_class = AdHocSerializer permission_classes = (IsOrgAdmin,) + def get_serializer_class(self): + if self.action == 'retrieve': + return AdHocDetailSerializer + return super().get_serializer_class() + def get_queryset(self): task_id = self.request.query_params.get('task') if task_id: diff --git a/apps/ops/forms.py b/apps/ops/forms.py deleted file mode 100644 index 6658980f8..000000000 --- a/apps/ops/forms.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django import forms - -from assets.models import SystemUser -from .models import CommandExecution - - -class CommandExecutionForm(forms.ModelForm): - class Meta: - model = CommandExecution - fields = ['run_as', 'command'] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - run_as_field = self.fields.get('run_as') - run_as_field.queryset = SystemUser.objects.all() diff --git a/apps/ops/migrations/0018_auto_20200509_1434.py b/apps/ops/migrations/0018_auto_20200509_1434.py new file mode 100644 index 000000000..5bbf87610 --- /dev/null +++ b/apps/ops/migrations/0018_auto_20200509_1434.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.10 on 2020-05-09 06:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0017_auto_20200306_1747'), + ] + + operations = [ + migrations.AlterField( + model_name='commandexecution', + name='date_created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'), + ), + migrations.AlterField( + model_name='commandexecution', + name='date_finished', + field=models.DateTimeField(null=True, verbose_name='Date finished'), + ), + migrations.AlterField( + model_name='commandexecution', + name='date_start', + field=models.DateTimeField(null=True, verbose_name='Date start'), + ), + migrations.AlterField( + model_name='commandexecution', + name='is_finished', + field=models.BooleanField(default=False, verbose_name='Is finished'), + ), + ] diff --git a/apps/ops/mixin.py b/apps/ops/mixin.py index d3d397220..fd6f9cf27 100644 --- a/apps/ops/mixin.py +++ b/apps/ops/mixin.py @@ -110,7 +110,7 @@ class PeriodTaskSerializerMixin(serializers.Serializer): max_length=128, allow_blank=True, allow_null=True, required=False, label=_('Regularly perform') ) - interval = serializers.IntegerField(allow_null=True, required=False) + interval = serializers.IntegerField(allow_null=True, required=False, label=_('Interval')) INTERVAL_MAX = 65535 INTERVAL_MIN = 1 diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 013fdc628..36ebd77e0 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -150,6 +150,10 @@ class AdHoc(OrgModelMixin): created_by = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Create by')) date_created = models.DateTimeField(auto_now_add=True, db_index=True) + @lazyproperty + def run_times(self): + return self.execution.count() + @property def inventory(self): if self.become: diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index 662131bc5..408c72915 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext from django.db import models - +from common.utils import lazyproperty from orgs.models import Organization from orgs.mixins.models import OrgModelMixin from ..ansible.runner import CommandRunner @@ -23,10 +23,10 @@ class CommandExecution(OrgModelMixin): command = models.TextField(verbose_name=_("Command")) _result = models.TextField(blank=True, null=True, verbose_name=_('Result')) user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True) - is_finished = models.BooleanField(default=False) - date_created = models.DateTimeField(auto_now_add=True) - date_start = models.DateTimeField(null=True) - date_finished = models.DateTimeField(null=True) + is_finished = models.BooleanField(default=False, verbose_name=_('Is finished')) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + date_start = models.DateTimeField(null=True, verbose_name=_('Date start')) + date_finished = models.DateTimeField(null=True, verbose_name=_('Date finished')) def __str__(self): return self.command[:10] @@ -40,6 +40,14 @@ class CommandExecution(OrgModelMixin): inv = JMSInventory(self.hosts.all(), run_as=username) return inv + @lazyproperty + def run_as_display(self): + return str(self.run_as) + + @lazyproperty + def user_display(self): + return str(self.user) + @property def result(self): if self._result: diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index 8d9e18e24..f07c1ca47 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -8,10 +8,16 @@ from ..models import Task, AdHoc, AdHocExecution, CommandExecution class AdHocExecutionSerializer(serializers.ModelSerializer): stat = serializers.SerializerMethodField() + last_success = serializers.ListField(source='success_hosts') + last_failure = serializers.DictField(source='failed_hosts') class Meta: model = AdHocExecution - fields = '__all__' + fields = [ + 'id', 'task', 'task_display', 'hosts_amount', 'adhoc', 'date_start', 'stat', + 'date_finished', 'timedelta', 'is_finished', 'is_success', 'result', 'summary', + 'short_id', 'adhoc_short_id', 'last_success', 'last_failure' + ] @staticmethod def get_task(obj): @@ -28,17 +34,15 @@ class AdHocExecutionSerializer(serializers.ModelSerializer): "failed": count_failed_hosts } - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields.extend(['short_id', 'adhoc_short_id']) - return fields - class AdHocExecutionExcludeResultSerializer(AdHocExecutionSerializer): - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields = [i for i in fields if i not in ['result', 'summary']] - return fields + class Meta: + model = AdHocExecution + fields = [ + 'id', 'task', 'task_display', 'hosts_amount', 'adhoc', 'date_start', 'stat', + 'date_finished', 'timedelta', 'is_finished', 'is_success', + 'short_id', 'adhoc_short_id', 'last_success', 'last_failure' + ] class TaskSerializer(serializers.ModelSerializer): @@ -59,8 +63,16 @@ class TaskSerializer(serializers.ModelSerializer): ] +class TaskDetailSerializer(TaskSerializer): + contents = serializers.ListField(source='latest_adhoc.tasks') + + class Meta(TaskSerializer.Meta): + fields = TaskSerializer.Meta.fields + ['contents'] + + class AdHocSerializer(serializers.ModelSerializer): become_display = serializers.ReadOnlyField() + tasks = serializers.ListField() class Meta: model = AdHoc @@ -78,6 +90,29 @@ class AdHocSerializer(serializers.ModelSerializer): } +class AdHocExecutionNestSerializer(serializers.ModelSerializer): + last_success = serializers.ListField(source='success_hosts') + last_failure = serializers.DictField(source='failed_hosts') + last_run = serializers.CharField(source='short_id') + + class Meta: + model = AdHocExecution + fields = ( + 'last_success', 'last_failure', 'last_run', 'timedelta', 'is_finished', + 'is_success' + ) + + +class AdHocDetailSerializer(AdHocSerializer): + latest_execution = AdHocExecutionNestSerializer(allow_null=True) + task_name = serializers.CharField(source='task.name') + + class Meta(AdHocSerializer.Meta): + fields = AdHocSerializer.Meta.fields + [ + 'latest_execution', 'created_by', 'run_times', 'task_name' + ] + + class CommandExecutionSerializer(serializers.ModelSerializer): result = serializers.JSONField(read_only=True) log_url = serializers.SerializerMethodField() diff --git a/apps/ops/templates/ops/adhoc_detail.html b/apps/ops/templates/ops/adhoc_detail.html deleted file mode 100644 index 1a28ee0e8..000000000 --- a/apps/ops/templates/ops/adhoc_detail.html +++ /dev/null @@ -1,201 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.task.name }}: {{ object.short_id }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - {% if object.run_as_admin %} - - - - - {% else %} - - - - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'ID' %}:{{ object.id }}
{% trans 'Hosts' %}:{{ object.hosts | length }}
{% trans 'Pattern' %}:{{ object.pattern }}
{% trans 'Options' %} - - {% for k, v in object.options.items %} - {{ k }} = {{ v }}
- {% endfor %} -
-
{% trans 'Run as' %} Admin
{% trans 'Run as' %}:{{ object.get_latest_execution.date_start }}
{% trans 'Become' %}{{ object.become_display }}
{% trans 'Created by' %}{{ object.created_by }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Run times' %}:{{ object.execution.all | length }}
{% trans 'Last run' %}:{{ object.latest_execution.short_id }}
{% trans 'Time delta' %}:{{ object.latest_execution.timedelta|floatformat}} s
{% trans 'Is finished' %}:{{ object.latest_execution.is_finished|yesno:"Yes,No,Unkown" }}
{% trans 'Is success ' %}:{{ object.latest_execution.is_success|yesno:"Yes,No,Unkown" }}
{% trans 'Tasks' %}: - - {% for task in object.tasks %} - {{ forloop.counter }}. {{ task.name }} ::: {{ task.action.module }}
- {% endfor %} -
-
-
-
-
-
-
-
- {% trans 'Last run failed hosts' %} -
-
- - - {% for host, task in object.latest_execution.failed_hosts.items %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - - {% empty %} - - - - {% endfor %} - -
{{ host }}: - {% for name, result in task.items %} - {{ name }} => {{ result.msg }} - {% endfor %} -
{% trans 'No hosts' %}
-
-
- -
-
- {% trans 'Last run success hosts' %} -
-
- - - {% for host in object.latest_execution.success_hosts %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - {% empty %} - - - - {% endfor %} - -
{{ host }}
{% trans 'No hosts' %}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/ops/templates/ops/adhoc_history.html b/apps/ops/templates/ops/adhoc_history.html deleted file mode 100644 index e9662cb45..000000000 --- a/apps/ops/templates/ops/adhoc_history.html +++ /dev/null @@ -1,146 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Executions of ' %} {{ object.task.name }}:{{ object.short_id }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - -
- - {% trans 'Date start' %}{% trans 'F/S/T' %}{% trans 'Ratio' %}{% trans 'Is finished' %}{% trans 'Is success' %}{% trans 'Time' %}{% trans 'Version' %}{% trans 'Action' %}
-
-
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/ops/templates/ops/adhoc_history_detail.html b/apps/ops/templates/ops/adhoc_history_detail.html deleted file mode 100644 index 52e5e03f0..000000000 --- a/apps/ops/templates/ops/adhoc_history_detail.html +++ /dev/null @@ -1,152 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Execution detail of' %} {{ object.task.name }}: {{ object.adhoc.short_id }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'ID' %}:{{ object.id }}
{% trans 'Task name' %}:{{ object.task.name }}
{% trans 'Version' %}:{{ object.adhoc.short_id }}
{% trans 'Date start' %}:{{ object.date_start }}
{% trans 'Time delta' %}:{{ object.timedelta|floatformat}} s
{% trans 'Is finished' %}:{{ object.is_finished|yesno:"Yes,No,Unkown" }}
{% trans 'Is success ' %}:{{ object.is_success|yesno:"Yes,No,Unkown" }}
-
-
-
-
-
-
- {% trans 'Failed assets' %} -
-
- - - {% for host, task in object.failed_hosts.items %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - - {% empty %} - - - - {% endfor %} - -
{{ host }}: - {% for name, result in task.items %} - {{ name }} => {{ result.msg }} - {% endfor %} -
{% trans 'No assets' %}
-
-
- -
-
- {% trans 'Success assets' %} -
-
- - - {% for host in object.success_hosts %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - {% empty %} - - - - {% endfor %} - -
{{ host }}
{% trans 'No assets' %}
-
-
-
-
-
-
-
-
- {% include 'users/_user_update_pk_modal.html' %} -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/ops/templates/ops/celery_task_log.html b/apps/ops/templates/ops/celery_task_log.html index 455fc28d6..c79ff42b2 100644 --- a/apps/ops/templates/ops/celery_task_log.html +++ b/apps/ops/templates/ops/celery_task_log.html @@ -88,4 +88,4 @@ }).on('resize', window, function () { window.fit.fit(term); }); - + \ No newline at end of file diff --git a/apps/ops/templates/ops/command_execution_create.html b/apps/ops/templates/ops/command_execution_create.html deleted file mode 100644 index 8912cbe71..000000000 --- a/apps/ops/templates/ops/command_execution_create.html +++ /dev/null @@ -1,335 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} -{% load bootstrap3 %} - -{% block custom_head_css_js %} - - - - - - - - - - - - -{% endblock %} - -{% block content %} -
-
-
-
-
-
-
- {% trans 'Loading' %} .. -
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- -
-
-
- - -
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/ops/templates/ops/command_execution_list.html b/apps/ops/templates/ops/command_execution_list.html deleted file mode 100644 index a21a1b108..000000000 --- a/apps/ops/templates/ops/command_execution_list.html +++ /dev/null @@ -1,129 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load common_tags %} - -{% block custom_head_css_js %} - - - -{% endblock %} - -{% block content_left_head %} -{% endblock %} - -{% block table_search %} -
-
-
- - - to - -
-
- {% if user_list %} -
- -
- {% endif %} -
- -
-
-
- -
-
-
-{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - {% for object in object_list %} - - - - - - - - - - - - {% endfor %} - -
{% trans 'Hosts' %}{% trans 'User' %}{% trans 'Command' %}{% trans 'Run as' %}{% trans 'Output' %}{% trans 'Finished' %}{% trans 'Success' %}{% trans 'Date start' %}
{{ forloop.counter }}{{ object.user.name }}{{ object.command| truncatechars:16 }}{{ object.run_as.username }}查看{{ object.is_finished | state_show | safe }}{{ object.is_success | state_show | safe }}{{ object.date_start }}
-{% endblock %} - -{% block custom_foot_js %} - - - -{% endblock %} - diff --git a/apps/ops/templates/ops/task_adhoc.html b/apps/ops/templates/ops/task_adhoc.html deleted file mode 100644 index bddd2388e..000000000 --- a/apps/ops/templates/ops/task_adhoc.html +++ /dev/null @@ -1,138 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Versions of ' %} {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - -
- - {% trans 'Version' %}{% trans 'Hosts' %}{% trans 'Pattern' %}{% trans 'Run as' %}{% trans 'Become' %}{% trans 'Datetime' %}{% trans 'Action' %}
-
-
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/ops/templates/ops/task_detail.html b/apps/ops/templates/ops/task_detail.html deleted file mode 100644 index 39696b3d9..000000000 --- a/apps/ops/templates/ops/task_detail.html +++ /dev/null @@ -1,192 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'ID' %}:{{ object.id }}
{% trans 'Name' %}:{{ object.name }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Total versions' %}{{ object.adhoc.all |length }}
{% trans 'Latest version' %}{{ object.latest_adhoc.short_id }}
{% trans 'Last run' %}:{{ object.latest_execution.date_start }}
{% trans 'Time delta' %}:{{ object.latest_execution.timedelta|floatformat}} s
{% trans 'Is finished' %}: - {% if object.latest_execution.is_finished %} - {% trans 'Yes' %} - {% else %} - {% trans 'No' %} - {% endif %} -
{% trans 'Is success ' %}: - {% if object.latest_execution.is_success %} - {% trans 'Yes' %} - {% else %} - {% trans 'No' %} - {% endif %} -
{% trans 'Contents' %}: - - {% for task in object.latest_adhoc.tasks %} - {{ forloop.counter }}. {{ task.name }} ::: {{ task.action.module }}
- {% endfor %} -
-
-
-
-
-
-
-
- {% trans 'Last run failed hosts' %} -
-
- - - {% for host, task in object.latest_execution.failed_hosts.items %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - - {% empty %} - - - - {% endfor %} - -
{{ host }}: - {% for name, result in task.items %} - {{ name }} => {{ result.msg }} - {% endfor %} -
{% trans 'No hosts' %}
-
-
- -
-
- {% trans 'Last run success hosts' %} -
-
- - - {% for host in object.latest_execution.success_hosts %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - {% empty %} - - - - {% endfor %} - -
{{ host }}
{% trans 'No hosts' %}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/ops/templates/ops/task_history.html b/apps/ops/templates/ops/task_history.html deleted file mode 100644 index 06dc3416c..000000000 --- a/apps/ops/templates/ops/task_history.html +++ /dev/null @@ -1,160 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Executions of ' %} {{ object.name }}:{{ object.short_id }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - -
- - {% trans 'Date start' %}{% trans 'F/S/T' %}{% trans 'Ratio' %}{% trans 'Is finished' %}{% trans 'Is success' %}{% trans 'Time' %}{% trans 'Version' %}{% trans 'Action' %}
-
-
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html deleted file mode 100644 index a004138df..000000000 --- a/apps/ops/templates/ops/task_list.html +++ /dev/null @@ -1,119 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} -{% block table_container %} - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Run times' %}{% trans 'Hosts' %}{% trans 'Success' %}{% trans 'Date' %}{% trans 'Time' %}{% trans 'Action' %}
-{% endblock %} - -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py index f17fb8662..21bc4cc41 100644 --- a/apps/ops/urls/view_urls.py +++ b/apps/ops/urls/view_urls.py @@ -10,15 +10,5 @@ app_name = "ops" urlpatterns = [ # Resource Task url - path('task/', views.TaskListView.as_view(), name='task-list'), - path('task//', views.TaskDetailView.as_view(), name='task-detail'), - path('task//adhoc/', views.TaskAdhocView.as_view(), name='task-adhoc'), - path('task//executions/', views.TaskExecutionView.as_view(), name='task-execution'), - path('adhoc//', views.AdHocDetailView.as_view(), name='adhoc-detail'), - path('adhoc//executions/', views.AdHocExecutionView.as_view(), name='adhoc-execution'), - path('adhoc/executions//', views.AdHocExecutionDetailView.as_view(), name='adhoc-execution-detail'), path('celery/task//log/', views.CeleryTaskLogView.as_view(), name='celery-task-log'), - - path('command-executions/', views.CommandExecutionListView.as_view(), name='command-execution-list'), - path('command-executions/create/', views.CommandExecutionCreateView.as_view(), name='command-execution-create'), -] +] \ No newline at end of file diff --git a/apps/ops/views/celery.py b/apps/ops/views.py similarity index 100% rename from apps/ops/views/celery.py rename to apps/ops/views.py diff --git a/apps/ops/views/__init__.py b/apps/ops/views/__init__.py deleted file mode 100644 index 58bb835a6..000000000 --- a/apps/ops/views/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .adhoc import * -from .celery import * -from .command import * \ No newline at end of file diff --git a/apps/ops/views/adhoc.py b/apps/ops/views/adhoc.py deleted file mode 100644 index e21c7b575..000000000 --- a/apps/ops/views/adhoc.py +++ /dev/null @@ -1,127 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from django.utils.translation import ugettext as _ -from django.conf import settings -from django.views.generic import ListView, DetailView, TemplateView - -from common.mixins import DatetimeSearchMixin -from common.permissions import PermissionsMixin, IsOrgAdmin -from orgs.utils import current_org -from ..models import Task, AdHoc, AdHocExecution - - -__all__ = [ - 'TaskListView', 'TaskDetailView', 'TaskExecutionView', - 'TaskAdhocView', 'AdHocDetailView', 'AdHocExecutionDetailView', - 'AdHocExecutionView' -] - - -class TaskListView(PermissionsMixin, TemplateView): - paginate_by = settings.DISPLAY_PER_PAGE - model = Task - ordering = ('-date_created',) - context_object_name = 'task_list' - template_name = 'ops/task_list.html' - keyword = '' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Task list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class TaskDetailView(PermissionsMixin, DetailView): - model = Task - template_name = 'ops/task_detail.html' - permission_classes = [IsOrgAdmin] - - def get_queryset(self): - queryset = super().get_queryset() - return queryset - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Task detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class TaskAdhocView(PermissionsMixin, DetailView): - model = Task - template_name = 'ops/task_adhoc.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Task versions'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class TaskExecutionView(PermissionsMixin, DetailView): - model = Task - template_name = 'ops/task_history.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Task execution list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdHocDetailView(PermissionsMixin, DetailView): - model = AdHoc - template_name = 'ops/adhoc_detail.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Task detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdHocExecutionView(PermissionsMixin, DetailView): - model = AdHoc - template_name = 'ops/adhoc_history.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Version run execution'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdHocExecutionDetailView(PermissionsMixin, DetailView): - model = AdHocExecution - template_name = 'ops/adhoc_history_detail.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Execution detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - - - diff --git a/apps/ops/views/command.py b/apps/ops/views/command.py deleted file mode 100644 index 4dfc3d2e7..000000000 --- a/apps/ops/views/command.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.utils.translation import ugettext as _ -from django.conf import settings -from django.views.generic import ListView, TemplateView - -from common.permissions import ( - PermissionsMixin, IsOrgAdmin, IsValidUser, IsOrgAuditor -) -from common.mixins import DatetimeSearchMixin -from orgs.utils import tmp_to_root_org -from ..models import CommandExecution -from ..forms import CommandExecutionForm - - -__all__ = [ - 'CommandExecutionListView', 'CommandExecutionCreateView' -] - - -class CommandExecutionListView(PermissionsMixin, DatetimeSearchMixin, ListView): - template_name = 'ops/command_execution_list.html' - model = CommandExecution - paginate_by = settings.DISPLAY_PER_PAGE - ordering = ('-date_created',) - context_object_name = 'task_list' - keyword = '' - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def _get_queryset(self): - self.keyword = self.request.GET.get('keyword', '') - queryset = super().get_queryset() - if self.date_from: - queryset = queryset.filter(date_start__gte=self.date_from) - if self.date_to: - queryset = queryset.filter(date_start__lte=self.date_to) - if self.keyword: - queryset = queryset.filter(command__icontains=self.keyword) - return queryset - - def get_queryset(self): - queryset = self._get_queryset().filter(user=self.request.user) - return queryset - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Command execution list'), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'keyword': self.keyword, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandExecutionCreateView(PermissionsMixin, TemplateView): - template_name = 'ops/command_execution_create.html' - form_class = CommandExecutionForm - permission_classes = [IsValidUser] - - def get_permissions(self): - if not settings.SECURITY_COMMAND_EXECUTION: - return [IsOrgAdmin] - return super().get_permissions() - - def get_user_system_users(self): - from perms.utils import AssetPermissionUtil - user = self.request.user - with tmp_to_root_org(): - util = AssetPermissionUtil(user) - system_users = util.get_system_users() - return system_users - - def get_context_data(self, **kwargs): - system_users = self.get_user_system_users() - context = { - 'app': _('Ops'), - 'action': _('Command execution'), - 'form': self.get_form(), - 'system_users': system_users, - 'ws_port': settings.WS_LISTEN_PORT - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def get_form(self): - return self.form_class() diff --git a/apps/orgs/api.py b/apps/orgs/api.py index 63eebb25d..466ac7254 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -10,7 +10,7 @@ from common.permissions import IsSuperUserOrAppUser from .models import Organization from .serializers import OrgSerializer, OrgReadSerializer, \ OrgMembershipUserSerializer, OrgMembershipAdminSerializer, \ - OrgAllUserSerializer + OrgAllUserSerializer, OrgRetrieveSerializer from users.models import User, UserGroup from assets.models import Asset, Domain, AdminUser, SystemUser, Label from perms.models import AssetPermission @@ -28,10 +28,11 @@ class OrgViewSet(BulkModelViewSet): org = None def get_serializer_class(self): - if self.action in ('list', 'retrieve'): - return OrgReadSerializer - else: - return super().get_serializer_class() + mapper = { + 'list': OrgReadSerializer, + 'retrieve': OrgRetrieveSerializer + } + return mapper.get(self.action, super().get_serializer_class()) def get_data_from_model(self, model): if model == User: diff --git a/apps/orgs/middleware.py b/apps/orgs/middleware.py index 3e491d3d2..efbee2dde 100644 --- a/apps/orgs/middleware.py +++ b/apps/orgs/middleware.py @@ -34,8 +34,7 @@ class OrgMiddleware: def __call__(self, request): self.set_permed_org_if_need(request) org = get_org_from_request(request) - if org is not None: - request.current_org = org - set_current_org(org) + request.current_org = org + set_current_org(org) response = self.get_response(request) return response diff --git a/apps/orgs/mixins/api.py b/apps/orgs/mixins/api.py index 7695292c3..635e415bf 100644 --- a/apps/orgs/mixins/api.py +++ b/apps/orgs/mixins/api.py @@ -1,16 +1,17 @@ # -*- coding: utf-8 -*- # from django.shortcuts import get_object_or_404 -from rest_framework.viewsets import ModelViewSet +from rest_framework.viewsets import ModelViewSet, GenericViewSet from rest_framework_bulk import BulkModelViewSet -from common.mixins import CommonApiMixin +from common.mixins import CommonApiMixin, RelationMixin +from orgs.utils import current_org -from ..utils import set_to_root_org, filter_org_queryset +from ..utils import set_to_root_org from ..models import Organization __all__ = [ 'RootOrgViewMixin', 'OrgMembershipModelViewSetMixin', 'OrgModelViewSet', - 'OrgBulkModelViewSet', 'OrgQuerySetMixin', + 'OrgBulkModelViewSet', 'OrgQuerySetMixin', 'OrgGenericViewSet', 'OrgRelationMixin' ] @@ -44,6 +45,10 @@ class OrgModelViewSet(CommonApiMixin, OrgQuerySetMixin, ModelViewSet): pass +class OrgGenericViewSet(CommonApiMixin, OrgQuerySetMixin, GenericViewSet): + pass + + class OrgBulkModelViewSet(CommonApiMixin, OrgQuerySetMixin, BulkModelViewSet): def allow_bulk_destroy(self, qs, filtered): qs_count = qs.count() @@ -76,3 +81,12 @@ class OrgMembershipModelViewSetMixin: def get_queryset(self): queryset = self.membership_class.objects.filter(organization=self.org) return queryset + + +class OrgRelationMixin(RelationMixin): + def get_queryset(self): + queryset = super().get_queryset() + org_id = current_org.org_id() + if org_id is not None: + queryset = queryset.filter(**{f'{self.from_field}__org_id': org_id}) + return queryset diff --git a/apps/orgs/mixins/serializers.py b/apps/orgs/mixins/serializers.py index a34f8d9b1..2b415e31b 100644 --- a/apps/orgs/mixins/serializers.py +++ b/apps/orgs/mixins/serializers.py @@ -5,7 +5,7 @@ from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator from common.validators import ProjectUniqueValidator -from common.mixins import BulkSerializerMixin +from common.mixins import BulkSerializerMixin, CommonSerializerMixin from ..utils import get_current_org_id_for_serializer @@ -16,7 +16,7 @@ __all__ = [ ] -class OrgResourceSerializerMixin(serializers.Serializer): +class OrgResourceSerializerMixin(CommonSerializerMixin, serializers.Serializer): """ 通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id (同时为serializer.is_valid()对Model的unique_together校验做准备) diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index 5ff3b5f4d..ae98420e0 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -1,8 +1,7 @@ -import re from rest_framework.serializers import ModelSerializer from rest_framework import serializers -from users.models import User, UserGroup +from users.models import UserGroup from assets.models import Asset, Domain, AdminUser, SystemUser, Label from perms.models import AssetPermission from common.serializers import AdaptedBulkListSerializer @@ -15,57 +14,22 @@ class OrgSerializer(ModelSerializer): class Meta: model = Organization list_serializer_class = AdaptedBulkListSerializer - fields = '__all__' + fields_mini = ['id', 'name'] + fields_small = fields_mini + [ + 'created_by', 'date_created', 'comment' + ] + fields_m2m = ['users', 'admins', 'auditors'] + fields = fields_small + fields_m2m read_only_fields = ['created_by', 'date_created'] + extra_kwargs = { + 'admins': {'write_only': True}, + 'users': {'write_only': True}, + 'auditors': {'write_only': True}, + } -class OrgReadSerializer(ModelSerializer): - admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) - auditors = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) - users = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) - user_groups = serializers.SerializerMethodField() - assets = serializers.SerializerMethodField() - domains = serializers.SerializerMethodField() - admin_users = serializers.SerializerMethodField() - system_users = serializers.SerializerMethodField() - labels = serializers.SerializerMethodField() - perms = serializers.SerializerMethodField() - - class Meta: - model = Organization - fields = '__all__' - - @staticmethod - def get_data_from_model(obj, model): - current_org = get_current_org() - set_current_org(Organization.root()) - if model == Asset: - data = [o.hostname for o in model.objects.filter(org_id=obj.id)] - else: - data = [o.name for o in model.objects.filter(org_id=obj.id)] - set_current_org(current_org) - return data - - def get_user_groups(self, obj): - return self.get_data_from_model(obj, UserGroup) - - def get_assets(self, obj): - return self.get_data_from_model(obj, Asset) - - def get_domains(self, obj): - return self.get_data_from_model(obj, Domain) - - def get_admin_users(self, obj): - return self.get_data_from_model(obj, AdminUser) - - def get_system_users(self, obj): - return self.get_data_from_model(obj, SystemUser) - - def get_labels(self, obj): - return self.get_data_from_model(obj, Label) - - def get_perms(self, obj): - return self.get_data_from_model(obj, AssetPermission) +class OrgReadSerializer(OrgSerializer): + pass class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer): @@ -92,3 +56,12 @@ class OrgAllUserSerializer(serializers.Serializer): @staticmethod def get_user_display(obj): return str(obj) + + +class OrgRetrieveSerializer(OrgReadSerializer): + admins = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + auditors = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + users = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + + class Meta(OrgReadSerializer.Meta): + pass diff --git a/apps/orgs/urls/views_urls.py b/apps/orgs/urls/views_urls.py index 225457827..581eed2b0 100644 --- a/apps/orgs/urls/views_urls.py +++ b/apps/orgs/urls/views_urls.py @@ -1,14 +1,7 @@ # -*- coding: utf-8 -*- # - -from django.urls import path - -from .. import views - app_name = 'orgs' urlpatterns = [ - path('/switch/', views.SwitchOrgView.as_view(), name='org-switch'), - path('switch-a-org/', views.SwitchToAOrgView.as_view(), name='switch-a-org') ] diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index d5ea4ca30..7a4576e23 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -11,12 +11,18 @@ from .models import Organization def get_org_from_request(request): - oid = request.META.get("HTTP_X_JMS_ORG") + # query中优先级最高 + oid = request.GET.get("oid") + + # 其次header + if not oid: + oid = request.META.get("HTTP_X_JMS_ORG") + # 其次cookie + if not oid: + oid = request.COOKIES.get('X-JMS-ORG') + # 其次session if not oid: oid = request.session.get("oid") - request_params_oid = request.GET.get("oid") - if request_params_oid: - oid = request.GET.get("oid") if not oid: oid = Organization.DEFAULT_ID @@ -24,7 +30,7 @@ def get_org_from_request(request): oid = Organization.DEFAULT_ID elif oid.lower() == "root": oid = Organization.ROOT_ID - org = Organization.get_instance(oid) + org = Organization.get_instance(oid, True) return org diff --git a/apps/orgs/views.py b/apps/orgs/views.py deleted file mode 100644 index 0cbcd00e0..000000000 --- a/apps/orgs/views.py +++ /dev/null @@ -1,53 +0,0 @@ -from django.shortcuts import redirect, reverse -from django.conf import settings -from django.http import HttpResponseForbidden - -from django.views.generic import DetailView, View - -from .models import Organization -from common.utils import UUID_PATTERN - - -class SwitchOrgView(DetailView): - model = Organization - object = None - - def get(self, request, *args, **kwargs): - pk = kwargs.get('pk') - self.object = Organization.get_instance(pk) - oid = str(self.object.id) - request.session['oid'] = oid - org_change_to_url = settings.ORG_CHANGE_TO_URL - if org_change_to_url: - return redirect(org_change_to_url) - host = request.get_host() - referer = request.META.get('HTTP_REFERER', '') - if referer.find(host) == -1: - return redirect(reverse('index')) - if UUID_PATTERN.search(referer): - return redirect(reverse('index')) - # 组织管理员切换到组织审计员时(403) - if not self.object.get_org_admins().filter(id=request.user.id): - return redirect(reverse('index')) - return redirect(referer) - - -class SwitchToAOrgView(View): - def get(self, request, *args, **kwargs): - if request.user.is_common_user: - return HttpResponseForbidden() - admin_orgs = request.user.admin_orgs - audit_orgs = request.user.audit_orgs - default_org = Organization.default() - if admin_orgs: - if default_org in admin_orgs: - redirect_org = default_org - else: - redirect_org = admin_orgs[0] - return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id})) - if audit_orgs: - if default_org in audit_orgs: - redirect_org = default_org - else: - redirect_org = audit_orgs[0] - return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id})) diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py index 61cbd7d58..cba965d00 100644 --- a/apps/perms/api/__init__.py +++ b/apps/perms/api/__init__.py @@ -6,7 +6,9 @@ from .user_permission import * from .asset_permission_relation import * from .user_group_permission import * from .remote_app_permission import * +from .remote_app_permission_relation import * from .user_remote_app_permission import * from .database_app_permission import * from .database_app_permission_relation import * from .user_database_app_permission import * +from .system_user_permission import * diff --git a/apps/perms/api/asset_permission.py b/apps/perms/api/asset_permission.py index ff477f5af..18061f236 100644 --- a/apps/perms/api/asset_permission.py +++ b/apps/perms/api/asset_permission.py @@ -22,10 +22,7 @@ class AssetPermissionViewSet(OrgModelViewSet): 资产授权列表的增删改查api """ model = AssetPermission - serializer_classes = { - 'default': serializers.AssetPermissionCreateUpdateSerializer, - 'display': serializers.AssetPermissionListSerializer - } + serializer_class = serializers.AssetPermissionSerializer filter_fields = ['name'] permission_classes = (IsOrgAdmin,) diff --git a/apps/perms/api/asset_permission_relation.py b/apps/perms/api/asset_permission_relation.py index 3b68af775..4207a995c 100644 --- a/apps/perms/api/asset_permission_relation.py +++ b/apps/perms/api/asset_permission_relation.py @@ -33,7 +33,7 @@ class AssetPermissionUserRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionUserRelationSerializer model = models.AssetPermission.users.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', "user", "assetpermission", ] search_fields = ("user__name", "user__username", "assetpermission__name") @@ -64,7 +64,7 @@ class AssetPermissionUserGroupRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionUserGroupRelationSerializer model = models.AssetPermission.user_groups.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', "usergroup", "assetpermission" ] search_fields = ["usergroup__name", "assetpermission__name"] @@ -80,7 +80,7 @@ class AssetPermissionAssetRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionAssetRelationSerializer model = models.AssetPermission.assets.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'asset', 'assetpermission', ] search_fields = ["id", "asset__hostname", "asset__ip", "assetpermission__name"] @@ -111,7 +111,7 @@ class AssetPermissionNodeRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionNodeRelationSerializer model = models.AssetPermission.nodes.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'node', 'assetpermission', ] search_fields = ["node__value", "assetpermission__name"] @@ -127,7 +127,7 @@ class AssetPermissionSystemUserRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionSystemUserRelationSerializer model = models.AssetPermission.system_users.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'systemuser', 'assetpermission', ] search_fields = [ diff --git a/apps/perms/api/base.py b/apps/perms/api/base.py new file mode 100644 index 000000000..d4ffc9246 --- /dev/null +++ b/apps/perms/api/base.py @@ -0,0 +1,15 @@ +from django.db.models import F +from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins.api import OrgRelationMixin + + +__all__ = [ + 'RelationViewSet' +] + + +class RelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.annotate(**{f'{self.from_field}_display': F(f'{self.from_field}__name')}) + return queryset diff --git a/apps/perms/api/database_app_permission_relation.py b/apps/perms/api/database_app_permission_relation.py index 32ab34355..572896e34 100644 --- a/apps/perms/api/database_app_permission_relation.py +++ b/apps/perms/api/database_app_permission_relation.py @@ -1,14 +1,12 @@ # coding: utf-8 # - from rest_framework import generics from django.db.models import F, Value from django.db.models.functions import Concat from django.shortcuts import get_object_or_404 -from orgs.mixins.api import OrgBulkModelViewSet -from orgs.utils import current_org from common.permissions import IsOrgAdmin +from .base import RelationViewSet from .. import models, serializers __all__ = [ @@ -21,21 +19,11 @@ __all__ = [ ] -class RelationMixin(OrgBulkModelViewSet): - def get_queryset(self): - queryset = self.model.objects.all() - org_id = current_org.org_id() - if org_id is not None: - queryset = queryset.filter(databaseapppermission__org_id=org_id) - queryset = queryset.annotate(databaseapppermission_display=F('databaseapppermission__name')) - return queryset - - -class DatabaseAppPermissionUserRelationViewSet(RelationMixin): +class DatabaseAppPermissionUserRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionUserRelationSerializer - model = models.DatabaseAppPermission.users.through + m2m_field = models.DatabaseAppPermission.users.field permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'user', 'databaseapppermission' ] search_fields = ('user__name', 'user__username', 'databaseapppermission__name') @@ -46,11 +34,11 @@ class DatabaseAppPermissionUserRelationViewSet(RelationMixin): return queryset -class DatabaseAppPermissionUserGroupRelationViewSet(RelationMixin): +class DatabaseAppPermissionUserGroupRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionUserGroupRelationSerializer - model = models.DatabaseAppPermission.user_groups.through + m2m_field = models.DatabaseAppPermission.user_groups.field permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', "usergroup", "databaseapppermission" ] search_fields = ["usergroup__name", "databaseapppermission__name"] @@ -77,11 +65,11 @@ class DatabaseAppPermissionAllUserListApi(generics.ListAPIView): return users -class DatabaseAppPermissionDatabaseAppRelationViewSet(RelationMixin): +class DatabaseAppPermissionDatabaseAppRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionDatabaseAppRelationSerializer - model = models.DatabaseAppPermission.database_apps.through + m2m_field = models.DatabaseAppPermission.database_apps.field permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'databaseapp', 'databaseapppermission', ] search_fields = [ @@ -110,11 +98,11 @@ class DatabaseAppPermissionAllDatabaseAppListApi(generics.ListAPIView): return database_apps -class DatabaseAppPermissionSystemUserRelationViewSet(RelationMixin): +class DatabaseAppPermissionSystemUserRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionSystemUserRelationSerializer - model = models.DatabaseAppPermission.system_users.through + m2m_field = models.DatabaseAppPermission.system_users.field permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'systemuser', 'databaseapppermission' ] search_fields = [ diff --git a/apps/perms/api/remote_app_permission.py b/apps/perms/api/remote_app_permission.py index b7fa6de19..cb1998675 100644 --- a/apps/perms/api/remote_app_permission.py +++ b/apps/perms/api/remote_app_permission.py @@ -11,10 +11,8 @@ from ..serializers import ( RemoteAppPermissionSerializer, RemoteAppPermissionUpdateUserSerializer, RemoteAppPermissionUpdateRemoteAppSerializer, - RemoteAppPermissionListSerializer, ) - __all__ = [ 'RemoteAppPermissionViewSet', 'RemoteAppPermissionAddUserApi', 'RemoteAppPermissionAddRemoteAppApi', @@ -26,10 +24,7 @@ class RemoteAppPermissionViewSet(OrgModelViewSet): model = RemoteAppPermission filter_fields = ('name', ) search_fields = filter_fields - serializer_classes = { - 'default': RemoteAppPermissionSerializer, - 'display': RemoteAppPermissionListSerializer, - } + serializer_class = RemoteAppPermissionSerializer permission_classes = (IsOrgAdmin,) diff --git a/apps/perms/api/remote_app_permission_relation.py b/apps/perms/api/remote_app_permission_relation.py new file mode 100644 index 000000000..fe0ab7355 --- /dev/null +++ b/apps/perms/api/remote_app_permission_relation.py @@ -0,0 +1,79 @@ +# coding: utf-8 +# +from perms.api.base import RelationViewSet +from rest_framework import generics +from django.db.models import F +from django.shortcuts import get_object_or_404 + +from common.permissions import IsOrgAdmin +from .. import models, serializers + +__all__ = [ + 'RemoteAppPermissionUserRelationViewSet', + 'RemoteAppPermissionRemoteAppRelationViewSet', + 'RemoteAppPermissionAllRemoteAppListApi', + 'RemoteAppPermissionAllUserListApi', +] + + +class RemoteAppPermissionAllUserListApi(generics.ListAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = serializers.PermissionAllUserSerializer + filter_fields = ("username", "name") + search_fields = filter_fields + + def get_queryset(self): + pk = self.kwargs.get("pk") + perm = get_object_or_404(models.RemoteAppPermission, pk=pk) + users = perm.all_users.only( + *self.serializer_class.Meta.only_fields + ) + return users + + +class RemoteAppPermissionUserRelationViewSet(RelationViewSet): + serializer_class = serializers.RemoteAppPermissionUserRelationSerializer + m2m_field = models.RemoteAppPermission.users.field + permission_classes = (IsOrgAdmin,) + filter_fields = [ + 'id', 'user', 'remoteapppermission' + ] + search_fields = ('user__name', 'user__username', 'remoteapppermission__name') + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.annotate(user_display=F('user__name')) + return queryset + + +class RemoteAppPermissionRemoteAppRelationViewSet(RelationViewSet): + serializer_class = serializers.RemoteAppPermissionRemoteAppRelationSerializer + m2m_field = models.RemoteAppPermission.remote_apps.field + permission_classes = (IsOrgAdmin,) + filter_fields = [ + 'id', 'remoteapp', 'remoteapppermission', + ] + search_fields = [ + "id", "remoteapp__name", "remoteapppermission__name" + ] + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset \ + .annotate(remoteapp_display=F('remoteapp__name')) + return queryset + + +class RemoteAppPermissionAllRemoteAppListApi(generics.ListAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = serializers.RemoteAppPermissionAllRemoteAppSerializer + filter_fields = ("name",) + search_fields = filter_fields + + def get_queryset(self): + pk = self.kwargs.get("pk") + perm = get_object_or_404(models.RemoteAppPermission, pk=pk) + remote_apps = perm.all_remote_apps.only( + *self.serializer_class.Meta.only_fields + ) + return remote_apps diff --git a/apps/perms/api/system_user_permission.py b/apps/perms/api/system_user_permission.py new file mode 100644 index 000000000..aa0ceda39 --- /dev/null +++ b/apps/perms/api/system_user_permission.py @@ -0,0 +1,21 @@ + + +from rest_framework import generics +from common.permissions import IsValidUser +from orgs.utils import tmp_to_root_org +from .. import serializers + + +class SystemUserPermission(generics.ListAPIView): + permission_classes = (IsValidUser,) + serializer_class = serializers.SystemUserSerializer + + def get_queryset(self): + return self.get_user_system_users() + + def get_user_system_users(self): + from perms.utils import AssetPermissionUtil + user = self.request.user + with tmp_to_root_org(): + util = AssetPermissionUtil(user) + return util.get_system_users() diff --git a/apps/perms/api/user_database_app_permission.py b/apps/perms/api/user_database_app_permission.py index 3a973b8c1..19885d2ef 100644 --- a/apps/perms/api/user_database_app_permission.py +++ b/apps/perms/api/user_database_app_permission.py @@ -26,8 +26,8 @@ __all__ = [ class UserGrantedDatabaseAppsApi(generics.ListAPIView): permission_classes = (IsOrgAdminOrAppUser,) serializer_class = DatabaseAppSerializer - filter_fields = ['id', 'name'] - search_fields = ['name'] + filter_fields = ['id', 'name', 'type', 'comment'] + search_fields = ['name', 'comment'] def get_object(self): user_id = self.kwargs.get('pk', '') diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index 6af3bb6ba..17900a6bc 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -5,7 +5,7 @@ import uuid 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, RetrieveAPIView + ListAPIView, get_object_or_404, RetrieveAPIView, DestroyAPIView ) from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin @@ -25,6 +25,7 @@ __all__ = [ 'UserGrantedAssetSystemUsersApi', 'ValidateUserAssetPermissionApi', 'GetUserAssetPermissionActionsApi', + 'UserAssetPermissionsCacheApi', ] @@ -117,3 +118,10 @@ class UserGrantedAssetSystemUsersApi(UserAssetPermissionMixin, ListAPIView): system_user.actions = actions return system_users + +class UserAssetPermissionsCacheApi(UserAssetPermissionMixin, DestroyAPIView): + permission_classes = (IsOrgAdmin,) + + def destroy(self, request, *args, **kwargs): + self.util.expire_user_tree_cache() + return Response(status=204) diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 4b0cc32a5..426f7d34d 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -2,6 +2,7 @@ # from common.utils import lazyproperty from common.tree import TreeNodeSerializer +from django.db.models import QuerySet from ..mixin import UserPermissionMixin from ...utils import AssetPermissionUtil, ParserNode from ...hands import Node, Asset @@ -32,7 +33,8 @@ class UserNodeTreeMixin: nodes_only_fields = ParserNode.nodes_only_fields def parse_nodes_to_queryset(self, nodes): - nodes = nodes.only(*self.nodes_only_fields) + if isinstance(nodes, QuerySet): + nodes = nodes.only(*self.nodes_only_fields) _queryset = [] for node in nodes: diff --git a/apps/perms/api/user_remote_app_permission.py b/apps/perms/api/user_remote_app_permission.py index 51a9217ce..8dca299a3 100644 --- a/apps/perms/api/user_remote_app_permission.py +++ b/apps/perms/api/user_remote_app_permission.py @@ -26,8 +26,8 @@ __all__ = [ class UserGrantedRemoteAppsApi(generics.ListAPIView): permission_classes = (IsOrgAdminOrAppUser,) serializer_class = RemoteAppSerializer - filter_fields = ['name', 'id'] - search_fields = ['name'] + filter_fields = ['name', 'id', 'type', 'comment'] + search_fields = ['name', 'comment'] def get_object(self): user_id = self.kwargs.get('pk', '') diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 1d92b9852..8552edc74 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -3,9 +3,9 @@ import logging from functools import reduce from django.db import models -from django.db.models import Q from django.utils.translation import ugettext_lazy as _ +from common.utils import lazyproperty from orgs.models import Organization from orgs.utils import get_current_org from assets.models import Asset, SystemUser, Node @@ -87,6 +87,18 @@ class AssetPermission(BasePermission): verbose_name = _("Asset permission") ordering = ('name',) + @lazyproperty + def assets_amount(self): + return self.assets.count() + + @lazyproperty + def nodes_amount(self): + return self.nodes.count() + + @lazyproperty + def system_users_amount(self): + return self.system_users.count() + @classmethod def get_queryset_with_prefetch(cls): return cls.objects.all().valid().prefetch_related( diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py index da40ced9d..4ad52b2ce 100644 --- a/apps/perms/models/base.py +++ b/apps/perms/models/base.py @@ -8,7 +8,7 @@ from django.db.models import Q from django.utils import timezone from orgs.mixins.models import OrgModelMixin -from common.utils import date_expired_default +from common.utils import date_expired_default, lazyproperty from orgs.mixins.models import OrgManager @@ -79,6 +79,23 @@ class BasePermission(OrgModelMixin): return True return False + @property + def all_users(self): + from users.models import User + + users_query = self._meta.get_field('users').related_query_name() + user_groups_query = self._meta.get_field('user_groups').related_query_name() + + users_q = Q(**{ + f'{users_query}': self + }) + + user_groups_q = Q(**{ + f'groups__{user_groups_query}': self + }) + + return User.objects.filter(users_q | user_groups_q).distinct() + def get_all_users(self): from users.models import User users_id = self.users.all().values_list('id', flat=True) @@ -87,3 +104,11 @@ class BasePermission(OrgModelMixin): Q(id__in=users_id) | Q(groups__id__in=groups_id) ).distinct() return users + + @lazyproperty + def users_amount(self): + return self.users.count() + + @lazyproperty + def user_groups_amount(self): + return self.user_groups.count() diff --git a/apps/perms/models/database_app_permission.py b/apps/perms/models/database_app_permission.py index de2693274..91b989128 100644 --- a/apps/perms/models/database_app_permission.py +++ b/apps/perms/models/database_app_permission.py @@ -4,6 +4,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.utils import lazyproperty from .base import BasePermission __all__ = [ @@ -28,3 +29,11 @@ class DatabaseAppPermission(BasePermission): def get_all_database_apps(self): return self.database_apps.all() + + @lazyproperty + def database_apps_amount(self): + return self.database_apps.count() + + @lazyproperty + def system_users_amount(self): + return self.system_users.count() diff --git a/apps/perms/models/remote_app_permission.py b/apps/perms/models/remote_app_permission.py index 57a806b80..40114875c 100644 --- a/apps/perms/models/remote_app_permission.py +++ b/apps/perms/models/remote_app_permission.py @@ -1,9 +1,9 @@ # coding: utf-8 # - from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.utils import lazyproperty from .base import BasePermission __all__ = [ @@ -22,3 +22,15 @@ class RemoteAppPermission(BasePermission): def get_all_remote_apps(self): return set(self.remote_apps.all()) + + @property + def all_remote_apps(self): + return self.remote_apps.all() + + @lazyproperty + def remote_apps_amount(self): + return self.remote_apps.count() + + @lazyproperty + def system_users_amount(self): + return self.system_users.count() diff --git a/apps/perms/serializers/__init__.py b/apps/perms/serializers/__init__.py index 7f83bae9b..43f221d6e 100644 --- a/apps/perms/serializers/__init__.py +++ b/apps/perms/serializers/__init__.py @@ -1,9 +1,11 @@ # coding: utf-8 # - +from .system_user_permission import * from .asset_permission import * from .user_permission import * from .remote_app_permission import * +from .remote_app_permission_relation import * from .asset_permission_relation import * from .database_app_permission import * from .database_app_permission_relation import * +from .base import * diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index 73612a7e6..a256a7a3c 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -3,12 +3,12 @@ from rest_framework import serializers -from common.fields import StringManyToManyField +from django.db.models import Count from orgs.mixins.serializers import BulkOrgResourceModelSerializer from perms.models import AssetPermission, Action __all__ = [ - 'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer', + 'AssetPermissionSerializer', 'ActionsField', ] @@ -34,27 +34,31 @@ class ActionsDisplayField(ActionsField): return [choices.get(i) for i in values] -class AssetPermissionCreateUpdateSerializer(BulkOrgResourceModelSerializer): +class AssetPermissionSerializer(BulkOrgResourceModelSerializer): actions = ActionsField(required=False, allow_null=True) + is_valid = serializers.BooleanField(read_only=True) + is_expired = serializers.BooleanField(read_only=True) class Meta: model = AssetPermission - exclude = ('created_by', 'date_created') - - -class AssetPermissionListSerializer(BulkOrgResourceModelSerializer): - users = StringManyToManyField(many=True, read_only=True) - user_groups = StringManyToManyField(many=True, read_only=True) - assets = StringManyToManyField(many=True, read_only=True) - nodes = StringManyToManyField(many=True, read_only=True) - system_users = StringManyToManyField(many=True, read_only=True) - actions = ActionsDisplayField() - is_valid = serializers.BooleanField() - is_expired = serializers.BooleanField() - - class Meta: - model = AssetPermission - fields = '__all__' - - + mini_fields = ['id', 'name'] + small_fields = mini_fields + [ + 'is_active', 'is_expired', 'is_valid', 'actions', 'created_by', 'date_created', + 'date_expired', 'date_start', 'comment' + ] + m2m_fields = [ + 'users', 'user_groups', 'assets', 'nodes', 'system_users', + 'users_amount', 'user_groups_amount', 'assets_amount', 'nodes_amount', 'system_users_amount', + ] + fields = small_fields + m2m_fields + read_only_fields = ['created_by', 'date_created'] + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.annotate( + users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True), + assets_amount=Count('assets', distinct=True), nodes_amount=Count('nodes', distinct=True), + system_users_amount=Count('system_users', distinct=True) + ) + return queryset diff --git a/apps/perms/serializers/base.py b/apps/perms/serializers/base.py new file mode 100644 index 000000000..33de4980b --- /dev/null +++ b/apps/perms/serializers/base.py @@ -0,0 +1,13 @@ +from rest_framework import serializers + + +class PermissionAllUserSerializer(serializers.Serializer): + user = serializers.UUIDField(read_only=True, source='id') + user_display = serializers.SerializerMethodField() + + class Meta: + only_fields = ['id', 'username', 'name'] + + @staticmethod + def get_user_display(obj): + return str(obj) diff --git a/apps/perms/serializers/database_app_permission.py b/apps/perms/serializers/database_app_permission.py index a8b8bafcd..0442a6122 100644 --- a/apps/perms/serializers/database_app_permission.py +++ b/apps/perms/serializers/database_app_permission.py @@ -1,6 +1,6 @@ # coding: utf-8 # - +from django.db.models import Count from rest_framework import serializers from common.fields import StringManyToManyField @@ -13,27 +13,41 @@ __all__ = [ ] -class DatabaseAppPermissionSerializer(BulkOrgResourceModelSerializer): +class AmountMixin: + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.annotate( + users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True), + database_apps_amount=Count('database_apps', distinct=True), + system_users_amount=Count('system_users', distinct=True) + ) + return queryset + + +class DatabaseAppPermissionSerializer(AmountMixin, BulkOrgResourceModelSerializer): class Meta: model = models.DatabaseAppPermission list_serializer_class = AdaptedBulkListSerializer fields = [ - 'id', 'name', 'users', 'user_groups', - 'database_apps', 'system_users', 'comment', 'is_active', - 'date_start', 'date_expired', 'is_valid', - 'created_by', 'date_created' + 'id', 'name', 'users', 'user_groups', 'database_apps', 'system_users', + 'comment', 'is_active', 'date_start', 'date_expired', 'is_valid', + 'created_by', 'date_created', 'users_amount', 'user_groups_amount', + 'database_apps_amount', 'system_users_amount', + ] + read_only_fields = [ + 'created_by', 'date_created', 'users_amount', 'user_groups_amount', + 'database_apps_amount', 'system_users_amount', ] - read_only_fields = ['created_by', 'date_created'] -class DatabaseAppPermissionListSerializer(BulkOrgResourceModelSerializer): - users = StringManyToManyField(many=True, read_only=True) - user_groups = StringManyToManyField(many=True, read_only=True) - database_apps = StringManyToManyField(many=True, read_only=True) - system_users = StringManyToManyField(many=True, read_only=True) - is_valid = serializers.BooleanField() +class DatabaseAppPermissionListSerializer(AmountMixin, BulkOrgResourceModelSerializer): is_expired = serializers.BooleanField() class Meta: model = models.DatabaseAppPermission - fields = '__all__' + fields = [ + 'id', 'name', 'comment', 'is_active', 'users_amount', 'user_groups_amount', + 'date_start', 'date_expired', 'is_valid', 'database_apps_amount', 'system_users_amount', + 'created_by', 'date_created', 'is_expired' + ] diff --git a/apps/perms/serializers/database_app_permission_relation.py b/apps/perms/serializers/database_app_permission_relation.py index 1a8263cda..deb761853 100644 --- a/apps/perms/serializers/database_app_permission_relation.py +++ b/apps/perms/serializers/database_app_permission_relation.py @@ -1,8 +1,8 @@ # coding: utf-8 # +from perms.serializers.base import PermissionAllUserSerializer from rest_framework import serializers -from applications.models import DatabaseApp from common.mixins import BulkSerializerMixin from common.serializers import AdaptedBulkListSerializer @@ -50,16 +50,9 @@ class DatabaseAppPermissionUserGroupRelationSerializer(RelationMixin, serializer ] -class DatabaseAppPermissionAllUserSerializer(serializers.Serializer): - user = serializers.UUIDField(read_only=True, source='id') - user_display = serializers.SerializerMethodField() - - class Meta: - only_fields = ['id', 'username', 'name'] - - @staticmethod - def get_user_display(obj): - return str(obj) +class DatabaseAppPermissionAllUserSerializer(PermissionAllUserSerializer): + class Meta(PermissionAllUserSerializer.Meta): + pass class DatabaseAppPermissionDatabaseAppRelationSerializer(RelationMixin, serializers.ModelSerializer): diff --git a/apps/perms/serializers/remote_app_permission.py b/apps/perms/serializers/remote_app_permission.py index 41c5d7022..a0bd7c410 100644 --- a/apps/perms/serializers/remote_app_permission.py +++ b/apps/perms/serializers/remote_app_permission.py @@ -1,9 +1,8 @@ # coding: utf-8 # - from rest_framework import serializers +from django.db.models import Count -from common.fields import StringManyToManyField from common.serializers import AdaptedBulkListSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import RemoteAppPermission @@ -13,7 +12,6 @@ __all__ = [ 'RemoteAppPermissionSerializer', 'RemoteAppPermissionUpdateUserSerializer', 'RemoteAppPermissionUpdateRemoteAppSerializer', - 'RemoteAppPermissionListSerializer', ] @@ -21,25 +19,27 @@ class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer): class Meta: model = RemoteAppPermission list_serializer_class = AdaptedBulkListSerializer - fields = [ - 'id', 'name', 'users', 'user_groups', 'remote_apps', 'system_users', + mini_fields = ['id', 'name'] + small_fields = mini_fields + [ 'comment', 'is_active', 'date_start', 'date_expired', 'is_valid', - 'created_by', 'date_created', + 'created_by', 'date_created' ] + m2m_fields = [ + 'users', 'user_groups', 'remote_apps', 'system_users', + 'users_amount', 'user_groups_amount', 'remote_apps_amount', + 'system_users_amount' + ] + fields = small_fields + m2m_fields read_only_fields = ['created_by', 'date_created'] - -class RemoteAppPermissionListSerializer(BulkOrgResourceModelSerializer): - users = StringManyToManyField(many=True, read_only=True) - user_groups = StringManyToManyField(many=True, read_only=True) - remote_apps = StringManyToManyField(many=True, read_only=True) - system_users = StringManyToManyField(many=True, read_only=True) - is_valid = serializers.BooleanField() - is_expired = serializers.BooleanField() - - class Meta: - model = RemoteAppPermission - fields = '__all__' + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.annotate( + users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True), + remote_apps_amount=Count('remote_apps', distinct=True), system_users_amount=Count('system_users', distinct=True) + ) + return queryset class RemoteAppPermissionUpdateUserSerializer(serializers.ModelSerializer): diff --git a/apps/perms/serializers/remote_app_permission_relation.py b/apps/perms/serializers/remote_app_permission_relation.py new file mode 100644 index 000000000..05d06a9da --- /dev/null +++ b/apps/perms/serializers/remote_app_permission_relation.py @@ -0,0 +1,49 @@ +# coding: utf-8 +# +from rest_framework import serializers + +from common.serializers import AdaptedBulkListSerializer +from ..models import RemoteAppPermission + + +__all__ = [ + 'RemoteAppPermissionRemoteAppRelationSerializer', + 'RemoteAppPermissionAllRemoteAppSerializer', + 'RemoteAppPermissionUserRelationSerializer', +] + + +class RemoteAppPermissionRemoteAppRelationSerializer(serializers.ModelSerializer): + remoteapp_display = serializers.ReadOnlyField() + remoteapppermission_display = serializers.ReadOnlyField() + + class Meta: + model = RemoteAppPermission.remote_apps.through + list_serializer_class = AdaptedBulkListSerializer + fields = [ + 'id', 'remoteapp', 'remoteapp_display', 'remoteapppermission', 'remoteapppermission_display' + ] + + +class RemoteAppPermissionAllRemoteAppSerializer(serializers.Serializer): + remoteapp = serializers.UUIDField(read_only=True, source='id') + remoteapp_display = serializers.SerializerMethodField() + + class Meta: + only_fields = ['id', 'name'] + + @staticmethod + def get_remoteapp_display(obj): + return str(obj) + + +class RemoteAppPermissionUserRelationSerializer(serializers.ModelSerializer): + user_display = serializers.ReadOnlyField() + remoteapppermission_display = serializers.ReadOnlyField() + + class Meta: + model = RemoteAppPermission.users.through + list_serializer_class = AdaptedBulkListSerializer + fields = [ + 'id', 'user', 'user_display', 'remoteapppermission', 'remoteapppermission_display' + ] diff --git a/apps/perms/serializers/system_user_permission.py b/apps/perms/serializers/system_user_permission.py new file mode 100644 index 000000000..a383ffbd0 --- /dev/null +++ b/apps/perms/serializers/system_user_permission.py @@ -0,0 +1,18 @@ +from rest_framework import serializers +from ..hands import SystemUser + +__all__ = [ + 'SystemUserSerializer', +] + + +class SystemUserSerializer(serializers.ModelSerializer): + class Meta: + model = SystemUser + fields = [ + 'id', 'name', 'username', 'protocol', + 'login_mode', 'login_mode_display', + 'priority', 'username_same_with_user', + 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', + 'sftp_root', 'date_created', 'created_by' + ] diff --git a/apps/perms/templates/perms/asset_permission_asset.html b/apps/perms/templates/perms/asset_permission_asset.html deleted file mode 100644 index b6079cdaa..000000000 --- a/apps/perms/templates/perms/asset_permission_asset.html +++ /dev/null @@ -1,282 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Asset list of ' %} {{ asset_permission.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - {% trans 'Asset' %}{% trans 'Action' %}
-
-
-
-
-
-
- {% trans 'Add asset to this permission' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
- {% trans 'Add node to this permission' %} -
-
- - - - - - - - - - - - {% for node in asset_permission.nodes.all %} - - - - - {% endfor %} - -
- -
- -
{{ node.full_value }} - -
-
-
-
-
- {% trans 'System user' %} -
-
- - - - - - - - - - - - {% for system_user in object.system_users.all %} - - - - - {% endfor %} - -
- -
- -
{{ system_user|truncatechars:21}} - -
-
-
-
-
-
-
-
-
-{% include 'assets/_asset_list_modal.html' %} -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html deleted file mode 100644 index 937e7273e..000000000 --- a/apps/perms/templates/perms/asset_permission_create_update.html +++ /dev/null @@ -1,220 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} - -

{% trans 'Basic' %}

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

{% trans 'User' %}

- {% bootstrap_field form.users layout="horizontal" %} - {% bootstrap_field form.user_groups layout="horizontal" %} - -
-

{% trans 'Asset' %}

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

{% trans 'Action' %}

-
- -
-
-
    -
  • -
    {{ form.actions.0}}
    -
      -
    • -
      {{ form.actions.1}}
      -
    • - -
    • -
      {{ form.actions.4}}
      -
        -
      • -
        {{ form.actions.2}}
        -
      • -
      • -
        {{ form.actions.3}}
        -
      • -
      -
    • -
    - -
  • -
-
{{ form.actions.help_text }}
-
-
-
- -
-

{% trans 'Other' %}

-
- -
- {{ form.is_active }} -
-
-
- -
-
- - {% if form.errors %} - - to - - {% else %} - - to - - {% endif %} -
- {{ form.date_expired.errors }} - {{ form.date_start.errors }} -
-
- {% bootstrap_field form.comment layout="horizontal" %} - -
-
- - -
-
- -
-
-
-
-
-
- {% include 'assets/_asset_list_modal.html' %} -{% endblock %} -{% block custom_foot_js %} - - - - - - -{% endblock %} diff --git a/apps/perms/templates/perms/asset_permission_detail.html b/apps/perms/templates/perms/asset_permission_detail.html deleted file mode 100644 index 6930cfc5a..000000000 --- a/apps/perms/templates/perms/asset_permission_detail.html +++ /dev/null @@ -1,178 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'User count' %}:{{ object.users.count }}
{% trans 'User group count' %}:{{ object.user_groups.count }}
{% trans 'Asset count' %}:{{ object.assets.count }}
{% trans 'Node count' %}:{{ object.nodes.count }}
{% trans 'System user count' %}:{{ object.system_users.count }}
{% trans 'Date start' %}:{{ object.date_start }}
{% trans 'Date expired' %}:{{ object.date_expired }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Created by' %}:{{ object.created_by }}
{% trans 'Comment' %}:{{ object.comment }}
-
-
-
- -
-
-
- {% trans 'Quick update' %} -
-
- - - - - - - -
{% trans 'Active' %} : -
-
- - -
-
-
-
-
- - -
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html deleted file mode 100644 index 1f44f7de1..000000000 --- a/apps/perms/templates/perms/asset_permission_list.html +++ /dev/null @@ -1,260 +0,0 @@ -{% extends '_base_asset_tree_list.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - - - - - -{% endblock %} - -{% block table_container %} -
- - - -
- - - - - - - - - - - - - - - - -
{% trans 'Name' %}{% trans 'User' %}{% trans 'User group' %}{% trans 'Asset' %}{% trans 'Node'%}{% trans 'System user' %}{% trans 'Validity' %}{% trans 'Action' %}
-{% include '_filter_dropdown.html' %} -{% endblock %} - -{% block custom_foot_js %} - - - -{% endblock %} diff --git a/apps/perms/templates/perms/asset_permission_user.html b/apps/perms/templates/perms/asset_permission_user.html deleted file mode 100644 index 088d80c75..000000000 --- a/apps/perms/templates/perms/asset_permission_user.html +++ /dev/null @@ -1,251 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'User list of ' %} {{ asset_permission.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Action' %}
-
-
-
-
-
-
- {% trans 'Add user to asset permission' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
- {% trans 'Add user group to asset permission' %} -
-
- - - - - - - - - - - - {% for user_group in asset_permission.user_groups.all %} - - - - - {% endfor %} - -
- -
- -
{{ user_group }} - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/database_app_permission_create_update.html b/apps/perms/templates/perms/database_app_permission_create_update.html deleted file mode 100644 index a779e5826..000000000 --- a/apps/perms/templates/perms/database_app_permission_create_update.html +++ /dev/null @@ -1,143 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} - -

{% trans 'Basic' %}

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

{% trans 'User' %}

- {% bootstrap_field form.users layout="horizontal" %} - {% bootstrap_field form.user_groups layout="horizontal" %} -
- -

{% trans 'DatabaseApp' %}

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

{% trans 'Other' %}

-
- -
- {{ form.is_active }} -
-
-
- -
-
- - {% if form.errors %} - - to - - {% else %} - - to - - {% endif %} -
- {{ form.date_expired.errors }} - {{ form.date_start.errors }} -
-
- - {% bootstrap_field form.comment layout="horizontal" %} - -
-
- - -
-
- -
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - - - - - - -{% endblock %} diff --git a/apps/perms/templates/perms/database_app_permission_database_app.html b/apps/perms/templates/perms/database_app_permission_database_app.html deleted file mode 100644 index 0a23618d0..000000000 --- a/apps/perms/templates/perms/database_app_permission_database_app.html +++ /dev/null @@ -1,237 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'DatabaseApp list of ' %} {{ database_app_permission.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - {% trans 'DatabaseApp' %}{% trans 'Action' %}
-
-
-
-
-
-
- {% trans 'Add DatabaseApp to this permission' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
- {% trans 'System user' %} -
-
- - - - - - - - - - - - {% for system_user in object.system_users.all %} - - - - - {% endfor %} - -
- -
- -
{{ system_user|truncatechars:21}} - -
-
-
- -
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/database_app_permission_detail.html b/apps/perms/templates/perms/database_app_permission_detail.html deleted file mode 100644 index feae4db24..000000000 --- a/apps/perms/templates/perms/database_app_permission_detail.html +++ /dev/null @@ -1,157 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'User count' %}:{{ object.users.count }}
{% trans 'User group count' %}:{{ object.user_groups.count }}
{% trans 'DatabaseApp count' %}:{{ object.database_apps.count }}
{% trans 'System user count' %}:{{ object.system_users.count }}
{% trans 'Date start' %}:{{ object.date_start }}
{% trans 'Date expired' %}:{{ object.date_expired }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Created by' %}:{{ object.created_by }}
{% trans 'Comment' %}:{{ object.comment }}
-
-
-
- -
-
-
- {% trans 'Quick update' %} -
-
- - - - - - - -
{% trans 'Active' %} : -
-
- - -
-
-
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/database_app_permission_list.html b/apps/perms/templates/perms/database_app_permission_list.html deleted file mode 100644 index b85454cb8..000000000 --- a/apps/perms/templates/perms/database_app_permission_list.html +++ /dev/null @@ -1,99 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} -{% block table_container %} - - - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'User' %}{% trans 'User group' %}{% trans 'DatabaseApp' %}{% trans 'System user' %}{% trans 'Validity' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/database_app_permission_user.html b/apps/perms/templates/perms/database_app_permission_user.html deleted file mode 100644 index 8b109fa1a..000000000 --- a/apps/perms/templates/perms/database_app_permission_user.html +++ /dev/null @@ -1,251 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'User list of ' %} {{ database_app_permission.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Action' %}
-
-
-
-
-
-
- {% trans 'Add user to permission' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
- {% trans 'Add user group to permission' %} -
-
- - - - - - - - - - - - {% for user_group in database_app_permission.user_groups.all %} - - - - - {% endfor %} - -
- -
- -
{{ user_group }} - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/delete_confirm.html b/apps/perms/templates/perms/delete_confirm.html deleted file mode 100644 index 777d1dbf9..000000000 --- a/apps/perms/templates/perms/delete_confirm.html +++ /dev/null @@ -1,15 +0,0 @@ -{% load i18n %} - - - - - {% trans 'Confirm delete' %} - - -
- {% csrf_token %} -

Are you sure you want to delete "{{ object.name }}"?

- -
- - \ No newline at end of file diff --git a/apps/perms/templates/perms/remote_app_permission_create_update.html b/apps/perms/templates/perms/remote_app_permission_create_update.html deleted file mode 100644 index f6e0cda7d..000000000 --- a/apps/perms/templates/perms/remote_app_permission_create_update.html +++ /dev/null @@ -1,143 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} - -

{% trans 'Basic' %}

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

{% trans 'User' %}

- {% bootstrap_field form.users layout="horizontal" %} - {% bootstrap_field form.user_groups layout="horizontal" %} -
- -

{% trans 'RemoteApp' %}

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

{% trans 'Other' %}

-
- -
- {{ form.is_active }} -
-
-
- -
-
- - {% if form.errors %} - - to - - {% else %} - - to - - {% endif %} -
- {{ form.date_expired.errors }} - {{ form.date_start.errors }} -
-
- - {% bootstrap_field form.comment layout="horizontal" %} - -
-
- - -
-
- -
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - - - - - - -{% endblock %} diff --git a/apps/perms/templates/perms/remote_app_permission_detail.html b/apps/perms/templates/perms/remote_app_permission_detail.html deleted file mode 100644 index aedef832e..000000000 --- a/apps/perms/templates/perms/remote_app_permission_detail.html +++ /dev/null @@ -1,242 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'User count' %}:{{ object.users.count }}
{% trans 'User group count' %}:{{ object.user_groups.count }}
{% trans 'RemoteApp count' %}:{{ object.remote_apps.count }}
{% trans 'Date start' %}:{{ object.date_start }}
{% trans 'Date expired' %}:{{ object.date_expired }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Created by' %}:{{ object.created_by }}
{% trans 'Comment' %}:{{ object.comment }}
-
-
-
- -
-
-
- {% trans 'Quick update' %} -
-
- - - - - - - -
{% trans 'Active' %} : -
-
- - -
-
-
-
-
-
-
- {% trans 'System user' %} -
-
- - - - - - - - - - - - {% for system_user in object.system_users.all %} - - - - - {% endfor %} - -
- -
- -
{{ system_user }} - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/remote_app_permission_list.html b/apps/perms/templates/perms/remote_app_permission_list.html deleted file mode 100644 index f6071430a..000000000 --- a/apps/perms/templates/perms/remote_app_permission_list.html +++ /dev/null @@ -1,99 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} -{% block table_container %} - - - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'User' %}{% trans 'User group' %}{% trans 'RemoteApp' %}{% trans 'System user' %}{% trans 'Validity' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/remote_app_permission_remote_app.html b/apps/perms/templates/perms/remote_app_permission_remote_app.html deleted file mode 100644 index 991489033..000000000 --- a/apps/perms/templates/perms/remote_app_permission_remote_app.html +++ /dev/null @@ -1,160 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'RemoteApp list of ' %} {{ remote_app_permission.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - {% for remote_app in object_list %} - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Type' %}
{{ remote_app.name }}{{ remote_app.get_type_display }} - -
-
- {% include '_pagination.html' %} -
-
-
-
-
-
-
- {% trans 'Add RemoteApp to this permission' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
-
-
-
-
- -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/remote_app_permission_user.html b/apps/perms/templates/perms/remote_app_permission_user.html deleted file mode 100644 index 9fa623585..000000000 --- a/apps/perms/templates/perms/remote_app_permission_user.html +++ /dev/null @@ -1,256 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'User list of ' %} {{ remote_app_permission.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - {% for user in object_list %} - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Username' %}
{{ user.name }}{{ user.username }} - -
-
- {% include '_pagination.html' %} -
-
-
-
-
-
-
- {% trans 'Add user to this permission' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
- {% trans 'Add user group to this permission' %} -
-
- - - - - - - - - - - - {% for user_group in remote_app_permission.user_groups.all %} - - - - - {% endfor %} - -
- -
- -
{{ user_group }} - -
-
-
-
-
-
-
-
-
- -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templatetags/perms/example_tags.py b/apps/perms/templatetags/perms/example_tags.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/perms/tests.py b/apps/perms/tests.py index 4fd76b0f2..344266b19 100644 --- a/apps/perms/tests.py +++ b/apps/perms/tests.py @@ -1,4 +1,3 @@ from django.test import TestCase from django.contrib.sessions.backends import file, db, cache -from django.contrib.auth.views import login \ No newline at end of file diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index 9ec3b754f..d70a824cc 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -5,10 +5,10 @@ from common import api as capi from .asset_permission import asset_permission_urlpatterns from .remote_app_permission import remote_app_permission_urlpatterns from .database_app_permission import database_app_permission_urlpatterns +from .system_user_permission import system_users_permission_urlpatterns app_name = 'perms' - old_version_urlpatterns = [ re_path('(?Puser|user-group|asset-permission|remote-app-permission)/.*', capi.redirect_plural_name_api) ] @@ -16,5 +16,5 @@ old_version_urlpatterns = [ urlpatterns = asset_permission_urlpatterns + \ remote_app_permission_urlpatterns + \ database_app_permission_urlpatterns + \ - old_version_urlpatterns - + old_version_urlpatterns + \ + system_users_permission_urlpatterns diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index c14db7c3e..ff8ac0c48 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -51,6 +51,11 @@ user_permission_urlpatterns = [ # Asset System users path('/assets//system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='user-asset-system-users'), path('assets//system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'), + + # Expire user permission cache + path('/asset-permissions/cache/', api.UserAssetPermissionsCacheApi.as_view(), + name='user-asset-permission-cache'), + path('asset-permissions/cache/', api.UserAssetPermissionsCacheApi.as_view(), name='my-asset-permission-cache'), ] user_group_permission_urlpatterns = [ diff --git a/apps/perms/urls/remote_app_permission.py b/apps/perms/urls/remote_app_permission.py index 8f83d72d0..798ca9639 100644 --- a/apps/perms/urls/remote_app_permission.py +++ b/apps/perms/urls/remote_app_permission.py @@ -7,6 +7,9 @@ from .. import api router = BulkRouter() router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remote-app-permission') +router.register('remote-app-permissions-users-relations', api.RemoteAppPermissionUserRelationViewSet, 'remote-app-permissions-users-relation') +router.register('remote-app-permissions-remote-apps-relations', api.RemoteAppPermissionRemoteAppRelationViewSet, 'remote-app-permissions-remote-apps-relation') + remote_app_permission_urlpatterns = [ # 查询用户授权的RemoteApp @@ -32,7 +35,9 @@ remote_app_permission_urlpatterns = [ path('remote-app-permissions//users/remove/', api.RemoteAppPermissionRemoveUserApi.as_view(), name='remote-app-permission-remove-user'), path('remote-app-permissions//remote-apps/remove/', api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), name='remote-app-permission-remove-remote-app'), path('remote-app-permissions//remote-apps/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'), + + path('remote-app-permissions//remote-apps/all/', api.RemoteAppPermissionAllRemoteAppListApi.as_view(), name='remote-app-permission-all-remote-apps'), + path('remote-app-permissions//users/all/', api.RemoteAppPermissionAllUserListApi.as_view(), name='remote-app-permission-all-users'), ] remote_app_permission_urlpatterns += router.urls - diff --git a/apps/perms/urls/system_user_permission.py b/apps/perms/urls/system_user_permission.py new file mode 100644 index 000000000..e5a5ba1e4 --- /dev/null +++ b/apps/perms/urls/system_user_permission.py @@ -0,0 +1,6 @@ +from django.urls import path +from .. import api + +system_users_permission_urlpatterns = [ + path('system-users-permission/', api.SystemUserPermission.as_view(), name='system-users-permission'), +] diff --git a/apps/perms/urls/views_urls.py b/apps/perms/urls/views_urls.py index f4deba8c8..89d8c241a 100644 --- a/apps/perms/urls/views_urls.py +++ b/apps/perms/urls/views_urls.py @@ -1,34 +1,5 @@ # coding:utf-8 - -from django.conf.urls import url -from django.urls import path -from .. import views - app_name = 'perms' urlpatterns = [ - # asset-permission - path('asset-permission/', views.AssetPermissionListView.as_view(), name='asset-permission-list'), - path('asset-permission/create/', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'), - path('asset-permission//update/', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'), - path('asset-permission//', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'), - path('asset-permission//delete/', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'), - path('asset-permission//user/', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'), - path('asset-permission//asset/', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'), - - # remote-app-permission - path('remote-app-permission/', views.RemoteAppPermissionListView.as_view(), name='remote-app-permission-list'), - path('remote-app-permission/create/', views.RemoteAppPermissionCreateView.as_view(), name='remote-app-permission-create'), - path('remote-app-permission//update/', views.RemoteAppPermissionUpdateView.as_view(), name='remote-app-permission-update'), - path('remote-app-permission//', views.RemoteAppPermissionDetailView.as_view(), name='remote-app-permission-detail'), - path('remote-app-permission//user/', views.RemoteAppPermissionUserView.as_view(), name='remote-app-permission-user-list'), - path('remote-app-permission//remote-app/', views.RemoteAppPermissionRemoteAppView.as_view(), name='remote-app-permission-remote-app-list'), - - # database-app-permission - path('database-app-permission/', views.DatabaseAppPermissionListView.as_view(), name='database-app-permission-list'), - path('database-app-permission/create/', views.DatabaseAppPermissionCreateView.as_view(), name='database-app-permission-create'), - path('database-app-permission//update/', views.DatabaseAppPermissionUpdateView.as_view(), name='database-app-permission-update'), - path('database-app-permission//', views.DatabaseAppPermissionDetailView.as_view(), name='database-app-permission-detail'), - path('database-app-permission//user/', views.DatabaseAppPermissionUserView.as_view(), name='database-app-permission-user-list'), - path('database-app-permission//database-app/', views.DatabaseAppPermissionDatabaseAppView.as_view(), name='database-app-permission-database-app-list'), ] diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index 80b1c19e5..b78d5f930 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -169,7 +169,7 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin): @property def permissions(self): - if self._permissions: + if self._permissions is not None: return self._permissions if self.object is None: return AssetPermission.objects.none() @@ -290,11 +290,12 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin): def parse_user_tree_to_full_tree(self, user_tree): """ 经过前面两个动作,用户授权的节点已放到树上,但是树不是完整的, - 这里要讲树构造成一个完整的树 + 这里要将树构造成一个完整的树 """ # 开始修正user_tree,保证父节点都在树上 root_children = user_tree.children('') for child in root_children: + # print("child: {}".format(child.identifier)) if child.identifier.isdigit(): continue if child.identifier.startswith('-'): @@ -302,6 +303,7 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin): ancestors = self.full_tree.ancestors( child.identifier, with_self=False, deep=True, ) + # print("Get ancestors: {}".format(len(ancestors))) if not ancestors: continue user_tree.safe_add_ancestors(child, ancestors) @@ -349,6 +351,7 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin): self.add_favorite_node_if_need(user_tree) self.set_user_tree_to_cache_if_need(user_tree) self.set_user_tree_to_local(user_tree) + # print(user_tree) return user_tree # Todo: 是否可以获取多个资产的系统用户 diff --git a/apps/perms/views/__init__.py b/apps/perms/views/__init__.py deleted file mode 100644 index c6581b858..000000000 --- a/apps/perms/views/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# coding: utf-8 -# - -from .asset_permission import * -from .remote_app_permission import * -from .database_app_permission import * diff --git a/apps/perms/views/asset_permission.py b/apps/perms/views/asset_permission.py deleted file mode 100644 index 71b4d5604..000000000 --- a/apps/perms/views/asset_permission.py +++ /dev/null @@ -1,175 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from __future__ import unicode_literals, absolute_import - -from django.utils.translation import ugettext as _ -from django.views.generic import ListView, CreateView, UpdateView, DetailView, TemplateView -from django.views.generic.edit import DeleteView, SingleObjectMixin -from django.urls import reverse_lazy -from django.conf import settings - -from common.permissions import PermissionsMixin, IsOrgAdmin -from orgs.utils import current_org -from perms.hands import Node, Asset, SystemUser, UserGroup -from perms.models import AssetPermission -from perms.forms import AssetPermissionForm - - -__all__ = [ - 'AssetPermissionListView', 'AssetPermissionCreateView', - 'AssetPermissionUpdateView', 'AssetPermissionDetailView', - 'AssetPermissionDeleteView', 'AssetPermissionUserView', - 'AssetPermissionAssetView', - -] - - -class AssetPermissionListView(PermissionsMixin, TemplateView): - template_name = 'perms/asset_permission_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Asset permission list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetPermissionCreateView(PermissionsMixin, CreateView): - model = AssetPermission - form_class = AssetPermissionForm - template_name = 'perms/asset_permission_create_update.html' - success_url = reverse_lazy('perms:asset-permission-list') - permission_classes = [IsOrgAdmin] - - def get_form(self, form_class=None): - form = super().get_form(form_class=form_class) - nodes_id = self.request.GET.get("nodes") - assets_id = self.request.GET.get("assets") - - if nodes_id: - nodes_id = nodes_id.split(",") - nodes = Node.objects.filter(id__in=nodes_id)\ - .exclude(id=Node.org_root().id) - form.set_nodes_initial(nodes) - if assets_id: - assets_id = assets_id.split(",") - assets = Asset.objects.filter(id__in=assets_id) - form.set_assets_initial(assets) - return form - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Create asset permission'), - 'api_action': "create", - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetPermissionUpdateView(PermissionsMixin, UpdateView): - model = AssetPermission - form_class = AssetPermissionForm - template_name = 'perms/asset_permission_create_update.html' - success_url = reverse_lazy("perms:asset-permission-list") - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Update asset permission'), - 'api_action': "update", - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetPermissionDetailView(PermissionsMixin, DetailView): - model = AssetPermission - form_class = AssetPermissionForm - template_name = 'perms/asset_permission_detail.html' - success_url = reverse_lazy("perms:asset-permission-list") - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Asset permission detail'), - - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetPermissionDeleteView(PermissionsMixin, DeleteView): - model = AssetPermission - template_name = 'delete_confirm.html' - success_url = reverse_lazy('perms:asset-permission-list') - permission_classes = [IsOrgAdmin] - - -class AssetPermissionUserView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/asset_permission_user.html' - context_object_name = 'asset_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - 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 = list(self.object.get_all_users()) - return queryset - - def get_context_data(self, **kwargs): - users = [str(i) for i in self.object.users.all().values_list('id', flat=True)] - user_groups_remain = UserGroup.objects.exclude( - assetpermission=self.object) - context = { - 'app': _('Perms'), - 'action': _('Asset permission user list'), - 'users': users, - 'user_groups_remain': user_groups_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetPermissionAssetView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/asset_permission_asset.html' - context_object_name = 'asset_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - 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 = list(self.object.get_all_assets()) - return queryset - - def get_context_data(self, **kwargs): - assets = self.object.assets.all().values_list('id', flat=True) - assets = [str(i) for i in assets] - system_users_remain = SystemUser.objects\ - .exclude(granted_by_permissions=self.object)\ - .exclude(protocol=SystemUser.PROTOCOL_MYSQL) - context = { - 'app': _('Perms'), - 'assets': assets, - 'action': _('Asset permission asset list'), - 'system_users_remain': system_users_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/perms/views/database_app_permission.py b/apps/perms/views/database_app_permission.py deleted file mode 100644 index 50627defe..000000000 --- a/apps/perms/views/database_app_permission.py +++ /dev/null @@ -1,152 +0,0 @@ -# coding: utf-8 -# - -from django.utils.translation import ugettext as _ - -from django.views.generic import ( - TemplateView, CreateView, UpdateView, DetailView, ListView -) -from django.views.generic.edit import SingleObjectMixin -from django.conf import settings - -from common.permissions import PermissionsMixin, IsOrgAdmin -from users.models import UserGroup -from applications.models import DatabaseApp -from assets.models import SystemUser - -from .. import models, forms - - -__all__ = [ - 'DatabaseAppPermissionListView', 'DatabaseAppPermissionCreateView', - 'DatabaseAppPermissionUpdateView', 'DatabaseAppPermissionDetailView', - 'DatabaseAppPermissionUserView', 'DatabaseAppPermissionDatabaseAppView', -] - - -class DatabaseAppPermissionListView(PermissionsMixin, TemplateView): - template_name = 'perms/database_app_permission_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('DatabaseApp permission list') - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppPermissionCreateView(PermissionsMixin, CreateView): - template_name = 'perms/database_app_permission_create_update.html' - model = models.DatabaseAppPermission - form_class = forms.DatabaseAppPermissionCreateUpdateForm - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Create DatabaseApp permission'), - 'api_action': 'create', - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppPermissionUpdateView(PermissionsMixin, UpdateView): - template_name = 'perms/database_app_permission_create_update.html' - model = models.DatabaseAppPermission - form_class = forms.DatabaseAppPermissionCreateUpdateForm - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Update DatabaseApp permission'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppPermissionDetailView(PermissionsMixin, DetailView): - template_name = 'perms/database_app_permission_detail.html' - model = models.DatabaseAppPermission - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('DatabaseApp permission detail') - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppPermissionUserView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/database_app_permission_user.html' - context_object_name = 'database_app_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=models.DatabaseAppPermission.objects.all()) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - queryset = list(self.object.get_all_users()) - return queryset - - def get_context_data(self, **kwargs): - users = [str(i) for i in self.object.users.all().values_list('id', flat=True)] - user_groups_remain = UserGroup.objects.exclude( - databaseapppermission=self.object) - context = { - 'app': _('Perms'), - 'action': _('DatabaseApp permission user list'), - 'users': users, - 'user_groups_remain': user_groups_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppPermissionDatabaseAppView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/database_app_permission_database_app.html' - context_object_name = 'database_app_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object( - queryset=models.DatabaseAppPermission.objects.all() - ) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - queryset = list(self.object.get_all_database_apps()) - return queryset - - def get_context_data(self, **kwargs): - database_apps = self.object.get_all_database_apps().values_list('id', flat=True) - database_apps = [str(i) for i in database_apps] - system_users_remain = SystemUser.objects\ - .exclude(granted_by_database_app_permissions=self.object)\ - .filter(protocol=SystemUser.PROTOCOL_MYSQL) - context = { - 'app': _('Perms'), - 'database_apps': database_apps, - 'database_apps_remain': DatabaseApp.objects.exclude( - granted_by_permissions=self.object - ), - 'system_users_remain': system_users_remain, - 'action': _('DatabaseApp permission DatabaseApp list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/perms/views/remote_app_permission.py b/apps/perms/views/remote_app_permission.py deleted file mode 100644 index 875600c68..000000000 --- a/apps/perms/views/remote_app_permission.py +++ /dev/null @@ -1,155 +0,0 @@ -# coding: utf-8 -# - -from django.utils.translation import ugettext as _ -from django.urls import reverse_lazy -from django.views.generic import ( - TemplateView, CreateView, UpdateView, DetailView, ListView -) -from django.views.generic.edit import SingleObjectMixin -from django.conf import settings - -from common.permissions import PermissionsMixin, IsOrgAdmin -from orgs.utils import current_org - -from ..hands import RemoteApp, UserGroup, SystemUser -from ..models import RemoteAppPermission -from ..forms import RemoteAppPermissionCreateUpdateForm - - -__all__ = [ - 'RemoteAppPermissionListView', 'RemoteAppPermissionCreateView', - 'RemoteAppPermissionUpdateView', 'RemoteAppPermissionDetailView', - 'RemoteAppPermissionUserView', 'RemoteAppPermissionRemoteAppView' -] - - -class RemoteAppPermissionListView(PermissionsMixin, TemplateView): - template_name = 'perms/remote_app_permission_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('RemoteApp permission list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppPermissionCreateView(PermissionsMixin, CreateView): - template_name = 'perms/remote_app_permission_create_update.html' - model = RemoteAppPermission - form_class = RemoteAppPermissionCreateUpdateForm - success_url = reverse_lazy('perms:remote-app-permission-list') - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Create RemoteApp permission'), - 'api_action': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppPermissionUpdateView(PermissionsMixin, UpdateView): - template_name = 'perms/remote_app_permission_create_update.html' - model = RemoteAppPermission - form_class = RemoteAppPermissionCreateUpdateForm - success_url = reverse_lazy('perms:remote-app-permission-list') - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Update RemoteApp permission'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppPermissionDetailView(PermissionsMixin, DetailView): - template_name = 'perms/remote_app_permission_detail.html' - model = RemoteAppPermission - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - system_users_remain = SystemUser.objects\ - .exclude(granted_by_remote_app_permissions=self.object)\ - .filter(protocol=SystemUser.PROTOCOL_RDP) - context = { - 'app': _('Perms'), - 'action': _('RemoteApp permission detail'), - 'system_users_remain': system_users_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppPermissionUserView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/remote_app_permission_user.html' - context_object_name = 'remote_app_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object( - queryset=RemoteAppPermission.objects.all()) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - queryset = list(self.object.get_all_users()) - return queryset - - def get_context_data(self, **kwargs): - user_remain = current_org.get_org_members(exclude=('Auditor',))\ - .exclude(remoteapppermission=self.object) - user_groups_remain = UserGroup.objects\ - .exclude(remoteapppermission=self.object) - context = { - 'app': _('Perms'), - 'action': _('RemoteApp permission user list'), - 'users_remain': user_remain, - 'user_groups_remain': user_groups_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppPermissionRemoteAppView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/remote_app_permission_remote_app.html' - context_object_name = 'remote_app_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object( - queryset=RemoteAppPermission.objects.all() - ) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - queryset = list(self.object.get_all_remote_apps()) - return queryset - - def get_context_data(self, **kwargs): - remote_app_granted = self.get_queryset() - remote_app_remain = RemoteApp.objects.exclude( - id__in=[a.id for a in remote_app_granted]) - context = { - 'app': _('Perms'), - 'action': _('RemoteApp permission RemoteApp list'), - 'remote_app_remain': remote_app_remain - } - kwargs.update(context) - return super().get_context_data(**kwargs) - diff --git a/apps/settings/api.py b/apps/settings/api.py index 38167a7e3..786c43bad 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -2,28 +2,28 @@ # import json - +from collections.abc import Iterable from smtplib import SMTPSenderRefused from rest_framework import generics from rest_framework.views import Response, APIView from django.conf import settings from django.core.mail import send_mail, get_connection from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers from .utils import ( LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil, - LDAP_USE_CACHE_FLAGS, LDAPTestUtil, + LDAP_USE_CACHE_FLAGS, LDAPTestUtil, ObjectDict ) from .tasks import sync_ldap_user_task from common.permissions import IsOrgAdmin, IsSuperUser from common.utils import get_logger from .serializers import ( MailTestSerializer, LDAPTestConfigSerializer, LDAPUserSerializer, - PublicSettingSerializer, LDAPTestLoginSerializer, + PublicSettingSerializer, LDAPTestLoginSerializer, SettingsSerializer ) from users.models import User - logger = get_logger(__file__) @@ -55,11 +55,11 @@ class MailTestingAPI(APIView): email_recipient = email_recipient or email_from connection = get_connection( host=email_host, port=email_port, - uesrname=email_host_user, password=email_host_password, + username=email_host_user, password=email_host_password, use_tls=email_use_tls, use_ssl=email_use_ssl, ) send_mail( - subject, message, email_from, [email_recipient], + subject, message, email_from, [email_recipient], connection=connection ) except SMTPSenderRefused as e: @@ -72,13 +72,13 @@ class MailTestingAPI(APIView): continue else: break - return Response({"error": str(resp)}, status=401) + return Response({"error": str(resp)}, status=400) except Exception as e: print(e) - return Response({"error": str(e)}, status=401) + return Response({"error": str(e)}, status=400) return Response({"msg": self.success_message.format(email_recipient)}) else: - return Response({"error": str(serializer.errors)}, status=401) + return Response({"error": str(serializer.errors)}, status=400) class LDAPTestingConfigAPI(APIView): @@ -88,10 +88,10 @@ class LDAPTestingConfigAPI(APIView): def post(self, request): serializer = self.serializer_class(data=request.data) if not serializer.is_valid(): - return Response({"error": str(serializer.errors)}, status=401) + return Response({"error": str(serializer.errors)}, status=400) config = self.get_ldap_config(serializer) ok, msg = LDAPTestUtil(config).test_config() - status = 200 if ok else 401 + status = 200 if ok else 400 return Response(msg, status=status) @staticmethod @@ -124,11 +124,11 @@ class LDAPTestingLoginAPI(APIView): def post(self, request): serializer = self.serializer_class(data=request.data) if not serializer.is_valid(): - return Response({"error": str(serializer.errors)}, status=401) + return Response({"error": str(serializer.errors)}, status=400) username = serializer.validated_data['username'] password = serializer.validated_data['password'] ok, msg = LDAPTestUtil().test_login(username, password) - status = 200 if ok else 401 + status = 200 if ok else 400 return Response(msg, status=status) @@ -236,14 +236,14 @@ class LDAPUserImportAPI(APIView): try: users = self.get_ldap_users() except Exception as e: - return Response({'error': str(e)}, status=401) + return Response({'error': str(e)}, status=400) if users is None: - return Response({'msg': _('Get ldap users is None')}, status=401) + return Response({'msg': _('Get ldap users is None')}, status=400) errors = LDAPImportUtil().perform_import(users) if errors: - return Response({'errors': errors}, status=401) + return Response({'errors': errors}, status=400) count = users if users is None else len(users) return Response({'msg': _('Imported {} users successfully').format(count)}) @@ -270,8 +270,46 @@ class PublicSettingApi(generics.RetrieveAPIView): "data": { "WINDOWS_SKIP_ALL_MANUAL_PASSWORD": settings.WINDOWS_SKIP_ALL_MANUAL_PASSWORD, "SECURITY_MAX_IDLE_TIME": settings.SECURITY_MAX_IDLE_TIME, + "XPACK_ENABLED": settings.XPACK_ENABLED, + "XPACK_LICENSE_IS_VALID": settings.XPACK_LICENSE_IS_VALID, + "LOGIN_CONFIRM_ENABLE": settings.LOGIN_CONFIRM_ENABLE, + "SECURITY_VIEW_AUTH_NEED_MFA": settings.SECURITY_VIEW_AUTH_NEED_MFA, + "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL, + "SECURITY_COMMAND_EXECUTION": settings.SECURITY_COMMAND_EXECUTION, + "LOGO_URLS": settings.LOGO_URLS, + "PASSWORD_RULE": { + 'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH, + 'SECURITY_PASSWORD_UPPER_CASE': settings.SECURITY_PASSWORD_UPPER_CASE, + 'SECURITY_PASSWORD_LOWER_CASE': settings.SECURITY_PASSWORD_LOWER_CASE, + 'SECURITY_PASSWORD_NUMBER': settings.SECURITY_PASSWORD_NUMBER, + 'SECURITY_PASSWORD_SPECIAL_CHAR': settings.SECURITY_PASSWORD_SPECIAL_CHAR, + } } } return instance +class SettingsApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsSuperUser,) + serializer_class = SettingsSerializer + + def get_object(self): + instance = {category: self._get_setting_fields_obj(list(category_serializer.get_fields())) + for category, category_serializer in self.serializer_class().get_fields().items() + if isinstance(category_serializer, serializers.Serializer)} + return ObjectDict(instance) + + def perform_update(self, serializer): + serializer.save() + + def _get_setting_fields_obj(self, category_fields): + if isinstance(category_fields, Iterable): + fields_data = {field_name: getattr(settings, field_name) + for field_name in category_fields} + return ObjectDict(fields_data) + + if isinstance(category_fields, str): + fields_data = {category_fields: getattr(settings, category_fields)} + return ObjectDict(fields_data) + + return ObjectDict() diff --git a/apps/settings/forms/__init__.py b/apps/settings/forms/__init__.py deleted file mode 100644 index 4c5a69e8c..000000000 --- a/apps/settings/forms/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# coding: utf-8 -# - -from .base import * -from .basic import * -from .email import * -from .ldap import * -from .security import * -from .terminal import * diff --git a/apps/settings/forms/base.py b/apps/settings/forms/base.py deleted file mode 100644 index b7b32dea6..000000000 --- a/apps/settings/forms/base.py +++ /dev/null @@ -1,57 +0,0 @@ -# coding: utf-8 -# - -import json -from django import forms -from django.db import transaction -from django.conf import settings - -from ..models import Setting -from common.fields import FormEncryptMixin - -__all__ = ['BaseForm'] - - -class BaseForm(forms.Form): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - for name, field in self.fields.items(): - value = getattr(settings, name, None) - if value is None: # and django_value is None: - continue - - if value is not None: - if isinstance(value, dict): - value = json.dumps(value) - initial_value = value - else: - initial_value = '' - field.initial = initial_value - - def save(self, category="default"): - if not self.is_bound: - raise ValueError("Form is not bound") - - # db_settings = Setting.objects.all() - if not self.is_valid(): - raise ValueError(self.errors) - - 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 == getattr(settings, name): - # continue - - encrypted = True if isinstance(field, FormEncryptMixin) else False - try: - setting = Setting.objects.get(name=name) - except Setting.DoesNotExist: - setting = Setting() - setting.name = name - setting.category = category - setting.encrypted = encrypted - setting.cleaned_value = value - setting.save() - diff --git a/apps/settings/forms/basic.py b/apps/settings/forms/basic.py deleted file mode 100644 index dd93c9537..000000000 --- a/apps/settings/forms/basic.py +++ /dev/null @@ -1,24 +0,0 @@ -# coding: utf-8 -# - -from django import forms -from django.utils.translation import ugettext_lazy as _ -from .base import BaseForm - -__all__ = ['BasicSettingForm'] - - -class BasicSettingForm(BaseForm): - SITE_URL = forms.URLField( - label=_("Current SITE URL"), - help_text="eg: http://jumpserver.abc.com:8080" - ) - USER_GUIDE_URL = forms.URLField( - label=_("User Guide URL"), required=False, - help_text=_("User first login update profile done redirect to it") - ) - EMAIL_SUBJECT_PREFIX = forms.CharField( - max_length=1024, label=_("Email Subject Prefix"), - help_text=_("Tips: Some word will be intercept by mail provider") - ) - diff --git a/apps/settings/forms/email.py b/apps/settings/forms/email.py deleted file mode 100644 index 6fa61148a..000000000 --- a/apps/settings/forms/email.py +++ /dev/null @@ -1,65 +0,0 @@ -# coding: utf-8 -# - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from common.fields import FormEncryptCharField -from .base import BaseForm - -__all__ = ['EmailSettingForm', 'EmailContentSettingForm'] - - -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 = FormEncryptCharField( - max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput, - required=False, - help_text=_("Tips: Some provider use token except password") - ) - EMAIL_FROM = forms.CharField( - max_length=128, label=_("Send user"), initial='', required=False, - help_text=_( - "Tips: Send mail account, default SMTP account as the send account" - ) - ) - EMAIL_RECIPIENT = forms.CharField( - max_length=128, label=_("Test recipient"), initial='', required=False, - help_text=_("Tips: Used only as a test mail recipient") - ) - EMAIL_USE_SSL = forms.BooleanField( - label=_("Use SSL"), initial=False, required=False, - help_text=_("If SMTP port is 465, may be select") - ) - EMAIL_USE_TLS = forms.BooleanField( - label=_("Use TLS"), initial=False, required=False, - help_text=_("If SMTP port is 587, may be select") - ) - - -class EmailContentSettingForm(BaseForm): - EMAIL_CUSTOM_USER_CREATED_SUBJECT = forms.CharField( - max_length=1024, required=False, label=_("Create user email subject"), - help_text=_("Tips: When creating a user, send the subject of the email" - " (eg:Create account successfully)") - ) - EMAIL_CUSTOM_USER_CREATED_HONORIFIC = forms.CharField( - max_length=1024, required=False, label=_("Create user honorific"), - help_text=_("Tips: When creating a user, send the honorific of the " - "email (eg:Hello)") - ) - EMAIL_CUSTOM_USER_CREATED_BODY = forms.CharField( - max_length=4096, required=False, widget=forms.Textarea(), - label=_('Create user email content'), - help_text=_('Tips:When creating a user, send the content of the email') - ) - EMAIL_CUSTOM_USER_CREATED_SIGNATURE = forms.CharField( - max_length=512, required=False, label=_("Signature"), - help_text=_("Tips: Email signature (eg:jumpserver)") - ) diff --git a/apps/settings/forms/ldap.py b/apps/settings/forms/ldap.py deleted file mode 100644 index c44d1c3e4..000000000 --- a/apps/settings/forms/ldap.py +++ /dev/null @@ -1,46 +0,0 @@ -# coding: utf-8 -# - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from common.fields import FormDictField, FormEncryptCharField -from .base import BaseForm - - -__all__ = ['LDAPSettingForm'] - - -class LDAPSettingForm(BaseForm): - AUTH_LDAP_SERVER_URI = forms.CharField( - label=_("LDAP server"), - ) - AUTH_LDAP_BIND_DN = forms.CharField( - required=False, label=_("Bind DN"), - ) - AUTH_LDAP_BIND_PASSWORD = FormEncryptCharField( - label=_("Password"), - widget=forms.PasswordInput, required=False - ) - AUTH_LDAP_SEARCH_OU = forms.CharField( - label=_("User OU"), - help_text=_("Use | split User OUs"), - required=False, - ) - AUTH_LDAP_SEARCH_FILTER = forms.CharField( - label=_("User search filter"), - help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)") - ) - AUTH_LDAP_USER_ATTR_MAP = FormDictField( - label=_("User attr map"), - help_text=_( - "User attr map present how to map LDAP user attr to jumpserver, " - "username,name,email is jumpserver attr" - ), - ) - # 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"), required=False - # ) - AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), required=False) diff --git a/apps/settings/forms/security.py b/apps/settings/forms/security.py deleted file mode 100644 index 6d30d8b73..000000000 --- a/apps/settings/forms/security.py +++ /dev/null @@ -1,93 +0,0 @@ -# coding: utf-8 -# - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from .base import BaseForm - - -__all__ = ['SecuritySettingForm'] - - -class SecuritySettingForm(BaseForm): - # MFA global setting - SECURITY_MFA_AUTH = forms.BooleanField( - required=False, label=_("MFA"), - help_text=_( - 'After opening, all user login must use MFA' - '(valid for all users, including administrators)' - ) - ) - # Execute commands for user - SECURITY_COMMAND_EXECUTION = forms.BooleanField( - required=False, label=_("Batch execute commands"), - help_text=_("Allow user batch execute commands") - ) - SECURITY_SERVICE_ACCOUNT_REGISTRATION = forms.BooleanField( - required=False, label=_("Service account registration"), - help_text=_("Allow using bootstrap token register service account, " - "when terminal setup, can disable it") - ) - # limit login count - SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField( - min_value=3, max_value=99999, - label=_("Limit the number of login failures") - ) - # limit login time - SECURITY_LOGIN_LIMIT_TIME = forms.IntegerField( - min_value=5, max_value=99999, label=_("No logon interval"), - help_text=_( - "Tip: (unit/minute) if the user has failed to log in for a limited " - "number of times, no login is allowed during this time interval." - ) - ) - # ssh max idle time - SECURITY_MAX_IDLE_TIME = forms.IntegerField( - min_value=1, max_value=99999, required=False, - label=_("Connection max idle time"), - help_text=_( - 'If idle time more than it, disconnect connection ' - 'Unit: minute' - ), - ) - # password expiration time - SECURITY_PASSWORD_EXPIRATION_TIME = forms.IntegerField( - min_value=1, max_value=99999, label=_("Password expiration time"), - help_text=_( - "Tip: (unit: day) " - "If the user does not update the password during the time, " - "the user password will expire failure;" - "The password expiration reminder mail will be automatic sent to the user " - "by system within 5 days (daily) before the password expires" - ) - ) - # min length - SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField( - min_value=6, max_value=30, label=_("Password minimum length"), - ) - # upper case - SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField( - required=False, label=_("Must contain capital letters"), - help_text=_( - 'After opening, the user password changes ' - 'and resets must contain uppercase letters') - ) - # lower case - SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField( - required=False, label=_("Must contain lowercase letters"), - help_text=_('After opening, the user password changes ' - 'and resets must contain lowercase letters') - ) - # number - SECURITY_PASSWORD_NUMBER = forms.BooleanField( - required=False, label=_("Must contain numeric characters"), - help_text=_('After opening, the user password changes ' - 'and resets must contain numeric characters') - ) - # special char - SECURITY_PASSWORD_SPECIAL_CHAR = forms.BooleanField( - required=False, label=_("Must contain special characters"), - help_text=_('After opening, the user password changes ' - 'and resets must contain special characters') - ) diff --git a/apps/settings/forms/terminal.py b/apps/settings/forms/terminal.py deleted file mode 100644 index d879e88d2..000000000 --- a/apps/settings/forms/terminal.py +++ /dev/null @@ -1,50 +0,0 @@ -# coding: utf-8 -# - - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from .base import BaseForm - -__all__ = ['TerminalSettingForm'] - - -class TerminalSettingForm(BaseForm): - SORT_BY_CHOICES = ( - ('hostname', _('Hostname')), - ('ip', _('IP')), - ) - PAGE_SIZE_CHOICES = ( - ('all', _('All')), - ('auto', _('Auto')), - (10, 10), - (15, 15), - (25, 25), - (50, 50), - ) - TERMINAL_PASSWORD_AUTH = forms.BooleanField( - required=False, label=_("Password auth") - ) - TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField( - required=False, label=_("Public key auth") - ) - TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField( - min_value=5, max_value=99999, label=_("Heartbeat interval"), - help_text=_("Units: seconds") - ) - TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField( - choices=SORT_BY_CHOICES, label=_("List sort by") - ) - TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField( - choices=PAGE_SIZE_CHOICES, label=_("List page size"), - ) - TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField( - min_value=1, max_value=99999, label=_("Session keep duration"), - help_text=_("Units: days, Session, record, command will be delete " - "if more than duration, only in database") - ) - TERMINAL_TELNET_REGEX = forms.CharField( - required=False, label=_("Telnet login regex"), - help_text=_("ex: Last\s*login|success|成功") - ) diff --git a/apps/settings/models.py b/apps/settings/models.py index 75b56bb54..30732a00c 100644 --- a/apps/settings/models.py +++ b/apps/settings/models.py @@ -10,7 +10,8 @@ from common.utils import signer class SettingQuerySet(models.QuerySet): def __getattr__(self, item): - instances = self.filter(name=item) + queryset = list(self) + instances = [i for i in queryset if i.name == item] if len(instances) == 1: return instances[0] else: diff --git a/apps/settings/serializers/__init__.py b/apps/settings/serializers/__init__.py index 045763364..5868a76df 100644 --- a/apps/settings/serializers/__init__.py +++ b/apps/settings/serializers/__init__.py @@ -4,3 +4,4 @@ from .email import * from .ldap import * from .public import * +from .settings import * diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py new file mode 100644 index 000000000..c25bd0196 --- /dev/null +++ b/apps/settings/serializers/settings.py @@ -0,0 +1,124 @@ +# coding: utf-8 + +from django.db import transaction +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers +from ..models import Setting + +__all__ = ['SettingsSerializer'] + + +class BasicSettingSerializer(serializers.Serializer): + SITE_URL = serializers.URLField(required=True) + USER_GUIDE_URL = serializers.URLField(required=False, allow_blank=True, ) + EMAIL_SUBJECT_PREFIX = serializers.CharField(max_length=1024, required=True) + + +class EmailSettingSerializer(serializers.Serializer): + encrypt_fields = ["EMAIL_HOST_PASSWORD", ] + + EMAIL_HOST = serializers.CharField(max_length=1024, required=True) + EMAIL_PORT = serializers.CharField(max_length=5, required=True) + EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True) + EMAIL_HOST_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False, ) + EMAIL_FROM = serializers.CharField(max_length=128, allow_blank=True, required=False) + EMAIL_RECIPIENT = serializers.CharField(max_length=128, allow_blank=True, required=False) + EMAIL_USE_SSL = serializers.BooleanField(required=False) + EMAIL_USE_TLS = serializers.BooleanField(required=False) + + +class EmailContentSettingSerializer(serializers.Serializer): + EMAIL_CUSTOM_USER_CREATED_SUBJECT = serializers.CharField(max_length=1024, allow_blank=True, required=False, ) + EMAIL_CUSTOM_USER_CREATED_HONORIFIC = serializers.CharField(max_length=1024, allow_blank=True, required=False, ) + EMAIL_CUSTOM_USER_CREATED_BODY = serializers.CharField(max_length=4096, allow_blank=True, required=False) + EMAIL_CUSTOM_USER_CREATED_SIGNATURE = serializers.CharField(max_length=512, allow_blank=True, required=False) + + +class LdapSettingSerializer(serializers.Serializer): + encrypt_fields = ["AUTH_LDAP_BIND_PASSWORD", ] + + AUTH_LDAP_SERVER_URI = serializers.CharField(required=True) + AUTH_LDAP_BIND_DN = serializers.CharField(required=False) + AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False) + AUTH_LDAP_SEARCH_OU = serializers.CharField(max_length=1024, allow_blank=True, required=False) + AUTH_LDAP_SEARCH_FILTER = serializers.CharField(max_length=1024, required=True) + AUTH_LDAP_USER_ATTR_MAP = serializers.DictField(required=True) + AUTH_LDAP = serializers.BooleanField(required=False) + + +class TerminalSettingSerializer(serializers.Serializer): + SORT_BY_CHOICES = ( + ('hostname', _('Hostname')), + ('ip', _('IP')) + ) + + PAGE_SIZE_CHOICES = ( + ('all', _('All')), + ('auto', _('Auto')), + ('10', '10'), + ('15', '15'), + ('25', '25'), + ('50', '50'), + ) + TERMINAL_PASSWORD_AUTH = serializers.BooleanField(required=False) + TERMINAL_PUBLIC_KEY_AUTH = serializers.BooleanField(required=False) + TERMINAL_HEARTBEAT_INTERVAL = serializers.IntegerField(min_value=5, max_value=99999, required=True) + TERMINAL_ASSET_LIST_SORT_BY = serializers.ChoiceField(SORT_BY_CHOICES, required=False) + TERMINAL_ASSET_LIST_PAGE_SIZE = serializers.ChoiceField(PAGE_SIZE_CHOICES, required=False) + TERMINAL_SESSION_KEEP_DURATION = serializers.IntegerField(min_value=1, max_value=99999, required=True) + TERMINAL_TELNET_REGEX = serializers.CharField(allow_blank=True, required=False) + + +class SecuritySettingSerializer(serializers.Serializer): + SECURITY_MFA_AUTH = serializers.BooleanField(required=False) + SECURITY_COMMAND_EXECUTION = serializers.BooleanField(required=False) + SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(required=True) + SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField(min_value=3, max_value=99999, required=True) + SECURITY_LOGIN_LIMIT_TIME = serializers.IntegerField(min_value=5, max_value=99999, required=True) + SECURITY_MAX_IDLE_TIME = serializers.IntegerField(min_value=1, max_value=99999, required=False) + SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField(min_value=1, max_value=99999, required=True) + SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField(min_value=6, max_value=30, required=True) + SECURITY_PASSWORD_UPPER_CASE = serializers.BooleanField(required=False) + SECURITY_PASSWORD_LOWER_CASE = serializers.BooleanField(required=False) + SECURITY_PASSWORD_NUMBER = serializers.BooleanField(required=False) + SECURITY_PASSWORD_SPECIAL_CHAR = serializers.BooleanField(required=False) + + +class SettingsSerializer(serializers.Serializer): + basic = BasicSettingSerializer(required=False) + email = EmailSettingSerializer(required=False) + email_content = EmailContentSettingSerializer(required=False) + ldap = LdapSettingSerializer(required=False) + terminal = TerminalSettingSerializer(required=False) + security = SecuritySettingSerializer(required=False) + + encrypt_fields = ["EMAIL_HOST_PASSWORD", "AUTH_LDAP_BIND_PASSWORD"] + + def create(self, validated_data): + pass + + def update(self, instance, validated_data): + for category, category_data in validated_data.items(): + if not category_data: + continue + self.update_validated_settings(category_data) + for field_name, field_value in category_data.items(): + setattr(getattr(instance, category), field_name, field_value) + + return instance + + def update_validated_settings(self, validated_data, category='default'): + if not validated_data: + return + with transaction.atomic(): + for field_name, field_value in validated_data.items(): + try: + setting = Setting.objects.get(name=field_name) + except Setting.DoesNotExist: + setting = Setting() + encrypted = True if field_name in self.encrypt_fields else False + setting.name = field_name + setting.category = category + setting.encrypted = encrypted + setting.cleaned_value = field_value + setting.save() diff --git a/apps/settings/templates/settings/_ldap_list_users_modal.html b/apps/settings/templates/settings/_ldap_list_users_modal.html deleted file mode 100644 index 8589b0066..000000000 --- a/apps/settings/templates/settings/_ldap_list_users_modal.html +++ /dev/null @@ -1,176 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% load static %} - -{% block modal_class %}modal-lg{% endblock %} -{% block modal_id %}ldap_list_users_modal{% endblock %} -{% block modal_title%}{% trans "LDAP user list" %}{% endblock %} - -{% block modal_help_message%}
{% trans 'Please submit the LDAP configuration before import' %}
{% endblock %} - -{% block modal_body %} - - - - - -
-
-
-
- - - - - - - - - - - - - -
{% trans 'Username' %}{% trans 'Name' %}{% trans 'Email' %}{% trans 'Existing' %}
-
-
{% trans 'Loading' %}...
-
-
-
-
-
- - -{% endblock %} - -{% block modal_button %} - - -{% endblock %} - - - diff --git a/apps/settings/templates/settings/_ldap_test_user_login_modal.html b/apps/settings/templates/settings/_ldap_test_user_login_modal.html deleted file mode 100644 index 3359d2468..000000000 --- a/apps/settings/templates/settings/_ldap_test_user_login_modal.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}test_user_login_modal{% endblock %} -{% block modal_title%}{% trans "Test LDAP user login" %}{% endblock %} -{% block modal_comment %}{% trans "Save the configuration before testing the login" %}{% endblock %} -{% block modal_body %} -
- {% csrf_token %} -
- -
- -
-
-
- -
- -
-
-
- -{% endblock %} -{% block modal_confirm_id %}btn_test_user_login_modal_confirm{% endblock %} diff --git a/apps/settings/templates/settings/_setting_tabs.html b/apps/settings/templates/settings/_setting_tabs.html deleted file mode 100644 index b012a6669..000000000 --- a/apps/settings/templates/settings/_setting_tabs.html +++ /dev/null @@ -1,37 +0,0 @@ -{% load i18n %} - - - diff --git a/apps/settings/templates/settings/basic_setting.html b/apps/settings/templates/settings/basic_setting.html deleted file mode 100644 index ac8cabb8b..000000000 --- a/apps/settings/templates/settings/basic_setting.html +++ /dev/null @@ -1,64 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% 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/settings/templates/settings/email_content_setting.html b/apps/settings/templates/settings/email_content_setting.html deleted file mode 100644 index c2c5b2720..000000000 --- a/apps/settings/templates/settings/email_content_setting.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} - -

{% trans "Create User setting" %}

- {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SUBJECT layout="horizontal" %} - {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_HONORIFIC layout="horizontal" %} - {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_BODY layout="horizontal" %} - {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SIGNATURE layout="horizontal" %} -
- -
-
- - -
-
-
-
-
-
-
-
-
-
- -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/settings/templates/settings/email_setting.html b/apps/settings/templates/settings/email_setting.html deleted file mode 100644 index d62a921cd..000000000 --- a/apps/settings/templates/settings/email_setting.html +++ /dev/null @@ -1,91 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% 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/settings/templates/settings/ldap_setting.html b/apps/settings/templates/settings/ldap_setting.html deleted file mode 100644 index 42902391b..000000000 --- a/apps/settings/templates/settings/ldap_setting.html +++ /dev/null @@ -1,99 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% 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 %} -
-
-
- - - - - -
-
-
-
-
-
-
-
-
-
- - {% include 'settings/_ldap_list_users_modal.html' %} - {% include 'settings/_ldap_test_user_login_modal.html' %} -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/settings/templates/settings/security_setting.html b/apps/settings/templates/settings/security_setting.html deleted file mode 100644 index 663632a72..000000000 --- a/apps/settings/templates/settings/security_setting.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} - -

{% trans "Security setting" %}

- {% for field in form %} - {% if forloop.counter == 8 %} -
-

{% trans "Password check rule" %}

- {% endif %} - - {% 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/settings/templates/settings/terminal_setting.html b/apps/settings/templates/settings/terminal_setting.html deleted file mode 100644 index a0b35aabb..000000000 --- a/apps/settings/templates/settings/terminal_setting.html +++ /dev/null @@ -1,72 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} -{% block help_message %} - {% trans "Command and Replay storage configuration migrated to" %} - {% trans "Sessions -> Terminal -> Storage configuration" %} - {% trans 'Here' %} -{% endblock %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% 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 %} -
-
-
- - -
-
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/settings/urls/api_urls.py b/apps/settings/urls/api_urls.py index 689e1ea82..0db9c7c54 100644 --- a/apps/settings/urls/api_urls.py +++ b/apps/settings/urls/api_urls.py @@ -14,5 +14,6 @@ urlpatterns = [ path('ldap/users/import/', api.LDAPUserImportAPI.as_view(), name='ldap-user-import'), path('ldap/cache/refresh/', api.LDAPCacheRefreshAPI.as_view(), name='ldap-cache-refresh'), + path('setting/', api.SettingsApi.as_view(), name='settings-setting'), path('public/', api.PublicSettingApi.as_view(), name='public-setting'), ] diff --git a/apps/settings/urls/view_urls.py b/apps/settings/urls/view_urls.py deleted file mode 100644 index 6a1c5baaf..000000000 --- a/apps/settings/urls/view_urls.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import absolute_import - -from django.conf.urls import url - -from .. import views - -app_name = 'common' - -urlpatterns = [ - url(r'^$', views.BasicSettingView.as_view(), name='basic-setting'), - url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'), - url(r'^email-content/$', views.EmailContentSettingView.as_view(), name='email-content-setting'), - url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'), - url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'), - url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'), -] diff --git a/apps/settings/utils/__init__.py b/apps/settings/utils/__init__.py index 87bc6198f..e17c4e43c 100644 --- a/apps/settings/utils/__init__.py +++ b/apps/settings/utils/__init__.py @@ -2,3 +2,4 @@ # from .ldap import * +from .common import * diff --git a/apps/settings/utils/common.py b/apps/settings/utils/common.py new file mode 100644 index 000000000..e64ceaf7f --- /dev/null +++ b/apps/settings/utils/common.py @@ -0,0 +1,18 @@ +# coding: utf-8 + + +class ObjectDict(dict): + def __getattr__(self, name): + if name in self: + return self[name] + else: + raise AttributeError("No such attribute: " + name) + + def __setattr__(self, name, value): + self[name] = value + + def __delattr__(self, name): + if name in self: + del self[name] + else: + raise AttributeError("No such attribute: " + name) diff --git a/apps/settings/utils/ldap.py b/apps/settings/utils/ldap.py index a13dcc063..516472d90 100644 --- a/apps/settings/utils/ldap.py +++ b/apps/settings/utils/ldap.py @@ -386,13 +386,13 @@ class LDAPTestUtil(object): try: self._test_server_uri() except LDAPSocketOpenError as e: - error = _("Host or port is disconnected: {}".format(e)) + error = _("Host or port is disconnected: {}").format(e) except LDAPSessionTerminatedByServerError as e: - error = _('The port is not the port of the LDAP service: {}'.format(e)) + error = _('The port is not the port of the LDAP service: {}').format(e) except LDAPSocketReceiveError as e: - error = _('Please add certificate: {}'.format(e)) + error = _('Please add certificate: {}').format(e) except Exception as e: - error = _('Unknown error: {}'.format(e)) + error = _('Unknown error: {}').format(e) else: return raise LDAPInvalidServerError(error) @@ -413,13 +413,13 @@ class LDAPTestUtil(object): try: self._test_bind_dn() except LDAPUserNameIsMandatoryError as e: - error = _('Please enter Bind DN: {}'.format(e)) + error = _('Please enter Bind DN: {}').format(e) except LDAPPasswordIsMandatoryError as e: - error = _('Please enter Password: {}'.format(e)) + error = _('Please enter Password: {}').format(e) except LDAPInvalidDnError as e: - error = _('Please enter correct Bind DN and Password: {}'.format(e)) + error = _('Please enter correct Bind DN and Password: {}').format(e) except Exception as e: - error = _('Unknown error: {}'.format(e)) + error = _('Unknown error: {}').format(e) else: return raise LDAPBindError(error) @@ -435,7 +435,7 @@ class LDAPTestUtil(object): user_entries = util.search_user_entries() logger.debug('Search ou: {}, count user: {}'.format(search_ou, len(user_entries))) if len(user_entries) == 0: - error = _('Invalid User OU or User search filter: {}'.format(search_ou)) + error = _('Invalid User OU or User search filter: {}').format(search_ou) raise self.LDAPInvalidSearchOuOrFilterError(error) def test_search_ou_and_filter(self): @@ -449,7 +449,7 @@ class LDAPTestUtil(object): error = e raise self.LDAPInvalidAttributeMapError(error) except Exception as e: - error = _('Unknown error: {}'.format(e)) + error = _('Unknown error: {}').format(e) else: return raise self.LDAPInvalidSearchOuOrFilterError(error) @@ -466,7 +466,7 @@ class LDAPTestUtil(object): actually_contain_attr = set(attr_map.keys()) result = should_contain_attr - actually_contain_attr if len(result) != 0: - error = _('LDAP User attr map not include: {}'.format(result)) + error = _('LDAP User attr map not include: {}').format(result) raise self.LDAPInvalidAttributeMapError(error) def test_attr_map(self): @@ -477,7 +477,7 @@ class LDAPTestUtil(object): except self.LDAPInvalidAttributeMapError as e: error = e except Exception as e: - error = _('Unknown error: {}'.format(e)) + error = _('Unknown error: {}').format(e) else: return raise self.LDAPInvalidAttributeMapError(error) @@ -510,20 +510,20 @@ class LDAPTestUtil(object): try: self._test_config() except LDAPInvalidServerError as e: - msg = _('Error (Invalid LDAP server): {}'.format(e)) + msg = _('Error (Invalid LDAP server): {}').format(e) except LDAPBindError as e: - msg = _('Error (Invalid Bind DN): {}'.format(e)) + msg = _('Error (Invalid Bind DN): {}').format(e) except self.LDAPInvalidAttributeMapError as e: - msg = _('Error (Invalid LDAP User attr map): {}'.format(e)) + msg = _('Error (Invalid LDAP User attr map): {}').format(e) except self.LDAPInvalidSearchOuOrFilterError as e: - msg = _('Error (Invalid User OU or User search filter): {}'.format(e)) + msg = _('Error (Invalid User OU or User search filter): {}').format(e) except self.LDAPNotEnabledAuthError as e: - msg = _('Error (Not enabled LDAP authentication): {}'.format(e)) + msg = _('Error (Not enabled LDAP authentication): {}').format(e) except Exception as e: msg = _('Error (Unknown): {}').format(e) else: status = True - msg = _('Succeed: Match {} s user'.format(len(self.user_entries))) + msg = _('Succeed: Match {} s user').format(len(self.user_entries)) if not status: logger.error(msg, exc_info=True) @@ -556,16 +556,16 @@ class LDAPTestUtil(object): try: self._test_login(username, password) except LDAPConfigurationError as e: - msg = _('Authentication failed (configuration incorrect): {}'.format(e)) + msg = _('Authentication failed (configuration incorrect): {}').format(e) except self.LDAPBeforeLoginCheckError as e: - msg = _('Authentication failed (before login check failed): {}'.format(e)) + msg = _('Authentication failed (before login check failed): {}').format(e) except LDAPUser.AuthenticationFailed as e: - msg = _('Authentication failed (username or password incorrect): {}'.format(e)) + msg = _('Authentication failed (username or password incorrect): {}').format(e) except Exception as e: - msg = _("Authentication failed (Unknown): {}".format(e)) + msg = _("Authentication failed (Unknown): {}").format(e) else: status = True - msg = _("Authentication success: {}".format(username)) + msg = _("Authentication success: {}").format(username) if not status: logger.error(msg, exc_info=True) diff --git a/apps/settings/views.py b/apps/settings/views.py deleted file mode 100644 index 6ebbeef97..000000000 --- a/apps/settings/views.py +++ /dev/null @@ -1,178 +0,0 @@ -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 common.permissions import PermissionsMixin, IsSuperUser -from .utils import LDAPSyncUtil -from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ - TerminalSettingForm, SecuritySettingForm, EmailContentSettingForm - - -class BasicSettingView(PermissionsMixin, TemplateView): - form_class = BasicSettingForm - template_name = "settings/basic_setting.html" - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Settings'), - 'action': _('Basic setting'), - 'form': self.form_class(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - return redirect('settings:basic-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) - - -class EmailSettingView(PermissionsMixin, TemplateView): - form_class = EmailSettingForm - template_name = "settings/email_setting.html" - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Settings'), - 'action': _('Email setting'), - 'form': self.form_class(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - return redirect('settings:email-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) - - -class LDAPSettingView(PermissionsMixin, TemplateView): - form_class = LDAPSettingForm - template_name = "settings/ldap_setting.html" - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Settings'), - 'action': _('LDAP setting'), - 'form': self.form_class(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - LDAPSyncUtil().clear_cache() - return redirect('settings:ldap-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) - - -class TerminalSettingView(PermissionsMixin, TemplateView): - form_class = TerminalSettingForm - template_name = "settings/terminal_setting.html" - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - from terminal.models import CommandStorage, ReplayStorage - command_storage = CommandStorage.objects.all() - replay_storage = ReplayStorage.objects.all() - - context = { - 'app': _('Settings'), - 'action': _('Terminal setting'), - 'form': self.form_class(), - 'replay_storage': replay_storage, - 'command_storage': 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") - messages.success(request, msg) - return redirect('settings:terminal-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) - - -class SecuritySettingView(PermissionsMixin, TemplateView): - form_class = SecuritySettingForm - template_name = "settings/security_setting.html" - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Settings'), - 'action': _('Security setting'), - 'form': self.form_class(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - return redirect('settings:security-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) - - -class EmailContentSettingView(PermissionsMixin, TemplateView): - template_name = "settings/email_content_setting.html" - form_class = EmailContentSettingForm - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Settings'), - 'action': _('Email content setting'), - 'form': self.form_class(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - return redirect('settings:email-content-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 9081d3a94..787235568 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -3,6 +3,10 @@ //此函数用于checkbox的全选和反选 var checked = false; +function gettext(s) { + return s +} + function check_all(form) { var checkboxes = document.getElementById(form); if (checked === false) { @@ -138,11 +142,11 @@ function setAjaxCSRFToken() { } function activeNav(prefix) { - var path = document.location.pathname; - if (prefix) { - path = path.replace(prefix, ''); - console.log(path); + if (!prefix) { + prefix = '/core' } + var path = document.location.pathname; + path = path.replace(prefix, ''); var urlArray = path.split("/"); var app = urlArray[1]; var resource = urlArray[2]; diff --git a/apps/templates/_base_only_content.html b/apps/templates/_base_only_content.html index 1e6a16c84..d773e6a78 100644 --- a/apps/templates/_base_only_content.html +++ b/apps/templates/_base_only_content.html @@ -11,7 +11,6 @@ {% include '_head_css_js.html' %} - -{% endblock %} - -{% block table_pagination %} -{% endblock %} - -{% block table_search %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - - -
{% trans 'Command' %}{% trans 'Risk level' %}{% trans 'User' %}{% trans 'Asset' %}{% trans 'System user'%}{% trans 'Session' %}{% trans 'Datetime' %}
- -
-
- -
- -
-
-
- -
-
-
- - - to - -
-
-
-{% include '_filter_dropdown.html' %} -{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - - - -{% endblock %} - - diff --git a/apps/terminal/templates/terminal/command_report.html b/apps/terminal/templates/terminal/command_report.html deleted file mode 100644 index 3542c1423..000000000 --- a/apps/terminal/templates/terminal/command_report.html +++ /dev/null @@ -1,103 +0,0 @@ -{% load common_tags %} -{% load static %} - - - - - Command Report - - - - -
-
-

Command Report

-
-

total: {{ total_count }}

-

date: {{ now | ts_to_date }}

-
- -
- -
- {% for command in queryset %} -
-

- [{{ command.user}} {{ command.system_user }}@{{ command.asset }} {{ command.timestamp | ts_to_date }}] - {{ forloop.counter }} -

- -

$ {{ command.input }}

- -
{{ command.output }}
-
- -
- {% endfor %} -
-
-
- - \ No newline at end of file diff --git a/apps/terminal/templates/terminal/command_storage_create_update.html b/apps/terminal/templates/terminal/command_storage_create_update.html deleted file mode 100644 index 8abb976ae..000000000 --- a/apps/terminal/templates/terminal/command_storage_create_update.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'terminal/base_storage_create_update.html' %} -{% load i18n static %} - -{% block custom_foot_js %} -{{ block.super }} - - -{% endblock %} diff --git a/apps/terminal/templates/terminal/command_storage_list.html b/apps/terminal/templates/terminal/command_storage_list.html deleted file mode 100644 index 724425cca..000000000 --- a/apps/terminal/templates/terminal/command_storage_list.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends 'terminal/base_storage_list.html' %} -{% load i18n static %} - -{% block create_storage_url %}{% url "terminal:command-storage-create" %}{% endblock%} -{% block create_storage_info %}{% trans 'Create command storage' %}{% endblock %} -{% block custom_foot_js %} -{{ block.super }} - -{% endblock %} - diff --git a/apps/terminal/templates/terminal/replay_storage_create_update.html b/apps/terminal/templates/terminal/replay_storage_create_update.html deleted file mode 100644 index 878301d93..000000000 --- a/apps/terminal/templates/terminal/replay_storage_create_update.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends 'terminal/base_storage_create_update.html' %} -{% load i18n static %} - -{% block custom_foot_js %} -{{ block.super }} - - -{% endblock %} diff --git a/apps/terminal/templates/terminal/replay_storage_list.html b/apps/terminal/templates/terminal/replay_storage_list.html deleted file mode 100644 index 786f4c844..000000000 --- a/apps/terminal/templates/terminal/replay_storage_list.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'terminal/base_storage_list.html' %} -{% load i18n static %} - -{% block create_storage_url %}{% url "terminal:replay-storage-create" %}{% endblock%} -{% block create_storage_info %}{% trans 'Create replay storage' %}{% endblock %} -{% block custom_foot_js %} -{{ block.super }} - -{% endblock %} diff --git a/apps/terminal/templates/terminal/session_commands.html b/apps/terminal/templates/terminal/session_commands.html deleted file mode 100644 index 3aeb2989f..000000000 --- a/apps/terminal/templates/terminal/session_commands.html +++ /dev/null @@ -1,96 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} -{% load common_tags %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Command list' %} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - {% for command in object_list %} - - - - - - - {% empty %} - - - - {% endfor %} - - - - - - -
ID{% trans 'Command' %}{% trans 'Datetime' %}
{{ forloop.counter }}{{ command.input | truncatechars:40 }}
-$ {{ command.input }}
-
-{{ command.output }}
-                                                
{{ command.timestamp|ts_to_date}}
{% trans "There is no command about this session" %}
-
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -{% endblock %} -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/terminal/templates/terminal/session_detail.html b/apps/terminal/templates/terminal/session_detail.html deleted file mode 100644 index 1863ad75f..000000000 --- a/apps/terminal/templates/terminal/session_detail.html +++ /dev/null @@ -1,211 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} -{% load common_tags %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
    -
    -
    -
    - -
    -
    -
    -
    - {{ object.id }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    {% trans 'User' %}:{{ object.user }}
    {% trans 'Asset' %}:{{ object.asset }}
    {% trans 'System user' %}:{{ object.system_user }}
    {% trans 'Protocol' %}:{{ object.protocol }}
    {% trans 'Login from' %}:{{ object.login_from_display }}
    {% trans 'Remote addr' %}:{{ object.remote_addr }}
    {% trans 'Date start' %}:{{ object.date_start }}
    {% trans 'Date end' %}:{{ object.date_end }}
    -
    -
    -
    -
    -
    -
    - {% trans 'Quick modify' %} -
    -
    - - - {% if object.is_finished %} - - - - - - - - - {% else %} - - - - - - - - - - - - - {% endif %} - -
    {% trans 'Replay session' %}: - - - -
    {% trans 'Download replay' %}: - - - -
    {% trans 'Terminate session' %}: - - - -
    -
    -
    -
    -
    -
    -
    -
    -
    - -{% endblock %} -{% block custom_foot_js %} - - - - -{% endblock %} diff --git a/apps/terminal/templates/terminal/session_list.html b/apps/terminal/templates/terminal/session_list.html deleted file mode 100644 index c7e7dce6c..000000000 --- a/apps/terminal/templates/terminal/session_list.html +++ /dev/null @@ -1,322 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load terminal_tags %} -{% load common_tags %} -{% block custom_head_css_js %} - -{% endblock %} - -{% block content_left_head %} -{% endblock %} - -{% block table_pagination %} -{% endblock %} - -{% block table_search %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - - - - - - -
    {% trans 'ID' %}{% trans 'User' %}{% trans 'Asset' %}{% trans 'System user' %}{% trans 'Remote addr' %}{% trans 'Protocol' %}{% trans 'Login from' %}{% trans 'Command' %}{% trans 'Date start' %}{% trans 'Duration' %}{% trans 'Action' %}
    - -
    - {% if type == "online" and request.user.can_admin_current_org %} -
    - -
    - -
    -
    - {% endif %} -
    - -
    -
    -
    - - - to - -
    -
    -
    - - -{% endblock %} - - -{% block custom_foot_js %} - - - -{% endblock %} - diff --git a/apps/terminal/templates/terminal/terminal_detail.html b/apps/terminal/templates/terminal/terminal_detail.html deleted file mode 100644 index 14aff902c..000000000 --- a/apps/terminal/templates/terminal/terminal_detail.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
    -
    -
    -
    - -
    -
    -
    -
    - {{ terminal.name }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    {% trans 'Name' %}:{{ terminal.name }}
    {% trans 'Remote addr' %}:{{ terminal.remote_addr }}
    {% trans 'SSH port' %}:{{ terminal.ssh_port }}
    {% trans 'Http port' %}:{{ terminal.http_port }}
    {% trans 'Date created' %}:{{ terminal.date_created }}
    {% trans 'Comment' %}:{{ asset.comment }}
    -
    -
    -
    -
    -
    -
    -
    -
    -{% endblock %} - diff --git a/apps/terminal/templates/terminal/terminal_list.html b/apps/terminal/templates/terminal/terminal_list.html deleted file mode 100644 index f77949bfa..000000000 --- a/apps/terminal/templates/terminal/terminal_list.html +++ /dev/null @@ -1,144 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block custom_head_css_js %} -{{ block.super }} - -{% endblock %} - -{% block table_search %}{% endblock %} - -{% block table_container %} - - - - - - - -{# #} -{# #} - - - - - - - - -
    -
    - -
    -
    {% trans 'Name' %}{% trans 'Addr' %}{% trans 'SSH port' %}{% trans 'Http port' %}{% trans 'Session' %}{% trans 'Active' %}{% trans 'Alive' %}{% trans 'Action' %}
    -{% include 'terminal/terminal_modal_accept.html' %} -{% endblock %} -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/terminal/templates/terminal/terminal_modal_accept.html b/apps/terminal/templates/terminal/terminal_modal_accept.html deleted file mode 100644 index ae81ff95e..000000000 --- a/apps/terminal/templates/terminal/terminal_modal_accept.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}modal_terminal_accept{% endblock %} -{% block modal_class %}modal-lg{% endblock %} -{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %} -{% block modal_body %} -{% load bootstrap3 %} -
    - {% csrf_token %} - - {% bootstrap_field form.name layout="horizontal" %} - {% 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.replay_storage layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} -
    - -{% endblock %} -{% block modal_confirm_id %}btn-confirm{% endblock %} \ No newline at end of file diff --git a/apps/terminal/templates/terminal/terminal_modal_test.html b/apps/terminal/templates/terminal/terminal_modal_test.html deleted file mode 100644 index 862e39f62..000000000 --- a/apps/terminal/templates/terminal/terminal_modal_test.html +++ /dev/null @@ -1,5 +0,0 @@ -
    - {% csrf_token %} - {{ form }} - -
    \ No newline at end of file diff --git a/apps/terminal/templates/terminal/terminal_update.html b/apps/terminal/templates/terminal/terminal_update.html deleted file mode 100644 index e140cd678..000000000 --- a/apps/terminal/templates/terminal/terminal_update.html +++ /dev/null @@ -1,70 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
    -
    -
    -
    -
    -
    {{ action }}
    - -
    -
    -
    - {% csrf_token %} -

    {% trans 'Info' %}

    - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.remote_addr layout="horizontal" %} - {% bootstrap_field form.command_storage layout="horizontal" %} - {% bootstrap_field form.replay_storage layout="horizontal" %} - -
    -

    {% trans 'Other' %}

    - {% bootstrap_field form.comment layout="horizontal" %} -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -{% endblock %} -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/terminal/templatetags/__init__.py b/apps/terminal/templatetags/__init__.py deleted file mode 100644 index ec51c5a2b..000000000 --- a/apps/terminal/templatetags/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -# diff --git a/apps/terminal/templatetags/terminal_tags.py b/apps/terminal/templatetags/terminal_tags.py deleted file mode 100644 index c0844eb31..000000000 --- a/apps/terminal/templatetags/terminal_tags.py +++ /dev/null @@ -1,14 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from django import template - -from ..backends import get_multi_command_storage - -register = template.Library() - - -@register.filter -def get_session_command_amount(session_id): - command_store = get_multi_command_storage() - return command_store.count(session=session_id) - diff --git a/apps/terminal/urls/views_urls.py b/apps/terminal/urls/views_urls.py deleted file mode 100644 index 048a79d71..000000000 --- a/apps/terminal/urls/views_urls.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# - -from django.urls import path - -from .. import views - -app_name = 'terminal' - -urlpatterns = [ - # Terminal view - path('terminal/', views.TerminalListView.as_view(), name='terminal-list'), - path('terminal//', views.TerminalDetailView.as_view(), name='terminal-detail'), - path('terminal//connect/', views.TerminalConnectView.as_view(), name='terminal-connect'), - path('terminal//update/', views.TerminalUpdateView.as_view(), name='terminal-update'), - path('/accept/', views.TerminalAcceptView.as_view(), name='terminal-accept'), - path('web-terminal/', views.WebTerminalView.as_view(), name='web-terminal'), - path('web-sftp/', views.WebSFTPView.as_view(), name='web-sftp'), - - # Session view - path('session-online/', views.SessionOnlineListView.as_view(), name='session-online-list'), - path('session-offline/', views.SessionOfflineListView.as_view(), name='session-offline-list'), - path('session//', views.SessionDetailView.as_view(), name='session-detail'), - path('session//commands/', views.SessionCommandsView.as_view(), name='session-commands'), - path('session//replay/download/', views.SessionReplayDownloadView.as_view(), name='session-replay-download'), - - # Command view - path('command/', views.CommandListView.as_view(), name='command-list'), - - # replay-storage - path('terminal/replay-storage/', views.ReplayStorageListView.as_view(), name='replay-storage-list'), - path('terminal/replay-storage/create/', views.ReplayStorageCreateView.as_view(), name='replay-storage-create'), - path('terminal/replay-storage//update/', views.ReplayStorageUpdateView.as_view(), name='replay-storage-update'), - - # command-storage - path('terminal/command-storage/', views.CommandStorageListView.as_view(), name='command-storage-list'), - path('terminal/command-storage/create/', views.CommandStorageCreateView.as_view(), name='command-storage-create'), - path('terminal/command-storage//update/', views.CommandStorageUpdateView.as_view(), name='command-storage-update'), - -] diff --git a/apps/terminal/views/__init__.py b/apps/terminal/views/__init__.py deleted file mode 100644 index a63c3cf9a..000000000 --- a/apps/terminal/views/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# -from .terminal import * -from .session import * -from .command import * -from .storage import * - -# from .replay_storage import * -# from .command_storage import * diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py deleted file mode 100644 index 3ac31eed5..000000000 --- a/apps/terminal/views/command.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.views.generic import TemplateView -from django.utils.translation import ugettext as _ -from django.utils import timezone - -from common.permissions import PermissionsMixin, IsOrgAdmin, IsOrgAuditor - -__all__ = ['CommandListView'] - - -class CommandListView(PermissionsMixin, TemplateView): - template_name = "terminal/command_list.html" - permission_classes = [IsOrgAdmin | IsOrgAuditor] - default_days_ago = 5 - - def get_context_data(self, **kwargs): - now = timezone.now() - context = { - 'app': _('Sessions'), - 'action': _('Command list'), - 'date_from': now - timezone.timedelta(days=self.default_days_ago), - 'date_to': now, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py deleted file mode 100644 index a7e56cd57..000000000 --- a/apps/terminal/views/session.py +++ /dev/null @@ -1,142 +0,0 @@ -# -*- coding: utf-8 -*- -# -import os -import tarfile - -from django.views.generic import ListView, TemplateView, DetailView -from django.views.generic.edit import SingleObjectMixin -from django.utils.translation import ugettext as _ -from django.utils import timezone -from django.utils.encoding import escape_uri_path -from django.http import FileResponse, HttpResponse -from django.core.files.storage import default_storage - -from common.permissions import PermissionsMixin, IsOrgAdmin, IsOrgAuditor -from common.utils import model_to_json -from ..models import Session -from ..backends import get_multi_command_storage -from .. import utils - - -__all__ = [ - 'SessionOnlineListView', 'SessionOfflineListView', - 'SessionDetailView', 'SessionReplayDownloadView', - 'SessionCommandsView', -] - - -class SessionListView(PermissionsMixin, TemplateView): - model = Session - template_name = 'terminal/session_list.html' - date_from = date_to = None - permission_classes = [IsOrgAdmin | IsOrgAuditor] - default_days_ago = 5 - - def get_context_data(self, **kwargs): - now = timezone.now() - context = { - 'date_from': now - timezone.timedelta(days=self.default_days_ago), - 'date_to': now, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SessionOnlineListView(SessionListView): - def get_context_data(self, **kwargs): - context = { - 'app': _('Sessions'), - 'action': _('Session online list'), - 'type': 'online', - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SessionOfflineListView(SessionListView): - def get_context_data(self, **kwargs): - context = { - 'app': _('Sessions'), - 'action': _('Session offline'), - 'type': 'offline', - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SessionDetailView(PermissionsMixin, DetailView): - template_name = 'terminal/session_detail.html' - model = Session - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Sessions'), - 'action': _('Session detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SessionCommandsView(SingleObjectMixin, PermissionsMixin, ListView): - template_name = 'terminal/session_commands.html' - model = Session - object = None - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=self.model.objects.all()) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - command_store = get_multi_command_storage() - return command_store.filter(session=self.object.id) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Sessions'), - 'action': _('Session detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SessionReplayDownloadView(PermissionsMixin, DetailView): - permission_classes = [IsOrgAdmin | IsOrgAuditor] - model = Session - - @staticmethod - def prepare_offline_file(session, local_path): - replay_path = default_storage.path(local_path) - current_dir = os.getcwd() - dir_path = os.path.dirname(replay_path) - replay_filename = os.path.basename(replay_path) - meta_filename = '{}.json'.format(session.id) - offline_filename = '{}.tar'.format(session.id) - os.chdir(dir_path) - - with open(meta_filename, 'wt') as f: - f.write(model_to_json(session)) - - with tarfile.open(offline_filename, 'w') as f: - f.add(replay_filename) - f.add(meta_filename) - file = open(offline_filename, 'rb') - os.chdir(current_dir) - return file - - def get(self, request, *args, **kwargs): - session = self.get_object() - local_path, url = utils.get_session_replay_url(session) - if local_path is None: - error = url - return HttpResponse(error) - file = self.prepare_offline_file(session, local_path) - response = FileResponse(file) - response['Content-Type'] = 'application/octet-stream' - # 这里要注意哦,网上查到的方法都是response['Content-Disposition']='attachment;filename="filename.py"', - # 但是如果文件名是英文名没问题,如果文件名包含中文,下载下来的文件名会被改为url中的path。 - filename = escape_uri_path('{}.tar'.format(session.id)) - disposition = "attachment; filename*=UTF-8''{}".format(filename) - response["Content-Disposition"] = disposition - return response diff --git a/apps/terminal/views/storage.py b/apps/terminal/views/storage.py deleted file mode 100644 index 778cdd489..000000000 --- a/apps/terminal/views/storage.py +++ /dev/null @@ -1,181 +0,0 @@ -# coding: utf-8 -# - -from django.http import Http404 -from django.views.generic import TemplateView -from django.views.generic.edit import CreateView, UpdateView -from django.utils.translation import ugettext as _ - -from common.permissions import PermissionsMixin, IsSuperUser -from terminal.models import ReplayStorage, CommandStorage -from .. import forms, const - - -__all__ = [ - 'ReplayStorageListView', 'ReplayStorageCreateView', - 'ReplayStorageUpdateView', 'CommandStorageListView', - 'CommandStorageCreateView', 'CommandStorageUpdateView' -] - - -class ReplayStorageListView(PermissionsMixin, TemplateView): - template_name = 'terminal/replay_storage_list.html' - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Replay storage list'), - 'is_replay': True, - 'type_choices': const.REPLAY_STORAGE_TYPE_CHOICES_EXTENDS, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandStorageListView(PermissionsMixin, TemplateView): - template_name = 'terminal/command_storage_list.html' - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Command storage list'), - 'type_choices': const.COMMAND_STORAGE_TYPE_CHOICES_EXTENDS, - 'is_command': True, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class BaseStorageCreateUpdateViewMixin: - permission_classes = [IsSuperUser] - default_type = None - form_class = None - form_class_choices = {} - - def get_initial(self): - return {'type': self.get_type()} - - def get_type(self): - return self.default_type - - def get_form_class(self): - tp = self.get_type() - form_class = self.form_class_choices.get(tp) - if not form_class: - raise Http404() - return form_class - - -class ReplayStorageCreateUpdateViewMixin(BaseStorageCreateUpdateViewMixin): - model = ReplayStorage - default_type = const.REPLAY_STORAGE_TYPE_S3 - form_class = forms.ReplayStorageS3Form - form_class_choices = { - const.REPLAY_STORAGE_TYPE_S3: forms.ReplayStorageS3Form, - const.REPLAY_STORAGE_TYPE_CEPH: forms.ReplayStorageCephForm, - const.REPLAY_STORAGE_TYPE_SWIFT: forms.ReplayStorageSwiftForm, - const.REPLAY_STORAGE_TYPE_OSS: forms.ReplayStorageOSSForm, - const.REPLAY_STORAGE_TYPE_AZURE: forms.ReplayStorageAzureForm - } - - -class ReplayStorageCreateView(ReplayStorageCreateUpdateViewMixin, - PermissionsMixin, CreateView): - template_name = 'terminal/replay_storage_create_update.html' - - def get_type(self): - tp = self.request.GET.get("type") - if tp: - return tp.lower() - return super().get_type() - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Create replay storage'), - 'api_action': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class ReplayStorageUpdateView(ReplayStorageCreateUpdateViewMixin, - PermissionsMixin, UpdateView): - template_name = 'terminal/replay_storage_create_update.html' - - def get_initial(self): - initial_data = super().get_initial() - for k, v in self.object.meta.items(): - _k = "{}_{}".format(self.object.type, k.lower()) - initial_data[_k] = v - return initial_data - - def get_type(self): - return self.object.type - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Update replay storage'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandStorageCreateUpdateViewMixin(BaseStorageCreateUpdateViewMixin): - model = CommandStorage - default_type = const.COMMAND_STORAGE_TYPE_ES - form_class = forms.CommandStorageTypeESForm - form_class_choices = { - const.COMMAND_STORAGE_TYPE_ES: forms.CommandStorageTypeESForm - } - - -class CommandStorageCreateView(CommandStorageCreateUpdateViewMixin, - PermissionsMixin, CreateView): - template_name = 'terminal/command_storage_create_update.html' - - def get_type(self): - tp = self.request.GET.get("type") - if tp: - return tp.lower() - return super().get_type() - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Create command storage'), - 'api_action': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandStorageUpdateView(CommandStorageCreateUpdateViewMixin, - PermissionsMixin, UpdateView): - template_name = 'terminal/command_storage_create_update.html' - - def get_initial(self): - initial_data = super().get_initial() - for k, v in self.object.meta.items(): - _k = "{}_{}".format(self.object.type, k.lower()) - if k == 'HOSTS': - v = ','.join(v) - initial_data[_k] = v - return initial_data - - def get_type(self): - return self.object.type - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Update command storage'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - diff --git a/apps/terminal/views/terminal.py b/apps/terminal/views/terminal.py deleted file mode 100644 index 56e3a4dc9..000000000 --- a/apps/terminal/views/terminal.py +++ /dev/null @@ -1,138 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -# -import time -from django.views.generic import ListView, UpdateView, DeleteView, \ - DetailView, View -from django.utils.translation import ugettext as _ -from django.shortcuts import redirect -from django.urls import reverse_lazy, reverse - -from common.mixins import JSONResponseMixin -from ..models import Terminal -from ..forms import TerminalForm -from common.permissions import PermissionsMixin, IsSuperUser - - -__all__ = [ - "TerminalListView", "TerminalUpdateView", "TerminalDetailView", - "TerminalDeleteView", "TerminalConnectView", "TerminalAcceptView", - "WebTerminalView", 'WebSFTPView', -] - - -class TerminalListView(PermissionsMixin, ListView): - model = Terminal - template_name = 'terminal/terminal_list.html' - form_class = TerminalForm - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = super(TerminalListView, self).get_context_data(**kwargs) - context.update({ - 'app': _('Sessions'), - 'action': _('Terminal list'), - 'form': self.form_class() - }) - return context - - -class TerminalUpdateView(PermissionsMixin, UpdateView): - model = Terminal - form_class = TerminalForm - template_name = 'terminal/terminal_update.html' - success_url = reverse_lazy('terminal:terminal-list') - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = super(TerminalUpdateView, self).get_context_data(**kwargs) - context.update({'app': _('Sessions'), 'action': _('Update terminal')}) - return context - - -class TerminalDetailView(PermissionsMixin, DetailView): - model = Terminal - template_name = 'terminal/terminal_detail.html' - context_object_name = 'terminal' - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = super(TerminalDetailView, self).get_context_data(**kwargs) - context.update({ - 'app': _('Sessions'), - 'action': _('Terminal detail') - }) - return context - - -class TerminalDeleteView(PermissionsMixin, DeleteView): - model = Terminal - template_name = 'delete_confirm.html' - success_url = reverse_lazy('terminal:terminal-list') - permission_classes = [IsSuperUser] - - -class TerminalAcceptView(PermissionsMixin, JSONResponseMixin, UpdateView): - model = Terminal - form_class = TerminalForm - template_name = 'terminal/terminal_modal_accept.html' - permission_classes = [IsSuperUser] - - def form_valid(self, form): - terminal = form.save() - terminal.create_app_user() - terminal.is_accepted = True - terminal.is_active = True - terminal.save() - data = { - 'success': True, - 'msg': 'success' - } - return self.render_json_response(data) - - def form_invalid(self, form): - data = { - 'success': False, - 'msg': str(form.errors), - } - return self.render_json_response(data) - - -class TerminalConnectView(PermissionsMixin, DetailView): - """ - Abandon - """ - template_name = 'flash_message_standalone.html' - model = Terminal - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - if self.object.type == 'Web': - context = { - 'title': _('Redirect to web terminal'), - 'messages': _('Redirect to web terminal') + self.object.url, - 'auto_redirect': True, - 'interval': 3, - 'redirect_url': self.object.url - } - else: - context = { - 'title': _('Connect ssh terminal'), - 'messages': _('You should use your ssh client tools ' - 'connect terminal: {}

    ' - '{}'.format(self.object.name, self.object.url)), - 'redirect_url': reverse('terminal:terminal-list') - } - - kwargs.update(context) - return super(TerminalConnectView, self).get_context_data(**kwargs) - - -class WebTerminalView(View): - def get(self, request, *args, **kwargs): - redirect_url = '/luna/?_={}&'.format(int(time.time())) - return redirect(redirect_url + request.GET.urlencode()) - - -class WebSFTPView(View): - def get(self, request, *args, **kwargs): - return redirect('/koko/elfinder/sftp/?' + request.GET.urlencode()) diff --git a/apps/tickets/serializers/ticket.py b/apps/tickets/serializers/ticket.py index e6ca13a2b..f6c995ae0 100644 --- a/apps/tickets/serializers/ticket.py +++ b/apps/tickets/serializers/ticket.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from .. import models @@ -12,7 +13,7 @@ class TicketSerializer(serializers.ModelSerializer): model = models.Ticket fields = [ 'id', 'user', 'user_display', 'title', 'body', - 'assignees', 'assignees_display', + 'assignees', 'assignees_display', 'assignee', 'assignee_display', 'status', 'action', 'date_created', 'date_updated', 'type', 'type_display', 'action_display', ] @@ -20,6 +21,11 @@ class TicketSerializer(serializers.ModelSerializer): 'user_display', 'assignees_display', 'date_created', 'date_updated', ] + extra_kwargs = { + 'status': {'label': _('Status')}, + 'action': {'label': _('Action')}, + 'user_display': {'label': _('User')} + } def create(self, validated_data): validated_data.pop('action') diff --git a/apps/tickets/templates/tickets/ticket_detail.html b/apps/tickets/templates/tickets/ticket_detail.html deleted file mode 100644 index bcbe83045..000000000 --- a/apps/tickets/templates/tickets/ticket_detail.html +++ /dev/null @@ -1,181 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
    -
    -
    -
    -
    -
    - {{ object.title }} -
    - -
    -
    -
    -
    -
    -
    -
    -
    {% trans 'User' %}:
    {{ object.user_display }}
    -
    {% trans 'Type' %}:
    {{ object.get_type_display | default_if_none:"" }}
    -
    {% trans 'Status' %}:
    -
    - {% if object.status == "open" %} - - {{ object.get_status_display }} - - {% elif object.status == "closed" %} - - {{ object.get_status_display }} - - {% endif %} -
    -
    -
    -
    -
    -
    {% trans 'Assignees' %}:
    {{ object.assignees_display }}
    -
    {% trans 'Assignee' %}:
    {{ object.assignee_display | default_if_none:"" }}
    -
    {% trans 'Date created' %}:
    {{ object.date_created }}
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - image - -
    - {{ object.user_display }} {{ object.date_created|timesince}} {% trans 'ago' %} -
    - {{ object.date_created }} -
    - {{ object.body_as_html | safe }} -
    -
    -
    - {% for comment in object.comments.all %} -
    - - image - -
    - {{ comment.user_display }} {{ comment.date_created|timesince}} {% trans 'ago' %} -
    - {{ comment.date_created }} -
    - {{ comment.body }} -
    -
    -
    - {% endfor %} -
    -
    - - image - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/tickets/templates/tickets/ticket_list.html b/apps/tickets/templates/tickets/ticket_list.html deleted file mode 100644 index dfbc973e7..000000000 --- a/apps/tickets/templates/tickets/ticket_list.html +++ /dev/null @@ -1,117 +0,0 @@ -{% extends 'base.html' %} -{% load i18n static %} - -{% block content %} -
    -
    -
    - -
    -
    -
    - {% if False %} -
    -
    - - -
    -
    - {% endif %} - - - - - - - - - - - - {% include '_filter_dropdown.html' %} - - -
    - - {% trans 'Title' %}{% trans 'User' %}{% trans 'Type' %}{% trans 'Status' %}{% trans 'Datetime' %}
    -
    -
    -
    -
    -
    -
    -{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/tickets/urls/views_urls.py b/apps/tickets/urls/views_urls.py deleted file mode 100644 index 46e15437e..000000000 --- a/apps/tickets/urls/views_urls.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.urls import path -from .. import views - -app_name = 'tickets' - -urlpatterns = [ - path('tickets/', views.TicketListView.as_view(), name='ticket-list'), - path('tickets//', views.TicketDetailView.as_view(), name='ticket-detail'), -] diff --git a/apps/tickets/views.py b/apps/tickets/views.py deleted file mode 100644 index 93b4aca2f..000000000 --- a/apps/tickets/views.py +++ /dev/null @@ -1,41 +0,0 @@ -from django.views.generic import TemplateView, DetailView -from django.utils.translation import ugettext as _ - -from common.permissions import PermissionsMixin, IsValidUser -from .models import Ticket -from . import mixins - - -class TicketListView(PermissionsMixin, TemplateView): - template_name = 'tickets/ticket_list.html' - permission_classes = (IsValidUser,) - - def get_context_data(self, **kwargs): - assign = self.request.GET.get('assign', '0') == '1' - context = super().get_context_data(**kwargs) - assigned_open_count = Ticket.get_assigned_tickets(self.request.user)\ - .filter(status=Ticket.STATUS_OPEN).count() - context.update({ - 'app': _("Tickets"), - 'action': _("Ticket list"), - 'assign': assign, - 'assigned_open_count': assigned_open_count - }) - return context - - -class TicketDetailView(PermissionsMixin, mixins.TicketMixin, DetailView): - template_name = 'tickets/ticket_detail.html' - permission_classes = (IsValidUser,) - queryset = Ticket.objects.all() - - def get_context_data(self, **kwargs): - ticket = self.get_object() - has_action_perm = ticket.is_assignee(self.request.user) - context = super().get_context_data(**kwargs) - context.update({ - 'app': _("Tickets"), - 'action': _("Ticket detail"), - 'has_action_perm': has_action_perm, - }) - return context diff --git a/apps/users/api/group.py b/apps/users/api/group.py index 860ca36b4..f91b1d3bd 100644 --- a/apps/users/api/group.py +++ b/apps/users/api/group.py @@ -16,4 +16,3 @@ class UserGroupViewSet(OrgBulkModelViewSet): search_fields = filter_fields permission_classes = (IsOrgAdmin,) serializer_class = UserGroupSerializer - diff --git a/apps/users/api/mixins.py b/apps/users/api/mixins.py index 2ff60b615..117c2c28a 100644 --- a/apps/users/api/mixins.py +++ b/apps/users/api/mixins.py @@ -3,10 +3,12 @@ from .. import utils from users.models import User +from orgs.utils import current_org + class UserQuerysetMixin: def get_queryset(self): - if self.request.query_params.get('all'): + if self.request.query_params.get('all') or not current_org.is_real(): queryset = User.objects.exclude(role=User.ROLE_APP) else: queryset = utils.get_current_org_members() diff --git a/apps/users/api/profile.py b/apps/users/api/profile.py index 70e028a85..e9631b5b7 100644 --- a/apps/users/api/profile.py +++ b/apps/users/api/profile.py @@ -14,6 +14,7 @@ from .mixins import UserQuerysetMixin __all__ = [ 'UserResetPasswordApi', 'UserResetPKApi', 'UserProfileApi', 'UserUpdatePKApi', + 'UserPasswordApi', 'UserPublicKeyApi' ] @@ -55,9 +56,9 @@ class UserUpdatePKApi(UserQuerysetMixin, generics.UpdateAPIView): user.save() -class UserProfileApi(generics.RetrieveAPIView): +class UserProfileApi(generics.RetrieveUpdateAPIView): permission_classes = (IsAuthenticated,) - serializer_class = serializers.UserSerializer + serializer_class = serializers.UserProfileSerializer def get_object(self): return self.request.user @@ -66,3 +67,19 @@ class UserProfileApi(generics.RetrieveAPIView): age = request.session.get_expiry_age() request.session.set_expiry(age) return super().retrieve(request, *args, **kwargs) + + +class UserPasswordApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = serializers.UserUpdatePasswordSerializer + + def get_object(self): + return self.request.user + + +class UserPublicKeyApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = serializers.UserUpdatePublicKeySerializer + + def get_object(self): + return self.request.user diff --git a/apps/users/api/user.py b/apps/users/api/user.py index fac0fabca..8fc184375 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -14,6 +14,7 @@ from common.mixins import CommonApiMixin from common.utils import get_logger from orgs.utils import current_org from .. import serializers +from ..serializers import UserSerializer, UserRetrieveSerializer from .mixins import UserQuerysetMixin from ..models import User from ..signals import post_user_create @@ -27,13 +28,13 @@ __all__ = [ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): - filter_fields = ('username', 'email', 'name', 'id') + filter_fields = ('username', 'email', 'name', 'id', 'source') search_fields = filter_fields - serializer_classes = { - 'default': serializers.UserSerializer, - 'display': serializers.UserDisplaySerializer - } permission_classes = (IsOrgAdmin, CanUpdateDeleteUser) + serializer_classes = { + 'default': UserSerializer, + 'retrieve': UserRetrieveSerializer + } def get_queryset(self): return super().get_queryset().prefetch_related('groups') diff --git a/apps/users/migrations/0026_auto_20200508_2105.py b/apps/users/migrations/0026_auto_20200508_2105.py new file mode 100644 index 000000000..0ccd09e09 --- /dev/null +++ b/apps/users/migrations/0026_auto_20200508_2105.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-05-08 13:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0025_auto_20200206_1216'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='source', + field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS')], default='ldap', max_length=30, verbose_name='Source'), + ), + ] diff --git a/apps/users/migrations/0027_auto_20200616_1503.py b/apps/users/migrations/0027_auto_20200616_1503.py new file mode 100644 index 000000000..2a7fb014e --- /dev/null +++ b/apps/users/migrations/0027_auto_20200616_1503.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-06-16 07:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0026_auto_20200508_2105'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='source', + field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS')], default='local', max_length=30, verbose_name='Source'), + ), + ] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 26f231fbc..89bfe4254 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -16,6 +16,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.shortcuts import reverse +from common.local import LOCAL_DYNAMIC_SETTINGS from orgs.utils import current_org from common.utils import signer, date_expired_default, get_logger, lazyproperty from common import fields @@ -47,6 +48,11 @@ class AuthMixin: post_user_change_password.send(self.__class__, user=self) super().set_password(raw_password) + def set_public_key(self, public_key): + if self.can_update_ssh_key(): + self.public_key = public_key + self.save() + def can_update_password(self): return self.is_local @@ -54,7 +60,7 @@ class AuthMixin: return self.can_use_ssh_key_login() def can_use_ssh_key_login(self): - return settings.TERMINAL_PUBLIC_KEY_AUTH + return self.is_local and settings.TERMINAL_PUBLIC_KEY_AUTH def is_public_key_valid(self): """ @@ -79,6 +85,17 @@ class AuthMixin: pass return PubKey() + def get_public_key_comment(self): + return self.public_key_obj.comment + + def get_public_key_hash_md5(self): + if not callable(self.public_key_obj.hash_md5): + return '' + try: + return self.public_key_obj.hash_md5() + except: + return '' + def reset_password(self, new_password): self.set_password(new_password) self.save() @@ -159,6 +176,16 @@ class RoleMixin: roles.append(str(_('User'))) return " | ".join(roles) + def current_org_roles(self): + roles = [] + if self.can_admin_current_org: + roles.append('Admin') + if self.can_audit_current_org: + roles.append('Auditor') + else: + roles.append('User') + return roles + @property def is_superuser(self): if self.role == 'Admin': @@ -368,7 +395,7 @@ class MFAMixin: @property def mfa_force_enabled(self): - if settings.SECURITY_MFA_AUTH: + if LOCAL_DYNAMIC_SETTINGS.SECURITY_MFA_AUTH: return True return self.mfa_level == 2 @@ -415,7 +442,7 @@ class MFAMixin: if not self.mfa_enabled: return False, None if self.mfa_is_otp() and not self.otp_secret_key: - return True, reverse('users:user-otp-enable-start') + return True, reverse('authentication:user-otp-enable-start') return False, None diff --git a/apps/users/serializers/group.py b/apps/users/serializers/group.py index 67b21668c..a6b9f67b9 100644 --- a/apps/users/serializers/group.py +++ b/apps/users/serializers/group.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.utils.translation import ugettext_lazy as _ - +from django.db.models import Prefetch from rest_framework import serializers from common.serializers import AdaptedBulkListSerializer @@ -18,15 +18,18 @@ __all__ = [ class UserGroupSerializer(BulkOrgResourceModelSerializer): users = serializers.PrimaryKeyRelatedField( required=False, many=True, queryset=User.objects, label=_('User'), - write_only=True + # write_only=True, # group can return many to many on detail ) class Meta: model = UserGroup list_serializer_class = AdaptedBulkListSerializer - fields = [ - 'id', 'name', 'users', 'users_amount', 'comment', - 'date_created', 'created_by', + fields_mini = ['id', 'name'] + fields_small = fields_mini + [ + 'comment', 'date_created', 'created_by' + ] + fields = fields_mini + fields_small + [ + 'users', 'users_amount', ] extra_kwargs = { 'created_by': {'label': _('Created by'), 'read_only': True} @@ -37,8 +40,9 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer): self.set_fields_queryset() def set_fields_queryset(self): - users_field = self.fields['users'] - users_field.child_relation.queryset = utils.get_current_org_members() + users_field = self.fields.get('users') + if users_field: + users_field.child_relation.queryset = utils.get_current_org_members(exclude=('Auditor',)) def validate_users(self, users): for user in users: @@ -50,5 +54,7 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.annotate(users_amount=Count('users')) + queryset = queryset.prefetch_related( + Prefetch('users', queryset=User.objects.only('id')) + ).annotate(users_amount=Count('users')) return queryset diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 156c4a3da..b01bddf42 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- # +from django.core.cache import cache +from django.conf import settings from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from common.utils import validate_ssh_public_key -from common.mixins import BulkSerializerMixin +from common.mixins import CommonBulkSerializerMixin from common.serializers import AdaptedBulkListSerializer from common.permissions import CanUpdateDeleteUser from ..models import User @@ -13,7 +15,9 @@ from ..models import User __all__ = [ 'UserSerializer', 'UserPKUpdateSerializer', 'ChangeUserPasswordSerializer', 'ResetOTPSerializer', - 'UserProfileSerializer', 'UserDisplaySerializer', + 'UserProfileSerializer', 'UserOrgSerializer', + 'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer', + 'UserRetrieveSerializer' ] @@ -22,20 +26,43 @@ class UserOrgSerializer(serializers.Serializer): name = serializers.CharField() -class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): - admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True) +class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): + EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user') + CUSTOM_PASSWORD = _('Set password') + PASSWORD_STRATEGY_CHOICES = ( + (0, EMAIL_SET_PASSWORD), + (1, CUSTOM_PASSWORD) + ) + password_strategy = serializers.ChoiceField( + choices=PASSWORD_STRATEGY_CHOICES, required=False, initial=0, + label=_('Password strategy'), write_only=True + ) + mfa_level_display = serializers.ReadOnlyField(source='get_mfa_level_display') + login_blocked = serializers.SerializerMethodField() + can_update = serializers.SerializerMethodField() + can_delete = serializers.SerializerMethodField() + + key_prefix_block = "_LOGIN_BLOCK_{}" class Meta: model = User list_serializer_class = AdaptedBulkListSerializer - fields = [ - 'id', 'name', 'username', 'password', 'email', 'public_key', - 'groups', 'role', 'wechat', 'phone', 'mfa_level', + # mini 是指能识别对象的最小单元 + fields_mini = ['id', 'name', 'username'] + # small 指的是 不需要计算的直接能从一张表中获取到的数据 + fields_small = fields_mini + [ + 'password', 'email', 'public_key', 'wechat', 'phone', 'mfa_level', 'mfa_enabled', + 'mfa_level_display', 'mfa_force_enabled', 'comment', 'source', 'is_valid', 'is_expired', 'is_active', 'created_by', 'is_first_login', - 'date_password_last_updated', 'date_expired', - 'avatar_url', 'admin_or_audit_orgs', + 'password_strategy', 'date_password_last_updated', 'date_expired', + 'avatar_url', 'source_display', 'date_joined', 'last_login' ] + fields = fields_small + [ + 'groups', 'role', 'groups_display', 'role_display', + 'can_update', 'can_delete', 'login_blocked', + ] + extra_kwargs = { 'password': {'write_only': True, 'required': False, 'allow_null': True, 'allow_blank': True}, 'public_key': {'write_only': True}, @@ -44,8 +71,25 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): 'is_expired': {'label': _('Is expired')}, 'avatar_url': {'label': _('Avatar url')}, 'created_by': {'read_only': True, 'allow_blank': True}, + 'can_update': {'read_only': True}, + 'can_delete': {'read_only': True}, + 'groups_display': {'label': _('Groups name')}, + 'source_display': {'label': _('Source name')}, + 'role_display': {'label': _('Role name')}, } + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_role_choices() + + def set_role_choices(self): + role = self.fields.get('role') + if not role: + return + choices = role._choices + choices.pop('App', None) + role._choices = choices + def validate_role(self, value): request = self.context.get('request') if not request.user.is_superuser and value != User.ROLE_USER: @@ -67,6 +111,9 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): return password def validate_groups(self, groups): + """ + 审计员不能加入到组中 + """ role = self.initial_data.get('role') if self.instance: role = role or self.instance.role @@ -92,19 +139,9 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): def validate(self, attrs): attrs = self.change_password_to_raw(attrs) attrs = self.clean_auth_fields(attrs) + attrs.pop('password_strategy', None) return attrs - -class UserDisplaySerializer(UserSerializer): - can_update = serializers.SerializerMethodField() - can_delete = serializers.SerializerMethodField() - - class Meta(UserSerializer.Meta): - fields = UserSerializer.Meta.fields + [ - 'groups_display', 'role_display', 'source_display', - 'can_update', 'can_delete', - ] - def get_can_update(self, obj): return CanUpdateDeleteUser.has_update_object_permission( self.context['request'], self.context['view'], obj @@ -115,16 +152,18 @@ class UserDisplaySerializer(UserSerializer): self.context['request'], self.context['view'], obj ) - def get_extra_kwargs(self): - kwargs = super().get_extra_kwargs() - kwargs.update({ - 'can_update': {'read_only': True}, - 'can_delete': {'read_only': True}, - 'groups_display': {'label': _('Groups name')}, - 'source_display': {'label': _('Source name')}, - 'role_display': {'label': _('Role name')}, - }) - return kwargs + def get_login_blocked(self, obj): + key_block = self.key_prefix_block.format(obj.username) + blocked = bool(cache.get(key_block)) + return blocked + + +class UserRetrieveSerializer(UserSerializer): + login_confirm_settings = serializers.PrimaryKeyRelatedField(read_only=True, + source='login_confirm_setting.reviewers', many=True) + + class Meta(UserSerializer.Meta): + fields = UserSerializer.Meta.fields + ['login_confirm_settings'] class UserPKUpdateSerializer(serializers.ModelSerializer): @@ -156,9 +195,128 @@ class ResetOTPSerializer(serializers.Serializer): pass -class UserProfileSerializer(serializers.ModelSerializer): +class UserRoleSerializer(serializers.Serializer): + name = serializers.CharField(max_length=24) + display = serializers.CharField(max_length=64) + + +class UserProfileSerializer(UserSerializer): + admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True) + current_org_roles = serializers.ListField(read_only=True) + public_key_comment = serializers.CharField( + source='get_public_key_comment', required=False, read_only=True, max_length=128 + ) + public_key_hash_md5 = serializers.CharField( + source='get_public_key_hash_md5', required=False, read_only=True, max_length=128 + ) + MFA_LEVEL_CHOICES = ( + (0, _('Disable')), + (1, _('Enable')), + ) + mfa_level = serializers.ChoiceField(choices=MFA_LEVEL_CHOICES, label=_('MFA'), required=False) + guide_url = serializers.SerializerMethodField() + + class Meta(UserSerializer.Meta): + fields = UserSerializer.Meta.fields + [ + 'public_key_comment', 'public_key_hash_md5', 'admin_or_audit_orgs', 'current_org_roles', + 'guide_url' + ] + extra_kwargs = dict(UserSerializer.Meta.extra_kwargs) + extra_kwargs.update({ + 'name': {'read_only': True, 'max_length': 128}, + 'username': {'read_only': True, 'max_length': 128}, + 'email': {'read_only': True}, + 'is_first_login': {'label': _('Is first login'), 'read_only': False}, + 'source': {'read_only': True}, + 'is_valid': {'read_only': True}, + 'is_active': {'read_only': True}, + 'groups': {'read_only': True}, + 'roles': {'read_only': True}, + 'password_strategy': {'read_only': True}, + 'date_expired': {'read_only': True}, + 'date_joined': {'read_only': True}, + 'last_login': {'read_only': True}, + 'role': {'read_only': True}, + }) + + if 'password' in fields: + fields.remove('password') + extra_kwargs.pop('password', None) + + @staticmethod + def get_guide_url(obj): + return settings.USER_GUIDE_URL + + def validate_mfa_level(self, mfa_level): + if self.instance and self.instance.mfa_force_enabled: + return 2 + return mfa_level + + def validate_public_key(self, public_key): + if self.instance and self.instance.can_update_ssh_key(): + if not validate_ssh_public_key(public_key): + raise serializers.ValidationError(_('Not a valid ssh public key')) + return public_key + return None + + +class UserUpdatePasswordSerializer(serializers.ModelSerializer): + old_password = serializers.CharField(required=True, max_length=128, write_only=True) + new_password = serializers.CharField(required=True, max_length=128, write_only=True) + new_password_again = serializers.CharField(required=True, max_length=128, write_only=True) + class Meta: model = User - fields = [ - 'id', 'username', 'name', 'role', 'email' - ] + fields = ['old_password', 'new_password', 'new_password_again'] + + def validate_old_password(self, value): + if not self.instance.check_password(value): + msg = _('The old password is incorrect') + raise serializers.ValidationError(msg) + return value + + @staticmethod + def validate_new_password(value): + from ..utils import check_password_rules + if not check_password_rules(value): + msg = _('Password does not match security rules') + raise serializers.ValidationError(msg) + return value + + def validate_new_password_again(self, value): + if value != self.initial_data.get('new_password', ''): + msg = _('The newly set password is inconsistent') + raise serializers.ValidationError(msg) + return value + + def update(self, instance, validated_data): + new_password = self.validated_data.get('new_password') + instance.reset_password(new_password) + return instance + + +class UserUpdatePublicKeySerializer(serializers.ModelSerializer): + public_key_comment = serializers.CharField( + source='get_public_key_comment', required=False, read_only=True, max_length=128 + ) + public_key_hash_md5 = serializers.CharField( + source='get_public_key_hash_md5', required=False, read_only=True, max_length=128 + ) + + class Meta: + model = User + fields = ['public_key_comment', 'public_key_hash_md5', 'public_key'] + extra_kwargs = { + 'public_key': {'required': True, 'write_only': True, 'max_length': 2048} + } + + @staticmethod + def validate_public_key(value): + if not validate_ssh_public_key(value): + raise serializers.ValidationError(_('Not a valid ssh public key')) + return value + + def update(self, instance, validated_data): + new_public_key = self.validated_data.get('public_key') + instance.set_public_key(new_public_key) + return instance diff --git a/apps/users/templates/users/first_login.html b/apps/users/templates/users/first_login.html index 417d20a3d..bffa2fc0e 100644 --- a/apps/users/templates/users/first_login.html +++ b/apps/users/templates/users/first_login.html @@ -1,145 +1,10 @@ -{% extends 'base.html' %} +{% extends '_base_only_content.html' %} {% load static %} {% load i18n %} {% load bootstrap3 %} - -{% block custom_head_css_js %} -{{ wizard.form.media }} - -{% endblock %} -{% block first_login_message %}{% endblock %} +{% block title %} {% trans 'First Login' %} {% endblock %} {% block content %} -
    -
    -
    -
    -
    -
    {% trans 'First Login' %}
    - -
    -
    -
    - -
    -
    -
    - {% csrf_token %} - {{ wizard.management_form }} - {% if form.finish_description %} - {{ form.finish_description }} -
    - - - - {% endif %} - - {% if wizard.steps.current == '1' and not request.user.can_update_ssh_key %} - {% trans 'User auth from {}, ssh key login is not supported' %} - {% else %} - {% bootstrap_form wizard.form %} - {% endif %} - - {% if form.mfa_description %} - {{ form.mfa_description }} - {% endif %} - - {% if form.pubkey_description and request.user.can_update_ssh_key %} - {{ form.pubkey_description }} - {% endif %} -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -{% endblock %} - -{% block custom_foot_js %} - + 使用UI重构这个页面 {% endblock %} diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index 63dd981be..28b30f73d 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -132,7 +132,7 @@ function initTable() { $(document).ready(function(){ usersTable = initTable(); - initCsvImportExport(usersTable, "{% trans 'User groups' %}") + initCsvImportExport(usersTable, "{% trans 'User' %}") }).on('click', '#btn_bulk_update', function(){ var action = $('#slct_bulk_update').val(); var id_list = usersTable.selected; diff --git a/apps/users/templates/users/user_otp_enable_install_app.html b/apps/users/templates/users/user_otp_enable_install_app.html index 212d63afa..8ac03416e 100644 --- a/apps/users/templates/users/user_otp_enable_install_app.html +++ b/apps/users/templates/users/user_otp_enable_install_app.html @@ -26,7 +26,7 @@

    {% trans 'After installation, click the next step to enter the binding page (if installed, go to the next step directly).' %}

    - + -