diff --git a/Dockerfile b/Dockerfile index 0e55498cb..9f4c5c22b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,8 @@ RUN useradd jumpserver COPY ./requirements /tmp/requirements -RUN yum -y install epel-release && rpm -ivh https://repo.mysql.com/mysql57-community-release-el6.rpm +RUN yum -y install epel-release && \ + echo -e "[mysql]\nname=mysql\nbaseurl=https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql57-community-el6/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt) RUN cd /tmp/requirements && pip install --upgrade pip setuptools && \ pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt || pip install -r requirements.txt diff --git a/apps/applications/__init__.py b/apps/applications/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/applications/admin.py b/apps/applications/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/apps/applications/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/applications/api/__init__.py b/apps/applications/api/__init__.py new file mode 100644 index 000000000..e6bc7adb4 --- /dev/null +++ b/apps/applications/api/__init__.py @@ -0,0 +1 @@ +from .remote_app import * diff --git a/apps/applications/api/remote_app.py b/apps/applications/api/remote_app.py new file mode 100644 index 000000000..c41c5d100 --- /dev/null +++ b/apps/applications/api/remote_app.py @@ -0,0 +1,31 @@ +# coding: utf-8 +# + + +from rest_framework import generics +from rest_framework.pagination import LimitOffsetPagination +from rest_framework_bulk import BulkModelViewSet + +from ..hands import IsOrgAdmin, IsAppUser +from ..models import RemoteApp +from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer + + +__all__ = [ + 'RemoteAppViewSet', 'RemoteAppConnectionInfoApi', +] + + +class RemoteAppViewSet(BulkModelViewSet): + filter_fields = ('name',) + search_fields = filter_fields + permission_classes = (IsOrgAdmin,) + queryset = RemoteApp.objects.all() + serializer_class = RemoteAppSerializer + pagination_class = LimitOffsetPagination + + +class RemoteAppConnectionInfoApi(generics.RetrieveAPIView): + queryset = RemoteApp.objects.all() + permission_classes = (IsAppUser, ) + serializer_class = RemoteAppConnectionInfoSerializer diff --git a/apps/applications/apps.py b/apps/applications/apps.py new file mode 100644 index 000000000..3c22ddedc --- /dev/null +++ b/apps/applications/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class ApplicationsConfig(AppConfig): + name = 'applications' diff --git a/apps/applications/const.py b/apps/applications/const.py new file mode 100644 index 000000000..a5b6da895 --- /dev/null +++ b/apps/applications/const.py @@ -0,0 +1,68 @@ +# coding: utf-8 +# + +from django.utils.translation import ugettext_lazy as _ + + +# RemoteApp +REMOTE_APP_BOOT_PROGRAM_NAME = '||jmservisor' + +REMOTE_APP_TYPE_CHROME = 'chrome' +REMOTE_APP_TYPE_MYSQL_WORKBENCH = 'mysql_workbench' +REMOTE_APP_TYPE_VMWARE_CLIENT = 'vmware_client' +REMOTE_APP_TYPE_CUSTOM = 'custom' + +REMOTE_APP_TYPE_CHOICES = ( + ( + _('Browser'), + ( + (REMOTE_APP_TYPE_CHROME, 'Chrome'), + ) + ), + ( + _('Database tools'), + ( + (REMOTE_APP_TYPE_MYSQL_WORKBENCH, 'MySQL Workbench'), + ) + ), + ( + _('Virtualization tools'), + ( + (REMOTE_APP_TYPE_VMWARE_CLIENT, 'vSphere Client'), + ) + ), + (REMOTE_APP_TYPE_CUSTOM, _('Custom')), + +) + +# Fields attribute write_only default => False + +REMOTE_APP_TYPE_CHROME_FIELDS = [ + {'name': 'chrome_target'}, + {'name': 'chrome_username'}, + {'name': 'chrome_password', 'write_only': True} +] +REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS = [ + {'name': 'mysql_workbench_ip'}, + {'name': 'mysql_workbench_name'}, + {'name': 'mysql_workbench_username'}, + {'name': 'mysql_workbench_password', 'write_only': True} +] +REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS = [ + {'name': 'vmware_target'}, + {'name': 'vmware_username'}, + {'name': 'vmware_password', 'write_only': True} +] +REMOTE_APP_TYPE_CUSTOM_FIELDS = [ + {'name': 'custom_cmdline'}, + {'name': 'custom_target'}, + {'name': 'custom_username'}, + {'name': 'custom_password', 'write_only': True} +] + +REMOTE_APP_TYPE_MAP_FIELDS = { + REMOTE_APP_TYPE_CHROME: REMOTE_APP_TYPE_CHROME_FIELDS, + REMOTE_APP_TYPE_MYSQL_WORKBENCH: REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS, + REMOTE_APP_TYPE_VMWARE_CLIENT: REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS, + REMOTE_APP_TYPE_CUSTOM: REMOTE_APP_TYPE_CUSTOM_FIELDS +} diff --git a/apps/applications/forms/__init__.py b/apps/applications/forms/__init__.py new file mode 100644 index 000000000..e6bc7adb4 --- /dev/null +++ b/apps/applications/forms/__init__.py @@ -0,0 +1 @@ +from .remote_app import * diff --git a/apps/applications/forms/remote_app.py b/apps/applications/forms/remote_app.py new file mode 100644 index 000000000..b5317fa1c --- /dev/null +++ b/apps/applications/forms/remote_app.py @@ -0,0 +1,132 @@ +# coding: utf-8 +# + +from django.utils.translation import ugettext as _ +from django import forms + +from orgs.mixins import OrgModelForm +from assets.models import Asset, SystemUser + +from ..models import RemoteApp +from .. import const + + +__all__ = [ + 'RemoteAppCreateUpdateForm', +] + + +class RemoteAppTypeChromeForm(forms.ModelForm): + 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 RemoteAppTypeMySQLWorkbenchForm(forms.ModelForm): + 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 RemoteAppTypeVMwareForm(forms.ModelForm): + 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 RemoteAppTypeCustomForm(forms.ModelForm): + 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 + ) + + +class RemoteAppTypeForms( + RemoteAppTypeChromeForm, + RemoteAppTypeMySQLWorkbenchForm, + RemoteAppTypeVMwareForm, + RemoteAppTypeCustomForm +): + pass + + +class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm): + def __init__(self, *args, **kwargs): + # 过滤RDP资产和系统用户 + super().__init__(*args, **kwargs) + field_asset = self.fields['asset'] + field_asset.queryset = field_asset.queryset.filter( + protocol=Asset.PROTOCOL_RDP + ) + field_system_user = self.fields['system_user'] + field_system_user.queryset = field_system_user.queryset.filter( + protocol=SystemUser.PROTOCOL_RDP + ) + + class Meta: + model = RemoteApp + fields = [ + 'name', 'asset', 'system_user', 'type', 'path', 'comment' + ] + widgets = { + 'asset': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Asset') + }), + 'system_user': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('System user') + }) + } + + def _clean_params(self): + app_type = self.data.get('type') + fields = const.REMOTE_APP_TYPE_MAP_FIELDS.get(app_type, []) + params = {} + for field in fields: + name = field['name'] + value = self.cleaned_data[name] + params.update({name: value}) + return params + + def _save_params(self, instance): + params = self._clean_params() + instance.params = params + instance.save() + return instance + + def save(self, commit=True): + instance = super().save(commit=commit) + instance = self._save_params(instance) + return instance diff --git a/apps/applications/hands.py b/apps/applications/hands.py new file mode 100644 index 000000000..ffe1e35c5 --- /dev/null +++ b/apps/applications/hands.py @@ -0,0 +1,16 @@ +""" + jumpserver.__app__.hands.py + ~~~~~~~~~~~~~~~~~ + + This app depends other apps api, function .. should be import or write mack here. + + Other module of this app shouldn't connect with other app. + + :copyright: (c) 2014-2018 by Jumpserver Team. + :license: GPL v2, see LICENSE for more details. +""" + + +from common.permissions import AdminUserRequiredMixin +from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser +from users.models import User, UserGroup diff --git a/apps/applications/migrations/0001_initial.py b/apps/applications/migrations/0001_initial.py new file mode 100644 index 000000000..221fd5e22 --- /dev/null +++ b/apps/applications/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 2.1.7 on 2019-05-20 11:04 + +import common.fields.model +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('assets', '0026_auto_20190325_2035'), + ] + + operations = [ + migrations.CreateModel( + name='RemoteApp', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'vSphere Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')), + ('path', models.CharField(max_length=128, verbose_name='App path')), + ('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), + ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')), + ('system_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='System user')), + ], + options={ + 'verbose_name': 'RemoteApp', + 'ordering': ('name',), + }, + ), + migrations.AlterUniqueTogether( + name='remoteapp', + unique_together={('org_id', 'name')}, + ), + ] diff --git a/apps/applications/migrations/__init__.py b/apps/applications/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/applications/models/__init__.py b/apps/applications/models/__init__.py new file mode 100644 index 000000000..e6bc7adb4 --- /dev/null +++ b/apps/applications/models/__init__.py @@ -0,0 +1 @@ +from .remote_app import * diff --git a/apps/applications/models/remote_app.py b/apps/applications/models/remote_app.py new file mode 100644 index 000000000..772d39834 --- /dev/null +++ b/apps/applications/models/remote_app.py @@ -0,0 +1,89 @@ +# coding: utf-8 +# + +import uuid +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from orgs.mixins import OrgModelMixin +from common.fields.model import EncryptJsonDictTextField + +from .. import const + + +__all__ = [ + 'RemoteApp', +] + + +class RemoteApp(OrgModelMixin): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_('Name')) + asset = models.ForeignKey( + 'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset') + ) + system_user = models.ForeignKey( + 'assets.SystemUser', on_delete=models.CASCADE, + verbose_name=_('System user') + ) + type = models.CharField( + default=const.REMOTE_APP_TYPE_CHROME, + choices=const.REMOTE_APP_TYPE_CHOICES, + max_length=128, verbose_name=_('App type') + ) + path = models.CharField( + max_length=128, blank=False, null=False, + verbose_name=_('App path') + ) + params = EncryptJsonDictTextField( + max_length=4096, default={}, blank=True, null=True, + verbose_name=_('Parameters') + ) + created_by = models.CharField( + max_length=32, null=True, blank=True, verbose_name=_('Created by') + ) + date_created = models.DateTimeField( + auto_now_add=True, null=True, blank=True, verbose_name=_('Date created') + ) + comment = models.TextField( + max_length=128, default='', blank=True, verbose_name=_('Comment') + ) + + class Meta: + verbose_name = _("RemoteApp") + unique_together = [('org_id', 'name')] + ordering = ('name', ) + + def __str__(self): + return self.name + + @property + def parameters(self): + """ + 返回Guacamole需要的RemoteApp配置参数信息中的parameters参数 + """ + _parameters = list() + _parameters.append(self.type) + path = '\"%s\"' % self.path + _parameters.append(path) + for field in const.REMOTE_APP_TYPE_MAP_FIELDS[self.type]: + value = self.params.get(field['name']) + if value is None: + continue + _parameters.append(value) + _parameters = ' '.join(_parameters) + return _parameters + + @property + def asset_info(self): + return { + 'id': self.asset.id, + 'hostname': self.asset.hostname + } + + @property + def system_user_info(self): + return { + 'id': self.system_user.id, + 'name': self.system_user.name + } diff --git a/apps/applications/serializers/__init__.py b/apps/applications/serializers/__init__.py new file mode 100644 index 000000000..e6bc7adb4 --- /dev/null +++ b/apps/applications/serializers/__init__.py @@ -0,0 +1 @@ +from .remote_app import * diff --git a/apps/applications/serializers/remote_app.py b/apps/applications/serializers/remote_app.py new file mode 100644 index 000000000..6957d11d5 --- /dev/null +++ b/apps/applications/serializers/remote_app.py @@ -0,0 +1,103 @@ +# coding: utf-8 +# + + +from rest_framework import serializers + +from common.mixins import BulkSerializerMixin +from common.serializers import AdaptedBulkListSerializer + +from .. import const +from ..models import RemoteApp + + +__all__ = [ + 'RemoteAppSerializer', 'RemoteAppConnectionInfoSerializer', +] + + +class RemoteAppParamsDictField(serializers.DictField): + """ + RemoteApp field => params + """ + @staticmethod + def filter_attribute(attribute, instance): + """ + 过滤掉params字段值中write_only特性的key-value值 + For example, the chrome_password field is not returned when serializing + { + 'chrome_target': 'http://www.jumpserver.org/', + 'chrome_username': 'admin', + 'chrome_password': 'admin', + } + """ + for field in const.REMOTE_APP_TYPE_MAP_FIELDS[instance.type]: + if field.get('write_only', False): + attribute.pop(field['name'], None) + return attribute + + def get_attribute(self, instance): + """ + 序列化时调用 + """ + attribute = super().get_attribute(instance) + attribute = self.filter_attribute(attribute, instance) + return attribute + + @staticmethod + def filter_value(dictionary, value): + """ + 过滤掉不属于当前app_type所包含的key-value值 + """ + app_type = dictionary.get('type', const.REMOTE_APP_TYPE_CHROME) + fields = const.REMOTE_APP_TYPE_MAP_FIELDS[app_type] + fields_names = [field['name'] for field in fields] + no_need_keys = [k for k in value.keys() if k not in fields_names] + for k in no_need_keys: + value.pop(k) + return value + + def get_value(self, dictionary): + """ + 反序列化时调用 + """ + value = super().get_value(dictionary) + value = self.filter_value(dictionary, value) + return value + + +class RemoteAppSerializer(BulkSerializerMixin, serializers.ModelSerializer): + params = RemoteAppParamsDictField() + + class Meta: + model = RemoteApp + list_serializer_class = AdaptedBulkListSerializer + fields = [ + 'id', 'name', 'asset', 'system_user', 'type', 'path', 'params', + 'comment', 'created_by', 'date_created', 'asset_info', + 'system_user_info', 'get_type_display', + ] + read_only_fields = [ + 'created_by', 'date_created', 'asset_info', + 'system_user_info', 'get_type_display' + ] + + +class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer): + parameter_remote_app = serializers.SerializerMethodField() + + class Meta: + model = RemoteApp + fields = [ + 'id', 'name', 'asset', 'system_user', 'parameter_remote_app', + ] + read_only_fields = ['parameter_remote_app'] + + @staticmethod + def get_parameter_remote_app(obj): + parameter = { + 'program': const.REMOTE_APP_BOOT_PROGRAM_NAME, + 'working_directory': '', + 'parameters': obj.parameters, + } + return parameter diff --git a/apps/applications/templates/applications/remote_app_create_update.html b/apps/applications/templates/applications/remote_app_create_update.html new file mode 100644 index 000000000..ecd7254a1 --- /dev/null +++ b/apps/applications/templates/applications/remote_app_create_update.html @@ -0,0 +1,123 @@ +{% extends '_base_create_update.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} + +{% block form %} +
+ {% if form.non_field_errors %} +
+ {{ form.non_field_errors }} +
+ {% endif %} + {% csrf_token %} + {% bootstrap_field form.name layout="horizontal" %} + {% bootstrap_field form.asset layout="horizontal" %} + {% bootstrap_field form.system_user layout="horizontal" %} + {% bootstrap_field form.type layout="horizontal" %} + {% bootstrap_field form.path layout="horizontal" %} + +
+ + {# chrome #} +
+ {% bootstrap_field form.chrome_target layout="horizontal" %} + {% bootstrap_field form.chrome_username layout="horizontal" %} + {% bootstrap_field form.chrome_password layout="horizontal" %} +
+ + {# mysql workbench #} +
+ {% bootstrap_field form.mysql_workbench_ip layout="horizontal" %} + {% bootstrap_field form.mysql_workbench_name layout="horizontal" %} + {% bootstrap_field form.mysql_workbench_username layout="horizontal" %} + {% bootstrap_field form.mysql_workbench_password layout="horizontal" %} +
+ + {# vmware #} +
+ {% bootstrap_field form.vmware_target layout="horizontal" %} + {% bootstrap_field form.vmware_username layout="horizontal" %} + {% bootstrap_field form.vmware_password layout="horizontal" %} +
+ + {# custom #} +
+ {% bootstrap_field form.custom_cmdline layout="horizontal" %} + {% bootstrap_field form.custom_target layout="horizontal" %} + {% bootstrap_field form.custom_username layout="horizontal" %} + {% bootstrap_field form.custom_password layout="horizontal" %} +
+ + {% bootstrap_field form.comment 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 new file mode 100644 index 000000000..d006bb51a --- /dev/null +++ b/apps/applications/templates/applications/remote_app_detail.html @@ -0,0 +1,109 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + +{% endblock %} + +{% block content %} +
+
+
+
+ +
+
+
+
+ {{ remote_app.name }} +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans 'Name' %}:{{ remote_app.name }}
{% trans 'Asset' %}:{{ remote_app.asset.hostname }}
{% trans 'System user' %}:{{ remote_app.system_user.name }}
{% 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 %} \ No newline at end of file diff --git a/apps/applications/templates/applications/remote_app_list.html b/apps/applications/templates/applications/remote_app_list.html new file mode 100644 index 000000000..54f1806e9 --- /dev/null +++ b/apps/applications/templates/applications/remote_app_list.html @@ -0,0 +1,90 @@ +{% 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 "Create RemoteApp" %} +
+ + + + + + + + + + + + + + +
+ + {% trans 'Name' %}{% trans 'App type' %}{% trans 'Asset' %}{% trans 'System user' %}{% trans 'Comment' %}{% trans 'Action' %}
+{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/applications/templates/applications/user_remote_app_list.html b/apps/applications/templates/applications/user_remote_app_list.html new file mode 100644 index 000000000..4199e805f --- /dev/null +++ b/apps/applications/templates/applications/user_remote_app_list.html @@ -0,0 +1,79 @@ +{% extends 'base.html' %} +{% load i18n static %} + +{% block custom_head_css_js %} + +{% endblock %} + +{% block content %} +
+ + + + + + + + + + + + + + +
+ + {% trans 'Name' %}{% trans 'App type' %}{% trans 'Asset' %}{% trans 'System user' %}{% trans 'Comment' %}{% trans 'Action' %}
+
+{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/applications/tests.py b/apps/applications/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/apps/applications/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/applications/urls/__init__.py b/apps/applications/urls/__init__.py new file mode 100644 index 000000000..3aab4972f --- /dev/null +++ b/apps/applications/urls/__init__.py @@ -0,0 +1,7 @@ +# coding: utf-8 +# + + +__all__ = [ + +] diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py new file mode 100644 index 000000000..97487b5a1 --- /dev/null +++ b/apps/applications/urls/api_urls.py @@ -0,0 +1,20 @@ +# coding:utf-8 +# + +from django.urls import path +from rest_framework_bulk.routes import BulkRouter + +from .. import api + +app_name = 'applications' + +router = BulkRouter() +router.register(r'remote-app', api.RemoteAppViewSet, 'remote-app') + +urlpatterns = [ + path('remote-apps//connection-info/', + api.RemoteAppConnectionInfoApi.as_view(), + name='remote-app-connection-info') +] + +urlpatterns += router.urls diff --git a/apps/applications/urls/views_urls.py b/apps/applications/urls/views_urls.py new file mode 100644 index 000000000..3ffcffc5c --- /dev/null +++ b/apps/applications/urls/views_urls.py @@ -0,0 +1,16 @@ +# 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') + +] diff --git a/apps/applications/views/__init__.py b/apps/applications/views/__init__.py new file mode 100644 index 000000000..e6bc7adb4 --- /dev/null +++ b/apps/applications/views/__init__.py @@ -0,0 +1 @@ +from .remote_app import * diff --git a/apps/applications/views/remote_app.py b/apps/applications/views/remote_app.py new file mode 100644 index 000000000..e21db3ad8 --- /dev/null +++ b/apps/applications/views/remote_app.py @@ -0,0 +1,99 @@ +# coding: utf-8 +# + +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 django.contrib.messages.views import SuccessMessageMixin +from django.contrib.auth.mixins import LoginRequiredMixin +from django.urls import reverse_lazy + + +from common.permissions import AdminUserRequiredMixin +from common.const import create_success_msg, update_success_msg + +from ..models import RemoteApp +from .. import forms + + +__all__ = [ + 'RemoteAppListView', 'RemoteAppCreateView', 'RemoteAppUpdateView', + 'RemoteAppDetailView', 'UserRemoteAppListView', +] + + +class RemoteAppListView(AdminUserRequiredMixin, TemplateView): + template_name = 'applications/remote_app_list.html' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('RemoteApp list'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): + template_name = 'applications/remote_app_create_update.html' + model = RemoteApp + form_class = forms.RemoteAppCreateUpdateForm + success_url = reverse_lazy('applications:remote-app-list') + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('Create RemoteApp'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + def get_success_message(self, cleaned_data): + return create_success_msg % ({'name': cleaned_data['name']}) + + +class RemoteAppUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): + template_name = 'applications/remote_app_create_update.html' + model = RemoteApp + form_class = forms.RemoteAppCreateUpdateForm + success_url = reverse_lazy('applications:remote-app-list') + + def get_initial(self): + return {k: v for k, v in self.object.params.items()} + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('Update RemoteApp'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + def get_success_message(self, cleaned_data): + return update_success_msg % ({'name': cleaned_data['name']}) + + +class RemoteAppDetailView(AdminUserRequiredMixin, DetailView): + template_name = 'applications/remote_app_detail.html' + model = RemoteApp + context_object_name = 'remote_app' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('RemoteApp detail'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class UserRemoteAppListView(LoginRequiredMixin, TemplateView): + template_name = 'applications/user_remote_app_list.html' + + 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 f2229022f..e84d9731a 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -20,7 +20,7 @@ from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from rest_framework.pagination import LimitOffsetPagination -from common.mixins import IDInFilterMixin +from common.mixins import IDInCacheFilterMixin from common.utils import get_logger from ..hands import IsOrgAdmin from ..models import AdminUser, Asset @@ -36,7 +36,7 @@ __all__ = [ ] -class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet): +class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): """ Admin user api set, for add,delete,update,list,retrieve resource """ diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index a734806fb..9e21c81be 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -16,8 +16,9 @@ from django.urls import reverse_lazy from django.core.cache import cache from django.db.models import Q -from common.mixins import IDInFilterMixin -from common.utils import get_logger +from common.mixins import IDInCacheFilterMixin + +from common.utils import get_logger, get_object_or_none from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX from ..models import Asset, AdminUser, Node @@ -35,7 +36,7 @@ __all__ = [ ] -class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): +class AssetViewSet(IDInCacheFilterMixin, LabelFilter, BulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ @@ -47,6 +48,19 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): pagination_class = LimitOffsetPagination permission_classes = (IsOrgAdminOrAppUser,) + def set_assets_node(self, assets): + if not isinstance(assets, list): + assets = [assets] + node = Node.objects.get(value='Default') + node_id = self.request.query_params.get('node_id') + if node_id: + node = get_object_or_none(Node, pk=node_id) + node.assets.add(*assets) + + def perform_create(self, serializer): + assets = serializer.save() + self.set_assets_node(assets) + def filter_node(self, queryset): node_id = self.request.query_params.get("node_id") if not node_id: @@ -89,7 +103,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): return queryset -class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): +class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView): """ Asset bulk update api """ diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 51e830efc..f772a2ace 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -39,6 +39,8 @@ __all__ = [ class NodeViewSet(viewsets.ModelViewSet): + filter_fields = ('value', 'key', ) + search_fields = filter_fields queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 9805872e7..f6398e974 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -21,6 +21,7 @@ from rest_framework.pagination import LimitOffsetPagination from common.utils import get_logger from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser +from common.mixins import IDInCacheFilterMixin from ..models import SystemUser, Asset from .. import serializers from ..tasks import push_system_user_to_assets_manual, \ @@ -38,7 +39,7 @@ __all__ = [ ] -class SystemUserViewSet(BulkModelViewSet): +class SystemUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): """ System user api set, for add,delete,update,list,retrieve resource """ diff --git a/apps/assets/backends/external/db.py b/apps/assets/backends/external/db.py index f3f6c16d6..eedafd336 100644 --- a/apps/assets/backends/external/db.py +++ b/apps/assets/backends/external/db.py @@ -11,7 +11,7 @@ class AuthBookBackend(BaseBackend): @classmethod def filter(cls, username=None, asset=None, latest=True): queryset = AuthBook.objects.all() - if username: + if username is not None: queryset = queryset.filter(username=username) if asset: queryset = queryset.filter(asset=asset) diff --git a/apps/assets/backends/internal/admin_user.py b/apps/assets/backends/internal/admin_user.py index e67b41fc9..abd32b5ae 100644 --- a/apps/assets/backends/internal/admin_user.py +++ b/apps/assets/backends/internal/admin_user.py @@ -27,7 +27,7 @@ class AdminUserBackend(BaseBackend): instances = [] assets = cls._get_assets(asset) for asset in assets: - if username and asset.admin_user.username != username: + if username is not None and asset.admin_user.username != username: continue instance = construct_authbook_object(asset.admin_user, asset) instances.append(instance) diff --git a/apps/assets/backends/internal/system_user.py b/apps/assets/backends/internal/system_user.py index 52b22215c..b1413fedb 100644 --- a/apps/assets/backends/internal/system_user.py +++ b/apps/assets/backends/internal/system_user.py @@ -30,7 +30,7 @@ class SystemUserBackend(BaseBackend): @classmethod def _filter_system_users_by_username(cls, system_users, username): _system_users = cls._distinct_system_users_by_username(system_users) - if username: + if username is not None: _system_users = [su for su in _system_users if username == su.username] return _system_users diff --git a/apps/assets/const.py b/apps/assets/const.py index a110683d0..0cd74ba0d 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- # +from django.utils.translation import ugettext_lazy as _ + + UPDATE_ASSETS_HARDWARE_TASKS = [ { 'name': "setup", @@ -51,3 +54,4 @@ TASK_OPTIONS = { CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}' + diff --git a/apps/assets/migrations/0027_auto_20190521_1703.py b/apps/assets/migrations/0027_auto_20190521_1703.py new file mode 100644 index 000000000..da38b1791 --- /dev/null +++ b/apps/assets/migrations/0027_auto_20190521_1703.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.7 on 2019-05-21 09:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0026_auto_20190325_2035'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='ip', + field=models.CharField(db_index=True, max_length=128, verbose_name='IP'), + ), + ] diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index b87a18796..c82c66a69 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -8,3 +8,4 @@ from .asset import * from .cmd_filter import * from .utils import * from .authbook import * +from applications.models.remote_app import * diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 078221e85..47a34243d 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -71,7 +71,7 @@ class Asset(OrgModelMixin): ) id = models.UUIDField(default=uuid.uuid4, primary_key=True) - ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) + ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol')) port = models.IntegerField(default=22, verbose_name=_('Port')) diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py index a040bbd0d..94e4f4cf4 100644 --- a/apps/assets/models/authbook.py +++ b/apps/assets/models/authbook.py @@ -78,7 +78,7 @@ class AuthBook(AssetUser): if host == self.asset.hostname: _connectivity = self.UNREACHABLE - for host in value.get('contacted', {}).keys(): + for host in value.get('contacted', []): if host == self.asset.hostname: _connectivity = self.REACHABLE diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index e44679995..66f25db87 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # from django.core.cache import cache +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from common.serializers import AdaptedBulkListSerializer @@ -15,14 +16,29 @@ class AdminUserSerializer(serializers.ModelSerializer): """ 管理用户 """ - assets_amount = serializers.SerializerMethodField() - unreachable_amount = serializers.SerializerMethodField() - reachable_amount = serializers.SerializerMethodField() + password = serializers.CharField( + required=False, write_only=True, label=_('Password') + ) + unreachable_amount = serializers.SerializerMethodField(label=_('Unreachable')) + assets_amount = serializers.SerializerMethodField(label=_('Asset')) + reachable_amount = serializers.SerializerMethodField(label=_('Reachable')) class Meta: list_serializer_class = AdaptedBulkListSerializer model = AdminUser - fields = '__all__' + fields = [ + 'id', 'org_id', 'name', 'username', 'assets_amount', + 'reachable_amount', 'unreachable_amount', 'password', 'comment', + 'date_created', 'date_updated', 'become', 'become_method', + 'become_user', 'created_by', + ] + + extra_kwargs = { + 'date_created': {'label': _('Date created')}, + 'date_updated': {'label': _('Date updated')}, + 'become': {'read_only': True}, 'become_method': {'read_only': True}, + 'become_user': {'read_only': True}, 'created_by': {'read_only': True} + } def get_field_names(self, declared_fields, info): fields = super().get_field_names(declared_fields, info) diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index c0f435adc..3e1cf39bb 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -2,6 +2,9 @@ # from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ + +from orgs.mixins import OrgResourceSerializerMixin from common.mixins import BulkSerializerMixin from common.serializers import AdaptedBulkListSerializer from ..models import Asset @@ -13,15 +16,35 @@ __all__ = [ ] -class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): +class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResourceSerializerMixin): """ 资产的数据结构 """ class Meta: model = Asset list_serializer_class = AdaptedBulkListSerializer - fields = '__all__' - validators = [] + # validators = [] # 解决批量导入时unique_together字段校验失败 + fields = [ + 'id', 'org_id', 'org_name', 'ip', 'hostname', 'protocol', 'port', + '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', 'connectivity' + ] + read_only_fields = ( + 'number', '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', + ) + extra_kwargs = { + 'hardware_info': {'label': _('Hardware info')}, + 'connectivity': {'label': _('Connectivity')}, + 'org_name': {'label': _('Org name')} + + } @classmethod def setup_eager_loading(cls, queryset): diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index c737f8cbe..88ae6eb38 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -1,5 +1,7 @@ from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ + from common.serializers import AdaptedBulkListSerializer from ..models import SystemUser, Asset @@ -10,16 +12,36 @@ class SystemUserSerializer(serializers.ModelSerializer): """ 系统用户 """ - unreachable_amount = serializers.SerializerMethodField() - reachable_amount = serializers.SerializerMethodField() - unreachable_assets = serializers.SerializerMethodField() - reachable_assets = serializers.SerializerMethodField() - assets_amount = serializers.SerializerMethodField() + password = serializers.CharField( + required=False, write_only=True, label=_('Password') + ) + unreachable_amount = serializers.SerializerMethodField( + label=_('Unreachable') + ) + unreachable_assets = serializers.SerializerMethodField( + label=_('Unreachable assets') + ) + reachable_assets = serializers.SerializerMethodField( + label=_('Reachable assets') + ) + reachable_amount = serializers.SerializerMethodField(label=_('Reachable')) + assets_amount = serializers.SerializerMethodField(label=_('Asset')) class Meta: model = SystemUser - exclude = ('_password', '_private_key', '_public_key') list_serializer_class = AdaptedBulkListSerializer + fields = [ + 'id', 'org_id', 'name', 'username', 'login_mode', + 'login_mode_display', 'priority', 'protocol', 'auto_push', + 'password', 'assets_amount', 'reachable_amount', 'reachable_assets', + 'unreachable_amount', 'unreachable_assets', 'cmd_filters', 'sudo', + 'shell', 'comment', 'nodes', 'assets' + ] + extra_kwargs = { + 'login_mode_display': {'label': _('Login mode display')}, + 'created_by': {'read_only': True}, 'nodes': {'read_only': True}, + 'assets': {'read_only': True} + } def get_field_names(self, declared_fields, info): fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) diff --git a/apps/assets/templates/assets/_admin_user_import_modal.html b/apps/assets/templates/assets/_admin_user_import_modal.html new file mode 100644 index 000000000..a4afc1a14 --- /dev/null +++ b/apps/assets/templates/assets/_admin_user_import_modal.html @@ -0,0 +1,6 @@ +{% extends '_import_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Import admin user" %}{% endblock %} + +{% block import_modal_download_template_url %}{% url "api-assets:admin-user-list" %}{% endblock %} diff --git a/apps/assets/templates/assets/_admin_user_update_modal.html b/apps/assets/templates/assets/_admin_user_update_modal.html new file mode 100644 index 000000000..9af051dd2 --- /dev/null +++ b/apps/assets/templates/assets/_admin_user_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update admin user" %}{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/_asset_import_modal.html b/apps/assets/templates/assets/_asset_import_modal.html index ca7729e05..2460cb053 100644 --- a/apps/assets/templates/assets/_asset_import_modal.html +++ b/apps/assets/templates/assets/_asset_import_modal.html @@ -1,29 +1,6 @@ -{% extends '_modal.html' %} +{% extends '_import_modal.html' %} {% load i18n %} -{% block modal_id %}asset_import_modal{% endblock %} -{% block modal_title%}{% trans "Import asset" %}{% endblock %} -{% block modal_body %} -
- {% csrf_token %} -
- - {% trans 'Download' %} -
-
- - - - {% trans 'If set id, will use this id update asset existed' %} - -
-
-

-

-

-

-

-

-

-

-{% endblock %} -{% block modal_confirm_id %}btn_asset_import{% endblock %} + +{% block modal_title%}{% trans "Import assets" %}{% endblock %} + +{% block import_modal_download_template_url %}{% url "api-assets:asset-list" %}{% endblock %} diff --git a/apps/assets/templates/assets/_asset_update_modal.html b/apps/assets/templates/assets/_asset_update_modal.html new file mode 100644 index 000000000..68b2ff8db --- /dev/null +++ b/apps/assets/templates/assets/_asset_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update assets" %}{% endblock %} diff --git a/apps/assets/templates/assets/_asset_user_view_auth_modal.html b/apps/assets/templates/assets/_asset_user_view_auth_modal.html new file mode 100644 index 000000000..05f3bf619 --- /dev/null +++ b/apps/assets/templates/assets/_asset_user_view_auth_modal.html @@ -0,0 +1,140 @@ +{% 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 %} + +
+
+ +
+ + {% trans "Need otp auth for view auth" %} +
+ +
+ +
+ + +{% endblock %} +{% block modal_button %} + +{% endblock %} diff --git a/apps/assets/templates/assets/_system_user_import_modal.html b/apps/assets/templates/assets/_system_user_import_modal.html new file mode 100644 index 000000000..b8687d696 --- /dev/null +++ b/apps/assets/templates/assets/_system_user_import_modal.html @@ -0,0 +1,6 @@ +{% extends '_import_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Import system user" %}{% endblock %} + +{% block import_modal_download_template_url %}{% url "api-assets:system-user-list" %}{% endblock %} diff --git a/apps/assets/templates/assets/_system_user_update_modal.html b/apps/assets/templates/assets/_system_user_update_modal.html new file mode 100644 index 000000000..9e2920e6a --- /dev/null +++ b/apps/assets/templates/assets/_system_user_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update system user" %}{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index c893ead80..328b21579 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -85,6 +85,7 @@ {% include 'assets/_asset_user_auth_modal.html' %} + {% include 'assets/_asset_user_view_auth_modal.html' %} {% 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 index 605e89060..4f4356539 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -1,8 +1,5 @@ {% extends '_base_list.html' %} {% load i18n static %} -{% block table_search %} -{% endblock %} - {% block help_message %}
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#} @@ -12,6 +9,30 @@ {% trans 'You can set any one for Windows or other hardware.' %}
{% endblock %} +{% block table_search %} +
+ +
+{% endblock %} {% block table_container %}
@@ -36,11 +57,14 @@ + {% include 'assets/_admin_user_import_modal.html' %} + {% include 'assets/_admin_user_update_modal.html' %} {% 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 index 9a330ce77..ef192d790 100644 --- a/apps/assets/templates/assets/asset_asset_user_list.html +++ b/apps/assets/templates/assets/asset_asset_user_list.html @@ -2,10 +2,6 @@ {% load common_tags %} {% load static %} {% load i18n %} - -{% block custom_head_css_js %} -{% endblock %} - {% block content %}
@@ -87,6 +83,7 @@
{% include 'assets/_asset_user_auth_modal.html' %} + {% include 'assets/_asset_user_view_auth_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index aa27de7a8..dc2bb7ebb 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -67,14 +67,26 @@
-
-
- - {% trans "Import" %} - - - {% trans "Export" %} - +
@@ -140,7 +152,7 @@ {#
  • {% trans 'Refresh' %}
  • #}
    - +{% include 'assets/_asset_update_modal.html' %} {% include 'assets/_asset_import_modal.html' %} {% include 'assets/_asset_list_modal.html' %} {% endblock %} @@ -457,49 +469,78 @@ $(document).ready(function(){ asset_table.search(val).draw(); }) .on('click', '.btn_export', function () { - var $data_table = $('#asset_list_table').DataTable(); - var rows = $data_table.rows('.selected').data(); - - var assets = []; - $.each(rows, function (index, obj) { - assets.push(obj.id) - }); - $.ajax({ - url: "{% url "assets:asset-export" %}", - method: 'POST', - data: JSON.stringify({assets_id: assets, node_id: current_node_id}), - dataType: "json", - success: function (data, textStatus) { - window.open(data.redirect) - }, - error: function () { - toastr.error('Export failed'); + var assets = asset_table.selected; + var data = { + 'resources': assets + }; + var search = $("input[type='search']").val(); + var props = { + method: "POST", + body: JSON.stringify(data), + success_url: "{% url 'api-assets:asset-list' %}", + format: 'csv', + params: { + search: search, + node_id: current_node_id || '' } - }) + }; + APIExportData(props); }) -.on('click', '#btn_asset_import', function () { - var $form = $('#fm_asset_import'); - var action = $form.attr("action"); +.on('click', '#btn_import_confirm', function () { + var file = document.getElementById('id_file').files[0]; + if(!file){ + toastr.error("{% trans "Please select file" %}"); + return + } + var url = "{% url 'api-assets:asset-list' %}"; if (current_node_id){ - action = setUrlParam(action, 'node_id', current_node_id); - $form.attr("action", action) + url = setUrlParam(url, 'node_id', current_node_id); } - $form.find('.help-block').remove(); - function success (data) { - if (data.valid === false) { - $('', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets')); - } else { - $('#id_created').html(data.created_info); - $('#id_created_detail').html(data.created.join(', ')); - $('#id_updated').html(data.updated_info); - $('#id_updated_detail').html(data.updated.join(', ')); - $('#id_failed').html(data.failed_info); - $('#id_failed_detail').html(data.failed.join(', ')); - var $data_table = $('#asset_list_table').DataTable(); - $data_table.ajax.reload(); + var data_table = $('#asset_list_table').DataTable(); + + APIImportData({ + url: url, + method: "POST", + body: file, + data_table: data_table + }); +}) +.on('click', '#download_update_template', function () { + var assets = asset_table.selected; + var data = { + 'resources': assets + }; + var search = $("input[type='search']").val(); + var props = { + method: "POST", + body: JSON.stringify(data), + success_url: "{% url 'api-assets:asset-list' %}?format=csv&template=update", + format: 'csv', + params: { + search: search, + node_id: current_node_id || '' } + }; + APIExportData(props); +}) +.on('click', '#btn_update_confirm', function () { + var file = document.getElementById('update_file').files[0]; + if(!file){ + toastr.error("{% trans "Please select file" %}"); + return } - $form.ajaxSubmit({success: success}); + var url = "{% url 'api-assets:asset-list' %}"; + if (current_node_id){ + url = setUrlParam(url, 'node_id', current_node_id); + } + var data_table = $('#asset_list_table').DataTable(); + + APIImportData({ + url: url, + method: "PUT", + body: file, + data_table: data_table + }); }) .on('click', '.btn-create-asset', function () { var url = "{% url 'assets:asset-create' %}"; @@ -584,15 +625,17 @@ $(document).ready(function(){ }) .on('click', '#btn_bulk_update', function () { var action = $('#slct_bulk_update').val(); - var $data_table = $('#asset_list_table').DataTable(); - var id_list = []; - $data_table.rows({selected: true}).every(function(){ - id_list.push(this.data().id); - }); + var id_list = asset_table.selected; if (id_list.length === 0) { return false; } var the_url = "{% url 'api-assets:asset-list' %}"; + var data = { + 'resources': id_list + }; + function refreshTag() { + $('#asset_list_table').DataTable().ajax.reload(); + } function doDeactive() { var data = []; @@ -601,7 +644,8 @@ $(document).ready(function(){ data.push(obj); }); function success() { - asset_table.ajax.reload() + setTimeout( function () { + window.location.reload();}, 500); } APIUpdateAttr({ url: the_url, @@ -617,7 +661,8 @@ $(document).ready(function(){ data.push(obj); }); function success() { - asset_table.ajax.reload() + setTimeout( function () { + window.location.reload();}, 300); } APIUpdateAttr({ url: the_url, @@ -636,68 +681,72 @@ $(document).ready(function(){ confirmButtonColor: "#DD6B55", confirmButtonText: "{% trans 'Confirm' %}", closeOnConfirm: false - }, function() { - var success = function() { + },function () { + function success(data) { + url = setUrlParam(the_url, 'spm', data.spm); + APIUpdateAttr({ + url:url, + method:'DELETE', + success:refreshTag, + flash_message:false, + }); var msg = "{% trans 'Asset Deleted.' %}"; swal("{% trans 'Asset Delete' %}", msg, "success"); - $('#asset_list_table').DataTable().ajax.reload(); - }; - var fail = function() { + } + function fail() { var msg = "{% trans 'Asset Deleting failed.' %}"; swal("{% trans 'Asset Delete' %}", msg, "error"); - }; - var url_delete = the_url + '?id__in=' + JSON.stringify(id_list); + } APIUpdateAttr({ - url: url_delete, - method: 'DELETE', - success: success, - error: fail - }); - $data_table.ajax.reload(); - jumpserver.checked = false; - }); + url: "{% url 'api-common:resources-cache' %}", + method:'POST', + body:JSON.stringify(data), + success:success, + error:fail + }) + }) } + function doUpdate() { - var data = { - 'assets_id':id_list - }; - function error(data) { - toastr.error(JSON.parse(data).error) + function fail(data) { + toastr.error(JSON.parse(data)) } function success(data) { - location.href = data.url; + var url = "{% url 'assets:asset-bulk-update' %}"; + location.href= setUrlParam(url, 'spm', data.spm); } APIUpdateAttr({ - 'url': "{% url 'api-assets:asset-bulk-update-select' %}", - 'method': 'POST', - 'body': JSON.stringify(data), - 'flash_message': false, - 'success': success, - 'error': error, + url: "{% url 'api-common:resources-cache' %}", + method:'POST', + body:JSON.stringify(data), + flash_message:false, + success:success, + error:fail }) - } + } function doRemove() { - var nodes = zTree.getSelectedNodes(); - if (!current_node_id) { - return - } + var nodes = zTree.getSelectedNodes(); + if (!current_node_id) { + return + } - var data = { - 'assets': id_list - }; + var data = { + 'assets': id_list + }; - var success = function () { - asset_table.ajax.reload() - }; + var success = function () { + asset_table.ajax.reload() + }; - APIUpdateAttr({ - 'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/', - 'method': 'PUT', - 'body': JSON.stringify(data), - 'success': success - }) + APIUpdateAttr({ + 'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/', + 'method': 'PUT', + 'body': JSON.stringify(data), + 'success': success + }) } + switch(action) { case 'deactive': doDeactive(); diff --git a/apps/assets/templates/assets/system_user_asset.html b/apps/assets/templates/assets/system_user_asset.html index 082e13fd8..f5df32bf5 100644 --- a/apps/assets/templates/assets/system_user_asset.html +++ b/apps/assets/templates/assets/system_user_asset.html @@ -133,6 +133,7 @@
    {% include 'assets/_asset_user_auth_modal.html' %} + {% include 'assets/_asset_user_view_auth_modal.html' %} {% 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 index b31039a46..2b1a63115 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -14,6 +14,28 @@ {% endblock %} {% block table_search %} +
    + +
    {% endblock %} {% block table_container %} @@ -41,9 +63,12 @@ + {% include 'assets/_system_user_import_modal.html' %} + {% include 'assets/_system_user_update_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 96bc451f0..653fa12c3 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -27,7 +27,9 @@ from django.contrib.messages.views import SuccessMessageMixin from common.mixins import JSONResponseMixin from common.utils import get_object_or_none, get_logger from common.permissions import AdminUserRequiredMixin -from common.const import create_success_msg, update_success_msg +from common.const import ( + create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID +) from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX from orgs.utils import current_org from .. import forms @@ -122,7 +124,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView): def get(self, request, *args, **kwargs): spm = request.GET.get('spm', '') - assets_id = cache.get(CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)) + assets_id = cache.get(KEY_CACHE_RESOURCES_ID.format(spm)) if kwargs.get('form'): self.form = kwargs['form'] elif assets_id: diff --git a/apps/authentication/api/auth.py b/apps/authentication/api/auth.py index 2d9cfe367..42c196c51 100644 --- a/apps/authentication/api/auth.py +++ b/apps/authentication/api/auth.py @@ -2,6 +2,7 @@ # import uuid +import time from django.core.cache import cache from django.urls import reverse @@ -10,10 +11,11 @@ from django.utils.translation import ugettext as _ from rest_framework.permissions import AllowAny from rest_framework.response import Response +from rest_framework.generics import CreateAPIView from rest_framework.views import APIView from common.utils import get_logger, get_request_ip -from common.permissions import IsOrgAdminOrAppUser +from common.permissions import IsOrgAdminOrAppUser, IsValidUser from orgs.mixins import RootOrgViewMixin from users.serializers import UserSerializer from users.models import User @@ -23,12 +25,13 @@ from users.utils import ( check_user_valid, check_otp_code, increase_login_failed_count, is_block_login, clean_failed_count ) - +from ..serializers import OtpVerifySerializer from ..signals import post_auth_success, post_auth_failed logger = get_logger(__name__) __all__ = [ 'UserAuthApi', 'UserConnectionTokenApi', 'UserOtpAuthApi', + 'UserOtpVerifyApi', ] @@ -179,3 +182,20 @@ class UserOtpAuthApi(RootOrgViewMixin, APIView): sender=self.__class__, username=username, request=self.request, reason=reason ) + + +class UserOtpVerifyApi(CreateAPIView): + permission_classes = (IsValidUser,) + serializer_class = OtpVerifySerializer + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + code = serializer.validated_data["code"] + + if request.user.check_otp(code): + request.session["OTP_LAST_VERIFY_TIME"] = int(time.time()) + return Response({"ok": "1"}) + else: + return Response({"error": "Code not valid"}, status=400) + diff --git a/apps/authentication/backends/ldap.py b/apps/authentication/backends/ldap.py index b35903420..3e58e08fa 100644 --- a/apps/authentication/backends/ldap.py +++ b/apps/authentication/backends/ldap.py @@ -7,6 +7,8 @@ from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django_auth_ldap.backend import _LDAPUser, LDAPBackend from django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPSearchUnion +from users.utils import construct_user_email + logger = _LDAPConfig.get_logger() @@ -86,13 +88,18 @@ class LDAPUser(_LDAPUser): return user_dn def _populate_user_from_attributes(self): - super()._populate_user_from_attributes() - if not hasattr(self._user, 'email') or '@' not in self._user.email: - if '@' not in self._user.username: - email = '{}@{}'.format(self._user.username, settings.EMAIL_SUFFIX) + for field, attr in self.settings.USER_ATTR_MAP.items(): + try: + value = self.attrs[attr][0] + except LookupError: + logger.warning("{} does not have a value for the attribute {}".format(self.dn, attr)) else: - email = self._user.username - setattr(self._user, 'email', email) - - + if not hasattr(self._user, field): + continue + if isinstance(getattr(self._user, field), bool): + value = value.lower() in ['true', '1'] + setattr(self._user, field, value) + email = getattr(self._user, 'email', '') + email = construct_user_email(email, self._user.username) + setattr(self._user, 'email', email) diff --git a/apps/authentication/backends/openid/middleware.py b/apps/authentication/backends/openid/middleware.py index b1556ff04..43c7dbd22 100644 --- a/apps/authentication/backends/openid/middleware.py +++ b/apps/authentication/backends/openid/middleware.py @@ -23,15 +23,12 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin): def process_request(self, request): # Don't need openid auth if AUTH_OPENID is False if not settings.AUTH_OPENID: - logger.debug("Not settings.AUTH_OPENID") return # Don't need check single logout if user not authenticated if not request.user.is_authenticated: - logger.debug("User is not authenticated") return elif not request.session[BACKEND_SESSION_KEY].endswith( BACKEND_OPENID_AUTH_CODE): - logger.debug("BACKEND_SESSION_KEY is not BACKEND_OPENID_AUTH_CODE") return # Check openid user single logout or not with access_token diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers.py index cf4968a56..2033b3e44 100644 --- a/apps/authentication/serializers.py +++ b/apps/authentication/serializers.py @@ -14,3 +14,7 @@ class AccessKeySerializer(serializers.ModelSerializer): model = AccessKey fields = ['id', 'secret'] read_only_fields = ['id', 'secret'] + + +class OtpVerifySerializer(serializers.Serializer): + code = serializers.CharField(max_length=6, min_length=6) diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index b22c49884..85f3aa522 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -16,5 +16,6 @@ urlpatterns = [ path('connection-token/', api.UserConnectionTokenApi.as_view(), name='connection-token'), path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'), + path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'), ] diff --git a/apps/common/api.py b/apps/common/api.py index 269d493d0..4f5f8da30 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -3,10 +3,18 @@ import os import uuid -from rest_framework.views import Response -from rest_framework import generics, serializers from django.core.cache import cache +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import generics, serializers + +from .const import KEY_CACHE_RESOURCES_ID + +__all__ = [ + 'LogTailApi', 'ResourcesIDCacheApi', +] + class OutputSerializer(serializers.Serializer): output = serializers.CharField() @@ -68,3 +76,14 @@ class LogTailApi(generics.RetrieveAPIView): data, end, new_mark = self.read_from_file() return Response({"data": data, 'end': end, 'mark': new_mark}) + + +class ResourcesIDCacheApi(APIView): + + def post(self, request, *args, **kwargs): + spm = str(uuid.uuid4()) + resources_id = request.data.get('resources') + if resources_id: + cache_key = KEY_CACHE_RESOURCES_ID.format(spm) + cache.set(cache_key, resources_id, 300) + return Response({'spm': spm}) diff --git a/apps/common/const.py b/apps/common/const.py index 018177d89..72d92da81 100644 --- a/apps/common/const.py +++ b/apps/common/const.py @@ -7,3 +7,4 @@ create_success_msg = _("%(name)s was created successfully") update_success_msg = _("%(name)s was updated successfully") FILE_END_GUARD = ">>> Content End <<<" celery_task_pre_key = "CELERY_" +KEY_CACHE_RESOURCES_ID = "RESOURCES_ID_{}" diff --git a/apps/common/drfmetadata.py b/apps/common/drfmetadata.py new file mode 100644 index 000000000..e29ac8641 --- /dev/null +++ b/apps/common/drfmetadata.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# +from __future__ import unicode_literals + +from collections import OrderedDict + +from django.core.exceptions import PermissionDenied +from django.http import Http404 +from django.utils.encoding import force_text + +from rest_framework.metadata import SimpleMetadata +from rest_framework import exceptions, serializers +from rest_framework.request import clone_request + + +class SimpleMetadataWithFilters(SimpleMetadata): + """Override SimpleMetadata, adding info about filters""" + + methods = {"PUT", "POST", "GET"} + attrs = [ + 'read_only', 'label', 'help_text', + 'min_length', 'max_length', + 'min_value', 'max_value', "write_only" + ] + + def determine_actions(self, request, view): + """ + For generic class based views we return information about + the fields that are accepted for 'PUT' and 'POST' methods. + """ + actions = {} + for method in self.methods & set(view.allowed_methods): + view.request = clone_request(request, method) + try: + # Test global permissions + if hasattr(view, 'check_permissions'): + view.check_permissions(view.request) + # Test object permissions + if method == 'PUT' and hasattr(view, 'get_object'): + view.get_object() + except (exceptions.APIException, PermissionDenied, Http404): + pass + else: + # If user has appropriate permissions for the view, include + # appropriate metadata about the fields that should be supplied. + serializer = view.get_serializer() + actions[method] = self.get_serializer_info(serializer) + finally: + view.request = request + return actions + + def get_field_info(self, field): + """ + Given an instance of a serializer field, return a dictionary + of metadata about it. + """ + field_info = OrderedDict() + field_info['type'] = self.label_lookup[field] + field_info['required'] = getattr(field, 'required', False) + + for attr in self.attrs: + value = getattr(field, attr, None) + if value is not None and value != '': + field_info[attr] = force_text(value, strings_only=True) + + if getattr(field, 'child', None): + field_info['child'] = self.get_field_info(field.child) + elif getattr(field, 'fields', None): + field_info['children'] = self.get_serializer_info(field) + + if (not field_info.get('read_only') and + not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and + hasattr(field, 'choices')): + field_info['choices'] = [ + { + 'value': choice_value, + 'display_name': force_text(choice_name, strings_only=True) + } + for choice_value, choice_name in field.choices.items() + ] + + return field_info + + def get_filters_fields(self, request, view): + fields = [] + if hasattr(view, 'get_filter_fields'): + fields = view.get_filter_fields(request) + elif hasattr(view, 'filter_fields'): + fields = view.filter_fields + return fields + + def get_ordering_fields(self, request, view): + fields = [] + if hasattr(view, 'get_ordering_fields'): + fields = view.get_filter_fields(request) + elif hasattr(view, 'ordering_fields'): + fields = view.filter_fields + return fields + + def determine_metadata(self, request, view): + metadata = super(SimpleMetadataWithFilters, self).determine_metadata(request, view) + filter_fields = self.get_filters_fields(request, view) + order_fields = self.get_ordering_fields(request, view) + + meta_get = metadata.get("actions", {}).get("GET", {}) + for k, v in meta_get.items(): + if k in filter_fields: + v["filter"] = True + if k in order_fields: + v["order"] = True + return metadata diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py index b844d3517..c2bb1e0be 100644 --- a/apps/common/fields/model.py +++ b/apps/common/fields/model.py @@ -11,7 +11,7 @@ __all__ = [ 'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin', 'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField', 'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField', - 'EncryptTextField', 'EncryptMixin', + 'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField', ] signer = get_signer() @@ -129,4 +129,7 @@ class EncryptCharField(EncryptMixin, models.CharField): super().__init__(*args, **kwargs) +class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField): + pass + diff --git a/apps/common/mixins.py b/apps/common/mixins.py index a5e9a58d3..8e4af26dd 100644 --- a/apps/common/mixins.py +++ b/apps/common/mixins.py @@ -3,12 +3,15 @@ from django.db import models from django.http import JsonResponse from django.utils import timezone +from django.core.cache import cache from django.utils.translation import ugettext_lazy as _ 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 +from .const import KEY_CACHE_RESOURCES_ID + class NoDeleteQuerySet(models.query.QuerySet): @@ -65,6 +68,27 @@ class IDInFilterMixin(object): return queryset +class IDInCacheFilterMixin(object): + + def filter_queryset(self, queryset): + queryset = super(IDInCacheFilterMixin, self).filter_queryset(queryset) + spm = self.request.query_params.get('spm') + cache_key = KEY_CACHE_RESOURCES_ID.format(spm) + resources_id = cache.get(cache_key) + if resources_id and isinstance(resources_id, list): + queryset = queryset.filter(id__in=resources_id) + return queryset + + +class IDExportFilterMixin(object): + def filter_queryset(self, queryset): + # 下载导入模版 + if self.request.query_params.get('template') == 'import': + return [] + else: + return super(IDExportFilterMixin, self).filter_queryset(queryset) + + class BulkSerializerMixin(object): """ Become rest_framework_bulk not support uuid as a primary key @@ -131,7 +155,11 @@ class BulkListSerializerMixin(object): for item in data: try: # prepare child serializer to only handle one instance - self.child.instance = self.instance.get(id=item['id']) if self.instance else None + if 'id' in item.keys(): + self.child.instance = self.instance.get(id=item['id']) if self.instance else None + if 'pk' in item.keys(): + self.child.instance = self.instance.get(id=item['pk']) if self.instance else None + self.child.initial_data = item # raw validated = self.child.run_validation(item) diff --git a/apps/common/parsers/__init__.py b/apps/common/parsers/__init__.py new file mode 100644 index 000000000..671c86586 --- /dev/null +++ b/apps/common/parsers/__init__.py @@ -0,0 +1 @@ +from .csv import * \ No newline at end of file diff --git a/apps/common/parsers/csv.py b/apps/common/parsers/csv.py new file mode 100644 index 000000000..b536a0f73 --- /dev/null +++ b/apps/common/parsers/csv.py @@ -0,0 +1,101 @@ +# ~*~ coding: utf-8 ~*~ +# + +import json +import unicodecsv + +from rest_framework.parsers import BaseParser +from rest_framework.exceptions import ParseError + +from ..utils import get_logger + +logger = get_logger(__file__) + + +class JMSCSVParser(BaseParser): + """ + Parses CSV file to serializer data + """ + + media_type = 'text/csv' + + @staticmethod + def _universal_newlines(stream): + """ + 保证在`通用换行模式`下打开文件 + """ + for line in stream.splitlines(): + yield line + + @staticmethod + def _gen_rows(csv_data, charset='utf-8', **kwargs): + csv_reader = unicodecsv.reader(csv_data, encoding=charset, **kwargs) + for row in csv_reader: + if not any(row): # 空行 + continue + yield row + + @staticmethod + def _get_fields_map(serializer): + fields_map = {} + fields = serializer.get_fields() + fields_map.update({v.label: k for k, v in fields.items()}) + fields_map.update({k: k for k, _ in fields.items()}) + return fields_map + + @staticmethod + def _process_row(row): + """ + 构建json数据前的行处理 + """ + _row = [] + for col in row: + # 列表转换 + 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 + + @staticmethod + def _process_row_data(row_data): + """ + 构建json数据后的行数据处理 + """ + _row_data = {} + for k, v in row_data.items(): + if isinstance(v, list) \ + or isinstance(v, str) and k.strip() and v.strip(): + _row_data[k] = v + return _row_data + + def parse(self, stream, media_type=None, parser_context=None): + parser_context = parser_context or {} + encoding = parser_context.get('encoding', 'utf-8') + try: + serializer = parser_context["view"].get_serializer() + except Exception as e: + logger.debug(e, exc_info=True) + raise ParseError('The resource does not support imports!') + + try: + stream_data = stream.read() + binary = self._universal_newlines(stream_data) + rows = self._gen_rows(binary, charset=encoding) + + header = next(rows) + fields_map = self._get_fields_map(serializer) + header = [fields_map.get(name, '') for name in header] + + data = [] + for row in rows: + row = self._process_row(row) + row_data = dict(zip(header, row)) + row_data = self._process_row_data(row_data) + data.append(row_data) + return data + except Exception as e: + logger.debug(e, exc_info=True) + raise ParseError('CSV parse error!') diff --git a/apps/common/permissions.py b/apps/common/permissions.py index 689444131..025d44ba3 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +import time from rest_framework import permissions from django.contrib.auth.mixins import UserPassesTestMixin diff --git a/apps/common/renders/__init__.py b/apps/common/renders/__init__.py new file mode 100644 index 000000000..671c86586 --- /dev/null +++ b/apps/common/renders/__init__.py @@ -0,0 +1 @@ +from .csv import * \ No newline at end of file diff --git a/apps/common/renders/csv.py b/apps/common/renders/csv.py new file mode 100644 index 000000000..3352c73a0 --- /dev/null +++ b/apps/common/renders/csv.py @@ -0,0 +1,83 @@ +# ~*~ coding: utf-8 ~*~ +# + +import unicodecsv +from datetime import datetime + +from six import BytesIO +from rest_framework.renderers import BaseRenderer +from rest_framework.utils import encoders, json + +from ..utils import get_logger + +logger = get_logger(__file__) + + +class JMSCSVRender(BaseRenderer): + + media_type = 'text/csv' + format = 'csv' + + @staticmethod + def _get_header(fields, template): + if template == 'import': + header = [ + k for k, v in fields.items() + if not v.read_only and k != 'org_id' + ] + elif template == 'update': + header = [k for k, v in fields.items() if not v.read_only] + else: + # template in ['export'] + header = [k for k, v in fields.items() if not v.write_only] + return header + + @staticmethod + def _gen_table(data, header, labels=None): + labels = labels or {} + yield [labels.get(k, k) for k in header] + + for item in data: + row = [item.get(key) for key in header] + yield row + + def set_response_disposition(self, serializer, context): + response = context.get('response') + if response and hasattr(serializer, 'Meta') and \ + hasattr(serializer.Meta, "model"): + model_name = serializer.Meta.model.__name__.lower() + now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + filename = "{}_{}.csv".format(model_name, now) + disposition = 'attachment; filename="{}"'.format(filename) + response['Content-Disposition'] = disposition + + def render(self, data, media_type=None, renderer_context=None): + renderer_context = renderer_context or {} + encoding = renderer_context.get('encoding', 'utf-8') + request = renderer_context['request'] + template = request.query_params.get('template', 'export') + view = renderer_context['view'] + data = json.loads(json.dumps(data, cls=encoders.JSONEncoder)) + if template == 'import': + data = [data[0]] if data else data + + try: + serializer = view.get_serializer() + self.set_response_disposition(serializer, renderer_context) + except Exception as e: + logger.debug(e, exc_info=True) + value = 'The resource not support export!'.encode('utf-8') + else: + fields = serializer.get_fields() + header = self._get_header(fields, template) + labels = {k: v.label for k, v in fields.items() if v.label} + table = self._gen_table(data, header, labels) + + csv_buffer = BytesIO() + csv_writer = unicodecsv.writer(csv_buffer, encoding=encoding) + for row in table: + csv_writer.writerow(row) + + value = csv_buffer.getvalue() + + return value diff --git a/apps/common/urls/__init__.py b/apps/common/urls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py new file mode 100644 index 000000000..01f164b00 --- /dev/null +++ b/apps/common/urls/api_urls.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# + +from django.urls import path + +from .. import api + +app_name = 'common' + +urlpatterns = [ + path('resources/cache/', + api.ResourcesIDCacheApi.as_view(), name='resources-cache'), +] diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index dcd7daf16..79146c039 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -144,6 +144,7 @@ def is_uuid(seq): def get_request_ip(request): x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',') + if x_forwarded_for and x_forwarded_for[0]: login_ip = x_forwarded_for[0] else: diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index ee6dcdbfd..28ac12456 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -194,7 +194,7 @@ class Config(dict): filename = os.path.join(self.root_path, filename) try: with open(filename, 'rt', encoding='utf8') as f: - obj = yaml.load(f) + obj = yaml.safe_load(f) except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False @@ -273,6 +273,19 @@ class Config(dict): if default_value is None: return v tp = type(default_value) + # 对bool特殊处理 + if tp is bool and isinstance(v, str): + if v in ("true", "True", "1"): + return True + else: + return False + if tp in [list, dict] and isinstance(v, str): + try: + v = json.loads(v) + return v + except json.JSONDecodeError: + return v + try: v = tp(v) except Exception: @@ -289,14 +302,10 @@ class Config(dict): except KeyError: value = None if value is not None: - return self.convert_type(item, value) + return value # 其次从环境变量来 value = os.environ.get(item, None) if value is not None: - if value.lower() == 'false': - value = False - elif value.lower() == 'true': - value = True return self.convert_type(item, value) return self.defaults.get(item) @@ -343,6 +352,7 @@ defaults = { 'TERMINAL_SESSION_KEEP_DURATION': 9999, 'TERMINAL_HOST_KEY': '', 'TERMINAL_TELNET_REGEX': '', + 'TERMINAL_COMMAND_STORAGE': {}, 'SECURITY_MFA_AUTH': False, 'SECURITY_LOGIN_LIMIT_COUNT': 7, 'SECURITY_LOGIN_LIMIT_TIME': 30, @@ -361,6 +371,7 @@ defaults = { 'HTTP_LISTEN_PORT': 8080, 'LOGIN_LOG_KEEP_DAYS': 90, 'ASSETS_PERM_CACHE_TIME': 3600, + } diff --git a/apps/jumpserver/const.py b/apps/jumpserver/const.py index dfd8d74a1..f8f85d7f9 100644 --- a/apps/jumpserver/const.py +++ b/apps/jumpserver/const.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- # -VERSION = '1.4.10' +VERSION = '1.5.0' diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index cc8a730c1..d9be8c346 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -67,6 +67,7 @@ INSTALLED_APPS = [ 'terminal.apps.TerminalConfig', 'audits.apps.AuditsConfig', 'authentication.apps.AuthenticationConfig', # authentication + 'applications.apps.ApplicationsConfig', 'rest_framework', 'rest_framework_swagger', 'drf_yasg', @@ -172,7 +173,7 @@ DATABASES = { 'OPTIONS': DB_OPTIONS } } -DB_CA_PATH = os.path.join(PROJECT_DIR, 'data', 'ca.pem') +DB_CA_PATH = os.path.join(PROJECT_DIR, 'data', 'certs', 'db_ca.pem') if CONFIG.DB_ENGINE.lower() == 'mysql': DB_OPTIONS['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'" if os.path.isfile(DB_CA_PATH): @@ -356,12 +357,30 @@ EMAIL_USE_SSL = False EMAIL_USE_TLS = False EMAIL_SUBJECT_PREFIX = '[JMS] ' +#Email custom content +EMAIL_CUSTOM_USER_CREATED_SUBJECT = '' +EMAIL_CUSTOM_USER_CREATED_HONORIFIC = '' +EMAIL_CUSTOM_USER_CREATED_BODY = '' +EMAIL_CUSTOM_USER_CREATED_SIGNATURE = '' + REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'DEFAULT_PERMISSION_CLASSES': ( 'common.permissions.IsOrgAdmin', ), + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + 'rest_framework.renderers.BrowsableAPIRenderer', + 'common.renders.JMSCSVRender', + ), + 'DEFAULT_PARSER_CLASSES': ( + 'rest_framework.parsers.JSONParser', + 'rest_framework.parsers.FormParser', + 'rest_framework.parsers.MultiPartParser', + 'common.parsers.JMSCSVParser', + 'rest_framework.parsers.FileUploadParser', + ), 'DEFAULT_AUTHENTICATION_CLASSES': ( # 'rest_framework.authentication.BasicAuthentication', 'authentication.backends.api.AccessKeyAuthentication', @@ -374,6 +393,7 @@ REST_FRAMEWORK = { 'rest_framework.filters.SearchFilter', 'rest_framework.filters.OrderingFilter', ), + 'DEFAULT_METADATA_CLASS': 'common.drfmetadata.SimpleMetadataWithFilters', 'ORDERING_PARAM': "order", 'SEARCH_PARAM': "search", 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z', @@ -406,6 +426,12 @@ AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org' AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)' AUTH_LDAP_START_TLS = False AUTH_LDAP_USER_ATTR_MAP = {"username": "cn", "name": "sn", "email": "mail"} +AUTH_LDAP_GLOBAL_OPTIONS = { + ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER, +} +LDAP_CERT_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ca.pem") +if os.path.isfile(LDAP_CERT_FILE): + AUTH_LDAP_GLOBAL_OPTIONS[ldap.OPT_X_TLS_CACERTFILE] = LDAP_CERT_FILE # AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU # AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER # AUTH_LDAP_GROUP_SEARCH = LDAPSearch( @@ -507,12 +533,7 @@ DEFAULT_TERMINAL_COMMAND_STORAGE = { }, } -TERMINAL_COMMAND_STORAGE = { - # 'ali-es': { - # 'TYPE': 'elasticsearch', - # 'HOSTS': ['http://elastic:changeme@localhost:9200'], - # }, -} +TERMINAL_COMMAND_STORAGE = CONFIG.TERMINAL_COMMAND_STORAGE DEFAULT_TERMINAL_REPLAY_STORAGE = { "default": { diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 5ef225828..96c702acc 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -20,6 +20,8 @@ api_v1 = [ path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')), path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')), path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')), + path('common/v1/', include('common.urls.api_urls', namespace='api-common')), + path('applications/v1/', include('applications.urls.api_urls', namespace='api-applications')), ] api_v2 = [ @@ -37,6 +39,7 @@ app_view_patterns = [ 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')), ] diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 52ca4190e..fb8aab644 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 17e132ca0..c1acf7792 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: 2019-04-29 12:22+0800\n" +"POT-Creation-Date: 2019-05-27 15:38+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -17,26 +17,605 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: assets/api/asset.py:112 +#: applications/const.py:17 +msgid "Browser" +msgstr "浏览器" + +#: applications/const.py:23 +msgid "Database tools" +msgstr "数据库工具" + +#: applications/const.py:29 +msgid "Virtualization tools" +msgstr "虚拟化工具" + +#: applications/const.py:34 +msgid "Custom" +msgstr "自定义" + +#: applications/forms/remote_app.py:21 +msgid "Target URL" +msgstr "目标URL" + +#: applications/forms/remote_app.py:24 applications/forms/remote_app.py:53 +#: applications/forms/remote_app.py:69 +msgid "Login username" +msgstr "登录账号" + +#: applications/forms/remote_app.py:28 applications/forms/remote_app.py:57 +#: applications/forms/remote_app.py:73 +msgid "Login password" +msgstr "登录密码" + +#: applications/forms/remote_app.py:34 +msgid "Database IP" +msgstr "数据库IP" + +#: applications/forms/remote_app.py:37 +msgid "Database name" +msgstr "数据库名" + +#: applications/forms/remote_app.py:40 +msgid "Database username" +msgstr "数据库账号" + +#: applications/forms/remote_app.py:44 +msgid "Database password" +msgstr "数据库密码" + +#: applications/forms/remote_app.py:50 applications/forms/remote_app.py:66 +msgid "Target address" +msgstr "目标地址" + +#: applications/forms/remote_app.py:63 +msgid "Operating parameter" +msgstr "运行参数" + +#: applications/forms/remote_app.py:106 applications/models/remote_app.py:23 +#: applications/templates/applications/remote_app_detail.html:57 +#: applications/templates/applications/remote_app_list.html:22 +#: applications/templates/applications/user_remote_app_list.html:18 +#: assets/forms/domain.py:15 assets/forms/label.py:13 +#: assets/models/asset.py:279 assets/models/authbook.py:27 +#: assets/serializers/admin_user.py:23 assets/serializers/system_user.py:28 +#: assets/templates/assets/admin_user_list.html:49 +#: assets/templates/assets/domain_detail.html:60 +#: assets/templates/assets/domain_list.html:26 +#: assets/templates/assets/label_list.html:16 +#: assets/templates/assets/system_user_list.html:55 audits/models.py:19 +#: audits/templates/audits/ftp_log_list.html:41 +#: audits/templates/audits/ftp_log_list.html:71 +#: perms/forms/asset_permission.py:46 perms/models/asset_permission.py:37 +#: perms/templates/perms/asset_permission_create_update.html:45 +#: perms/templates/perms/asset_permission_list.html:56 +#: perms/templates/perms/asset_permission_list.html:125 +#: terminal/backends/command/models.py:13 terminal/models.py:155 +#: terminal/templates/terminal/command_list.html:40 +#: terminal/templates/terminal/command_list.html:73 +#: terminal/templates/terminal/session_list.html:41 +#: terminal/templates/terminal/session_list.html:72 +#: xpack/plugins/change_auth_plan/forms.py:114 +#: xpack/plugins/change_auth_plan/models.py:409 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46 +#: 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:187 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 +#: xpack/plugins/orgs/templates/orgs/org_list.html:16 +msgid "Asset" +msgstr "资产" + +#: applications/forms/remote_app.py:109 applications/models/remote_app.py:27 +#: applications/templates/applications/remote_app_detail.html:61 +#: applications/templates/applications/remote_app_list.html:23 +#: applications/templates/applications/user_remote_app_list.html:19 +#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:168 +#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49 +#: audits/templates/audits/ftp_log_list.html:72 +#: perms/forms/asset_permission.py:52 perms/models/asset_permission.py:39 +#: perms/models/asset_permission.py:59 +#: perms/templates/perms/asset_permission_detail.html:140 +#: perms/templates/perms/asset_permission_list.html:58 +#: perms/templates/perms/asset_permission_list.html:79 +#: perms/templates/perms/asset_permission_list.html:131 templates/_nav.html:25 +#: terminal/backends/command/models.py:14 terminal/models.py:156 +#: terminal/templates/terminal/command_list.html:48 +#: terminal/templates/terminal/command_list.html:74 +#: terminal/templates/terminal/session_list.html:49 +#: terminal/templates/terminal/session_list.html:73 +#: xpack/plugins/orgs/templates/orgs/org_list.html:19 +msgid "System user" +msgstr "系统用户" + +#: applications/models/remote_app.py:21 +#: applications/templates/applications/remote_app_detail.html:53 +#: applications/templates/applications/remote_app_list.html:20 +#: applications/templates/applications/user_remote_app_list.html:16 +#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146 +#: assets/models/base.py:26 assets/models/cluster.py:18 +#: assets/models/cmd_filter.py:20 assets/models/domain.py:20 +#: assets/models/group.py:20 assets/models/label.py:18 +#: assets/templates/assets/admin_user_detail.html:56 +#: assets/templates/assets/admin_user_list.html:47 +#: assets/templates/assets/cmd_filter_detail.html:61 +#: assets/templates/assets/cmd_filter_list.html:24 +#: assets/templates/assets/domain_detail.html:56 +#: assets/templates/assets/domain_gateway_list.html:67 +#: assets/templates/assets/domain_list.html:25 +#: assets/templates/assets/label_list.html:14 +#: assets/templates/assets/system_user_detail.html:58 +#: assets/templates/assets/system_user_list.html:51 ops/models/adhoc.py:37 +#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27 +#: orgs/models.py:12 perms/models/asset_permission.py:22 +#: perms/models/base.py:35 +#: perms/templates/perms/asset_permission_detail.html:62 +#: perms/templates/perms/asset_permission_list.html:53 +#: perms/templates/perms/asset_permission_list.html:72 +#: perms/templates/perms/asset_permission_user.html:54 +#: perms/templates/perms/remote_app_permission_detail.html:62 +#: perms/templates/perms/remote_app_permission_list.html:14 +#: perms/templates/perms/remote_app_permission_remote_app.html:53 +#: perms/templates/perms/remote_app_permission_user.html:53 +#: settings/models.py:29 +#: settings/templates/settings/_ldap_list_users_modal.html:38 +#: settings/templates/settings/command_storage_create.html:41 +#: settings/templates/settings/replay_storage_create.html:44 +#: settings/templates/settings/terminal_setting.html:83 +#: settings/templates/settings/terminal_setting.html:105 terminal/models.py:22 +#: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43 +#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 +#: users/models/user.py:61 users/templates/users/_select_user_modal.html:13 +#: users/templates/users/user_detail.html:63 +#: users/templates/users/user_group_detail.html:55 +#: users/templates/users/user_group_list.html:35 +#: users/templates/users/user_list.html:35 +#: users/templates/users/user_profile.html:51 +#: users/templates/users/user_pubkey_update.html:53 +#: xpack/plugins/change_auth_plan/forms.py:97 +#: xpack/plugins/change_auth_plan/models.py:58 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 +#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:50 +#: 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/orgs/templates/orgs/org_detail.html:52 +#: xpack/plugins/orgs/templates/orgs/org_list.html:12 +msgid "Name" +msgstr "名称" + +#: applications/models/remote_app.py:32 +#: applications/templates/applications/remote_app_detail.html:65 +#: applications/templates/applications/remote_app_list.html:21 +#: applications/templates/applications/user_remote_app_list.html:17 +msgid "App type" +msgstr "应用类型" + +#: applications/models/remote_app.py:36 +#: applications/templates/applications/remote_app_detail.html:69 +msgid "App path" +msgstr "应用路径" + +#: applications/models/remote_app.py:40 +msgid "Parameters" +msgstr "参数" + +#: applications/models/remote_app.py:43 +#: applications/templates/applications/remote_app_detail.html:77 +#: assets/models/asset.py:109 assets/models/base.py:34 +#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 +#: assets/models/cmd_filter.py:58 assets/models/group.py:21 +#: assets/templates/assets/admin_user_detail.html:68 +#: assets/templates/assets/asset_detail.html:128 +#: assets/templates/assets/cmd_filter_detail.html:77 +#: assets/templates/assets/domain_detail.html:72 +#: assets/templates/assets/system_user_detail.html:100 +#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 +#: perms/models/asset_permission.py:62 perms/models/base.py:41 +#: perms/templates/perms/asset_permission_detail.html:98 +#: perms/templates/perms/remote_app_permission_detail.html:90 +#: users/models/user.py:102 users/serializers/v1.py:72 +#: users/templates/users/user_detail.html:111 +#: xpack/plugins/change_auth_plan/models.py:103 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 +#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127 +msgid "Created by" +msgstr "创建者" + +# msgid "Created by" +# msgstr "创建者" +#: applications/models/remote_app.py:46 +#: applications/templates/applications/remote_app_detail.html:73 +#: assets/models/asset.py:110 assets/models/cluster.py:26 +#: assets/models/domain.py:23 assets/models/group.py:22 +#: assets/models/label.py:25 assets/serializers/admin_user.py:37 +#: assets/templates/assets/admin_user_detail.html:64 +#: assets/templates/assets/cmd_filter_detail.html:69 +#: assets/templates/assets/domain_detail.html:68 +#: assets/templates/assets/system_user_detail.html:96 +#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 +#: orgs/models.py:16 perms/models/asset_permission.py:63 +#: perms/models/base.py:42 +#: perms/templates/perms/asset_permission_detail.html:94 +#: perms/templates/perms/remote_app_permission_detail.html:86 +#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 +#: users/templates/users/user_group_detail.html:63 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 +#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:60 +msgid "Date created" +msgstr "创建日期" + +# msgid "Date created" +# msgstr "创建日期" +#: applications/models/remote_app.py:49 +#: applications/templates/applications/remote_app_detail.html:81 +#: applications/templates/applications/remote_app_list.html:24 +#: applications/templates/applications/user_remote_app_list.html:20 +#: assets/models/asset.py:111 assets/models/base.py:31 +#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 +#: assets/models/cmd_filter.py:55 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:72 +#: assets/templates/assets/admin_user_list.html:53 +#: assets/templates/assets/asset_detail.html:136 +#: assets/templates/assets/cmd_filter_detail.html:65 +#: assets/templates/assets/cmd_filter_list.html:27 +#: assets/templates/assets/cmd_filter_rule_list.html:62 +#: assets/templates/assets/domain_detail.html:76 +#: assets/templates/assets/domain_gateway_list.html:72 +#: assets/templates/assets/domain_list.html:28 +#: assets/templates/assets/system_user_detail.html:104 +#: assets/templates/assets/system_user_list.html:59 +#: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43 +#: orgs/models.py:17 perms/models/asset_permission.py:64 +#: perms/models/base.py:43 +#: perms/templates/perms/asset_permission_detail.html:102 +#: perms/templates/perms/remote_app_permission_detail.html:94 +#: settings/models.py:34 terminal/models.py:32 +#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15 +#: users/models/user.py:94 users/templates/users/user_detail.html:127 +#: users/templates/users/user_group_detail.html:67 +#: users/templates/users/user_group_list.html:37 +#: users/templates/users/user_profile.html:134 +#: xpack/plugins/change_auth_plan/models.py:99 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 +#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:70 +#: xpack/plugins/cloud/templates/cloud/account_list.html:15 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:64 +#: xpack/plugins/orgs/templates/orgs/org_list.html:22 +msgid "Comment" +msgstr "备注" + +#: applications/models/remote_app.py:53 perms/forms/remote_app_permission.py:37 +#: perms/models/remote_app_permission.py:15 +#: perms/templates/perms/remote_app_permission_create_update.html:48 +#: perms/templates/perms/remote_app_permission_detail.html:27 +#: perms/templates/perms/remote_app_permission_list.html:17 +#: perms/templates/perms/remote_app_permission_remote_app.html:26 +#: perms/templates/perms/remote_app_permission_user.html:26 +#: templates/_nav.html:35 templates/_nav.html:45 templates/_nav_user.html:14 +msgid "RemoteApp" +msgstr "远程应用" + +#: applications/templates/applications/remote_app_create_update.html:56 +#: assets/templates/assets/_system_user.html:75 +#: assets/templates/assets/admin_user_create_update.html:45 +#: assets/templates/assets/asset_bulk_update.html:23 +#: assets/templates/assets/asset_create.html:67 +#: assets/templates/assets/asset_update.html:71 +#: assets/templates/assets/cmd_filter_create_update.html:15 +#: assets/templates/assets/cmd_filter_rule_create_update.html:40 +#: assets/templates/assets/domain_create_update.html:16 +#: assets/templates/assets/gateway_create_update.html:58 +#: assets/templates/assets/label_create_update.html:18 +#: perms/templates/perms/asset_permission_create_update.html:83 +#: perms/templates/perms/remote_app_permission_create_update.html:83 +#: settings/templates/settings/basic_setting.html:64 +#: settings/templates/settings/command_storage_create.html:79 +#: settings/templates/settings/email_content_setting.html:54 +#: settings/templates/settings/email_setting.html:65 +#: settings/templates/settings/ldap_setting.html:64 +#: settings/templates/settings/replay_storage_create.html:152 +#: settings/templates/settings/security_setting.html:73 +#: settings/templates/settings/terminal_setting.html:71 +#: terminal/templates/terminal/terminal_update.html:45 +#: users/templates/users/_user.html:50 +#: users/templates/users/user_bulk_update.html:23 +#: users/templates/users/user_detail.html:176 +#: users/templates/users/user_password_update.html:71 +#: users/templates/users/user_profile.html:204 +#: users/templates/users/user_profile_update.html:63 +#: users/templates/users/user_pubkey_update.html:70 +#: users/templates/users/user_pubkey_update.html:76 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:71 +#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:35 +#: xpack/plugins/interface/templates/interface/interface.html:72 +msgid "Reset" +msgstr "重置" + +#: applications/templates/applications/remote_app_create_update.html:58 +#: assets/templates/assets/_system_user.html:76 +#: assets/templates/assets/admin_user_create_update.html:46 +#: assets/templates/assets/asset_bulk_update.html:24 +#: assets/templates/assets/asset_create.html:68 +#: assets/templates/assets/asset_list.html:125 +#: assets/templates/assets/asset_update.html:72 +#: assets/templates/assets/cmd_filter_create_update.html:16 +#: assets/templates/assets/cmd_filter_rule_create_update.html:41 +#: assets/templates/assets/domain_create_update.html:17 +#: assets/templates/assets/gateway_create_update.html:59 +#: assets/templates/assets/label_create_update.html:19 +#: audits/templates/audits/login_log_list.html:89 +#: perms/templates/perms/asset_permission_create_update.html:84 +#: perms/templates/perms/remote_app_permission_create_update.html:84 +#: settings/templates/settings/basic_setting.html:65 +#: settings/templates/settings/command_storage_create.html:80 +#: settings/templates/settings/email_content_setting.html:55 +#: settings/templates/settings/email_setting.html:66 +#: settings/templates/settings/ldap_setting.html:67 +#: settings/templates/settings/replay_storage_create.html:153 +#: settings/templates/settings/security_setting.html:74 +#: settings/templates/settings/terminal_setting.html:73 +#: terminal/templates/terminal/command_list.html:103 +#: terminal/templates/terminal/session_list.html:126 +#: terminal/templates/terminal/terminal_update.html:46 +#: users/templates/users/_user.html:51 +#: users/templates/users/forgot_password.html:42 +#: users/templates/users/user_bulk_update.html:24 +#: users/templates/users/user_list.html:57 +#: users/templates/users/user_password_update.html:72 +#: users/templates/users/user_profile_update.html:64 +#: users/templates/users/user_pubkey_update.html:77 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72 +#: xpack/plugins/interface/templates/interface/interface.html:74 +msgid "Submit" +msgstr "提交" + +#: applications/templates/applications/remote_app_detail.html:18 +#: assets/templates/assets/admin_user_assets.html:18 +#: assets/templates/assets/admin_user_detail.html:18 +#: assets/templates/assets/cmd_filter_detail.html:19 +#: assets/templates/assets/cmd_filter_rule_list.html:19 +#: assets/templates/assets/domain_detail.html:18 +#: assets/templates/assets/domain_gateway_list.html:20 +#: assets/templates/assets/system_user_asset.html:18 +#: assets/templates/assets/system_user_detail.html:18 +#: ops/templates/ops/adhoc_history.html:130 +#: ops/templates/ops/task_adhoc.html:116 +#: ops/templates/ops/task_history.html:136 +#: perms/templates/perms/asset_permission_asset.html:18 +#: perms/templates/perms/asset_permission_detail.html:18 +#: perms/templates/perms/asset_permission_user.html:18 +#: perms/templates/perms/remote_app_permission_detail.html:18 +#: perms/templates/perms/remote_app_permission_remote_app.html:17 +#: perms/templates/perms/remote_app_permission_user.html:17 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:17 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:20 +#: 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:83 +msgid "Detail" +msgstr "详情" + +#: applications/templates/applications/remote_app_detail.html:21 +#: applications/templates/applications/remote_app_list.html:56 +#: assets/templates/assets/admin_user_detail.html:24 +#: assets/templates/assets/admin_user_list.html:29 +#: assets/templates/assets/admin_user_list.html:111 +#: assets/templates/assets/asset_detail.html:27 +#: assets/templates/assets/asset_list.html:86 +#: assets/templates/assets/asset_list.html:190 +#: assets/templates/assets/cmd_filter_detail.html:29 +#: assets/templates/assets/cmd_filter_list.html:58 +#: assets/templates/assets/cmd_filter_rule_list.html:86 +#: assets/templates/assets/domain_detail.html:24 +#: assets/templates/assets/domain_detail.html:103 +#: assets/templates/assets/domain_gateway_list.html:97 +#: assets/templates/assets/domain_list.html:54 +#: assets/templates/assets/label_list.html:39 +#: assets/templates/assets/system_user_detail.html:26 +#: assets/templates/assets/system_user_list.html:33 +#: assets/templates/assets/system_user_list.html:117 audits/models.py:33 +#: perms/templates/perms/asset_permission_detail.html:30 +#: perms/templates/perms/asset_permission_list.html:181 +#: perms/templates/perms/remote_app_permission_detail.html:30 +#: perms/templates/perms/remote_app_permission_list.html:59 +#: terminal/templates/terminal/terminal_detail.html:16 +#: terminal/templates/terminal/terminal_list.html:72 +#: users/templates/users/user_detail.html:25 +#: users/templates/users/user_group_detail.html:28 +#: users/templates/users/user_group_list.html:20 +#: users/templates/users/user_group_list.html:69 +#: users/templates/users/user_list.html:20 +#: users/templates/users/user_list.html:96 +#: users/templates/users/user_list.html:99 +#: users/templates/users/user_profile.html:177 +#: users/templates/users/user_profile.html:187 +#: users/templates/users/user_profile.html:196 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:23 +#: xpack/plugins/cloud/templates/cloud/account_list.html:39 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:25 +#: xpack/plugins/orgs/templates/orgs/org_list.html:87 +msgid "Update" +msgstr "更新" + +#: applications/templates/applications/remote_app_detail.html:25 +#: applications/templates/applications/remote_app_list.html:57 +#: assets/templates/assets/admin_user_detail.html:28 +#: assets/templates/assets/admin_user_list.html:112 +#: assets/templates/assets/asset_detail.html:31 +#: assets/templates/assets/asset_list.html:191 +#: assets/templates/assets/cmd_filter_detail.html:33 +#: assets/templates/assets/cmd_filter_list.html:59 +#: assets/templates/assets/cmd_filter_rule_list.html:87 +#: assets/templates/assets/domain_detail.html:28 +#: assets/templates/assets/domain_detail.html:104 +#: assets/templates/assets/domain_gateway_list.html:98 +#: assets/templates/assets/domain_list.html:55 +#: assets/templates/assets/label_list.html:40 +#: assets/templates/assets/system_user_detail.html:30 +#: assets/templates/assets/system_user_list.html:118 audits/models.py:34 +#: ops/templates/ops/task_list.html:64 +#: perms/templates/perms/asset_permission_detail.html:34 +#: perms/templates/perms/asset_permission_list.html:182 +#: perms/templates/perms/remote_app_permission_detail.html:34 +#: perms/templates/perms/remote_app_permission_list.html:60 +#: settings/templates/settings/terminal_setting.html:93 +#: settings/templates/settings/terminal_setting.html:115 +#: terminal/templates/terminal/terminal_list.html:74 +#: users/templates/users/user_detail.html:30 +#: users/templates/users/user_group_detail.html:32 +#: users/templates/users/user_group_list.html:71 +#: users/templates/users/user_list.html:104 +#: users/templates/users/user_list.html:108 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:27 +#: xpack/plugins/cloud/templates/cloud/account_list.html:41 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:29 +#: xpack/plugins/orgs/templates/orgs/org_list.html:89 +msgid "Delete" +msgstr "删除" + +#: applications/templates/applications/remote_app_list.html:5 +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:6 +msgid "Download application loader" +msgstr "下载应用加载器" + +#: applications/templates/applications/remote_app_list.html:12 +#: applications/views/remote_app.py:47 +msgid "Create RemoteApp" +msgstr "创建远程应用" + +#: applications/templates/applications/remote_app_list.html:25 +#: applications/templates/applications/user_remote_app_list.html:21 +#: assets/models/cmd_filter.py:54 +#: assets/templates/assets/admin_user_assets.html:52 +#: assets/templates/assets/admin_user_list.html:54 +#: assets/templates/assets/asset_asset_user_list.html:48 +#: assets/templates/assets/asset_list.html:108 +#: assets/templates/assets/cmd_filter_list.html:28 +#: assets/templates/assets/cmd_filter_rule_list.html:63 +#: assets/templates/assets/domain_gateway_list.html:73 +#: assets/templates/assets/domain_list.html:29 +#: assets/templates/assets/label_list.html:17 +#: assets/templates/assets/system_user_asset.html:54 +#: assets/templates/assets/system_user_list.html:60 +#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38 +#: audits/templates/audits/operate_log_list.html:41 +#: audits/templates/audits/operate_log_list.html:67 +#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 +#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:34 +#: perms/forms/asset_permission.py:55 perms/models/asset_permission.py:26 +#: perms/models/asset_permission.py:40 +#: perms/templates/perms/asset_permission_create_update.html:50 +#: perms/templates/perms/asset_permission_list.html:60 +#: perms/templates/perms/asset_permission_list.html:134 +#: perms/templates/perms/remote_app_permission_list.html:19 +#: settings/templates/settings/terminal_setting.html:85 +#: settings/templates/settings/terminal_setting.html:107 +#: terminal/templates/terminal/session_list.html:81 +#: terminal/templates/terminal/terminal_list.html:36 +#: users/templates/users/user_group_list.html:38 +#: users/templates/users/user_list.html:41 +#: 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_list.html:18 +#: xpack/plugins/orgs/templates/orgs/org_list.html:23 +msgid "Action" +msgstr "动作" + +#: applications/templates/applications/user_remote_app_list.html:57 +#: assets/templates/assets/user_asset_list.html:100 perms/const.py:19 +msgid "Connect" +msgstr "连接" + +#: applications/views/remote_app.py:31 applications/views/remote_app.py:46 +#: applications/views/remote_app.py:67 applications/views/remote_app.py:84 +#: assets/models/user.py:134 +#: assets/templates/assets/_asset_group_bulk_update_modal.html:11 +#: assets/templates/assets/system_user_asset.html:22 +#: assets/templates/assets/system_user_detail.html:22 +#: assets/views/admin_user.py:29 assets/views/admin_user.py:47 +#: assets/views/admin_user.py:63 assets/views/admin_user.py:78 +#: assets/views/admin_user.py:102 assets/views/asset.py:53 +#: assets/views/asset.py:69 assets/views/asset.py:106 assets/views/asset.py:147 +#: assets/views/asset.py:164 assets/views/asset.py:188 +#: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46 +#: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78 +#: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130 +#: assets/views/cmd_filter.py:163 assets/views/domain.py:29 +#: assets/views/domain.py:45 assets/views/domain.py:61 +#: assets/views/domain.py:74 assets/views/domain.py:98 +#: assets/views/domain.py:126 assets/views/domain.py:145 +#: assets/views/label.py:26 assets/views/label.py:43 assets/views/label.py:69 +#: assets/views/system_user.py:28 assets/views/system_user.py:44 +#: assets/views/system_user.py:60 assets/views/system_user.py:74 +#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:65 +msgid "Assets" +msgstr "资产管理" + +#: applications/views/remote_app.py:32 +msgid "RemoteApp list" +msgstr "远程应用列表" + +#: applications/views/remote_app.py:68 +msgid "Update RemoteApp" +msgstr "更新远程应用" + +#: applications/views/remote_app.py:85 +msgid "RemoteApp detail" +msgstr "远程应用详情" + +#: applications/views/remote_app.py:96 +msgid "My RemoteApp" +msgstr "我的远程应用" + +#: assets/api/asset.py:126 msgid "Please select assets that need to be updated" msgstr "请选择需要更新的资产" -#: assets/api/node.py:58 +#: assets/api/node.py:60 msgid "You can't update the root node name" msgstr "不能修改根节点名称" -#: assets/api/node.py:282 +#: assets/api/node.py:285 msgid "Update node asset hardware information: {}" msgstr "更新节点资产硬件信息: {}" -#: assets/api/node.py:296 +#: assets/api/node.py:299 msgid "Test if the assets under the node are connectable: {}" msgstr "测试节点下资产是否可连接: {}" #: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:133 #: assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/asset_detail.html:202 -#: assets/templates/assets/system_user_asset.html:95 perms/models.py:51 +#: assets/templates/assets/system_user_asset.html:95 +#: perms/models/asset_permission.py:38 #: xpack/plugins/change_auth_plan/models.py:69 msgid "Nodes" msgstr "节点管理" @@ -45,7 +624,7 @@ msgstr "节点管理" #: assets/models/cluster.py:19 assets/models/user.py:91 #: assets/templates/assets/asset_detail.html:80 templates/_nav.html:24 #: xpack/plugins/cloud/models.py:124 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:67 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65 #: xpack/plugins/orgs/templates/orgs/org_list.html:18 msgid "Admin user" msgstr "管理用户" @@ -53,7 +632,7 @@ msgstr "管理用户" #: assets/forms/asset.py:33 assets/forms/asset.py:69 assets/forms/asset.py:109 #: assets/templates/assets/asset_create.html:36 #: assets/templates/assets/asset_create.html:38 -#: assets/templates/assets/asset_list.html:81 +#: assets/templates/assets/asset_list.html:93 #: assets/templates/assets/asset_update.html:41 #: assets/templates/assets/asset_update.html:43 #: assets/templates/assets/user_asset_list.html:33 @@ -72,8 +651,9 @@ msgstr "网域" #: assets/forms/asset.py:41 assets/forms/asset.py:63 assets/forms/asset.py:77 #: assets/forms/asset.py:112 assets/models/node.py:31 #: assets/templates/assets/asset_create.html:30 -#: assets/templates/assets/asset_update.html:35 perms/forms.py:45 -#: perms/forms.py:55 perms/models.py:105 +#: assets/templates/assets/asset_update.html:35 +#: perms/forms/asset_permission.py:49 perms/forms/asset_permission.py:59 +#: perms/models/asset_permission.py:57 #: perms/templates/perms/asset_permission_list.html:57 #: perms/templates/perms/asset_permission_list.html:78 #: perms/templates/perms/asset_permission_list.html:128 @@ -81,8 +661,8 @@ msgstr "网域" #: 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:123 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:63 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:61 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 msgid "Node" msgstr "节点" @@ -111,36 +691,6 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, msgid "Select assets" msgstr "选择资产" -#: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:279 assets/models/authbook.py:27 -#: assets/templates/assets/admin_user_list.html:28 -#: assets/templates/assets/domain_detail.html:60 -#: assets/templates/assets/domain_list.html:26 -#: assets/templates/assets/label_list.html:16 -#: assets/templates/assets/system_user_list.html:33 audits/models.py:19 -#: audits/templates/audits/ftp_log_list.html:41 -#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:42 -#: perms/models.py:50 -#: perms/templates/perms/asset_permission_create_update.html:45 -#: perms/templates/perms/asset_permission_list.html:56 -#: perms/templates/perms/asset_permission_list.html:125 -#: terminal/backends/command/models.py:13 terminal/models.py:155 -#: terminal/templates/terminal/command_list.html:40 -#: terminal/templates/terminal/command_list.html:73 -#: terminal/templates/terminal/session_list.html:41 -#: terminal/templates/terminal/session_list.html:72 -#: xpack/plugins/change_auth_plan/forms.py:114 -#: xpack/plugins/change_auth_plan/models.py:409 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46 -#: 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:187 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 -#: xpack/plugins/orgs/templates/orgs/org_list.html:16 -msgid "Asset" -msgstr "资产" - #: assets/forms/domain.py:51 msgid "Password should not contain special characters" msgstr "不能包含特殊字符" @@ -149,72 +699,26 @@ msgstr "不能包含特殊字符" msgid "SSH gateway support proxy SSH,RDP,VNC" msgstr "SSH网关,支持代理SSH,RDP和VNC" -#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146 -#: assets/models/base.py:26 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:20 assets/models/domain.py:20 -#: assets/models/group.py:20 assets/models/label.py:18 -#: assets/templates/assets/admin_user_detail.html:56 -#: assets/templates/assets/admin_user_list.html:26 -#: assets/templates/assets/cmd_filter_detail.html:61 -#: assets/templates/assets/cmd_filter_list.html:24 -#: assets/templates/assets/domain_detail.html:56 -#: assets/templates/assets/domain_gateway_list.html:67 -#: assets/templates/assets/domain_list.html:25 -#: assets/templates/assets/label_list.html:14 -#: assets/templates/assets/system_user_detail.html:58 -#: assets/templates/assets/system_user_list.html:29 ops/models/adhoc.py:37 -#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27 -#: orgs/models.py:12 perms/models.py:17 perms/models.py:47 -#: perms/templates/perms/asset_permission_detail.html:62 -#: perms/templates/perms/asset_permission_list.html:53 -#: perms/templates/perms/asset_permission_list.html:72 -#: perms/templates/perms/asset_permission_user.html:54 settings/models.py:29 -#: settings/templates/settings/_ldap_list_users_modal.html:38 -#: settings/templates/settings/command_storage_create.html:41 -#: settings/templates/settings/replay_storage_create.html:44 -#: settings/templates/settings/terminal_setting.html:80 -#: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22 -#: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43 -#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 -#: users/models/user.py:61 users/templates/users/_select_user_modal.html:13 -#: users/templates/users/user_detail.html:63 -#: users/templates/users/user_group_detail.html:55 -#: users/templates/users/user_group_list.html:12 -#: users/templates/users/user_list.html:23 -#: users/templates/users/user_profile.html:51 -#: users/templates/users/user_pubkey_update.html:53 -#: xpack/plugins/change_auth_plan/forms.py:97 -#: xpack/plugins/change_auth_plan/models.py:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 -#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:52 -#: xpack/plugins/cloud/templates/cloud/account_list.html:12 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:55 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:52 -#: xpack/plugins/orgs/templates/orgs/org_list.html:12 -msgid "Name" -msgstr "名称" - #: assets/forms/domain.py:74 assets/forms/user.py:85 assets/forms/user.py:147 #: assets/models/base.py:27 #: assets/templates/assets/_asset_user_auth_modal.html:15 +#: assets/templates/assets/_asset_user_view_auth_modal.html:31 #: assets/templates/assets/admin_user_detail.html:60 -#: assets/templates/assets/admin_user_list.html:27 -#: assets/templates/assets/asset_asset_user_list.html:48 +#: assets/templates/assets/admin_user_list.html:48 +#: assets/templates/assets/asset_asset_user_list.html:44 #: assets/templates/assets/domain_gateway_list.html:71 #: assets/templates/assets/system_user_detail.html:62 -#: assets/templates/assets/system_user_list.html:30 audits/models.py:94 +#: assets/templates/assets/system_user_list.html:52 audits/models.py:94 #: audits/templates/audits/login_log_list.html:51 authentication/forms.py:11 #: authentication/templates/authentication/login.html:64 #: authentication/templates/authentication/new_login.html:90 #: ops/models/adhoc.py:164 perms/templates/perms/asset_permission_list.html:74 #: perms/templates/perms/asset_permission_user.html:55 +#: perms/templates/perms/remote_app_permission_user.html:54 #: settings/templates/settings/_ldap_list_users_modal.html:37 users/forms.py:13 #: users/models/user.py:59 users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:67 -#: users/templates/users/user_list.html:24 +#: users/templates/users/user_list.html:36 #: users/templates/users/user_profile.html:47 #: xpack/plugins/change_auth_plan/forms.py:99 #: xpack/plugins/change_auth_plan/models.py:60 @@ -231,14 +735,15 @@ msgid "Password or private key passphrase" msgstr "密码或密钥密码" #: assets/forms/user.py:26 assets/models/base.py:28 -#: assets/serializers/asset_user.py:19 +#: assets/serializers/admin_user.py:20 assets/serializers/asset_user.py:19 +#: assets/serializers/system_user.py:16 #: assets/templates/assets/_asset_user_auth_modal.html:21 +#: assets/templates/assets/_asset_user_view_auth_modal.html:37 #: authentication/forms.py:13 #: authentication/templates/authentication/login.html:67 #: authentication/templates/authentication/new_login.html:93 -#: settings/forms.py:103 users/forms.py:15 users/forms.py:27 +#: settings/forms.py:103 users/forms.py:15 users/forms.py:33 #: users/templates/users/reset_password.html:53 -#: users/templates/users/user_create.html:10 #: users/templates/users/user_password_authentication.html:18 #: users/templates/users/user_password_update.html:43 #: users/templates/users/user_profile_update.html:40 @@ -298,7 +803,7 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/admin_user_assets.html:49 #: assets/templates/assets/asset_detail.html:64 -#: assets/templates/assets/asset_list.html:93 +#: assets/templates/assets/asset_list.html:105 #: assets/templates/assets/domain_gateway_list.html:68 #: assets/templates/assets/system_user_asset.html:51 #: assets/templates/assets/user_asset_list.html:45 @@ -313,9 +818,10 @@ msgstr "IP" #: assets/models/asset.py:75 assets/templates/assets/_asset_list_modal.html:45 #: assets/templates/assets/_asset_user_auth_modal.html:9 +#: assets/templates/assets/_asset_user_view_auth_modal.html:25 #: assets/templates/assets/admin_user_assets.html:48 #: assets/templates/assets/asset_detail.html:60 -#: assets/templates/assets/asset_list.html:92 +#: assets/templates/assets/asset_list.html:104 #: assets/templates/assets/system_user_asset.html:50 #: assets/templates/assets/user_asset_list.html:44 #: assets/templates/assets/user_asset_list.html:162 @@ -331,7 +837,7 @@ msgstr "主机名" #: assets/models/user.py:136 assets/templates/assets/asset_detail.html:76 #: assets/templates/assets/domain_gateway_list.html:70 #: assets/templates/assets/system_user_detail.html:70 -#: assets/templates/assets/system_user_list.html:31 +#: assets/templates/assets/system_user_list.html:53 #: assets/templates/assets/user_asset_list.html:165 #: terminal/templates/terminal/session_list.html:75 msgid "Protocol" @@ -431,92 +937,21 @@ msgstr "主机名原始" msgid "Labels" msgstr "标签管理" -#: assets/models/asset.py:109 assets/models/base.py:34 -#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 -#: assets/models/cmd_filter.py:58 assets/models/group.py:21 -#: assets/templates/assets/admin_user_detail.html:68 -#: assets/templates/assets/asset_detail.html:128 -#: assets/templates/assets/cmd_filter_detail.html:77 -#: assets/templates/assets/domain_detail.html:72 -#: assets/templates/assets/system_user_detail.html:100 -#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:57 -#: perms/models.py:110 perms/templates/perms/asset_permission_detail.html:98 -#: users/models/user.py:102 users/templates/users/user_detail.html:111 -#: xpack/plugins/change_auth_plan/models.py:103 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 -#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127 -msgid "Created by" -msgstr "创建者" - -#: assets/models/asset.py:110 assets/models/cluster.py:26 -#: assets/models/domain.py:23 assets/models/group.py:22 -#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64 -#: assets/templates/assets/cmd_filter_detail.html:69 -#: assets/templates/assets/domain_detail.html:68 -#: assets/templates/assets/system_user_detail.html:96 -#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 -#: orgs/models.py:16 perms/models.py:58 perms/models.py:111 -#: perms/templates/perms/asset_permission_detail.html:94 -#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 -#: users/templates/users/user_group_detail.html:63 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 -#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:68 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:79 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:60 -msgid "Date created" -msgstr "创建日期" - -#: assets/models/asset.py:111 assets/models/base.py:31 -#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 -#: assets/models/cmd_filter.py:55 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:72 -#: assets/templates/assets/admin_user_list.html:32 -#: assets/templates/assets/asset_detail.html:136 -#: assets/templates/assets/cmd_filter_detail.html:65 -#: assets/templates/assets/cmd_filter_list.html:27 -#: assets/templates/assets/cmd_filter_rule_list.html:62 -#: assets/templates/assets/domain_detail.html:76 -#: assets/templates/assets/domain_gateway_list.html:72 -#: assets/templates/assets/domain_list.html:28 -#: assets/templates/assets/system_user_detail.html:104 -#: assets/templates/assets/system_user_list.html:37 -#: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43 -#: orgs/models.py:17 perms/models.py:59 perms/models.py:112 -#: perms/templates/perms/asset_permission_detail.html:102 settings/models.py:34 -#: terminal/models.py:32 terminal/templates/terminal/terminal_detail.html:63 -#: users/models/group.py:15 users/models/user.py:94 -#: users/templates/users/user_detail.html:127 -#: users/templates/users/user_group_detail.html:67 -#: users/templates/users/user_group_list.html:14 -#: users/templates/users/user_profile.html:134 -#: xpack/plugins/change_auth_plan/models.py:99 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 -#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:72 -#: xpack/plugins/cloud/templates/cloud/account_list.html:15 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:71 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:64 -#: xpack/plugins/orgs/templates/orgs/org_list.html:22 -msgid "Comment" -msgstr "备注" - #: assets/models/asset.py:117 assets/models/base.py:38 -#: assets/templates/assets/admin_user_list.html:30 -#: assets/templates/assets/system_user_list.html:35 +#: assets/serializers/admin_user.py:22 assets/serializers/system_user.py:19 +#: assets/templates/assets/admin_user_list.html:51 +#: assets/templates/assets/system_user_list.html:57 msgid "Unreachable" msgstr "不可达" #: assets/models/asset.py:118 assets/models/base.py:39 +#: assets/serializers/admin_user.py:24 assets/serializers/system_user.py:27 #: assets/templates/assets/admin_user_assets.html:51 -#: assets/templates/assets/admin_user_list.html:29 -#: assets/templates/assets/asset_asset_user_list.html:50 -#: assets/templates/assets/asset_list.html:95 +#: assets/templates/assets/admin_user_list.html:50 +#: assets/templates/assets/asset_asset_user_list.html:46 +#: assets/templates/assets/asset_list.html:107 #: assets/templates/assets/system_user_asset.html:53 -#: assets/templates/assets/system_user_list.html:34 +#: assets/templates/assets/system_user_list.html:56 #: users/templates/users/user_group_granted_asset.html:47 msgid "Reachable" msgstr "可连接" @@ -584,7 +1019,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:475 +#: users/models/user.py:479 msgid "System" msgstr "系统" @@ -636,10 +1071,11 @@ msgstr "过滤器" #: assets/models/cmd_filter.py:50 #: assets/templates/assets/cmd_filter_rule_list.html:58 #: audits/templates/audits/login_log_list.html:52 +#: perms/templates/perms/remote_app_permission_remote_app.html:54 #: settings/templates/settings/command_storage_create.html:31 #: settings/templates/settings/replay_storage_create.html:31 -#: settings/templates/settings/terminal_setting.html:81 -#: settings/templates/settings/terminal_setting.html:103 +#: settings/templates/settings/terminal_setting.html:84 +#: settings/templates/settings/terminal_setting.html:106 msgid "Type" msgstr "类型" @@ -662,42 +1098,6 @@ msgstr "内容" msgid "One line one command" msgstr "每行一个命令" -#: assets/models/cmd_filter.py:54 -#: assets/templates/assets/admin_user_assets.html:52 -#: assets/templates/assets/admin_user_list.html:33 -#: assets/templates/assets/asset_asset_user_list.html:52 -#: assets/templates/assets/asset_list.html:96 -#: assets/templates/assets/cmd_filter_list.html:28 -#: assets/templates/assets/cmd_filter_rule_list.html:63 -#: assets/templates/assets/domain_gateway_list.html:73 -#: assets/templates/assets/domain_list.html:29 -#: assets/templates/assets/label_list.html:17 -#: assets/templates/assets/system_user_asset.html:54 -#: assets/templates/assets/system_user_list.html:38 -#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38 -#: audits/templates/audits/operate_log_list.html:41 -#: audits/templates/audits/operate_log_list.html:67 -#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 -#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:34 -#: perms/forms.py:51 perms/models.py:21 perms/models.py:53 -#: perms/templates/perms/asset_permission_create_update.html:50 -#: perms/templates/perms/asset_permission_list.html:60 -#: perms/templates/perms/asset_permission_list.html:134 -#: settings/templates/settings/terminal_setting.html:82 -#: settings/templates/settings/terminal_setting.html:104 -#: terminal/templates/terminal/session_list.html:81 -#: terminal/templates/terminal/terminal_list.html:36 -#: users/templates/users/user_group_list.html:15 -#: users/templates/users/user_list.html:29 -#: 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_list.html:18 -#: xpack/plugins/orgs/templates/orgs/org_list.html:23 -msgid "Action" -msgstr "动作" - #: assets/models/cmd_filter.py:64 msgid "Command filter rule" msgstr "命令过滤规则" @@ -725,19 +1125,22 @@ msgstr "默认资产组" #: audits/templates/audits/password_change_log_list.html:33 #: audits/templates/audits/password_change_log_list.html:50 #: ops/templates/ops/command_execution_list.html:35 -#: ops/templates/ops/command_execution_list.html:60 perms/forms.py:36 -#: perms/models.py:48 +#: ops/templates/ops/command_execution_list.html:60 +#: perms/forms/asset_permission.py:40 perms/forms/remote_app_permission.py:31 +#: perms/models/base.py:36 #: perms/templates/perms/asset_permission_create_update.html:41 #: perms/templates/perms/asset_permission_list.html:54 -#: perms/templates/perms/asset_permission_list.html:119 templates/index.html:87 -#: terminal/backends/command/models.py:12 terminal/models.py:154 -#: terminal/templates/terminal/command_list.html:32 +#: perms/templates/perms/asset_permission_list.html:119 +#: perms/templates/perms/remote_app_permission_create_update.html:43 +#: perms/templates/perms/remote_app_permission_list.html:15 +#: templates/index.html:87 terminal/backends/command/models.py:12 +#: terminal/models.py:154 terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:283 -#: users/models/user.py:36 users/models/user.py:463 +#: terminal/templates/terminal/session_list.html:71 users/forms.py:293 +#: users/models/user.py:36 users/models/user.py:467 users/serializers/v1.py:61 #: users/templates/users/user_group_detail.html:78 -#: users/templates/users/user_group_list.html:13 users/views/user.py:395 +#: users/templates/users/user_group_list.html:36 users/views/user.py:399 #: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113 #: xpack/plugins/orgs/templates/orgs/org_list.html:14 @@ -757,7 +1160,7 @@ msgstr "分类" msgid "Key" msgstr "键" -#: assets/models/node.py:128 +#: assets/models/node.py:133 msgid "New node" msgstr "新节点" @@ -769,29 +1172,6 @@ msgstr "自动登录" msgid "Manually login" msgstr "手动登录" -#: assets/models/user.py:134 -#: assets/templates/assets/_asset_group_bulk_update_modal.html:11 -#: assets/templates/assets/system_user_asset.html:22 -#: assets/templates/assets/system_user_detail.html:22 -#: assets/views/admin_user.py:29 assets/views/admin_user.py:47 -#: assets/views/admin_user.py:63 assets/views/admin_user.py:78 -#: assets/views/admin_user.py:102 assets/views/asset.py:51 -#: assets/views/asset.py:67 assets/views/asset.py:104 assets/views/asset.py:145 -#: assets/views/asset.py:162 assets/views/asset.py:186 -#: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46 -#: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78 -#: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130 -#: assets/views/cmd_filter.py:163 assets/views/domain.py:29 -#: assets/views/domain.py:45 assets/views/domain.py:61 -#: assets/views/domain.py:74 assets/views/domain.py:98 -#: assets/views/domain.py:126 assets/views/domain.py:145 -#: assets/views/label.py:26 assets/views/label.py:43 assets/views/label.py:69 -#: assets/views/system_user.py:28 assets/views/system_user.py:44 -#: assets/views/system_user.py:60 assets/views/system_user.py:74 -#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:65 -msgid "Assets" -msgstr "资产管理" - #: assets/models/user.py:137 assets/templates/assets/_system_user.html:59 #: assets/templates/assets/system_user_detail.html:122 #: assets/templates/assets/system_user_update.html:10 @@ -807,33 +1187,35 @@ msgid "Shell" msgstr "Shell" #: assets/models/user.py:140 assets/templates/assets/system_user_detail.html:66 -#: assets/templates/assets/system_user_list.html:32 +#: assets/templates/assets/system_user_list.html:54 msgid "Login mode" msgstr "登录模式" -#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:168 -#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49 -#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:48 -#: perms/models.py:52 perms/models.py:107 -#: perms/templates/perms/asset_permission_detail.html:140 -#: perms/templates/perms/asset_permission_list.html:58 -#: perms/templates/perms/asset_permission_list.html:79 -#: perms/templates/perms/asset_permission_list.html:131 templates/_nav.html:25 -#: terminal/backends/command/models.py:14 terminal/models.py:156 -#: terminal/templates/terminal/command_list.html:48 -#: terminal/templates/terminal/command_list.html:74 -#: terminal/templates/terminal/session_list.html:49 -#: terminal/templates/terminal/session_list.html:73 -#: xpack/plugins/orgs/templates/orgs/org_list.html:19 -msgid "System user" -msgstr "系统用户" - #: assets/models/utils.py:29 #, python-format msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/serializers/asset_user.py:23 users/forms.py:230 +#: assets/serializers/admin_user.py:38 +#: assets/templates/assets/asset_asset_user_list.html:47 +#: assets/templates/assets/cmd_filter_detail.html:73 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 +msgid "Date updated" +msgstr "更新日期" + +#: assets/serializers/asset.py:43 +msgid "Hardware info" +msgstr "硬件信息" + +#: assets/serializers/asset.py:44 +msgid "Connectivity" +msgstr "连接" + +#: assets/serializers/asset.py:45 +msgid "Org name" +msgstr "组织名" + +#: assets/serializers/asset_user.py:23 users/forms.py:240 #: users/models/user.py:91 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:46 #: users/templates/users/user_profile.html:68 @@ -842,6 +1224,18 @@ msgstr "%(value)s is not an even number" msgid "Public key" msgstr "ssh公钥" +#: assets/serializers/system_user.py:22 +msgid "Unreachable assets" +msgstr "不可达资产" + +#: assets/serializers/system_user.py:25 +msgid "Reachable assets" +msgstr "可连接资产" + +#: assets/serializers/system_user.py:41 +msgid "Login mode display" +msgstr "登录模式显示" + #: assets/tasks.py:31 msgid "Asset has been disabled, skipped: {}" msgstr "资产或许不支持ansible, 跳过: {}" @@ -911,6 +1305,15 @@ msgstr "推送系统用户到入资产: {} => {}" msgid "Test asset user connectivity: {}" msgstr "测试资产用户可连接性: {}" +#: assets/templates/assets/_admin_user_import_modal.html:4 +msgid "Import admin user" +msgstr "导入管理用户" + +#: assets/templates/assets/_admin_user_update_modal.html:4 +#: assets/views/admin_user.py:64 +msgid "Update admin user" +msgstr "更新管理用户" + #: assets/templates/assets/_asset_group_bulk_update_modal.html:5 msgid "Update asset group" msgstr "更新用户组" @@ -940,32 +1343,18 @@ msgid "Enable-MFA" msgstr "启用MFA" #: assets/templates/assets/_asset_import_modal.html:4 -msgid "Import asset" +msgid "Import assets" msgstr "导入资产" -#: assets/templates/assets/_asset_import_modal.html:9 -#: users/templates/users/_user_import_modal.html:10 -msgid "Template" -msgstr "模板" - -#: assets/templates/assets/_asset_import_modal.html:10 -#: users/templates/users/_user_import_modal.html:11 -msgid "Download" -msgstr "下载" - -#: assets/templates/assets/_asset_import_modal.html:13 -msgid "Asset csv file" -msgstr "资产csv文件" - -#: assets/templates/assets/_asset_import_modal.html:16 -msgid "If set id, will use this id update asset existed" -msgstr "如果设置了id,则会使用该行信息更新该id的资产" - -#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:52 +#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:54 #: templates/_nav.html:22 xpack/plugins/change_auth_plan/views.py:110 msgid "Asset list" msgstr "资产列表" +#: assets/templates/assets/_asset_update_modal.html:4 +msgid "Update assets" +msgstr "更新资产" + #: assets/templates/assets/_asset_user_auth_modal.html:4 msgid "Update asset user auth" msgstr "更新资产用户认证信息" @@ -975,6 +1364,61 @@ msgstr "更新资产用户认证信息" msgid "Please input password" msgstr "请输入密码" +#: assets/templates/assets/_asset_user_view_auth_modal.html:5 +msgid "Asset user auth" +msgstr "资产用户信息" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:14 +#: audits/models.py:99 audits/templates/audits/login_log_list.html:56 +#: users/forms.py:152 users/models/user.py:83 +#: users/templates/users/first_login.html:45 +msgid "MFA" +msgstr "MFA" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:17 +msgid "Need otp auth for view auth" +msgstr "需要二次认证来查看账号信息" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:20 +#: assets/templates/assets/admin_user_detail.html:100 +#: assets/templates/assets/asset_detail.html:211 +#: assets/templates/assets/asset_list.html:700 +#: assets/templates/assets/cmd_filter_detail.html:106 +#: assets/templates/assets/system_user_asset.html:112 +#: assets/templates/assets/system_user_detail.html:182 +#: assets/templates/assets/system_user_list.html:168 +#: settings/templates/settings/terminal_setting.html:168 +#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108 +#: users/templates/users/user_detail.html:388 +#: users/templates/users/user_detail.html:414 +#: users/templates/users/user_detail.html:437 +#: users/templates/users/user_detail.html:482 +#: users/templates/users/user_group_create_update.html:32 +#: users/templates/users/user_group_list.html:114 +#: users/templates/users/user_list.html:269 +#: users/templates/users/user_profile.html:238 +#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 +#: xpack/plugins/interface/templates/interface/interface.html:103 +#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 +msgid "Confirm" +msgstr "确认" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:63 +msgid "Copy success" +msgstr "复制成功" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:79 +msgid "Get auth info error" +msgstr "获取认证信息错误" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:139 +#: assets/templates/assets/_user_asset_detail_modal.html:23 +#: settings/templates/settings/_ldap_list_users_modal.html:99 +#: templates/_modal.html:22 +msgid "Close" +msgstr "关闭" + #: assets/templates/assets/_gateway_test_modal.html:4 msgid "Test gateway test connection" msgstr "测试连接网关" @@ -992,6 +1436,7 @@ msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口" #: assets/templates/assets/asset_update.html:21 #: assets/templates/assets/gateway_create_update.html:37 #: perms/templates/perms/asset_permission_create_update.html:38 +#: perms/templates/perms/remote_app_permission_create_update.html:39 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:43 msgid "Basic" msgstr "基本" @@ -1013,114 +1458,27 @@ msgstr "自动生成密钥" #: assets/templates/assets/asset_update.html:64 #: assets/templates/assets/gateway_create_update.html:53 #: perms/templates/perms/asset_permission_create_update.html:53 +#: perms/templates/perms/remote_app_permission_create_update.html:52 #: terminal/templates/terminal/terminal_update.html:40 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:67 msgid "Other" msgstr "其它" -#: assets/templates/assets/_system_user.html:75 -#: assets/templates/assets/admin_user_create_update.html:45 -#: assets/templates/assets/asset_bulk_update.html:23 -#: assets/templates/assets/asset_create.html:67 -#: assets/templates/assets/asset_update.html:71 -#: assets/templates/assets/cmd_filter_create_update.html:15 -#: assets/templates/assets/cmd_filter_rule_create_update.html:40 -#: assets/templates/assets/domain_create_update.html:16 -#: assets/templates/assets/gateway_create_update.html:58 -#: assets/templates/assets/label_create_update.html:18 -#: perms/templates/perms/asset_permission_create_update.html:83 -#: settings/templates/settings/basic_setting.html:61 -#: settings/templates/settings/command_storage_create.html:79 -#: settings/templates/settings/email_setting.html:62 -#: settings/templates/settings/ldap_setting.html:61 -#: settings/templates/settings/replay_storage_create.html:152 -#: settings/templates/settings/security_setting.html:70 -#: settings/templates/settings/terminal_setting.html:68 -#: terminal/templates/terminal/terminal_update.html:45 -#: users/templates/users/_user.html:50 -#: users/templates/users/user_bulk_update.html:23 -#: users/templates/users/user_detail.html:176 -#: users/templates/users/user_password_update.html:71 -#: users/templates/users/user_profile.html:204 -#: users/templates/users/user_profile_update.html:63 -#: users/templates/users/user_pubkey_update.html:70 -#: users/templates/users/user_pubkey_update.html:76 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:71 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:35 -#: xpack/plugins/interface/templates/interface/interface.html:72 -msgid "Reset" -msgstr "重置" +#: assets/templates/assets/_system_user_import_modal.html:4 +msgid "Import system user" +msgstr "导入系统用户" -#: assets/templates/assets/_system_user.html:76 -#: assets/templates/assets/admin_user_create_update.html:46 -#: assets/templates/assets/asset_bulk_update.html:24 -#: assets/templates/assets/asset_create.html:68 -#: assets/templates/assets/asset_list.html:113 -#: assets/templates/assets/asset_update.html:72 -#: assets/templates/assets/cmd_filter_create_update.html:16 -#: assets/templates/assets/cmd_filter_rule_create_update.html:41 -#: assets/templates/assets/domain_create_update.html:17 -#: assets/templates/assets/gateway_create_update.html:59 -#: assets/templates/assets/label_create_update.html:19 -#: audits/templates/audits/login_log_list.html:89 -#: perms/templates/perms/asset_permission_create_update.html:84 -#: settings/templates/settings/basic_setting.html:62 -#: settings/templates/settings/command_storage_create.html:80 -#: settings/templates/settings/email_setting.html:63 -#: settings/templates/settings/ldap_setting.html:64 -#: settings/templates/settings/replay_storage_create.html:153 -#: settings/templates/settings/security_setting.html:71 -#: settings/templates/settings/terminal_setting.html:70 -#: terminal/templates/terminal/command_list.html:103 -#: terminal/templates/terminal/session_list.html:126 -#: terminal/templates/terminal/terminal_update.html:46 -#: users/templates/users/_user.html:51 -#: users/templates/users/forgot_password.html:42 -#: users/templates/users/user_bulk_update.html:24 -#: users/templates/users/user_list.html:45 -#: users/templates/users/user_password_update.html:72 -#: users/templates/users/user_profile_update.html:64 -#: users/templates/users/user_pubkey_update.html:77 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72 -#: xpack/plugins/interface/templates/interface/interface.html:74 -msgid "Submit" -msgstr "提交" +#: assets/templates/assets/_system_user_update_modal.html:4 +#: assets/views/system_user.py:61 +msgid "Update system user" +msgstr "更新系统用户" #: assets/templates/assets/_user_asset_detail_modal.html:11 -#: assets/templates/assets/asset_asset_user_list.html:17 -#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:187 +#: assets/templates/assets/asset_asset_user_list.html:13 +#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:189 msgid "Asset detail" msgstr "资产详情" -#: assets/templates/assets/_user_asset_detail_modal.html:23 -#: settings/templates/settings/_ldap_list_users_modal.html:96 -#: templates/_modal.html:22 -msgid "Close" -msgstr "关闭" - -#: assets/templates/assets/admin_user_assets.html:18 -#: assets/templates/assets/admin_user_detail.html:18 -#: assets/templates/assets/cmd_filter_detail.html:19 -#: assets/templates/assets/cmd_filter_rule_list.html:19 -#: assets/templates/assets/domain_detail.html:18 -#: assets/templates/assets/domain_gateway_list.html:20 -#: assets/templates/assets/system_user_asset.html:18 -#: assets/templates/assets/system_user_detail.html:18 -#: ops/templates/ops/adhoc_history.html:130 -#: ops/templates/ops/task_adhoc.html:116 -#: ops/templates/ops/task_history.html:136 -#: perms/templates/perms/asset_permission_asset.html:18 -#: perms/templates/perms/asset_permission_detail.html:18 -#: perms/templates/perms/asset_permission_user.html:18 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:17 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:20 -#: 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:83 -msgid "Detail" -msgstr "详情" - #: assets/templates/assets/admin_user_assets.html:21 #: assets/templates/assets/admin_user_detail.html:21 msgid "Assets list" @@ -1136,119 +1494,55 @@ msgstr "资产列表" #: assets/templates/assets/system_user_asset.html:66 #: assets/templates/assets/system_user_detail.html:116 #: perms/templates/perms/asset_permission_detail.html:114 +#: perms/templates/perms/remote_app_permission_detail.html:106 msgid "Quick update" msgstr "快速更新" #: assets/templates/assets/admin_user_assets.html:70 -#: assets/templates/assets/asset_asset_user_list.html:71 +#: assets/templates/assets/asset_asset_user_list.html:67 #: assets/templates/assets/asset_detail.html:179 msgid "Test connective" msgstr "测试可连接性" #: assets/templates/assets/admin_user_assets.html:73 -#: assets/templates/assets/admin_user_assets.html:115 -#: assets/templates/assets/asset_asset_user_list.html:74 -#: assets/templates/assets/asset_asset_user_list.html:122 +#: assets/templates/assets/admin_user_assets.html:118 +#: assets/templates/assets/asset_asset_user_list.html:70 +#: assets/templates/assets/asset_asset_user_list.html:119 #: assets/templates/assets/asset_detail.html:182 #: assets/templates/assets/system_user_asset.html:75 -#: assets/templates/assets/system_user_asset.html:165 +#: assets/templates/assets/system_user_asset.html:166 #: assets/templates/assets/system_user_detail.html:151 msgid "Test" msgstr "测试" #: assets/templates/assets/admin_user_assets.html:116 -#: assets/templates/assets/asset_asset_user_list.html:120 -#: assets/templates/assets/system_user_asset.html:167 +#: assets/templates/assets/asset_asset_user_list.html:117 +#: assets/templates/assets/system_user_asset.html:169 msgid "Update auth" msgstr "更新认证" -#: assets/templates/assets/admin_user_assets.html:192 -#: assets/templates/assets/asset_asset_user_list.html:176 +#: assets/templates/assets/admin_user_assets.html:117 +#: assets/templates/assets/asset_asset_user_list.html:118 +#: assets/templates/assets/system_user_asset.html:167 +msgid "View auth" +msgstr "查看认证" + +#: assets/templates/assets/admin_user_assets.html:196 +#: assets/templates/assets/asset_asset_user_list.html:162 #: assets/templates/assets/asset_detail.html:311 -#: assets/templates/assets/system_user_asset.html:351 +#: assets/templates/assets/system_user_asset.html:353 #: users/templates/users/user_detail.html:307 #: users/templates/users/user_detail.html:334 #: xpack/plugins/interface/views.py:34 msgid "Update successfully!" msgstr "更新成功" -#: assets/templates/assets/admin_user_assets.html:195 -#: assets/templates/assets/asset_asset_user_list.html:179 -#: assets/templates/assets/system_user_asset.html:354 +#: assets/templates/assets/admin_user_assets.html:199 +#: assets/templates/assets/asset_asset_user_list.html:165 +#: assets/templates/assets/system_user_asset.html:356 msgid "Update failed!" msgstr "更新失败" -#: assets/templates/assets/admin_user_detail.html:24 -#: assets/templates/assets/admin_user_list.html:88 -#: assets/templates/assets/asset_detail.html:27 -#: assets/templates/assets/asset_list.html:178 -#: assets/templates/assets/cmd_filter_detail.html:29 -#: assets/templates/assets/cmd_filter_list.html:58 -#: assets/templates/assets/cmd_filter_rule_list.html:86 -#: assets/templates/assets/domain_detail.html:24 -#: assets/templates/assets/domain_detail.html:103 -#: assets/templates/assets/domain_gateway_list.html:97 -#: assets/templates/assets/domain_list.html:54 -#: assets/templates/assets/label_list.html:39 -#: assets/templates/assets/system_user_detail.html:26 -#: assets/templates/assets/system_user_list.html:93 audits/models.py:33 -#: perms/templates/perms/asset_permission_detail.html:30 -#: perms/templates/perms/asset_permission_list.html:181 -#: terminal/templates/terminal/terminal_detail.html:16 -#: terminal/templates/terminal/terminal_list.html:72 -#: users/templates/users/user_detail.html:25 -#: users/templates/users/user_group_detail.html:28 -#: users/templates/users/user_group_list.html:45 -#: users/templates/users/user_list.html:83 -#: users/templates/users/user_list.html:86 -#: users/templates/users/user_profile.html:177 -#: users/templates/users/user_profile.html:187 -#: users/templates/users/user_profile.html:196 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:54 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:25 -#: xpack/plugins/cloud/templates/cloud/account_list.html:38 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:25 -#: xpack/plugins/orgs/templates/orgs/org_list.html:85 -msgid "Update" -msgstr "更新" - -#: assets/templates/assets/admin_user_detail.html:28 -#: assets/templates/assets/admin_user_list.html:89 -#: assets/templates/assets/asset_detail.html:31 -#: assets/templates/assets/asset_list.html:179 -#: assets/templates/assets/cmd_filter_detail.html:33 -#: assets/templates/assets/cmd_filter_list.html:59 -#: assets/templates/assets/cmd_filter_rule_list.html:87 -#: assets/templates/assets/domain_detail.html:28 -#: assets/templates/assets/domain_detail.html:104 -#: assets/templates/assets/domain_gateway_list.html:98 -#: assets/templates/assets/domain_list.html:55 -#: assets/templates/assets/label_list.html:40 -#: assets/templates/assets/system_user_detail.html:30 -#: assets/templates/assets/system_user_list.html:94 audits/models.py:34 -#: ops/templates/ops/task_list.html:64 -#: perms/templates/perms/asset_permission_detail.html:34 -#: perms/templates/perms/asset_permission_list.html:182 -#: settings/templates/settings/terminal_setting.html:90 -#: settings/templates/settings/terminal_setting.html:112 -#: terminal/templates/terminal/terminal_list.html:74 -#: users/templates/users/user_detail.html:30 -#: users/templates/users/user_group_detail.html:32 -#: users/templates/users/user_group_list.html:47 -#: users/templates/users/user_list.html:91 -#: users/templates/users/user_list.html:95 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:56 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:29 -#: xpack/plugins/cloud/templates/cloud/account_list.html:40 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:32 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:54 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:29 -#: xpack/plugins/orgs/templates/orgs/org_list.html:87 -msgid "Delete" -msgstr "删除" - #: assets/templates/assets/admin_user_detail.html:83 msgid "Replace node assets admin user with this" msgstr "替换资产的管理员" @@ -1260,79 +1554,81 @@ msgstr "替换资产的管理员" msgid "Select nodes" msgstr "选择节点" -#: assets/templates/assets/admin_user_detail.html:100 -#: assets/templates/assets/asset_detail.html:211 -#: assets/templates/assets/asset_list.html:637 -#: assets/templates/assets/cmd_filter_detail.html:106 -#: assets/templates/assets/system_user_asset.html:112 -#: assets/templates/assets/system_user_detail.html:182 -#: assets/templates/assets/system_user_list.html:144 -#: settings/templates/settings/terminal_setting.html:165 -#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108 -#: users/templates/users/user_detail.html:388 -#: users/templates/users/user_detail.html:414 -#: users/templates/users/user_detail.html:437 -#: users/templates/users/user_detail.html:482 -#: users/templates/users/user_group_create_update.html:32 -#: users/templates/users/user_group_list.html:90 -#: users/templates/users/user_list.html:215 -#: users/templates/users/user_profile.html:238 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 -#: xpack/plugins/interface/templates/interface/interface.html:103 -#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 -msgid "Confirm" -msgstr "确认" - -#: assets/templates/assets/admin_user_list.html:10 +#: assets/templates/assets/admin_user_list.html:7 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:11 +#: assets/templates/assets/admin_user_list.html:8 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:12 +#: assets/templates/assets/admin_user_list.html:9 msgid "You can set any one for Windows or other hardware." msgstr "Windows或其它硬件可以随意设置一个" -#: assets/templates/assets/admin_user_list.html:18 +#: assets/templates/assets/admin_user_list.html:19 +#: assets/templates/assets/asset_list.html:76 +#: assets/templates/assets/system_user_list.html:23 +#: audits/templates/audits/login_log_list.html:85 +#: users/templates/users/user_group_list.html:10 +#: users/templates/users/user_list.html:10 +msgid "Export" +msgstr "导出" + +#: assets/templates/assets/admin_user_list.html:24 +#: assets/templates/assets/asset_list.html:81 +#: assets/templates/assets/system_user_list.html:28 +#: settings/templates/settings/_ldap_list_users_modal.html:100 +#: users/templates/users/user_group_list.html:15 +#: users/templates/users/user_list.html:15 +#: xpack/plugins/license/templates/license/license_detail.html:110 +msgid "Import" +msgstr "导入" + +#: assets/templates/assets/admin_user_list.html:39 #: assets/views/admin_user.py:48 msgid "Create admin user" msgstr "创建管理用户" -#: assets/templates/assets/admin_user_list.html:31 -#: assets/templates/assets/system_user_list.html:36 +#: assets/templates/assets/admin_user_list.html:52 +#: assets/templates/assets/system_user_list.html:58 #: ops/templates/ops/adhoc_history.html:54 #: ops/templates/ops/task_history.html:60 msgid "Ratio" msgstr "比例" -#: assets/templates/assets/asset_asset_user_list.html:20 -#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:68 +#: assets/templates/assets/admin_user_list.html:159 +#: assets/templates/assets/admin_user_list.html:197 +#: assets/templates/assets/asset_list.html:499 +#: assets/templates/assets/asset_list.html:543 +#: assets/templates/assets/system_user_list.html:226 +#: assets/templates/assets/system_user_list.html:262 +#: users/templates/users/user_group_list.html:163 +#: users/templates/users/user_group_list.html:199 +#: users/templates/users/user_list.html:162 +#: users/templates/users/user_list.html:198 +msgid "Please select file" +msgstr "选择文件" + +#: assets/templates/assets/asset_asset_user_list.html:16 +#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:70 msgid "Asset user list" msgstr "资产用户列表" -#: assets/templates/assets/asset_asset_user_list.html:28 +#: assets/templates/assets/asset_asset_user_list.html:24 msgid "Asset users of" msgstr "资产用户" -#: assets/templates/assets/asset_asset_user_list.html:49 +#: assets/templates/assets/asset_asset_user_list.html:45 msgid "Password version" msgstr "密码版本" -#: assets/templates/assets/asset_asset_user_list.html:51 -#: assets/templates/assets/cmd_filter_detail.html:73 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 -msgid "Date updated" -msgstr "更新日期" - -#: assets/templates/assets/asset_asset_user_list.html:64 +#: assets/templates/assets/asset_asset_user_list.html:60 #: assets/templates/assets/asset_detail.html:148 #: terminal/templates/terminal/session_detail.html:81 #: users/templates/users/user_detail.html:138 @@ -1367,10 +1663,12 @@ msgid "Date joined" msgstr "创建日期" #: assets/templates/assets/asset_detail.html:154 -#: assets/templates/assets/user_asset_list.html:46 perms/models.py:54 -#: perms/models.py:108 +#: assets/templates/assets/user_asset_list.html:46 +#: perms/models/asset_permission.py:60 perms/models/base.py:38 #: perms/templates/perms/asset_permission_create_update.html:55 #: perms/templates/perms/asset_permission_detail.html:120 +#: perms/templates/perms/remote_app_permission_create_update.html:54 +#: perms/templates/perms/remote_app_permission_detail.html:112 #: terminal/templates/terminal/terminal_list.html:34 #: users/templates/users/_select_user_modal.html:18 #: users/templates/users/user_detail.html:144 @@ -1397,150 +1695,137 @@ msgstr "" "左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的," "右侧是属于该节点下的资产" -#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:105 +#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:107 msgid "Create asset" msgstr "创建资产" -#: assets/templates/assets/asset_list.html:73 -#: settings/templates/settings/_ldap_list_users_modal.html:97 -#: users/templates/users/user_list.html:7 -#: xpack/plugins/license/templates/license/license_detail.html:110 -msgid "Import" -msgstr "导入" - -#: assets/templates/assets/asset_list.html:76 -#: audits/templates/audits/login_log_list.html:85 -#: users/templates/users/user_list.html:10 -msgid "Export" -msgstr "导出" - -#: assets/templates/assets/asset_list.html:94 +#: assets/templates/assets/asset_list.html:106 msgid "Hardware" msgstr "硬件" -#: assets/templates/assets/asset_list.html:105 -#: users/templates/users/user_list.html:38 +#: assets/templates/assets/asset_list.html:117 +#: users/templates/users/user_list.html:50 msgid "Delete selected" msgstr "批量删除" -#: assets/templates/assets/asset_list.html:106 -#: users/templates/users/user_list.html:39 +#: assets/templates/assets/asset_list.html:118 +#: users/templates/users/user_list.html:51 msgid "Update selected" msgstr "批量更新" -#: assets/templates/assets/asset_list.html:107 +#: assets/templates/assets/asset_list.html:119 msgid "Remove from this node" msgstr "从节点移除" -#: assets/templates/assets/asset_list.html:108 -#: users/templates/users/user_list.html:40 +#: assets/templates/assets/asset_list.html:120 +#: users/templates/users/user_list.html:52 msgid "Deactive selected" msgstr "禁用所选" -#: assets/templates/assets/asset_list.html:109 -#: users/templates/users/user_list.html:41 +#: assets/templates/assets/asset_list.html:121 +#: users/templates/users/user_list.html:53 msgid "Active selected" msgstr "激活所选" -#: assets/templates/assets/asset_list.html:126 +#: assets/templates/assets/asset_list.html:138 msgid "Add node" msgstr "新建节点" -#: assets/templates/assets/asset_list.html:127 +#: assets/templates/assets/asset_list.html:139 msgid "Rename node" msgstr "重命名节点" -#: assets/templates/assets/asset_list.html:128 +#: assets/templates/assets/asset_list.html:140 msgid "Delete node" msgstr "删除节点" -#: assets/templates/assets/asset_list.html:130 +#: assets/templates/assets/asset_list.html:142 msgid "Add assets to node" msgstr "添加资产到节点" -#: assets/templates/assets/asset_list.html:131 +#: assets/templates/assets/asset_list.html:143 msgid "Move assets to node" msgstr "移动资产到节点" -#: assets/templates/assets/asset_list.html:133 +#: assets/templates/assets/asset_list.html:145 msgid "Refresh node hardware info" msgstr "更新节点资产硬件信息" -#: assets/templates/assets/asset_list.html:134 +#: assets/templates/assets/asset_list.html:146 msgid "Test node connective" msgstr "测试节点资产可连接性" -#: assets/templates/assets/asset_list.html:136 +#: assets/templates/assets/asset_list.html:148 msgid "Refresh all node assets amount" msgstr "刷新所有节点资产数量" -#: assets/templates/assets/asset_list.html:138 +#: assets/templates/assets/asset_list.html:150 msgid "Display only current node assets" msgstr "仅显示当前节点资产" -#: assets/templates/assets/asset_list.html:139 +#: assets/templates/assets/asset_list.html:151 msgid "Displays all child node assets" msgstr "显示所有子节点资产" -#: assets/templates/assets/asset_list.html:217 +#: assets/templates/assets/asset_list.html:229 msgid "Create node failed" msgstr "创建节点失败" -#: assets/templates/assets/asset_list.html:229 +#: assets/templates/assets/asset_list.html:241 msgid "Have child node, cancel" msgstr "存在子节点,不能删除" -#: assets/templates/assets/asset_list.html:231 +#: assets/templates/assets/asset_list.html:243 msgid "Have assets, cancel" msgstr "存在资产,不能删除" -#: assets/templates/assets/asset_list.html:302 +#: assets/templates/assets/asset_list.html:314 msgid "Rename success" msgstr "重命名成功" -#: assets/templates/assets/asset_list.html:303 +#: assets/templates/assets/asset_list.html:315 msgid "Rename failed, do not change the root node name" msgstr "重命名失败,不能更改root节点的名称" -#: assets/templates/assets/asset_list.html:631 -#: assets/templates/assets/system_user_list.html:138 +#: assets/templates/assets/asset_list.html:694 +#: assets/templates/assets/system_user_list.html:162 #: users/templates/users/user_detail.html:382 #: users/templates/users/user_detail.html:408 #: users/templates/users/user_detail.html:476 -#: users/templates/users/user_group_list.html:84 -#: users/templates/users/user_list.html:209 +#: users/templates/users/user_group_list.html:108 +#: users/templates/users/user_list.html:263 #: xpack/plugins/interface/templates/interface/interface.html:97 msgid "Are you sure?" msgstr "你确认吗?" -#: assets/templates/assets/asset_list.html:632 +#: assets/templates/assets/asset_list.html:695 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" -#: assets/templates/assets/asset_list.html:635 -#: assets/templates/assets/system_user_list.html:142 -#: settings/templates/settings/terminal_setting.html:163 +#: assets/templates/assets/asset_list.html:698 +#: assets/templates/assets/system_user_list.html:166 +#: settings/templates/settings/terminal_setting.html:166 #: users/templates/users/user_detail.html:386 #: users/templates/users/user_detail.html:412 #: users/templates/users/user_detail.html:480 #: users/templates/users/user_group_create_update.html:31 -#: users/templates/users/user_group_list.html:88 -#: users/templates/users/user_list.html:213 +#: users/templates/users/user_group_list.html:112 +#: users/templates/users/user_list.html:267 #: xpack/plugins/interface/templates/interface/interface.html:101 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:32 msgid "Cancel" msgstr "取消" -#: assets/templates/assets/asset_list.html:641 +#: assets/templates/assets/asset_list.html:711 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:642 -#: assets/templates/assets/asset_list.html:647 +#: assets/templates/assets/asset_list.html:712 +#: assets/templates/assets/asset_list.html:716 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:646 +#: assets/templates/assets/asset_list.html:715 msgid "Asset Deleting failed." msgstr "删除失败" @@ -1623,8 +1908,8 @@ msgstr "创建网关" #: assets/templates/assets/domain_gateway_list.html:99 #: assets/templates/assets/domain_gateway_list.html:101 -#: settings/templates/settings/email_setting.html:61 -#: settings/templates/settings/ldap_setting.html:62 +#: settings/templates/settings/email_setting.html:64 +#: settings/templates/settings/ldap_setting.html:65 msgid "Test connection" msgstr "测试连接" @@ -1672,7 +1957,7 @@ msgid "Push system user now" msgstr "立刻推送系统" #: assets/templates/assets/system_user_asset.html:84 -#: assets/templates/assets/system_user_asset.html:163 +#: assets/templates/assets/system_user_asset.html:164 #: assets/templates/assets/system_user_detail.html:142 msgid "Push" msgstr "推送" @@ -1723,61 +2008,53 @@ msgstr "" "资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。目前还不" "支持Windows的自动推送" -#: assets/templates/assets/system_user_list.html:21 +#: assets/templates/assets/system_user_list.html:43 #: assets/views/system_user.py:45 msgid "Create system user" msgstr "创建系统用户" -#: assets/templates/assets/system_user_list.html:139 +#: assets/templates/assets/system_user_list.html:163 msgid "This will delete the selected System Users !!!" msgstr "删除选择系统用户" -#: assets/templates/assets/system_user_list.html:148 +#: assets/templates/assets/system_user_list.html:172 msgid "System Users Deleted." msgstr "已被删除" -#: assets/templates/assets/system_user_list.html:149 -#: assets/templates/assets/system_user_list.html:154 +#: assets/templates/assets/system_user_list.html:173 +#: assets/templates/assets/system_user_list.html:178 msgid "System Users Delete" msgstr "删除系统用户" -#: assets/templates/assets/system_user_list.html:153 +#: assets/templates/assets/system_user_list.html:177 msgid "System Users Deleting failed." msgstr "系统用户删除失败" -#: assets/templates/assets/user_asset_list.html:100 perms/const.py:19 -msgid "Connect" -msgstr "连接" - #: assets/views/admin_user.py:30 msgid "Admin user list" msgstr "管理用户列表" -#: assets/views/admin_user.py:64 -msgid "Update admin user" -msgstr "更新管理用户" - #: assets/views/admin_user.py:79 assets/views/admin_user.py:103 msgid "Admin user detail" msgstr "管理用户详情" -#: assets/views/asset.py:79 templates/_nav_user.html:4 +#: assets/views/asset.py:81 templates/_nav_user.html:4 msgid "My assets" msgstr "我的资产" -#: assets/views/asset.py:119 +#: assets/views/asset.py:121 msgid "Bulk update asset success" msgstr "批量更新资产成功" -#: assets/views/asset.py:146 +#: assets/views/asset.py:148 msgid "Bulk update asset" msgstr "批量更新资产" -#: assets/views/asset.py:163 +#: assets/views/asset.py:165 msgid "Update asset" msgstr "更新资产" -#: assets/views/asset.py:304 +#: assets/views/asset.py:306 msgid "already exists" msgstr "已经存在" @@ -1837,10 +2114,6 @@ msgstr "更新标签" msgid "System user list" msgstr "系统用户列表" -#: assets/views/system_user.py:61 -msgid "Update system user" -msgstr "更新系统用户" - #: assets/views/system_user.py:75 msgid "System user detail" msgstr "系统用户详情" @@ -1948,12 +2221,6 @@ msgstr "登录城市" msgid "User agent" msgstr "Agent" -#: audits/models.py:99 audits/templates/audits/login_log_list.html:56 -#: users/forms.py:142 users/models/user.py:83 -#: users/templates/users/first_login.html:45 -msgid "MFA" -msgstr "MFA" - #: audits/models.py:100 audits/templates/audits/login_log_list.html:57 #: xpack/plugins/change_auth_plan/models.py:413 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 @@ -1965,7 +2232,7 @@ msgstr "原因" #: audits/models.py:101 audits/templates/audits/login_log_list.html:58 #: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:188 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 msgid "Status" msgstr "状态" @@ -1977,9 +2244,10 @@ msgstr "登录日期" #: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/command_execution_list.html:66 -#: ops/templates/ops/task_history.html:58 perms/models.py:55 -#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:165 -#: terminal/templates/terminal/session_list.html:78 +#: ops/templates/ops/task_history.html:58 perms/models/base.py:39 +#: perms/templates/perms/asset_permission_detail.html:86 +#: perms/templates/perms/remote_app_permission_detail.html:78 +#: terminal/models.py:165 terminal/templates/terminal/session_list.html:78 #: xpack/plugins/change_auth_plan/models.py:246 #: xpack/plugins/change_auth_plan/models.py:416 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59 @@ -1989,6 +2257,7 @@ msgstr "开始日期" #: audits/templates/audits/login_log_list.html:28 #: perms/templates/perms/asset_permission_user.html:88 +#: perms/templates/perms/remote_app_permission_user.html:87 msgid "Select user" msgstr "选择用户" @@ -2003,7 +2272,7 @@ msgstr "选择用户" #: terminal/templates/terminal/command_list.html:60 #: terminal/templates/terminal/session_list.html:61 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:50 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48 msgid "Search" msgstr "搜索" @@ -2013,7 +2282,7 @@ msgstr "搜索" #: ops/templates/ops/task_detail.html:56 #: terminal/templates/terminal/session_list.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:64 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:60 msgid "ID" msgstr "ID" @@ -2039,23 +2308,23 @@ msgid "Datetime" msgstr "日期" #: audits/views.py:85 audits/views.py:129 audits/views.py:165 -#: audits/views.py:209 audits/views.py:241 templates/_nav.html:72 +#: audits/views.py:209 audits/views.py:241 templates/_nav.html:83 msgid "Audits" msgstr "日志审计" -#: audits/views.py:86 templates/_nav.html:76 +#: audits/views.py:86 templates/_nav.html:87 msgid "FTP log" msgstr "FTP日志" -#: audits/views.py:130 templates/_nav.html:77 +#: audits/views.py:130 templates/_nav.html:88 msgid "Operate log" msgstr "操作日志" -#: audits/views.py:166 templates/_nav.html:78 +#: audits/views.py:166 templates/_nav.html:89 msgid "Password change log" msgstr "改密日志" -#: audits/views.py:210 templates/_nav.html:75 +#: audits/views.py:210 templates/_nav.html:86 msgid "Login log" msgstr "登录日志" @@ -2063,25 +2332,25 @@ msgstr "登录日志" msgid "Command execution log" msgstr "命令执行" -#: authentication/api/auth.py:46 +#: authentication/api/auth.py:49 #: authentication/templates/authentication/login.html:52 #: authentication/templates/authentication/new_login.html:77 msgid "Log in frequently and try again later" msgstr "登录频繁, 稍后重试" -#: authentication/api/auth.py:64 +#: authentication/api/auth.py:67 msgid "The user {} password has expired, please update." msgstr "用户 {} 密码已经过期,请更新。" -#: authentication/api/auth.py:83 +#: authentication/api/auth.py:86 msgid "Please carry seed value and conduct MFA secondary certification" msgstr "请携带seed值, 进行MFA二次认证" -#: authentication/api/auth.py:163 +#: authentication/api/auth.py:166 msgid "Please verify the user name and password first" msgstr "请先进行用户名和密码验证" -#: authentication/api/auth.py:168 +#: authentication/api/auth.py:171 msgid "MFA certification failed" msgstr "MFA认证失败" @@ -2274,8 +2543,8 @@ msgstr "欢迎回来,请输入用户名和密码登录" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: authentication/views/login.py:172 users/views/user.py:541 -#: users/views/user.py:566 +#: authentication/views/login.py:172 users/views/user.py:545 +#: users/views/user.py:570 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" @@ -2333,11 +2602,11 @@ msgstr "" msgid "Encrypt field using Secret Key" msgstr "" -#: common/mixins.py:32 +#: common/mixins.py:35 msgid "is discard" msgstr "" -#: common/mixins.py:33 +#: common/mixins.py:36 msgid "discard time" msgstr "" @@ -2415,7 +2684,7 @@ msgid "Become" msgstr "Become" #: ops/models/adhoc.py:166 users/templates/users/user_group_detail.html:59 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:64 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:62 #: xpack/plugins/orgs/templates/orgs/org_detail.html:56 msgid "Create by" msgstr "创建者" @@ -2661,8 +2930,8 @@ msgstr "版本" #: ops/templates/ops/task_list.html:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:137 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:52 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:53 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:53 msgid "Run" msgstr "执行" @@ -2680,7 +2949,7 @@ msgstr "更新任务内容: {}" msgid "Ops" msgstr "作业中心" -#: ops/views/adhoc.py:45 templates/_nav.html:66 +#: ops/views/adhoc.py:45 templates/_nav.html:77 msgid "Task list" msgstr "任务列表" @@ -2692,11 +2961,11 @@ msgstr "执行历史" msgid "Command execution list" msgstr "命令执行列表" -#: ops/views/command.py:69 templates/_nav_user.html:9 +#: ops/views/command.py:69 templates/_nav_user.html:21 msgid "Command execution" msgstr "命令执行" -#: orgs/mixins.py:77 orgs/models.py:24 +#: orgs/mixins.py:81 orgs/models.py:24 msgid "Organization" msgstr "组织管理" @@ -2712,46 +2981,57 @@ msgstr "上传文件" msgid "Download file" msgstr "下载文件" -#: perms/forms.py:39 perms/models.py:49 perms/models.py:106 +#: perms/forms/asset_permission.py:43 perms/forms/remote_app_permission.py:34 +#: perms/models/asset_permission.py:58 perms/models/base.py:37 #: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:75 -#: perms/templates/perms/asset_permission_list.html:122 templates/_nav.html:14 -#: users/forms.py:253 users/models/group.py:26 users/models/user.py:67 -#: users/templates/users/_select_user_modal.html:16 +#: perms/templates/perms/asset_permission_list.html:122 +#: perms/templates/perms/remote_app_permission_list.html:16 +#: templates/_nav.html:14 users/forms.py:263 users/models/group.py:26 +#: users/models/user.py:67 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:213 -#: users/templates/users/user_list.html:26 +#: users/templates/users/user_list.html:38 #: xpack/plugins/orgs/templates/orgs/org_list.html:15 msgid "User group" msgstr "用户组" -#: perms/forms.py:58 +#: perms/forms/asset_permission.py:62 msgid "" "Tips: The RDP protocol does not support separate controls for uploading or " "downloading files" msgstr "提示:RDP 协议不支持单独控制上传或下载文件" -#: perms/forms.py:68 +#: perms/forms/asset_permission.py:72 perms/forms/remote_app_permission.py:47 msgid "User or group at least one required" msgstr "用户和用户组至少选一个" -#: perms/forms.py:77 +#: perms/forms/asset_permission.py:81 msgid "Asset or group at least one required" msgstr "资产和节点至少选一个" -#: perms/models.py:56 perms/models.py:109 +#: perms/models/asset_permission.py:44 perms/models/asset_permission.py:70 +#: templates/_nav.html:42 +msgid "Asset permission" +msgstr "资产授权" + +#: perms/models/asset_permission.py:61 perms/models/base.py:40 #: perms/templates/perms/asset_permission_detail.html:90 +#: perms/templates/perms/remote_app_permission_detail.html:82 #: users/models/user.py:99 users/templates/users/user_detail.html:107 #: users/templates/users/user_profile.html:116 msgid "Date expired" msgstr "失效日期" -#: perms/models.py:65 perms/models.py:118 templates/_nav.html:34 -msgid "Asset permission" -msgstr "资产授权" +#: perms/models/remote_app_permission.py:19 +msgid "RemoteApp permission" +msgstr "远程应用授权" #: perms/templates/perms/asset_permission_asset.html:22 #: perms/templates/perms/asset_permission_detail.html:22 #: perms/templates/perms/asset_permission_user.html:22 +#: perms/templates/perms/remote_app_permission_detail.html:22 +#: perms/templates/perms/remote_app_permission_remote_app.html:21 +#: perms/templates/perms/remote_app_permission_user.html:21 msgid "Users and user groups" msgstr "用户或用户组" @@ -2772,8 +3052,11 @@ msgstr "添加资产" #: perms/templates/perms/asset_permission_detail.html:157 #: perms/templates/perms/asset_permission_user.html:97 #: perms/templates/perms/asset_permission_user.html:125 -#: settings/templates/settings/terminal_setting.html:95 -#: settings/templates/settings/terminal_setting.html:117 +#: perms/templates/perms/remote_app_permission_remote_app.html:96 +#: perms/templates/perms/remote_app_permission_user.html:96 +#: perms/templates/perms/remote_app_permission_user.html:124 +#: settings/templates/settings/terminal_setting.html:98 +#: settings/templates/settings/terminal_setting.html:120 #: users/templates/users/user_group_detail.html:95 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:93 #: xpack/plugins/orgs/templates/orgs/org_detail.html:93 @@ -2792,17 +3075,20 @@ msgid "Join" msgstr "加入" #: perms/templates/perms/asset_permission_create_update.html:61 +#: perms/templates/perms/remote_app_permission_create_update.html:60 msgid "Validity period" msgstr "有效期" #: perms/templates/perms/asset_permission_detail.html:66 +#: perms/templates/perms/remote_app_permission_detail.html:66 #: xpack/plugins/license/templates/license/license_detail.html:76 msgid "User count" msgstr "用户数量" #: perms/templates/perms/asset_permission_detail.html:70 +#: perms/templates/perms/remote_app_permission_detail.html:70 msgid "User group count" -msgstr "用户组列表" +msgstr "用户组数量" #: perms/templates/perms/asset_permission_detail.html:74 #: xpack/plugins/license/templates/license/license_detail.html:72 @@ -2822,18 +3108,21 @@ msgid "Select system users" msgstr "选择系统用户" #: perms/templates/perms/asset_permission_list.html:46 +#: perms/templates/perms/remote_app_permission_list.html:6 msgid "Create permission" msgstr "创建授权规则" #: perms/templates/perms/asset_permission_list.html:59 #: perms/templates/perms/asset_permission_list.html:73 -#: users/templates/users/user_list.html:28 xpack/plugins/cloud/models.py:53 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:60 +#: perms/templates/perms/remote_app_permission_list.html:18 +#: users/templates/users/user_list.html:40 xpack/plugins/cloud/models.py:53 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:58 #: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" msgstr "有效" #: perms/templates/perms/asset_permission_user.html:35 +#: perms/templates/perms/remote_app_permission_user.html:34 msgid "User list of " msgstr "用户列表" @@ -2846,35 +3135,95 @@ msgid "Add user group to asset permission" msgstr "添加用户组" #: perms/templates/perms/asset_permission_user.html:116 +#: perms/templates/perms/remote_app_permission_user.html:115 msgid "Select user groups" msgstr "选择用户组" -#: perms/views.py:24 perms/views.py:56 perms/views.py:71 perms/views.py:86 -#: perms/views.py:121 perms/views.py:153 templates/_nav.html:31 +#: perms/templates/perms/remote_app_permission_detail.html:74 +msgid "RemoteApp count" +msgstr "远程应用数量" + +#: perms/templates/perms/remote_app_permission_remote_app.html:34 +msgid "RemoteApp list of " +msgstr "远程应用列表" + +#: perms/templates/perms/remote_app_permission_remote_app.html:79 +msgid "Add RemoteApp to this permission" +msgstr "添加远程应用" + +#: perms/templates/perms/remote_app_permission_remote_app.html:87 +msgid "Select RemoteApp" +msgstr "选择远程应用" + +#: perms/templates/perms/remote_app_permission_user.html:79 +msgid "Add user to this permission" +msgstr "添加用户" + +#: perms/templates/perms/remote_app_permission_user.html:107 +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:80 perms/views/asset_permission.py:95 +#: perms/views/asset_permission.py:130 perms/views/asset_permission.py:162 +#: perms/views/remote_app_permission.py:33 +#: perms/views/remote_app_permission.py:48 +#: perms/views/remote_app_permission.py:63 +#: perms/views/remote_app_permission.py:76 +#: perms/views/remote_app_permission.py:102 +#: perms/views/remote_app_permission.py:138 templates/_nav.html:39 #: xpack/plugins/orgs/templates/orgs/org_list.html:21 msgid "Perms" msgstr "权限管理" -#: perms/views.py:25 +#: perms/views/asset_permission.py:34 msgid "Asset permission list" msgstr "资产授权列表" -#: perms/views.py:57 +#: perms/views/asset_permission.py:66 msgid "Create asset permission" msgstr "创建权限规则" -#: perms/views.py:72 perms/views.py:87 +#: perms/views/asset_permission.py:81 msgid "Update asset permission" msgstr "更新资产授权" -#: perms/views.py:122 +#: perms/views/asset_permission.py:96 +msgid "Asset permission detail" +msgstr "资产授权详情" + +#: perms/views/asset_permission.py:131 msgid "Asset permission user list" msgstr "资产授权用户列表" -#: perms/views.py:154 +#: perms/views/asset_permission.py:163 msgid "Asset permission asset list" msgstr "资产授权资产列表" +#: perms/views/remote_app_permission.py:34 +msgid "RemoteApp permission list" +msgstr "远程应用授权列表" + +#: perms/views/remote_app_permission.py:49 +msgid "Create RemoteApp permission" +msgstr "创建远程应用授权规则" + +#: perms/views/remote_app_permission.py:64 +msgid "Update RemoteApp permission" +msgstr "更新远程应用授权规则" + +#: perms/views/remote_app_permission.py:77 +msgid "RemoteApp permission detail" +msgstr "远程应用授权详情" + +#: perms/views/remote_app_permission.py:103 +msgid "RemoteApp permission user list" +msgstr "远程应用授权用户列表" + +#: perms/views/remote_app_permission.py:139 +msgid "RemoteApp permission RemoteApp list" +msgstr "远程应用授权远程应用列表" + #: settings/api.py:26 msgid "Test mail sent to {}, please check" msgstr "邮件已经发送{}, 请检查" @@ -2902,7 +3251,7 @@ msgid "Create succeed" msgstr "创建成功" #: settings/api.py:166 settings/api.py:204 -#: settings/templates/settings/terminal_setting.html:151 +#: settings/templates/settings/terminal_setting.html:154 msgid "Delete succeed" msgstr "删除成功" @@ -3058,35 +3407,35 @@ msgid "" "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: settings/forms.py:185 +#: settings/forms.py:186 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: settings/forms.py:189 +#: settings/forms.py:190 msgid "No logon interval" msgstr "禁止登录时间间隔" -#: settings/forms.py:191 +#: settings/forms.py:192 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.py:197 +#: settings/forms.py:199 msgid "Connection max idle time" msgstr "SSH最大空闲时间" -#: settings/forms.py:199 +#: settings/forms.py:201 msgid "" "If idle time more than it, disconnect connection(only ssh now) Unit: minute" msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)" -#: settings/forms.py:205 +#: settings/forms.py:207 msgid "Password expiration time" msgstr "密码过期时间" -#: settings/forms.py:208 +#: settings/forms.py:209 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 " @@ -3096,50 +3445,84 @@ msgstr "" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" -#: settings/forms.py:217 +#: settings/forms.py:218 msgid "Password minimum length" msgstr "密码最小长度 " -#: settings/forms.py:221 +#: settings/forms.py:222 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: settings/forms.py:223 +#: settings/forms.py:224 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: settings/forms.py:228 +#: settings/forms.py:229 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: settings/forms.py:229 +#: settings/forms.py:230 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: settings/forms.py:234 +#: settings/forms.py:235 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: settings/forms.py:235 +#: settings/forms.py:236 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: settings/forms.py:240 +#: settings/forms.py:241 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: settings/forms.py:241 +#: settings/forms.py:242 msgid "" "After opening, the user password changes and resets must contain special " "characters" msgstr "开启后,用户密码修改、重置必须包含特殊字符" +#: settings/forms.py:249 +msgid "Create user email subject" +msgstr "创建用户邮件的主题" + +#: settings/forms.py:250 +msgid "" +"Tips: When creating a user, send the subject of the email (eg:Create account " +"successfully)" +msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" + +#: settings/forms.py:254 +msgid "Create user honorific" +msgstr "创建用户邮件的敬语" + +#: settings/forms.py:255 +msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" +msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" + +#: settings/forms.py:260 +msgid "Create user email content" +msgstr "创建用户邮件的内容" + +#: settings/forms.py:261 +msgid "Tips:When creating a user, send the content of the email" +msgstr "提示: 创建用户时,发送设置密码邮件的内容" + +#: settings/forms.py:264 +msgid "Signature" +msgstr "署名" + +#: settings/forms.py:265 +msgid "Tips: Email signature (eg:jumpserver)" +msgstr "提示: 邮件的署名 (例如: jumpserver)" + #: settings/models.py:128 users/templates/users/reset_password.html:68 #: users/templates/users/user_profile.html:20 msgid "Setting" @@ -3164,15 +3547,17 @@ msgid "Existing" msgstr "已存在" #: settings/templates/settings/basic_setting.html:15 +#: settings/templates/settings/email_content_setting.html:15 #: settings/templates/settings/email_setting.html:15 #: settings/templates/settings/ldap_setting.html:15 #: settings/templates/settings/security_setting.html:15 #: settings/templates/settings/terminal_setting.html:16 -#: settings/templates/settings/terminal_setting.html:46 settings/views.py:19 +#: settings/templates/settings/terminal_setting.html:49 settings/views.py:19 msgid "Basic setting" msgstr "基本设置" #: settings/templates/settings/basic_setting.html:18 +#: settings/templates/settings/email_content_setting.html:18 #: settings/templates/settings/email_setting.html:18 #: settings/templates/settings/ldap_setting.html:18 #: settings/templates/settings/security_setting.html:18 @@ -3181,27 +3566,39 @@ msgid "Email setting" msgstr "邮件设置" #: settings/templates/settings/basic_setting.html:21 +#: settings/templates/settings/email_content_setting.html:21 #: settings/templates/settings/email_setting.html:21 #: settings/templates/settings/ldap_setting.html:21 #: settings/templates/settings/security_setting.html:21 -#: settings/templates/settings/terminal_setting.html:24 settings/views.py:71 -msgid "LDAP setting" -msgstr "LDAP设置" +#: settings/templates/settings/terminal_setting.html:23 settings/views.py:178 +msgid "Email content setting" +msgstr "邮件内容设置" #: settings/templates/settings/basic_setting.html:24 +#: settings/templates/settings/email_content_setting.html:24 #: settings/templates/settings/email_setting.html:24 #: settings/templates/settings/ldap_setting.html:24 #: settings/templates/settings/security_setting.html:24 -#: settings/templates/settings/terminal_setting.html:28 settings/views.py:100 -msgid "Terminal setting" -msgstr "终端设置" +#: settings/templates/settings/terminal_setting.html:27 settings/views.py:71 +msgid "LDAP setting" +msgstr "LDAP设置" #: settings/templates/settings/basic_setting.html:27 +#: settings/templates/settings/email_content_setting.html:27 #: settings/templates/settings/email_setting.html:27 #: settings/templates/settings/ldap_setting.html:27 #: settings/templates/settings/security_setting.html:27 -#: settings/templates/settings/security_setting.html:42 -#: settings/templates/settings/terminal_setting.html:31 settings/views.py:152 +#: settings/templates/settings/terminal_setting.html:31 settings/views.py:100 +msgid "Terminal setting" +msgstr "终端设置" + +#: settings/templates/settings/basic_setting.html:30 +#: settings/templates/settings/email_content_setting.html:30 +#: settings/templates/settings/email_setting.html:30 +#: settings/templates/settings/ldap_setting.html:30 +#: settings/templates/settings/security_setting.html:30 +#: settings/templates/settings/security_setting.html:45 +#: settings/templates/settings/terminal_setting.html:34 settings/views.py:152 msgid "Security setting" msgstr "安全设置" @@ -3217,11 +3614,15 @@ msgstr "索引" msgid "Doc type" msgstr "文档类型" -#: settings/templates/settings/ldap_setting.html:65 +#: settings/templates/settings/email_content_setting.html:45 +msgid "Create User setting" +msgstr "创建用户设置" + +#: settings/templates/settings/ldap_setting.html:68 msgid "Bulk import" msgstr "一键导入" -#: settings/templates/settings/ldap_setting.html:116 +#: settings/templates/settings/ldap_setting.html:119 msgid "" "User is not currently selected, please check the user you want to import" msgstr "当前无勾选用户,请勾选你想要导入的用户" @@ -3283,8 +3684,8 @@ msgstr "端点后缀" #: settings/templates/settings/replay_storage_create.html:136 #: xpack/plugins/cloud/models.py:186 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:81 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 msgid "Region" msgstr "地域" @@ -3308,48 +3709,44 @@ msgstr "提交中" msgid "Endpoint need contain protocol, ex: http" msgstr "端点需要包含协议,如 http" -#: settings/templates/settings/security_setting.html:46 +#: settings/templates/settings/security_setting.html:49 msgid "Password check rule" msgstr "密码校验规则" -#: settings/templates/settings/terminal_setting.html:76 terminal/forms.py:27 +#: settings/templates/settings/terminal_setting.html:79 terminal/forms.py:27 #: terminal/models.py:26 msgid "Command storage" msgstr "命令存储" -#: settings/templates/settings/terminal_setting.html:98 terminal/forms.py:32 +#: settings/templates/settings/terminal_setting.html:101 terminal/forms.py:32 #: terminal/models.py:27 msgid "Replay storage" msgstr "录像存储" -#: settings/templates/settings/terminal_setting.html:154 +#: settings/templates/settings/terminal_setting.html:157 msgid "Delete failed" msgstr "删除失败" -#: settings/templates/settings/terminal_setting.html:159 +#: settings/templates/settings/terminal_setting.html:162 msgid "Are you sure about deleting it?" msgstr "您确定删除吗?" -#: settings/utils.py:69 -msgid "User does not exist" -msgstr "用户不存在" - -#: settings/utils.py:72 -msgid "The user source is not LDAP" -msgstr "用户来源不是LDAP" - -#: settings/utils.py:146 +#: settings/utils.py:85 msgid "Search no entry matched in ou {}" msgstr "在ou:{}中没有匹配条目" +#: settings/utils.py:113 +msgid "The user source is not LDAP" +msgstr "用户来源不是LDAP" + #: settings/views.py:18 settings/views.py:44 settings/views.py:70 #: settings/views.py:99 settings/views.py:126 settings/views.py:138 -#: settings/views.py:151 templates/_nav.html:107 +#: settings/views.py:151 settings/views.py:177 templates/_nav.html:118 msgid "Settings" msgstr "系统设置" #: settings/views.py:29 settings/views.py:55 settings/views.py:81 -#: settings/views.py:112 settings/views.py:162 +#: settings/views.py:112 settings/views.py:162 settings/views.py:188 msgid "Update setting successfully" msgstr "更新设置成功" @@ -3373,14 +3770,14 @@ msgstr "文档" msgid "Commercial support" msgstr "商业支持" -#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:121 +#: templates/_header_bar.html:89 templates/_nav_user.html:26 users/forms.py:131 #: users/templates/users/_user.html:43 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:40 #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:57 -#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:377 +#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:381 msgid "Profile" msgstr "个人信息" @@ -3400,6 +3797,18 @@ msgstr "注销登录" msgid "Dashboard" msgstr "仪表盘" +#: templates/_import_modal.html:12 +msgid "Download the imported template or use the exported CSV file format" +msgstr "下载导入的模板或使用导出的csv格式" + +#: templates/_import_modal.html:13 +msgid "Download the import template" +msgstr "下载导入模版" + +#: templates/_import_modal.html:17 templates/_update_modal.html:17 +msgid "Select the CSV file to import" +msgstr "请选择csv文件导入" + #: templates/_message.html:7 #, python-format msgid "" @@ -3462,13 +3871,13 @@ msgstr "" #: templates/_nav.html:10 users/views/group.py:27 users/views/group.py:43 #: users/views/group.py:59 users/views/group.py:75 users/views/group.py:91 -#: users/views/login.py:154 users/views/user.py:68 users/views/user.py:83 -#: users/views/user.py:122 users/views/user.py:203 users/views/user.py:364 -#: users/views/user.py:414 users/views/user.py:454 +#: users/views/login.py:154 users/views/user.py:70 users/views/user.py:86 +#: users/views/user.py:129 users/views/user.py:207 users/views/user.py:368 +#: users/views/user.py:418 users/views/user.py:458 msgid "Users" msgstr "用户管理" -#: templates/_nav.html:13 users/views/user.py:69 +#: templates/_nav.html:13 users/views/user.py:71 msgid "User list" msgstr "用户列表" @@ -3476,62 +3885,78 @@ msgstr "用户列表" msgid "Command filters" msgstr "命令过滤" -#: templates/_nav.html:40 +#: templates/_nav.html:32 +msgid "Applications" +msgstr "应用管理" + +#: templates/_nav.html:51 msgid "Sessions" msgstr "会话管理" -#: templates/_nav.html:43 +#: templates/_nav.html:54 msgid "Session online" msgstr "在线会话" -#: templates/_nav.html:44 +#: templates/_nav.html:55 msgid "Session offline" msgstr "历史会话" -#: templates/_nav.html:45 +#: templates/_nav.html:56 msgid "Commands" msgstr "命令记录" -#: templates/_nav.html:48 templates/_nav_user.html:19 +#: templates/_nav.html:59 templates/_nav_user.html:31 msgid "Web terminal" msgstr "Web终端" -#: templates/_nav.html:53 templates/_nav_user.html:24 +#: templates/_nav.html:64 templates/_nav_user.html:36 msgid "File manager" msgstr "文件管理" -#: templates/_nav.html:57 terminal/views/command.py:50 -#: terminal/views/session.py:75 terminal/views/session.py:93 +#: templates/_nav.html:68 terminal/views/command.py:50 +#: terminal/views/session.py:74 terminal/views/session.py:92 #: terminal/views/session.py:115 terminal/views/terminal.py:31 #: terminal/views/terminal.py:46 terminal/views/terminal.py:58 msgid "Terminal" msgstr "终端管理" -#: templates/_nav.html:63 +#: templates/_nav.html:74 msgid "Job Center" msgstr "作业中心" -#: templates/_nav.html:67 templates/_nav.html:79 +#: templates/_nav.html:78 templates/_nav.html:90 msgid "Batch command" msgstr "批量命令" -#: templates/_nav.html:85 +#: templates/_nav.html:96 msgid "XPack" msgstr "" -#: templates/_nav.html:93 xpack/plugins/cloud/views.py:26 +#: templates/_nav.html:104 xpack/plugins/cloud/views.py:26 msgid "Account list" msgstr "账户列表" -#: templates/_nav.html:94 +#: templates/_nav.html:105 msgid "Sync instance" msgstr "同步实例" +#: templates/_nav_user.html:9 +msgid "My Applications" +msgstr "我的应用" + #: templates/_pagination.html:59 msgid "" "Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项" +#: templates/_update_modal.html:12 +msgid "Download the update template or use the exported CSV file format" +msgstr "下载更新的模板或使用导出的csv格式" + +#: templates/_update_modal.html:13 +msgid "Download the update template" +msgstr "下载更新模版" + #: templates/captcha/image.html:3 msgid "Play CAPTCHA as audio file" msgstr "语言播放验证码" @@ -3869,11 +4294,11 @@ msgstr "接受终端注册" msgid "Info" msgstr "信息" -#: terminal/views/session.py:76 +#: terminal/views/session.py:75 msgid "Session online list" msgstr "在线会话" -#: terminal/views/session.py:94 +#: terminal/views/session.py:93 msgid "Session offline list" msgstr "离线会话" @@ -3898,47 +4323,63 @@ msgid "" "You should use your ssh client tools connect terminal: {}

    {}" msgstr "你可以使用ssh客户端工具连接终端" -#: users/api/user.py:69 users/api/user.py:80 users/api/user.py:106 +#: users/api/user.py:75 users/api/user.py:86 users/api/user.py:112 msgid "You do not have permission." msgstr "你没有权限" -#: users/api/user.py:210 +#: users/api/user.py:216 msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" -#: users/forms.py:32 users/models/user.py:71 +#: users/forms.py:25 +msgid "Reset link will be generated and sent to the user" +msgstr "生成重置密码链接,通过邮件发送给用户" + +#: users/forms.py:26 +msgid "Set password" +msgstr "设置密码" + +#: users/forms.py:38 users/models/user.py:71 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 -#: users/templates/users/user_list.html:25 +#: users/templates/users/user_list.html:37 #: users/templates/users/user_profile.html:55 msgid "Role" msgstr "角色" -#: users/forms.py:35 users/forms.py:200 +#: users/forms.py:41 users/forms.py:210 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:36 users/forms.py:201 +#: users/forms.py:42 users/forms.py:211 msgid "ssh-rsa AAAA..." msgstr "" -#: users/forms.py:37 +#: users/forms.py:43 msgid "Paste user id_rsa.pub here." msgstr "复制用户公钥到这里" -#: users/forms.py:51 users/templates/users/user_detail.html:221 +#: users/forms.py:47 xpack/plugins/change_auth_plan/models.py:83 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69 +#: 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 +msgid "Password strategy" +msgstr "密码策略" + +#: users/forms.py:61 users/templates/users/user_detail.html:221 msgid "Join user groups" msgstr "添加到用户组" -#: users/forms.py:85 users/forms.py:215 +#: users/forms.py:95 users/forms.py:225 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:89 users/forms.py:219 users/serializers/v1.py:38 +#: users/forms.py:99 users/forms.py:229 users/serializers/v1.py:47 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms.py:127 +#: users/forms.py:137 msgid "" "Tip: when enabled, you will enter the MFA binding process the next time you " "log in. you can also directly bind in \"personal information -> quick " @@ -3947,11 +4388,11 @@ msgstr "" "提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修" "改->更改MFA设置)中直接绑定!" -#: users/forms.py:137 +#: users/forms.py:147 msgid "* Enable MFA authentication to make the account more secure." msgstr "* 启用MFA认证,使账号更加安全." -#: users/forms.py:147 +#: users/forms.py:157 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -3960,46 +4401,46 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,启用MFA认证)" -#: users/forms.py:154 users/templates/users/first_login.html:48 +#: users/forms.py:164 users/templates/users/first_login.html:48 #: users/templates/users/first_login.html:107 #: users/templates/users/first_login.html:130 msgid "Finish" msgstr "完成" -#: users/forms.py:160 +#: users/forms.py:170 msgid "Old password" msgstr "原来密码" -#: users/forms.py:165 +#: users/forms.py:175 msgid "New password" msgstr "新密码" -#: users/forms.py:170 +#: users/forms.py:180 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:180 +#: users/forms.py:190 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:188 +#: users/forms.py:198 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:198 +#: users/forms.py:208 msgid "Automatically configure and download the SSH key" msgstr "自动配置并下载SSH密钥" -#: users/forms.py:202 +#: users/forms.py:212 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:236 users/forms.py:241 users/forms.py:287 +#: users/forms.py:246 users/forms.py:251 users/forms.py:297 #: xpack/plugins/orgs/forms.py:30 msgid "Select users" msgstr "选择用户" -#: users/models/user.py:35 users/models/user.py:471 +#: users/models/user.py:35 users/models/user.py:475 msgid "Administrator" msgstr "管理员" @@ -4031,7 +4472,7 @@ msgid "Wechat" msgstr "微信" #: users/models/user.py:106 users/templates/users/user_detail.html:103 -#: users/templates/users/user_list.html:27 +#: users/templates/users/user_list.html:39 #: users/templates/users/user_profile.html:100 msgid "Source" msgstr "用户来源" @@ -4041,14 +4482,42 @@ msgid "Date password last updated" msgstr "最后更新密码日期" #: users/models/user.py:136 users/templates/users/user_update.html:22 -#: users/views/login.py:47 users/views/login.py:108 users/views/user.py:427 +#: users/views/login.py:47 users/views/login.py:108 users/views/user.py:431 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" -#: users/models/user.py:474 +#: users/models/user.py:478 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" +#: users/serializers/v1.py:28 +msgid "Groups name" +msgstr "用户组名" + +#: users/serializers/v1.py:29 +msgid "Source name" +msgstr "用户来源名" + +#: users/serializers/v1.py:30 +msgid "Is first login" +msgstr "首次登录" + +#: users/serializers/v1.py:31 +msgid "Role name" +msgstr "角色名" + +#: users/serializers/v1.py:32 +msgid "Is valid" +msgstr "账户是否有效" + +#: users/serializers/v1.py:33 +msgid "Is expired" +msgstr " 是否过期" + +#: users/serializers/v1.py:34 +msgid "Avatar url" +msgstr "头像路径" + #: users/serializers_v2/user.py:36 msgid "name not unique" msgstr "名称重复" @@ -4064,7 +4533,7 @@ msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:44 users/templates/users/_user.html:13 #: users/templates/users/user_profile_update.html:51 #: xpack/plugins/cloud/models.py:120 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:59 +#: 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 "账户" @@ -4085,21 +4554,23 @@ msgstr "资产数量" msgid "Security and Role" msgstr "角色安全" +#: users/templates/users/_user_groups_import_modal.html:4 +msgid "Import user groups" +msgstr "导入用户组" + +#: users/templates/users/_user_groups_update_modal.html:4 +#: users/views/group.py:60 +msgid "Update user group" +msgstr "更新用户组" + #: users/templates/users/_user_import_modal.html:4 -msgid "Import user" -msgstr "导入" +msgid "Import users" +msgstr "导入用户" -#: users/templates/users/_user_import_modal.html:6 -msgid "Download template or use export csv format" -msgstr "下载模板或使用导出的csv格式" - -#: users/templates/users/_user_import_modal.html:14 -msgid "Users csv file" -msgstr "用户csv文件" - -#: users/templates/users/_user_import_modal.html:16 -msgid "If set id, will use this id update user existed" -msgstr "如果设置了id,则会使用该行信息更新该id的用户" +#: users/templates/users/_user_update_modal.html:4 +#: users/templates/users/user_update.html:4 users/views/user.py:130 +msgid "Update user" +msgstr "更新用户" #: users/templates/users/_user_update_pk_modal.html:4 msgid "Update User SSH Public Key" @@ -4170,17 +4641,19 @@ msgid "Always young, always with tears in my eyes. Stay foolish Stay hungry" msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry" #: users/templates/users/reset_password.html:46 -#: users/templates/users/user_detail.html:373 users/utils.py:77 +#: users/templates/users/user_detail.html:373 users/utils.py:98 msgid "Reset password" msgstr "重置密码" #: users/templates/users/reset_password.html:59 +#: users/templates/users/user_create.html:15 #: users/templates/users/user_password_update.html:61 #: users/templates/users/user_update.html:13 msgid "Your password must satisfy" msgstr "您的密码必须满足:" #: users/templates/users/reset_password.html:60 +#: users/templates/users/user_create.html:16 #: users/templates/users/user_password_update.html:62 #: users/templates/users/user_update.html:14 msgid "Password strength" @@ -4191,52 +4664,54 @@ msgid "Password again" msgstr "再次输入密码" #: users/templates/users/reset_password.html:105 +#: users/templates/users/user_create.html:35 #: users/templates/users/user_password_update.html:99 #: users/templates/users/user_update.html:46 msgid "Very weak" msgstr "很弱" #: users/templates/users/reset_password.html:106 +#: users/templates/users/user_create.html:36 #: users/templates/users/user_password_update.html:100 #: users/templates/users/user_update.html:47 msgid "Weak" msgstr "弱" #: users/templates/users/reset_password.html:107 +#: users/templates/users/user_create.html:37 #: users/templates/users/user_password_update.html:101 #: users/templates/users/user_update.html:48 msgid "Normal" msgstr "正常" #: users/templates/users/reset_password.html:108 +#: users/templates/users/user_create.html:38 #: users/templates/users/user_password_update.html:102 #: users/templates/users/user_update.html:49 msgid "Medium" msgstr "一般" #: users/templates/users/reset_password.html:109 +#: users/templates/users/user_create.html:39 #: users/templates/users/user_password_update.html:103 #: users/templates/users/user_update.html:50 msgid "Strong" msgstr "强" #: users/templates/users/reset_password.html:110 +#: users/templates/users/user_create.html:40 #: users/templates/users/user_password_update.html:104 #: users/templates/users/user_update.html:51 msgid "Very strong" msgstr "很强" #: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:16 users/views/user.py:83 +#: users/templates/users/user_list.html:28 users/views/user.py:87 msgid "Create user" msgstr "创建用户" -#: users/templates/users/user_create.html:12 -msgid "Reset link will be generated and sent to the user. " -msgstr "生成重置密码连接,通过邮件发送给用户" - #: users/templates/users/user_detail.html:19 -#: users/templates/users/user_granted_asset.html:18 users/views/user.py:204 +#: users/templates/users/user_granted_asset.html:18 users/views/user.py:208 msgid "User detail" msgstr "用户详情" @@ -4348,49 +4823,49 @@ msgstr "用户组详情" msgid "Add user" msgstr "添加用户" -#: users/templates/users/user_group_list.html:5 users/views/group.py:44 +#: users/templates/users/user_group_list.html:28 users/views/group.py:44 msgid "Create user group" msgstr "创建用户组" -#: users/templates/users/user_group_list.html:85 +#: users/templates/users/user_group_list.html:109 msgid "This will delete the selected groups !!!" msgstr "删除选择组" -#: users/templates/users/user_group_list.html:94 +#: users/templates/users/user_group_list.html:118 msgid "UserGroups Deleted." msgstr "用户组删除" -#: users/templates/users/user_group_list.html:95 -#: users/templates/users/user_group_list.html:100 +#: users/templates/users/user_group_list.html:119 +#: users/templates/users/user_group_list.html:124 msgid "UserGroups Delete" msgstr "用户组删除" -#: users/templates/users/user_group_list.html:99 +#: users/templates/users/user_group_list.html:123 msgid "UserGroup Deleting failed." msgstr "用户组删除失败" -#: users/templates/users/user_list.html:210 +#: users/templates/users/user_list.html:264 msgid "This will delete the selected users !!!" msgstr "删除选中用户 !!!" -#: users/templates/users/user_list.html:219 +#: users/templates/users/user_list.html:280 msgid "User Deleted." msgstr "已被删除" -#: users/templates/users/user_list.html:220 -#: users/templates/users/user_list.html:225 +#: users/templates/users/user_list.html:281 +#: users/templates/users/user_list.html:285 msgid "User Delete" msgstr "删除" -#: users/templates/users/user_list.html:224 +#: users/templates/users/user_list.html:284 msgid "User Deleting failed." msgstr "用户删除失败" -#: users/templates/users/user_list.html:260 +#: users/templates/users/user_list.html:337 msgid "User is expired" msgstr "用户已失效" -#: users/templates/users/user_list.html:263 +#: users/templates/users/user_list.html:340 msgid "User is inactive" msgstr "用户已禁用" @@ -4439,8 +4914,8 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接 msgid "Administrator Settings force MFA login" msgstr "管理员设置强制使用MFA登录" -#: users/templates/users/user_profile.html:120 users/views/user.py:240 -#: users/views/user.py:294 +#: users/templates/users/user_profile.html:120 users/views/user.py:244 +#: users/views/user.py:298 msgid "User groups" msgstr "用户组" @@ -4490,60 +4965,61 @@ msgid "" "corresponding private key." msgstr "新的公钥已设置成功,请下载对应的私钥" -#: users/templates/users/user_update.html:4 users/views/user.py:123 -msgid "Update user" -msgstr "更新用户" - #: users/utils.py:38 -msgid "Create account successfully" -msgstr "创建账户成功" - -#: users/utils.py:40 #, python-format msgid "" "\n" -" Hello %(name)s:\n" -"
    \n" -" Your account has been created successfully\n" -"
    \n" -" Username: %(username)s\n" -"
    \n" -" click " -"here to set your password\n" -"
    \n" -" This link is valid for 1 hour. After it expires, \n" +"

    \n" +" \n" +" Username: %(username)s.\n" +" \n" +" \n" +" click here to set your password\n" +" \n" +" \n" +" This link is valid for 1 hour. After it expires, request new one\n" -"\n" -"
    \n" -" ---\n" -"\n" -"
    \n" -" Login direct\n" -"\n" -"
    \n" -" " +"
    \n" +" \n" +" Login direct\n" +" \n" +"

    \n" +" " msgstr "" "\n" -" 你好 %(name)s:\n" -"
    \n" -" 恭喜您,您的账号已经创建成功
    \n" -" 用户名: %(username)s\n" -"
    \n" -" 请点击这" -"里设置密码
    \n" -" 这个链接有效期1小时, 超过时间您可以 重新申请\n" -"\n" -"
    \n" -" ---\n" -"\n" -"
    \n" -" Login direct\n" -"\n" -"
    \n" +" \n" +"

    \n" +" \n" +" 用户名: %(username)s.\n" +" \n" +" \n" +" " +"请点击这里设置密码\n" +" \n" +" \n" +" 这个链接有效期1小时, 超过时间您可以 重新申请\n" +" \n" +" \n" +" ---登录页面\n" +" \n" +"

    \n" " " -#: users/utils.py:79 +#: users/utils.py:73 +msgid "Create account successfully" +msgstr "创建账户成功" + +#: users/utils.py:77 +#, python-format +msgid "Hello %(name)s" +msgstr "您好 %(name)s" + +#: users/utils.py:100 #, python-format msgid "" "\n" @@ -4587,11 +5063,11 @@ msgstr "" "
    \n" " " -#: users/utils.py:110 +#: users/utils.py:131 msgid "Security notice" msgstr "安全通知" -#: users/utils.py:112 +#: users/utils.py:133 #, python-format msgid "" "\n" @@ -4640,11 +5116,11 @@ msgstr "" "
    \n" " " -#: users/utils.py:148 +#: users/utils.py:169 msgid "SSH Key Reset" msgstr "重置ssh密钥" -#: users/utils.py:150 +#: users/utils.py:171 #, python-format msgid "" "\n" @@ -4669,15 +5145,15 @@ msgstr "" "
    \n" " " -#: users/utils.py:183 +#: users/utils.py:204 msgid "User not exist" msgstr "用户不存在" -#: users/utils.py:185 +#: users/utils.py:206 msgid "Disabled or expired" msgstr "禁用或失效" -#: users/utils.py:198 +#: users/utils.py:219 msgid "Password or SSH public key invalid" msgstr "密码或密钥不合法" @@ -4685,10 +5161,6 @@ msgstr "密码或密钥不合法" msgid "User group list" msgstr "用户组列表" -#: users/views/group.py:60 -msgid "Update user group" -msgstr "更新用户组" - #: users/views/group.py:92 msgid "User group granted asset" msgstr "用户组授权资产" @@ -4722,7 +5194,7 @@ msgstr "Token错误或失效" msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:115 users/views/user.py:137 users/views/user.py:437 +#: users/views/login.py:115 users/views/user.py:144 users/views/user.py:441 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" @@ -4730,51 +5202,51 @@ msgstr "* 您的密码不符合要求" msgid "First login" msgstr "首次登录" -#: users/views/user.py:154 +#: users/views/user.py:161 msgid "Bulk update user success" msgstr "批量更新用户成功" -#: users/views/user.py:184 +#: users/views/user.py:188 msgid "Bulk update user" msgstr "批量更新用户" -#: users/views/user.py:269 +#: users/views/user.py:273 msgid "Invalid file." msgstr "文件不合法" -#: users/views/user.py:365 +#: users/views/user.py:369 msgid "User granted assets" msgstr "用户授权资产" -#: users/views/user.py:396 +#: users/views/user.py:400 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:415 +#: users/views/user.py:419 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:455 +#: users/views/user.py:459 msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:496 +#: users/views/user.py:500 msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/user.py:596 +#: users/views/user.py:600 msgid "MFA enable success" msgstr "MFA 绑定成功" -#: users/views/user.py:597 +#: users/views/user.py:601 msgid "MFA enable success, return login page" msgstr "MFA 绑定成功,返回到登录页面" -#: users/views/user.py:599 +#: users/views/user.py:603 msgid "MFA disable success" msgstr "MFA 解绑成功" -#: users/views/user.py:600 +#: users/views/user.py:604 msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" @@ -4867,14 +5339,6 @@ msgstr "周期执行" msgid "Regularly perform" msgstr "定期执行" -#: xpack/plugins/change_auth_plan/models.py:83 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69 -#: 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 -msgid "Password strategy" -msgstr "密码策略" - #: xpack/plugins/change_auth_plan/models.py:87 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74 msgid "Password rules" @@ -4947,7 +5411,7 @@ msgid "Run plan manually" msgstr "手动执行计划" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:179 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:101 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:102 msgid "Execute failed" msgstr "执行失败" @@ -5033,7 +5497,7 @@ msgid "Unavailable" msgstr "无效" #: xpack/plugins/cloud/models.py:50 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:56 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:54 #: xpack/plugins/cloud/templates/cloud/account_list.html:13 msgid "Provider" msgstr "云服务商" @@ -5059,7 +5523,7 @@ msgid "Instances" msgstr "实例" #: xpack/plugins/cloud/models.py:126 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:75 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:73 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:17 msgid "Date last sync" msgstr "最后同步日期" @@ -5078,7 +5542,7 @@ msgstr "" #: xpack/plugins/cloud/models.py:173 xpack/plugins/cloud/models.py:189 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:71 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:68 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 msgid "Date sync" msgstr "同步日期" @@ -5095,8 +5559,8 @@ msgid "Sync instance task history" msgstr "同步实例任务历史" #: xpack/plugins/cloud/models.py:185 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:89 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:61 msgid "Instance" msgstr "实例" @@ -5116,7 +5580,7 @@ msgstr "AWS (国际)" msgid "Qcloud" msgstr "腾讯云" -#: xpack/plugins/cloud/templates/cloud/account_detail.html:22 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:20 #: xpack/plugins/cloud/views.py:72 msgid "Account detail" msgstr "账户详情" @@ -5134,23 +5598,23 @@ msgstr "加载中..." msgid "Load failed" msgstr "加载失败" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:22 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:20 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:25 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:23 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21 #: xpack/plugins/cloud/views.py:122 msgid "Sync task detail" msgstr "同步任务详情" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:25 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:28 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:26 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24 #: xpack/plugins/cloud/views.py:137 msgid "Sync task history" msgstr "同步历史列表" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:28 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:31 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:29 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:27 #: xpack/plugins/cloud/views.py:188 msgid "Sync instance list" msgstr "同步实例列表" @@ -5183,7 +5647,7 @@ msgstr "执行次数" msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:92 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:93 msgid "Sync success" msgstr "同步成功" @@ -5267,6 +5731,7 @@ msgid "This will restore default Settings of the interface !!!" msgstr "您确定要恢复默认初始化吗?" #: xpack/plugins/interface/templates/interface/interface.html:107 +#: xpack/plugins/interface/views.py:53 msgid "Restore default successfully." msgstr "恢复默认成功!" @@ -5279,13 +5744,9 @@ msgid "Interface" msgstr "界面" #: xpack/plugins/interface/views.py:49 -msgid "It is already in the default setting state!" +msgid "It is already in the default setting state!" msgstr "当前已经是初始化状态!" -#: xpack/plugins/interface/views.py:53 -msgid "Restore default successfully!" -msgstr "恢复默认成功!" - #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 #: xpack/plugins/license/templates/license/license_detail.html:50 #: xpack/plugins/license/templates/license/license_detail.html:55 @@ -5423,6 +5884,126 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#~ msgid "User does not exist" +#~ msgstr "用户不存在" + +#~ msgid "Restore default successfully!" +#~ msgstr "恢复默认成功!" + +#~ msgid "The initial password has been cleared." +#~ msgstr "当前初始密码已经清除." + +#~ msgid "Clear initial password successfully." +#~ msgstr "清除初始密码成功." + +#~ msgid "User Initial Password" +#~ msgstr "用户初始密码" + +#~ msgid "Clear initial password" +#~ msgstr "清除初始密码" + +#~ msgid "This will be clear the initial password !!!" +#~ msgstr "这将会清除用户初始密码!!!" + +#~ msgid "Clear Initial Password" +#~ msgstr "清除初始密码" + +#~ msgid "Clear initial password failed." +#~ msgstr "清除初始密码失败." + +#~ msgid "INITIAL_PASSWORD" +#~ msgstr "初始化密码" + +#~ msgid "CUSTOM_PASSWORD" +#~ msgstr "自定义密码" + +#~ msgid "EMAIL_SET_PASSWORD" +#~ msgstr "邮件设置密码" + +#~ msgid "" +#~ "This will use the default initial password by the system. Please go to " +#~ "the system Settings application to set the initial password" +#~ msgstr "这将会使用系统设置的初始密码. 请先到系统设置应用里去设置初始密码." + +#~ msgid "The password cannot be the same as the initial password" +#~ msgstr "密码设置不能和系统设置的初始密码一致" + +#~ msgid "Update user groups" +#~ msgstr "更新用户组" + +# msgid "Update user" +# msgstr "更新用户" +#~ msgid "" +#~ "\n" +#~ " \n" +#~ "

    \n" +#~ " \n" +#~ " click here to set your password\n" +#~ " \n" +#~ " \n" +#~ " This link is valid for 1 hour. After it expires, request new one\n" +#~ " \n" +#~ " \n" +#~ " Login direct\n" +#~ " \n" +#~ "

    \n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ " \n" +#~ "

    \n" +#~ " \n" +#~ " 请点击这里设置密码\n" +#~ " \n" +#~ " \n" +#~ " 这个链接有效期1小时, 超过时间您可以, 重新申请\n" +#~ " \n" +#~ " \n" +#~ " Login direct\n" +#~ " \n" +#~ "

    \n" +#~ " " + +#~ msgid "Template" +#~ msgstr "模板" + +#~ msgid "Download" +#~ msgstr "下载" + +#~ msgid "Asset csv file" +#~ msgstr "资产csv文件" + +#~ msgid "If set id, will use this id update asset existed" +#~ msgstr "如果设置了id,则会使用该行信息更新该id的资产" + +#~ msgid "Users csv file" +#~ msgstr "用户csv文件" + +#~ msgid "If set id, will use this id update user existed" +#~ msgstr "如果设置了id,则会使用该行信息更新该id的用户" + +#~ msgid "MFA Confirm" +#~ msgstr "确认" + +#~ msgid "Monitor" +#~ msgstr "监控" + +#, fuzzy +#~| msgid "Select user" +#~ msgid "Select time" +#~ msgstr "选择用户" + +#, fuzzy +#~| msgid "Select Asset" +#~ msgid "Select host" +#~ msgstr "选择资产" + #~ msgid "Beijing Duizhan Tech, Inc." #~ msgstr "北京堆栈科技有限公司" @@ -5453,9 +6034,6 @@ msgstr "更新组织" #~ "object has no attribute 'keys'" #~ msgstr "导入 {} 个用户成功; 导入 {} 这些用户失败,因为对象没有属性'keys'" -#~ msgid "Monitor" -#~ msgstr "监控" - #~ msgid "Invalid private key" #~ msgstr "ssh密钥不合法" @@ -5498,11 +6076,6 @@ msgstr "更新组织" #~ msgid "Date finished" #~ msgstr "结束日期" -#, fuzzy -#~| msgid "Audits" -#~ msgid "Audit" -#~ msgstr "日志审计" - #~ msgid "User id" #~ msgstr "用户" @@ -5512,11 +6085,6 @@ msgstr "更新组织" #~ msgid "Start" #~ msgstr "开始" -#, fuzzy -#~| msgid "Update setting successfully" -#~ msgid "Update setting successfully, please restart program" -#~ msgstr "更新设置成功" - #~ msgid "User login settings" #~ msgstr "用户登录设置" @@ -5549,9 +6117,6 @@ msgstr "更新组织" #~ "Are you sure to remove authentication information for the system user ?" #~ msgstr "你确定清除该系统用户的认证信息吗 ?" -#~ msgid "Clear auth" -#~ msgstr "清除认证信息" - #~ msgid "success" #~ msgstr "成功" diff --git a/apps/locale/zh/LC_MESSAGES/djangojs.mo b/apps/locale/zh/LC_MESSAGES/djangojs.mo index 189ddbc8e..aab4a06e6 100644 Binary files a/apps/locale/zh/LC_MESSAGES/djangojs.mo and b/apps/locale/zh/LC_MESSAGES/djangojs.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/djangojs.po b/apps/locale/zh/LC_MESSAGES/djangojs.po index 720927f06..8e0ec5047 100644 --- a/apps/locale/zh/LC_MESSAGES/djangojs.po +++ b/apps/locale/zh/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-11-21 19:14+0800\n" +"POT-Creation-Date: 2019-05-27 15:53+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,58 +17,58 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: static/js/jumpserver.js:168 +#: static/js/jumpserver.js:249 msgid "Update is successful!" msgstr "更新成功" -#: static/js/jumpserver.js:170 +#: static/js/jumpserver.js:251 msgid "An unknown error occurred while updating.." msgstr "更新时发生未知错误" -#: static/js/jumpserver.js:236 static/js/jumpserver.js:273 -#: static/js/jumpserver.js:276 +#: static/js/jumpserver.js:315 static/js/jumpserver.js:352 +#: static/js/jumpserver.js:355 msgid "Error" msgstr "错误" -#: static/js/jumpserver.js:236 +#: static/js/jumpserver.js:315 msgid "Being used by the asset, please unbind the asset first." msgstr "正在被资产使用中,请先解除资产绑定" -#: static/js/jumpserver.js:242 static/js/jumpserver.js:283 +#: static/js/jumpserver.js:321 static/js/jumpserver.js:362 msgid "Delete the success" msgstr "删除成功" -#: static/js/jumpserver.js:248 +#: static/js/jumpserver.js:327 msgid "Are you sure about deleting it?" msgstr "你确定删除吗 ?" -#: static/js/jumpserver.js:252 static/js/jumpserver.js:293 +#: static/js/jumpserver.js:331 static/js/jumpserver.js:372 msgid "Cancel" msgstr "取消" -#: static/js/jumpserver.js:254 static/js/jumpserver.js:295 +#: static/js/jumpserver.js:333 static/js/jumpserver.js:374 msgid "Confirm" msgstr "确认" -#: static/js/jumpserver.js:273 +#: static/js/jumpserver.js:352 msgid "" "The organization contains undeleted information. Please try again after " "deleting" msgstr "组织中包含未删除信息,请删除后重试" -#: static/js/jumpserver.js:276 +#: static/js/jumpserver.js:355 msgid "" "Do not perform this operation under this organization. Try again after " "switching to another organization" msgstr "请勿在此组织下执行此操作,切换到其他组织后重试" -#: static/js/jumpserver.js:289 +#: static/js/jumpserver.js:368 msgid "" "Please ensure that the following information in the organization has been " "deleted" msgstr "请确保组织内的以下信息已删除" -#: static/js/jumpserver.js:290 +#: static/js/jumpserver.js:369 msgid "" "User list、User group、Asset list、Domain list、Admin user、System user、" "Labels、Asset permission" @@ -76,52 +76,76 @@ msgstr "" "用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权" "规则" -#: static/js/jumpserver.js:329 +#: static/js/jumpserver.js:408 msgid "Loading ..." msgstr "加载中 ..." -#: static/js/jumpserver.js:330 +#: static/js/jumpserver.js:409 msgid "Search" msgstr "搜索" -#: static/js/jumpserver.js:333 +#: static/js/jumpserver.js:412 #, javascript-format msgid "Selected item %d" msgstr "选中 %d 项" -#: static/js/jumpserver.js:337 +#: static/js/jumpserver.js:416 msgid "Per page _MENU_" msgstr "每页 _MENU_" -#: static/js/jumpserver.js:338 +#: static/js/jumpserver.js:417 msgid "" "Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项" -#: static/js/jumpserver.js:341 +#: static/js/jumpserver.js:420 msgid "No match" msgstr "没有匹配项" -#: static/js/jumpserver.js:342 +#: static/js/jumpserver.js:421 msgid "No record" msgstr "没有记录" -#: static/js/jumpserver.js:701 +#: static/js/jumpserver.js:563 +msgid "Unknown error occur" +msgstr "" + +#: static/js/jumpserver.js:800 msgid "Password minimum length {N} bits" msgstr "密码最小长度 {N} 位" -#: static/js/jumpserver.js:702 +#: static/js/jumpserver.js:801 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: static/js/jumpserver.js:703 +#: static/js/jumpserver.js:802 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: static/js/jumpserver.js:704 +#: static/js/jumpserver.js:803 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: static/js/jumpserver.js:705 +#: static/js/jumpserver.js:804 msgid "Must contain special characters" msgstr "必须包含特殊字符" + +#: static/js/jumpserver.js:976 +msgid "Export failed" +msgstr "导出失败" + +#: static/js/jumpserver.js:993 +msgid "Import Success" +msgstr "导入成功" + +#: static/js/jumpserver.js:998 +msgid "Update Success" +msgstr "更新成功" + +#: static/js/jumpserver.js:1028 +msgid "Import failed" +msgstr "导入失败" + +#: static/js/jumpserver.js:1033 +msgid "Update failed" +msgstr "更新失败" diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 02004285a..694bb9e27 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -124,6 +124,9 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule): def display_ok_hosts(self): pass + def display_failed_stderr(self): + pass + class CommandResultCallback(AdHocResultCallback): """ diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index 4c8f87888..5f25ee2c4 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -1,8 +1,11 @@ # ~*~ coding: utf-8 ~*~ import os +import shutil from collections import namedtuple +from ansible import context +from ansible.module_utils.common.collections import ImmutableDict from ansible.executor.task_queue_manager import TaskQueueManager from ansible.vars.manager import VariableManager from ansible.parsing.dataloader import DataLoader @@ -33,29 +36,18 @@ Options = namedtuple('Options', [ def get_default_options(): - options = Options( - listtags=False, - listtasks=False, - listhosts=False, + options = dict( syntax=False, timeout=30, connection='ssh', - module_path='', forks=10, remote_user='root', private_key_file=None, - ssh_common_args="", - ssh_extra_args="", - sftp_extra_args="", - scp_extra_args="", become=None, become_method=None, become_user=None, - verbosity=None, - extra_vars=[], + verbosity=1, check=False, - playbook_path='/etc/ansible/', - passwords=None, diff=False, gathering='implicit', remote_tmp='/tmp/.ansible' @@ -108,9 +100,9 @@ class PlayBookRunner: inventory=self.inventory, variable_manager=self.variable_manager, loader=self.loader, - options=self.options, - passwords=self.passwords + passwords={"conn_pass": self.passwords} ) + context.CLIARGS = ImmutableDict(self.options) if executor._tqm: executor._tqm._stdout_callback = self.results_callback @@ -185,11 +177,10 @@ class AdHocRunner: return cleaned_tasks def update_options(self, options): + _options = {k: v for k, v in self.default_options.items()} if options and isinstance(options, dict): - options = self.__class__.default_options._replace(**options) - else: - options = self.__class__.default_options - return options + _options.update(options) + return _options def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'): """ @@ -202,6 +193,7 @@ class AdHocRunner: self.check_pattern(pattern) self.results_callback = self.get_result_callback() cleaned_tasks = self.clean_tasks(tasks) + context.CLIARGS = ImmutableDict(self.options) play_source = dict( name=play_name, @@ -220,9 +212,8 @@ class AdHocRunner: inventory=self.inventory, variable_manager=self.variable_manager, loader=self.loader, - options=self.options, stdout_callback=self.results_callback, - passwords=self.options.passwords, + passwords={"conn_pass": self.options.get("password", "")} ) try: tqm.run(play) @@ -230,8 +221,9 @@ class AdHocRunner: except Exception as e: raise AnsibleError(e) finally: - tqm.cleanup() - self.loader.cleanup_all_tmp_files() + if tqm is not None: + tqm.cleanup() + shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) class CommandRunner(AdHocRunner): diff --git a/apps/ops/ansible/test_runner.py b/apps/ops/ansible/test_runner.py index 07c9051f3..e38168a6c 100644 --- a/apps/ops/ansible/test_runner.py +++ b/apps/ops/ansible/test_runner.py @@ -15,7 +15,7 @@ class TestAdHocRunner(unittest.TestCase): host_data = [ { "hostname": "testserver", - "ip": "192.168.244.168", + "ip": "192.168.244.185", "port": 22, "username": "root", "password": "redhat", diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py index e4f11bec1..002b6fbda 100644 --- a/apps/ops/inventory.py +++ b/apps/ops/inventory.py @@ -35,7 +35,6 @@ class JMSBaseInventory(BaseInventory): info["vars"].update({ label.name: label.value }) - info["groups"].append("{}:{}".format(label.name, label.value)) if asset.domain: info["vars"].update({ "domain": asset.domain.name, diff --git a/apps/orgs/mixins.py b/apps/orgs/mixins.py index 5c4879204..431443ebe 100644 --- a/apps/orgs/mixins.py +++ b/apps/orgs/mixins.py @@ -8,9 +8,12 @@ from django.shortcuts import redirect, get_object_or_404 from django.forms import ModelForm from django.http.response import HttpResponseForbidden from django.core.exceptions import ValidationError +from rest_framework import serializers from common.utils import get_logger -from .utils import current_org, set_current_org, set_to_root_org +from .utils import ( + current_org, set_current_org, set_to_root_org, get_current_org_id +) from .models import Organization logger = get_logger(__file__) @@ -18,7 +21,8 @@ tl = Local() __all__ = [ 'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm', - 'RootOrgViewMixin', 'OrgMembershipSerializerMixin', 'OrgMembershipModelViewSetMixin' + 'RootOrgViewMixin', 'OrgMembershipSerializerMixin', + 'OrgMembershipModelViewSetMixin', 'OrgResourceSerializerMixin', ] @@ -202,3 +206,11 @@ class OrgMembershipModelViewSetMixin: def get_queryset(self): queryset = self.membership_class.objects.filter(organization=self.org) return queryset + + +class OrgResourceSerializerMixin(serializers.Serializer): + """ + 通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id + (同时为serializer.is_valid()对Model的unique_together校验做准备) + """ + org_id = serializers.HiddenField(default=get_current_org_id) diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 7898e0343..808536984 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -38,4 +38,10 @@ def get_current_org(): return _find('current_org') +def get_current_org_id(): + org = get_current_org() + org_id = str(org.id) if org.is_real() else '' + return org_id + + current_org = LocalProxy(partial(_find, 'current_org')) diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py index e90e0262c..aebfb2d33 100644 --- a/apps/perms/api/__init__.py +++ b/apps/perms/api/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # -from .permission import * +from .asset_permission import * from .user_permission import * from .user_group_permission import * +from .remote_app_permission import * diff --git a/apps/perms/api/permission.py b/apps/perms/api/asset_permission.py similarity index 100% rename from apps/perms/api/permission.py rename to apps/perms/api/asset_permission.py diff --git a/apps/perms/api/remote_app_permission.py b/apps/perms/api/remote_app_permission.py new file mode 100644 index 000000000..d75e80b8f --- /dev/null +++ b/apps/perms/api/remote_app_permission.py @@ -0,0 +1,101 @@ +# coding: utf-8 +# + + +from rest_framework import viewsets, generics +from rest_framework.pagination import LimitOffsetPagination +from rest_framework.views import Response + +from common.permissions import IsOrgAdmin + +from ..models import RemoteAppPermission +from ..serializers import ( + RemoteAppPermissionSerializer, + RemoteAppPermissionUpdateUserSerializer, + RemoteAppPermissionUpdateRemoteAppSerializer, +) + + +__all__ = [ + 'RemoteAppPermissionViewSet', + 'RemoteAppPermissionAddUserApi', 'RemoteAppPermissionAddRemoteAppApi', + 'RemoteAppPermissionRemoveUserApi', 'RemoteAppPermissionRemoveRemoteAppApi', +] + + +class RemoteAppPermissionViewSet(viewsets.ModelViewSet): + filter_fields = ('name', ) + search_fields = filter_fields + queryset = RemoteAppPermission.objects.all() + serializer_class = RemoteAppPermissionSerializer + pagination_class = LimitOffsetPagination + permission_classes = (IsOrgAdmin,) + + +class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = RemoteAppPermissionUpdateUserSerializer + queryset = RemoteAppPermission.objects.all() + + def update(self, request, *args, **kwargs): + perm = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + users = serializer.validated_data.get('users') + if users: + perm.users.add(*tuple(users)) + return Response({"msg": "ok"}) + else: + return Response({"error": serializer.errors}) + + +class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = RemoteAppPermissionUpdateUserSerializer + queryset = RemoteAppPermission.objects.all() + + def update(self, request, *args, **kwargs): + perm = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + users = serializer.validated_data.get('users') + if users: + perm.users.remove(*tuple(users)) + return Response({"msg": "ok"}) + else: + return Response({"error": serializer.errors}) + + +class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer + queryset = RemoteAppPermission.objects.all() + + def update(self, request, *args, **kwargs): + perm = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + remote_apps = serializer.validated_data.get('remote_apps') + if remote_apps: + perm.remote_apps.add(*tuple(remote_apps)) + return Response({"msg": "ok"}) + else: + return Response({"error": serializer.errors}) + + +class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer + queryset = RemoteAppPermission.objects.all() + + def update(self, request, *args, **kwargs): + perm = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + remote_apps = serializer.validated_data.get('remote_apps') + if remote_apps: + perm.remote_apps.remove(*tuple(remote_apps)) + return Response({"msg": "ok"}) + else: + return Response({"error": serializer.errors}) + diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index 82b71bb63..4e5c8acd9 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -10,10 +10,12 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.tree import TreeNodeSerializer from orgs.utils import set_to_root_org from ..utils import ( - AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node + AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node, + RemoteAppPermissionUtil, ) from ..hands import ( - AssetGrantedSerializer, UserGroup, Node, NodeSerializer + AssetGrantedSerializer, UserGroup, Node, NodeSerializer, + RemoteAppSerializer, ) from .. import serializers @@ -22,6 +24,7 @@ __all__ = [ 'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi', 'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi', 'UserGroupGrantedNodesWithAssetsAsTreeApi', + 'UserGroupGrantedRemoteAppsApi', ] @@ -138,3 +141,20 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView): for asset, system_users in assets.items(): asset.system_users_granted = system_users return assets + + +# RemoteApp permission + +class UserGroupGrantedRemoteAppsApi(ListAPIView): + permission_classes = (IsOrgAdmin, ) + serializer_class = RemoteAppSerializer + + def get_queryset(self): + queryset = [] + user_group_id = self.kwargs.get('pk') + if not user_group_id: + return queryset + user_group = get_object_or_404(UserGroup, id=user_group_id) + util = RemoteAppPermissionUtil(user_group) + queryset = util.get_remote_apps() + return queryset diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index 8d1a42c26..377bd735c 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -17,14 +17,15 @@ from common.utils import get_logger from orgs.utils import set_to_root_org from ..utils import ( AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node, - check_system_user_action + check_system_user_action, RemoteAppPermissionUtil, + construct_remote_apps_tree_root, parse_remote_app_to_tree_node, ) from ..hands import ( - AssetGrantedSerializer, User, Asset, Node, - SystemUser, NodeSerializer + User, Asset, Node, SystemUser, RemoteApp, AssetGrantedSerializer, + NodeSerializer, RemoteAppSerializer, ) from .. import serializers -from ..mixins import AssetsFilterMixin +from ..mixins import AssetsFilterMixin, RemoteAppFilterMixin from ..models import Action logger = get_logger(__name__) @@ -34,6 +35,8 @@ __all__ = [ 'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi', 'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi', 'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi', + 'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi', + 'UserGrantedRemoteAppsAsTreeApi', ] @@ -447,3 +450,79 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView): actions = [action.name for action in getattr(_su, 'actions', [])] return Response({'actions': actions}, status=200) + + +# RemoteApp permission + +class UserGrantedRemoteAppsApi(RemoteAppFilterMixin, ListAPIView): + permission_classes = (IsOrgAdminOrAppUser,) + serializer_class = RemoteAppSerializer + pagination_class = LimitOffsetPagination + + def get_object(self): + user_id = self.kwargs.get('pk', '') + if user_id: + user = get_object_or_404(User, id=user_id) + else: + user = self.request.user + return user + + def get_queryset(self): + util = RemoteAppPermissionUtil(self.get_object()) + queryset = util.get_remote_apps() + queryset = list(queryset) + return queryset + + def get_permissions(self): + if self.kwargs.get('pk') is None: + self.permission_classes = (IsValidUser,) + return super().get_permissions() + + +class UserGrantedRemoteAppsAsTreeApi(ListAPIView): + serializer_class = TreeNodeSerializer + permission_classes = (IsOrgAdminOrAppUser,) + + def get_object(self): + user_id = self.kwargs.get('pk', '') + if not user_id: + user = self.request.user + else: + user = get_object_or_404(User, id=user_id) + return user + + def get_queryset(self): + queryset = [] + tree_root = construct_remote_apps_tree_root() + queryset.append(tree_root) + + util = RemoteAppPermissionUtil(self.get_object()) + remote_apps = util.get_remote_apps() + for remote_app in remote_apps: + node = parse_remote_app_to_tree_node(tree_root, remote_app) + queryset.append(node) + + queryset = sorted(queryset) + return queryset + + def get_permissions(self): + if self.kwargs.get('pk') is None: + self.permission_classes = (IsValidUser,) + return super().get_permissions() + + +class ValidateUserRemoteAppPermissionApi(APIView): + permission_classes = (IsOrgAdminOrAppUser,) + + def get(self, request, *args, **kwargs): + user_id = request.query_params.get('user_id', '') + remote_app_id = request.query_params.get('remote_app_id', '') + user = get_object_or_404(User, id=user_id) + remote_app = get_object_or_404(RemoteApp, id=remote_app_id) + + util = RemoteAppPermissionUtil(user) + remote_apps = util.get_remote_apps() + if remote_app not in remote_apps: + return Response({'msg': False}, status=403) + + return Response({'msg': True}, status=200) diff --git a/apps/perms/forms/__init__.py b/apps/perms/forms/__init__.py new file mode 100644 index 000000000..129901afc --- /dev/null +++ b/apps/perms/forms/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# + +from .asset_permission import * +from .remote_app_permission import * diff --git a/apps/perms/forms.py b/apps/perms/forms/asset_permission.py similarity index 97% rename from apps/perms/forms.py rename to apps/perms/forms/asset_permission.py index 35d8a2528..81845fb77 100644 --- a/apps/perms/forms.py +++ b/apps/perms/forms/asset_permission.py @@ -6,9 +6,13 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins import OrgModelForm from orgs.utils import current_org -from .models import AssetPermission +from perms.models import AssetPermission from assets.models import Asset +__all__ = [ + 'AssetPermissionForm', +] + class AssetPermissionForm(OrgModelForm): def __init__(self, *args, **kwargs): diff --git a/apps/perms/forms/remote_app_permission.py b/apps/perms/forms/remote_app_permission.py new file mode 100644 index 000000000..2e0cc1b66 --- /dev/null +++ b/apps/perms/forms/remote_app_permission.py @@ -0,0 +1,49 @@ +# coding: utf-8 +# + +from django.utils.translation import ugettext as _ +from django import forms +from orgs.mixins import OrgModelForm +from orgs.utils import current_org + +from ..models import RemoteAppPermission + + +__all__ = [ + 'RemoteAppPermissionCreateUpdateForm', +] + + +class RemoteAppPermissionCreateUpdateForm(OrgModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + users_field = self.fields.get('users') + if hasattr(users_field, 'queryset'): + users_field.queryset = current_org.get_org_users() + + class Meta: + model = RemoteAppPermission + exclude = ( + 'id', 'date_created', 'created_by', 'org_id' + ) + widgets = { + 'users': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('User')} + ), + 'user_groups': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('User group')} + ), + 'remote_apps': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('RemoteApp')} + ) + } + + def clean_user_groups(self): + users = self.cleaned_data.get('users') + user_groups = self.cleaned_data.get('user_groups') + + if not users and not user_groups: + raise forms.ValidationError( + _("User or group at least one required") + ) + return self.cleaned_data['user_groups'] diff --git a/apps/perms/hands.py b/apps/perms/hands.py index 56a60cba7..2a208fefa 100644 --- a/apps/perms/hands.py +++ b/apps/perms/hands.py @@ -3,8 +3,11 @@ from common.permissions import AdminUserRequiredMixin from users.models import User, UserGroup -from assets.models import Asset, SystemUser, Node -from assets.serializers import AssetGrantedSerializer, NodeSerializer +from assets.models import Asset, SystemUser, Node, RemoteApp +from assets.serializers import ( + AssetGrantedSerializer, NodeSerializer +) +from applications.serializers import RemoteAppSerializer diff --git a/apps/perms/migrations/0005_auto_20190521_1619.py b/apps/perms/migrations/0005_auto_20190521_1619.py new file mode 100644 index 000000000..f000b8685 --- /dev/null +++ b/apps/perms/migrations/0005_auto_20190521_1619.py @@ -0,0 +1,55 @@ +# Generated by Django 2.1.7 on 2019-05-21 08:19 + +import common.utils.django +from django.conf import settings +from django.db import migrations, models +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0019_auto_20190304_1459'), + ('applications', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('perms', '0004_assetpermission_actions'), + ] + + operations = [ + migrations.CreateModel( + name='RemoteAppPermission', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('is_active', models.BooleanField(default=True, verbose_name='Active')), + ('date_start', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Date start')), + ('date_expired', models.DateTimeField(db_index=True, default=common.utils.django.date_expired_default, verbose_name='Date expired')), + ('created_by', models.CharField(blank=True, max_length=128, verbose_name='Created by')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('remote_apps', models.ManyToManyField(blank=True, related_name='granted_by_permissions', to='applications.RemoteApp', verbose_name='RemoteApp')), + ('user_groups', models.ManyToManyField(blank=True, to='users.UserGroup', verbose_name='User group')), + ('users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'RemoteApp permission', + 'ordering': ('name',), + }, + ), + migrations.AlterField( + model_name='assetpermission', + name='user_groups', + field=models.ManyToManyField(blank=True, to='users.UserGroup', verbose_name='User group'), + ), + migrations.AlterField( + model_name='assetpermission', + name='users', + field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), + migrations.AlterUniqueTogether( + name='remoteapppermission', + unique_together={('org_id', 'name')}, + ), + ] diff --git a/apps/perms/mixins.py b/apps/perms/mixins.py index e41c0529b..3adaa6e5b 100644 --- a/apps/perms/mixins.py +++ b/apps/perms/mixins.py @@ -2,6 +2,11 @@ # +__all__ = [ + 'AssetsFilterMixin', 'RemoteAppFilterMixin', +] + + class AssetsFilterMixin(object): """ 对资产进行过滤(查询,排序) @@ -34,3 +39,38 @@ class AssetsFilterMixin(object): queryset = sort_assets(queryset, order_by=order_by, reverse=reverse) return queryset + + +class RemoteAppFilterMixin(object): + """ + 对RemoteApp进行过滤(查询,排序) + """ + + def filter_queryset(self, queryset): + queryset = self.search_remote_apps(queryset) + queryset = self.sort_remote_apps(queryset) + return queryset + + def search_remote_apps(self, queryset): + value = self.request.query_params.get('search') + if not value: + return queryset + queryset = [ + remote_app for remote_app in queryset if value in remote_app.name + ] + return queryset + + def sort_remote_apps(self, queryset): + order_by = self.request.query_params.get('order') + if not order_by: + order_by = 'name' + if order_by.startswith('-'): + order_by = order_by.lstrip('-') + reverse = True + else: + reverse = False + + queryset = sorted( + queryset, key=lambda x: getattr(x, order_by), reverse=reverse + ) + return queryset diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py new file mode 100644 index 000000000..129901afc --- /dev/null +++ b/apps/perms/models/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# + +from .asset_permission import * +from .remote_app_permission import * diff --git a/apps/perms/models.py b/apps/perms/models/asset_permission.py similarity index 53% rename from apps/perms/models.py rename to apps/perms/models/asset_permission.py index c524c4f58..ed0aaa535 100644 --- a/apps/perms/models.py +++ b/apps/perms/models/asset_permission.py @@ -2,12 +2,17 @@ import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.utils import timezone from common.utils import date_expired_default, set_or_append_attr_bulk -from orgs.mixins import OrgModelMixin, OrgManager +from orgs.mixins import OrgModelMixin -from .const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL +from ..const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL +from .base import BasePermission + + +__all__ = [ + 'Action', 'AssetPermission', 'NodePermission', +] class Action(models.Model): @@ -28,69 +33,16 @@ class Action(models.Model): return cls.objects.get(name=PERMS_ACTION_NAME_ALL) -class AssetPermissionQuerySet(models.QuerySet): - def active(self): - return self.filter(is_active=True) - - def valid(self): - return self.active().filter(date_start__lt=timezone.now())\ - .filter(date_expired__gt=timezone.now()) - - -class AssetPermissionManager(OrgManager): - def valid(self): - return self.get_queryset().valid() - - -class AssetPermission(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, verbose_name=_('Name')) - users = models.ManyToManyField('users.User', related_name='asset_permissions', blank=True, verbose_name=_("User")) - user_groups = models.ManyToManyField('users.UserGroup', related_name='asset_permissions', blank=True, verbose_name=_("User group")) +class AssetPermission(BasePermission): assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user")) actions = models.ManyToManyField('Action', related_name='permissions', blank=True, verbose_name=_('Action')) - is_active = models.BooleanField(default=True, verbose_name=_('Active')) - date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) - date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) - created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) - comment = models.TextField(verbose_name=_('Comment'), blank=True) - - objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)() class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset permission") - def __str__(self): - return self.name - - @property - def id_str(self): - return str(self.id) - - @property - def is_expired(self): - if self.date_expired > timezone.now() > self.date_start: - return False - return True - - @property - def is_valid(self): - if not self.is_expired and self.is_active: - return True - return False - - def get_all_users(self): - users = set(self.users.all()) - for group in self.user_groups.all(): - _users = group.users.all() - set_or_append_attr_bulk(_users, 'inherit', group.name) - users.update(set(_users)) - return users - def get_all_assets(self): assets = set(self.assets.all()) for node in self.nodes.all(): diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py new file mode 100644 index 000000000..345ebd4e8 --- /dev/null +++ b/apps/perms/models/base.py @@ -0,0 +1,75 @@ +# coding: utf-8 +# + +import uuid +from django.utils.translation import ugettext_lazy as _ +from django.db import models +from django.utils import timezone +from orgs.mixins import OrgModelMixin + +from common.utils import date_expired_default, set_or_append_attr_bulk +from orgs.mixins import OrgManager + + +__all__ = [ + 'BasePermission', +] + + +class BasePermissionQuerySet(models.QuerySet): + def active(self): + return self.filter(is_active=True) + + def valid(self): + return self.active().filter(date_start__lt=timezone.now()) \ + .filter(date_expired__gt=timezone.now()) + + +class BasePermissionManager(OrgManager): + def valid(self): + return self.get_queryset().valid() + + +class BasePermission(OrgModelMixin): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_('Name')) + users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User")) + user_groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User group")) + is_active = models.BooleanField(default=True, verbose_name=_('Active')) + date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) + date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) + created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + comment = models.TextField(verbose_name=_('Comment'), blank=True) + + objects = BasePermissionManager.from_queryset(BasePermissionQuerySet)() + + class Meta: + abstract = True + + def __str__(self): + return self.name + + @property + def id_str(self): + return str(self.id) + + @property + def is_expired(self): + if self.date_expired > timezone.now() > self.date_start: + return False + return True + + @property + def is_valid(self): + if not self.is_expired and self.is_active: + return True + return False + + def get_all_users(self): + users = set(self.users.all()) + for group in self.user_groups.all(): + _users = group.users.all() + set_or_append_attr_bulk(_users, 'inherit', group.name) + users.update(set(_users)) + return users diff --git a/apps/perms/models/remote_app_permission.py b/apps/perms/models/remote_app_permission.py new file mode 100644 index 000000000..706467396 --- /dev/null +++ b/apps/perms/models/remote_app_permission.py @@ -0,0 +1,23 @@ +# coding: utf-8 +# + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from .base import BasePermission + +__all__ = [ + 'RemoteAppPermission', +] + + +class RemoteAppPermission(BasePermission): + remote_apps = models.ManyToManyField('applications.RemoteApp', related_name='granted_by_permissions', blank=True, verbose_name=_("RemoteApp")) + + class Meta: + unique_together = [('org_id', 'name')] + verbose_name = _('RemoteApp permission') + ordering = ('name',) + + def get_all_remote_apps(self): + return set(self.remote_apps.all()) diff --git a/apps/perms/serializers/__init__.py b/apps/perms/serializers/__init__.py new file mode 100644 index 000000000..129901afc --- /dev/null +++ b/apps/perms/serializers/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# + +from .asset_permission import * +from .remote_app_permission import * diff --git a/apps/perms/serializers.py b/apps/perms/serializers/asset_permission.py similarity index 97% rename from apps/perms/serializers.py rename to apps/perms/serializers/asset_permission.py index 01c5a077a..a1eec6710 100644 --- a/apps/perms/serializers.py +++ b/apps/perms/serializers/asset_permission.py @@ -4,7 +4,7 @@ from rest_framework import serializers from common.fields import StringManyToManyField -from .models import AssetPermission, Action +from perms.models import AssetPermission, Action from assets.models import Node, Asset, SystemUser from assets.serializers import AssetGrantedSerializer @@ -13,7 +13,7 @@ __all__ = [ 'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer', 'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', 'GrantedAssetSerializer', 'GrantedSystemUserSerializer', - 'ActionSerializer', + 'ActionSerializer', 'NodeGrantedSerializer', ] diff --git a/apps/perms/serializers/remote_app_permission.py b/apps/perms/serializers/remote_app_permission.py new file mode 100644 index 000000000..bb95d910c --- /dev/null +++ b/apps/perms/serializers/remote_app_permission.py @@ -0,0 +1,36 @@ +# coding: utf-8 +# + +from rest_framework import serializers + +from ..models import RemoteAppPermission + + +__all__ = [ + 'RemoteAppPermissionSerializer', + 'RemoteAppPermissionUpdateUserSerializer', + 'RemoteAppPermissionUpdateRemoteAppSerializer', +] + + +class RemoteAppPermissionSerializer(serializers.ModelSerializer): + class Meta: + model = RemoteAppPermission + fields = [ + 'id', 'name', 'users', 'user_groups', 'remote_apps', 'comment', + 'is_active', 'date_start', 'date_expired', 'is_valid', + 'created_by', 'date_created', 'org_id' + ] + read_only_fields = ['created_by', 'date_created'] + + +class RemoteAppPermissionUpdateUserSerializer(serializers.ModelSerializer): + class Meta: + model = RemoteAppPermission + fields = ['id', 'users'] + + +class RemoteAppPermissionUpdateRemoteAppSerializer(serializers.ModelSerializer): + class Meta: + model = RemoteAppPermission + fields = ['id', 'remote_apps'] diff --git a/apps/perms/templates/perms/remote_app_permission_create_update.html b/apps/perms/templates/perms/remote_app_permission_create_update.html new file mode 100644 index 000000000..3d6154991 --- /dev/null +++ b/apps/perms/templates/perms/remote_app_permission_create_update.html @@ -0,0 +1,120 @@ +{% 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" %} +
    + +

    {% 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 %} \ No newline at end of file diff --git a/apps/perms/templates/perms/remote_app_permission_detail.html b/apps/perms/templates/perms/remote_app_permission_detail.html new file mode 100644 index 000000000..98bc6633e --- /dev/null +++ b/apps/perms/templates/perms/remote_app_permission_detail.html @@ -0,0 +1,169 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + +{% endblock %} + +{% 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' %} : +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/perms/templates/perms/remote_app_permission_list.html b/apps/perms/templates/perms/remote_app_permission_list.html new file mode 100644 index 000000000..8f1681781 --- /dev/null +++ b/apps/perms/templates/perms/remote_app_permission_list.html @@ -0,0 +1,93 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block table_search %}{% endblock %} +{% block table_container %} + + + + + + + + + + + + + + + +
    + + {% trans 'Name' %}{% trans 'User' %}{% trans 'User group' %}{% trans 'RemoteApp' %}{% 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 new file mode 100644 index 000000000..63d395941 --- /dev/null +++ b/apps/perms/templates/perms/remote_app_permission_remote_app.html @@ -0,0 +1,164 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + +{% endblock %} +{% 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 %} \ No newline at end of file diff --git a/apps/perms/templates/perms/remote_app_permission_user.html b/apps/perms/templates/perms/remote_app_permission_user.html new file mode 100644 index 000000000..7433327c5 --- /dev/null +++ b/apps/perms/templates/perms/remote_app_permission_user.html @@ -0,0 +1,256 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + +{% endblock %} +{% 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 %} \ No newline at end of file diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index 5894d3835..48f2ace61 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -9,8 +9,9 @@ app_name = 'perms' router = routers.DefaultRouter() router.register('actions', api.ActionViewSet, 'action') router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission') +router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remote-app-permission') -urlpatterns = [ +asset_permission_urlpatterns = [ # 查询某个用户授权的资产和资产组 path('user//assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'), @@ -35,7 +36,6 @@ urlpatterns = [ path('user/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-assets-as-tree'), - # 查询某个用户组授权的资产和资产组 path('user-group//assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), @@ -72,5 +72,48 @@ urlpatterns = [ name='get-user-asset-permission-actions'), ] + +remote_app_permission_urlpatterns = [ + # 查询用户授权的RemoteApp + path('user//remote-apps/', + api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'), + path('user/remote-apps/', + api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'), + + # 获取用户授权的RemoteApp树 + path('user//remote-apps/tree/', + api.UserGrantedRemoteAppsAsTreeApi.as_view(), + name='user-remote-apps-as-tree'), + path('user/remote-apps/tree/', + api.UserGrantedRemoteAppsAsTreeApi.as_view(), + name='my-remote-apps-as-tree'), + + # 查询用户组授权的RemoteApp + path('user-group//remote-apps/', + api.UserGroupGrantedRemoteAppsApi.as_view(), + name='user-group-remote-apps'), + + # 校验用户对RemoteApp的权限 + path('remote-app-permission/user/validate/', + api.ValidateUserRemoteAppPermissionApi.as_view(), + name='validate-user-remote-app-permission'), + + # 用户和RemoteApp变更 + path('remote-app-permissions//user/add/', + api.RemoteAppPermissionAddUserApi.as_view(), + name='remote-app-permission-add-user'), + path('remote-app-permissions//user/remove/', + api.RemoteAppPermissionRemoveUserApi.as_view(), + name='remote-app-permission-remove-user'), + path('remote-app-permissions//remote-app/remove/', + api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), + name='remote-app-permission-remove-remote-app'), + path('remote-app-permissions//remote-app/add/', + api.RemoteAppPermissionAddRemoteAppApi.as_view(), + name='remote-app-permission-add-remote-app'), +] + +urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns + urlpatterns += router.urls diff --git a/apps/perms/urls/views_urls.py b/apps/perms/urls/views_urls.py index 8b8348d28..964025db3 100644 --- a/apps/perms/urls/views_urls.py +++ b/apps/perms/urls/views_urls.py @@ -7,6 +7,7 @@ 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'), @@ -14,4 +15,12 @@ urlpatterns = [ 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'), ] diff --git a/apps/perms/utils/__init__.py b/apps/perms/utils/__init__.py new file mode 100644 index 000000000..129901afc --- /dev/null +++ b/apps/perms/utils/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# + +from .asset_permission import * +from .remote_app_permission import * diff --git a/apps/perms/utils.py b/apps/perms/utils/asset_permission.py similarity index 86% rename from apps/perms/utils.py rename to apps/perms/utils/asset_permission.py index bd5094def..fb0676a5e 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils/asset_permission.py @@ -12,12 +12,19 @@ from django.conf import settings from common.utils import get_logger from common.tree import TreeNode -from .models import AssetPermission, Action -from .hands import Node +from perms.models import AssetPermission, Action +from perms.hands import Node logger = get_logger(__file__) +__all__ = [ + 'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets', + 'parse_asset_to_tree_node', 'parse_node_to_tree_node', + 'check_system_user_action', +] + + class GenerateTree: def __init__(self): """ @@ -153,51 +160,53 @@ class AssetPermissionUtil: self._permissions = self.permissions.filter(**filters) self._filter_id = md5(filters_json.encode()).hexdigest() + @staticmethod + def _structured_system_user(system_users, actions): + """ + 结构化系统用户 + :param system_users: + :param actions: + :return: {system_user1: {'actions': set(), }, } + """ + _attr = {'actions': set(actions)} + _system_users = {system_user: _attr for system_user in system_users} + return _system_users + def get_nodes_direct(self): """ 返回用户/组授权规则直接关联的节点 - :return: {node1: set(system_user1,)} + :return: {asset1: {system_user1: {'actions': set()},}} """ - nodes = defaultdict(set) + nodes = defaultdict(dict) permissions = self.permissions.prefetch_related('nodes', 'system_users') for perm in permissions: + actions = perm.actions.all() for node in perm.nodes.all(): - nodes[node].update(perm.system_users.all()) + system_users = perm.system_users.all() + system_users = self._structured_system_user(system_users, actions) + nodes[node].update(system_users) return nodes def get_assets_direct(self): """ + 返回用户授权规则直接关联的资产 - :return: {asset1: set(system_user1,)} + :return: {asset1: {system_user1: {'actions': set()},}} """ - assets = defaultdict(set) + assets = defaultdict(dict) permissions = self.permissions.prefetch_related('assets', 'system_users') for perm in permissions: + actions = perm.actions.all() for asset in perm.assets.all().valid().prefetch_related('nodes'): - assets[asset].update( - perm.system_users.filter(protocol=asset.protocol) - ) + system_users = perm.system_users.filter(protocol=asset.protocol) + system_users = self._structured_system_user(system_users, actions) + assets[asset].update(system_users) return assets - def _setattr_actions_to_system_user(self): - """ - 动态给system_use设置属性actions - """ - for asset, system_users in self._assets.items(): - # 获取资产和资产的祖先节点的所有授权规则 - perms = get_asset_permissions(asset, include_node=True) - # 过滤当前self.permission的授权规则 - perms = perms.filter(id__in=[perm.id for perm in self.permissions]) - - for system_user in system_users: - actions = set() - _perms = perms.filter(system_users=system_user).\ - prefetch_related('actions') - for _perm in _perms: - actions.update(_perm.actions.all()) - setattr(system_user, 'actions', actions) - def get_assets_without_cache(self): + """ + :return: {asset1: set(system_user1,)} + """ if self._assets: return self._assets assets = self.get_assets_direct() @@ -205,11 +214,22 @@ class AssetPermissionUtil: for node, system_users in nodes.items(): _assets = node.get_all_assets().valid().prefetch_related('nodes') for asset in _assets: - assets[asset].update( - [s for s in system_users if s.protocol == asset.protocol] - ) - self._assets = assets - self._setattr_actions_to_system_user() + for system_user, attr_dict in system_users.items(): + if system_user.protocol != asset.protocol: + continue + if system_user in assets[asset]: + actions = assets[asset][system_user]['actions'] + attr_dict['actions'].update(actions) + system_users.update({system_user: attr_dict}) + assets[asset].update(system_users) + + __assets = defaultdict(set) + for asset, system_users in assets.items(): + for system_user, attr_dict in system_users.items(): + setattr(system_user, 'actions', attr_dict['actions']) + __assets[asset] = set(system_users.keys()) + + self._assets = __assets return self._assets def get_cache_key(self, resource): @@ -378,7 +398,7 @@ def sort_assets(assets, order_by='hostname', reverse=False): def parse_node_to_tree_node(node): - from . import serializers + from .. import serializers name = '{} ({})'.format(node.value, node.assets_amount) node_serializer = serializers.GrantedNodeSerializer(node) data = { @@ -444,11 +464,6 @@ def parse_asset_to_tree_node(node, asset, system_users): return tree_node -# -# actions -# - - def check_system_user_action(system_user, action): """ :param system_user: SystemUser object (包含动态属性: actions) diff --git a/apps/perms/utils/remote_app_permission.py b/apps/perms/utils/remote_app_permission.py new file mode 100644 index 000000000..0f67e32dc --- /dev/null +++ b/apps/perms/utils/remote_app_permission.py @@ -0,0 +1,81 @@ +# coding: utf-8 +# + +from django.db.models import Q + +from common.tree import TreeNode + +from ..models import RemoteAppPermission + + +__all__ = [ + 'RemoteAppPermissionUtil', + 'construct_remote_apps_tree_root', + 'parse_remote_app_to_tree_node', +] + + +def get_user_remote_app_permissions(user, include_group=True): + if include_group: + groups = user.groups.all() + arg = Q(users=user) | Q(user_groups__in=groups) + else: + arg = Q(users=user) + return RemoteAppPermission.objects.all().valid().filter(arg) + + +def get_user_group_remote_app_permissions(user_group): + return RemoteAppPermission.objects.all().valid().filter( + user_groups=user_group + ) + + +class RemoteAppPermissionUtil: + get_permissions_map = { + "User": get_user_remote_app_permissions, + "UserGroup": get_user_group_remote_app_permissions, + } + + def __init__(self, obj): + self.object = obj + + @property + def permissions(self): + obj_class = self.object.__class__.__name__ + func = self.get_permissions_map[obj_class] + _permissions = func(self.object) + return _permissions + + def get_remote_apps(self): + remote_apps = set() + for perm in self.permissions: + remote_apps.update(list(perm.remote_apps.all())) + return remote_apps + + +def construct_remote_apps_tree_root(): + tree_root = { + 'id': 'ID_REMOTE_APP_ROOT', + 'name': 'RemoteApp', + 'title': 'RemoteApp', + 'pId': '', + 'open': False, + 'isParent': True, + 'iconSkin': '', + 'meta': {'type': 'remote_app'} + } + return TreeNode(**tree_root) + + +def parse_remote_app_to_tree_node(parent, remote_app): + tree_node = { + 'id': remote_app.id, + 'name': remote_app.name, + 'title': remote_app.name, + 'pId': parent.id, + 'open': False, + 'isParent': False, + 'iconSkin': 'file', + 'meta': {'type': 'remote_app'} + } + return TreeNode(**tree_node) diff --git a/apps/perms/views/__init__.py b/apps/perms/views/__init__.py new file mode 100644 index 000000000..129901afc --- /dev/null +++ b/apps/perms/views/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# + +from .asset_permission import * +from .remote_app_permission import * diff --git a/apps/perms/views.py b/apps/perms/views/asset_permission.py similarity index 89% rename from apps/perms/views.py rename to apps/perms/views/asset_permission.py index 0e02b38a7..e85acaf39 100644 --- a/apps/perms/views.py +++ b/apps/perms/views/asset_permission.py @@ -10,10 +10,19 @@ from django.conf import settings from common.permissions import AdminUserRequiredMixin from orgs.utils import current_org -from .hands import Node, Asset, SystemUser, User, UserGroup -from .models import AssetPermission, Action -from .forms import AssetPermissionForm -from .const import PERMS_ACTION_NAME_ALL +from perms.hands import Node, Asset, SystemUser, User, UserGroup +from perms.models import AssetPermission, Action +from perms.forms import AssetPermissionForm +from perms.const import PERMS_ACTION_NAME_ALL + + +__all__ = [ + 'AssetPermissionListView', 'AssetPermissionCreateView', + 'AssetPermissionUpdateView', 'AssetPermissionDetailView', + 'AssetPermissionDeleteView', 'AssetPermissionUserView', + 'AssetPermissionAssetView', + +] class AssetPermissionListView(AdminUserRequiredMixin, TemplateView): @@ -84,7 +93,7 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView): def get_context_data(self, **kwargs): context = { 'app': _('Perms'), - 'action': _('Update asset permission'), + 'action': _('Asset permission detail'), 'system_users_remain': SystemUser.objects.exclude( granted_by_permissions=self.object ), @@ -121,10 +130,10 @@ class AssetPermissionUserView(AdminUserRequiredMixin, 'app': _('Perms'), 'action': _('Asset permission user list'), 'users_remain': current_org.get_org_users().exclude( - asset_permissions=self.object + assetpermission=self.object ), 'user_groups_remain': UserGroup.objects.exclude( - asset_permissions=self.object + assetpermission=self.object ) } kwargs.update(context) diff --git a/apps/perms/views/remote_app_permission.py b/apps/perms/views/remote_app_permission.py new file mode 100644 index 000000000..2e3db2f17 --- /dev/null +++ b/apps/perms/views/remote_app_permission.py @@ -0,0 +1,144 @@ +# 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 AdminUserRequiredMixin +from orgs.utils import current_org +from users.models import UserGroup +from assets.models import RemoteApp + +from ..models import RemoteAppPermission +from ..forms import RemoteAppPermissionCreateUpdateForm + + +__all__ = [ + 'RemoteAppPermissionListView', 'RemoteAppPermissionCreateView', + 'RemoteAppPermissionUpdateView', 'RemoteAppPermissionDetailView', + 'RemoteAppPermissionUserView', 'RemoteAppPermissionRemoteAppView' +] + + +class RemoteAppPermissionListView(AdminUserRequiredMixin, TemplateView): + template_name = 'perms/remote_app_permission_list.html' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Perms'), + 'action': _('RemoteApp permission list'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppPermissionCreateView(AdminUserRequiredMixin, CreateView): + template_name = 'perms/remote_app_permission_create_update.html' + model = RemoteAppPermission + form_class = RemoteAppPermissionCreateUpdateForm + success_url = reverse_lazy('perms:remote-app-permission-list') + + def get_context_data(self, **kwargs): + context = { + 'app': _('Perms'), + 'action': _('Create RemoteApp permission'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppPermissionUpdateView(AdminUserRequiredMixin, UpdateView): + template_name = 'perms/remote_app_permission_create_update.html' + model = RemoteAppPermission + form_class = RemoteAppPermissionCreateUpdateForm + success_url = reverse_lazy('perms:remote-app-permission-list') + + def get_context_data(self, **kwargs): + context = { + 'app': _('Perms'), + 'action': _('Update RemoteApp permission') + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppPermissionDetailView(AdminUserRequiredMixin, DetailView): + template_name = 'perms/remote_app_permission_detail.html' + model = RemoteAppPermission + + def get_context_data(self, **kwargs): + context = { + 'app': _('Perms'), + 'action': _('RemoteApp permission detail'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppPermissionUserView(AdminUserRequiredMixin, + SingleObjectMixin, + ListView): + template_name = 'perms/remote_app_permission_user.html' + context_object_name = 'remote_app_permission' + paginate_by = settings.DISPLAY_PER_PAGE + object = None + + 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): + context = { + 'app': _('Perms'), + 'action': _('RemoteApp permission user list'), + 'users_remain': current_org.get_org_users().exclude( + remoteapppermissions=self.object + ), + 'user_groups_remain': UserGroup.objects.exclude( + remoteapppermissions=self.object + ) + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppPermissionRemoteAppView(AdminUserRequiredMixin, + 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 + + 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 a65df113d..29986637e 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -5,7 +5,9 @@ import os import json import jms_storage +from rest_framework import generics from rest_framework.views import Response, APIView +from rest_framework.pagination import LimitOffsetPagination from django.conf import settings from django.core.mail import send_mail from django.utils.translation import ugettext_lazy as _ @@ -79,7 +81,7 @@ class LDAPTestingAPI(APIView): util = self.get_ldap_util(serializer) try: - users = util.get_search_user_items() + users = util.search_user_items() except Exception as e: return Response({"error": str(e)}, status=401) @@ -89,30 +91,66 @@ class LDAPTestingAPI(APIView): return Response({"error": "Have user but attr mapping error"}, status=401) -class LDAPUserListApi(APIView): +class LDAPUserListApi(generics.ListAPIView): + pagination_class = LimitOffsetPagination permission_classes = (IsOrgAdmin,) - def get(self, request): + def get_queryset(self): util = LDAPUtil() try: - users = util.get_search_user_items() + users = util.search_user_items() except Exception as e: users = [] logger.error(e, exc_info=True) + # 前端data_table会根据row.id对table.selected值进行操作 + for user in users: + user['id'] = user['username'] + return users + + def filter_queryset(self, queryset): + search = self.request.query_params.get('search') + if not search: + return queryset + search = search.lower() + queryset = [ + q for q in queryset + if + search in q['username'].lower() + or search in q['name'].lower() + or search in q['email'].lower() + ] + return queryset + + def sort_queryset(self, queryset): + order_by = self.request.query_params.get('order') + if not order_by: + order_by = 'existing' + if order_by.startswith('-'): + order_by = order_by.lstrip('-') + reverse = True else: - users = sorted(users, key=lambda u: (u['existing'], u['username'])) - return Response(users) + reverse = False + queryset = sorted(queryset, key=lambda x: x[order_by], reverse=reverse) + return queryset + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + queryset = self.sort_queryset(queryset) + page = self.paginate_queryset(queryset) + if page is not None: + return self.get_paginated_response(page) + return Response(queryset) class LDAPUserSyncAPI(APIView): permission_classes = (IsOrgAdmin,) def post(self, request): - user_names = request.data.get('user_names', '') + username_list = request.data.get('username_list', []) util = LDAPUtil() try: - result = util.sync_users(username_set=user_names) + result = util.sync_users(username_list) except Exception as e: logger.error(e, exc_info=True) return Response({'error': str(e)}, status=401) @@ -220,7 +258,4 @@ class DjangoSettingsAPI(APIView): data[k] = v except (json.JSONDecodeError, TypeError): data[k] = str(v) - return Response(data) - - - + return Response(data) \ No newline at end of file diff --git a/apps/settings/forms.py b/apps/settings/forms.py index d87845ea9..3f6a690a8 100644 --- a/apps/settings/forms.py +++ b/apps/settings/forms.py @@ -121,9 +121,9 @@ class LDAPSettingForm(BaseForm): ) # 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_START_TLS = forms.BooleanField( + # label=_("Use SSL"), required=False + # ) AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), required=False) @@ -147,7 +147,7 @@ class TerminalSettingForm(BaseForm): required=False, label=_("Public key auth") ) TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField( - min_value=5, label=_("Heartbeat interval"), + min_value=5, max_value=99999, label=_("Heartbeat interval"), help_text=_("Units: seconds") ) TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField( @@ -157,7 +157,7 @@ class TerminalSettingForm(BaseForm): choices=PAGE_SIZE_CHOICES, label=_("List page size"), ) TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField( - min_value=1, label=_("Session keep duration"), + 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") ) @@ -182,11 +182,12 @@ class SecuritySettingForm(BaseForm): ) # limit login count SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField( - min_value=3, label=_("Limit the number of login failures") + 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, label=_("No logon interval"), + 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." @@ -194,7 +195,8 @@ class SecuritySettingForm(BaseForm): ) # ssh max idle time SECURITY_MAX_IDLE_TIME = forms.IntegerField( - required=False, label=_("Connection max idle time"), + min_value=1, max_value=99999, required=False, + label=_("Connection max idle time"), help_text=_( 'If idle time more than it, disconnect connection(only ssh now) ' 'Unit: minute' @@ -202,8 +204,7 @@ class SecuritySettingForm(BaseForm): ) # password expiration time SECURITY_PASSWORD_EXPIRATION_TIME = forms.IntegerField( - label=_("Password expiration time"), - min_value=1, max_value=99999, + 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, " @@ -214,7 +215,7 @@ class SecuritySettingForm(BaseForm): ) # min length SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField( - min_value=6, label=_("Password minimum length"), + min_value=6, max_value=30, label=_("Password minimum length"), ) # upper case SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField( @@ -242,3 +243,26 @@ class SecuritySettingForm(BaseForm): 'and resets must contain special characters') ) + +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/templates/settings/_ldap_list_users_modal.html b/apps/settings/templates/settings/_ldap_list_users_modal.html index 2009e9b58..c0d062714 100644 --- a/apps/settings/templates/settings/_ldap_list_users_modal.html +++ b/apps/settings/templates/settings/_ldap_list_users_modal.html @@ -52,12 +52,15 @@ var ldap_users_table = 0; function initLdapUsersTable() { if(ldap_users_table){ - return + return ldap_users_table } var options = { ele: $('#ldap_list_users_table'), ajax_url: '{% url "api-settings:ldap-user-list" %}', columnDefs: [ + {targets: 0, createdCell: function (td, cellData, rowData) { + $(td).html("".replace("ID_USERNAME", cellData)) + }}, {targets: 4, createdCell: function (td, cellData, rowData) { if(cellData){ $(td).html('') @@ -70,10 +73,10 @@ function initLdapUsersTable() { {data: "username" },{data: "username" }, {data: "name" }, {data:"email"}, {data:'existing'} ], - pageLength: 10 + pageLength: 15 }; - ldap_users_table = jumpserver.initDataTable(options); + ldap_users_table = jumpserver.initServerSideDataTable(options); return ldap_users_table } diff --git a/apps/settings/templates/settings/basic_setting.html b/apps/settings/templates/settings/basic_setting.html index 17c8057bc..4c26e8bb3 100644 --- a/apps/settings/templates/settings/basic_setting.html +++ b/apps/settings/templates/settings/basic_setting.html @@ -17,6 +17,9 @@
  • {% trans 'Email setting' %}
  • +
  • + {% trans 'Email content setting' %} +
  • {% trans 'LDAP setting' %}
  • diff --git a/apps/settings/templates/settings/email_content_setting.html b/apps/settings/templates/settings/email_content_setting.html new file mode 100644 index 000000000..16cac426e --- /dev/null +++ b/apps/settings/templates/settings/email_content_setting.html @@ -0,0 +1,72 @@ +{% extends 'base.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block content %} +
    +
    +
    +
    + +
    +
    +
    +
    + {% if form.non_field_errors %} +
    + {{ form.non_field_errors }} +
    + {% endif %} + {% csrf_token %} + +

    {% trans "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 index 46846d7dc..46c4f5dac 100644 --- a/apps/settings/templates/settings/email_setting.html +++ b/apps/settings/templates/settings/email_setting.html @@ -17,6 +17,9 @@
  • {% trans 'Email setting' %}
  • +
  • + {% trans 'Email content setting' %} +
  • {% trans 'LDAP setting' %}
  • diff --git a/apps/settings/templates/settings/ldap_setting.html b/apps/settings/templates/settings/ldap_setting.html index 63d56cea0..58e4ae71b 100644 --- a/apps/settings/templates/settings/ldap_setting.html +++ b/apps/settings/templates/settings/ldap_setting.html @@ -17,6 +17,9 @@
  • {% trans 'Email setting' %}
  • +
  • + {% trans 'Email content setting' %} +
  • {% trans 'LDAP setting' %}
  • @@ -107,13 +110,10 @@ $(document).ready(function () { }); }) .on("click","#btn_ldap_modal_confirm",function () { - var user_names=[]; - $("tbody input[type='checkbox']:checked").each(function () { - user_names.push($(this).attr('id')); - }); + var username_list = ldap_users_table.selected; - if (user_names.length === 0){ - var msg = "{% trans 'User is not currently selected, please check the user you want to import'%}" + if (username_list.length === 0){ + var msg = "{% trans 'User is not currently selected, please check the user you want to import'%}"; toastr.error(msg); return } @@ -129,7 +129,7 @@ $(document).ready(function () { } APIUpdateAttr({ url: the_url, - body: JSON.stringify({'user_names':user_names}), + body: JSON.stringify({'username_list':username_list}), method: "POST", flash_message: false, success: success, diff --git a/apps/settings/templates/settings/security_setting.html b/apps/settings/templates/settings/security_setting.html index adc00191f..48206676d 100644 --- a/apps/settings/templates/settings/security_setting.html +++ b/apps/settings/templates/settings/security_setting.html @@ -16,6 +16,9 @@
  • {% trans 'Email setting' %} +
  • +
  • + {% trans 'Email content setting' %}
  • {% trans 'LDAP setting' %} @@ -30,8 +33,8 @@
    -
    -
    +
    + {% if form.non_field_errors %}
    {{ form.non_field_errors }} diff --git a/apps/settings/templates/settings/terminal_setting.html b/apps/settings/templates/settings/terminal_setting.html index e6eb72982..3a9a4973d 100644 --- a/apps/settings/templates/settings/terminal_setting.html +++ b/apps/settings/templates/settings/terminal_setting.html @@ -18,6 +18,9 @@
  • {% trans 'Email setting' %} +
  • +
  • + {% trans 'Email content setting' %}
  • "; + html += li + } + }); + html = "
      " + html + "
    " + } + else { + html = error.responseText + } + if(props.method === 'POST'){ + $('#success_created').html(''); + $('#success_created_detail').html(''); + $('#created_failed').html(gettext("Import failed")); + $('#created_failed_detail').html(html); + }else{ + $('#success_updated').html(''); + $('#success_updated_detail').html(''); + $('#updated_failed').html(gettext("Update failed")); + $('#updated_failed_detail').html(html); + } + } + }) +} + function htmlEscape ( d ) { return typeof d === 'string' ? d.replace(//g, '>').replace(/"/g, '"') : d; -} \ No newline at end of file +} diff --git a/apps/static/js/plugins/clipboard/clipboard.min.js b/apps/static/js/plugins/clipboard/clipboard.min.js new file mode 100755 index 000000000..4e2a012a6 --- /dev/null +++ b/apps/static/js/plugins/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v1.5.5 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,r){function o(a,c){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!c&&s)return s(a,!0);if(i)return i(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[a]={exports:{}};e[a][0].call(l.exports,function(t){var n=e[a][1][t];return o(n?n:t)},l,l.exports,t,e,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;ar;r++)n[r].fn.apply(n[r].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),r=n[t],o=[];if(r&&e)for(var i=0,a=r.length;a>i;i++)r[i].fn!==e&&r[i].fn._!==e&&o.push(r[i]);return o.length?n[t]=o:delete n[t],this}},e.exports=r},{}],8:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}n.__esModule=!0;var i=function(){function t(t,e){for(var n=0;n + {% csrf_token %} +
    + +
    + + +
    + + +
    +

    +

    +

    +

    +
    +{% endblock %} diff --git a/apps/templates/_modal.html b/apps/templates/_modal.html index 237e5618b..7b5f55de4 100644 --- a/apps/templates/_modal.html +++ b/apps/templates/_modal.html @@ -8,7 +8,7 @@
  • + diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 5c3124f8e..1acb752c9 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -27,12 +27,27 @@
  • {% trans 'Command filters' %}
  • +{% if LICENSE_VALID %} +
  • + + {% trans 'Applications' %} + + +
  • +{% endif %}
  • {% trans 'Perms' %}
  • diff --git a/apps/templates/_nav_user.html b/apps/templates/_nav_user.html index 151ed124e..5412dc37d 100644 --- a/apps/templates/_nav_user.html +++ b/apps/templates/_nav_user.html @@ -4,6 +4,18 @@ {% trans 'My assets' %}
  • +
  • + + {% trans 'My Applications' %} + + +
  • {% trans 'Command execution' %} diff --git a/apps/templates/_update_modal.html b/apps/templates/_update_modal.html new file mode 100644 index 000000000..db2b14110 --- /dev/null +++ b/apps/templates/_update_modal.html @@ -0,0 +1,28 @@ +{% extends '_modal.html' %} +{% load i18n %} + +{% block modal_id %}update_modal{% endblock %} + +{% block modal_confirm_id %}btn_update_confirm{% endblock %} + +{% block modal_body %} +
    + {% csrf_token %} + + +
    + + +
    +
    + +
    +

    +

    +

    +

    +
    +{% endblock %} diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index 39d18b159..f52d7b2b7 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -67,7 +67,6 @@ class CommandViewSet(viewsets.ViewSet): """ command_store = get_command_storage() - multi_command_storage = get_multi_command_storage() serializer_class = SessionCommandSerializer permission_classes = (IsOrgAdminOrAppUser,) @@ -88,7 +87,8 @@ class CommandViewSet(viewsets.ViewSet): return Response({"msg": msg}, status=401) def list(self, request, *args, **kwargs): - queryset = self.multi_command_storage.filter() + multi_command_storage = get_multi_command_storage() + queryset = multi_command_storage.filter() serializer = self.serializer_class(queryset, many=True) return Response(serializer.data) diff --git a/apps/terminal/templatetags/terminal_tags.py b/apps/terminal/templatetags/terminal_tags.py index e8ba23186..c0844eb31 100644 --- a/apps/terminal/templatetags/terminal_tags.py +++ b/apps/terminal/templatetags/terminal_tags.py @@ -5,10 +5,10 @@ from django import template from ..backends import get_multi_command_storage register = template.Library() -command_store = get_multi_command_storage() @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/views/session.py b/apps/terminal/views/session.py index bc49e1b65..706d6ee8a 100644 --- a/apps/terminal/views/session.py +++ b/apps/terminal/views/session.py @@ -19,7 +19,6 @@ __all__ = [ 'SessionDetailView', ] -command_store = get_multi_command_storage() class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): @@ -108,6 +107,7 @@ class SessionDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView): 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): diff --git a/apps/users/api/group.py b/apps/users/api/group.py index 8cf9fcb0e..fc9a84928 100644 --- a/apps/users/api/group.py +++ b/apps/users/api/group.py @@ -9,13 +9,13 @@ from ..serializers import UserGroupSerializer, \ UserGroupUpdateMemberSerializer from ..models import UserGroup from common.permissions import IsOrgAdmin -from common.mixins import IDInFilterMixin +from common.mixins import IDInCacheFilterMixin __all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi'] -class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet): +class UserGroupViewSet(IDInCacheFilterMixin, BulkModelViewSet): filter_fields = ("name",) search_fields = filter_fields queryset = UserGroup.objects.all() diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 63c3dab12..c7668cb86 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -15,7 +15,7 @@ from rest_framework.pagination import LimitOffsetPagination from common.permissions import ( IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser ) -from common.mixins import IDInFilterMixin +from common.mixins import IDInCacheFilterMixin from common.utils import get_logger from orgs.utils import current_org from ..serializers import UserSerializer, UserPKUpdateSerializer, \ @@ -32,7 +32,7 @@ __all__ = [ ] -class UserViewSet(IDInFilterMixin, BulkModelViewSet): +class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): filter_fields = ('username', 'email', 'name', 'id') search_fields = filter_fields queryset = User.objects.exclude(role=User.ROLE_APP) @@ -40,9 +40,15 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet): permission_classes = (IsOrgAdmin,) pagination_class = LimitOffsetPagination + def send_created_signal(self, users): + if not isinstance(users, list): + users = [users] + for user in users: + post_user_create.send(self.__class__, user=user) + def perform_create(self, serializer): - user = serializer.save() - post_user_create.send(self.__class__, user=user) + users = serializer.save() + self.send_created_signal(users) def get_queryset(self): queryset = current_org.get_org_users() @@ -213,4 +219,4 @@ class UserResetOTPApi(generics.RetrieveAPIView): user.otp_secret_key = '' user.save() logout(request) - return Response({"msg": "success"}) + return Response({"msg": "success"}) \ No newline at end of file diff --git a/apps/users/forms.py b/apps/users/forms.py index 2ab6f11ac..43ef0b594 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -21,7 +21,7 @@ class UserCheckOtpCodeForm(forms.Form): otp_code = forms.CharField(label=_('MFA code'), max_length=6) -class UserCreateUpdateForm(OrgModelForm): +class UserCreateUpdateFormMixin(OrgModelForm): role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP) password = forms.CharField( label=_('Password'), widget=forms.PasswordInput, @@ -55,7 +55,7 @@ class UserCreateUpdateForm(OrgModelForm): def __init__(self, *args, **kwargs): self.request = kwargs.pop("request", None) - super(UserCreateUpdateForm, self).__init__(*args, **kwargs) + super(UserCreateUpdateFormMixin, self).__init__(*args, **kwargs) roles = [] # Super admin user @@ -105,6 +105,23 @@ class UserCreateUpdateForm(OrgModelForm): return user +class UserCreateForm(UserCreateUpdateFormMixin): + 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 = forms.ChoiceField( + choices=PASSWORD_STRATEGY_CHOICES, required=True, initial=0, + widget=forms.RadioSelect(), label=_('Password strategy') + ) + + +class UserUpdateForm(UserCreateUpdateFormMixin): + pass + + class UserProfileForm(forms.ModelForm): username = forms.CharField(disabled=True) name = forms.CharField(disabled=True) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 9d2fc1a08..70b774486 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -147,6 +147,10 @@ class User(AbstractUser): def otp_secret_key(self, item): self._otp_secret_key = signer.sign(item) + def check_otp(self, code): + from ..utils import check_otp_code + return check_otp_code(self.otp_secret_key, code) + def get_absolute_url(self): return reverse('users:user-detail', args=(self.id,)) diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/v1.py index b8c91417d..a0a13d4fa 100644 --- a/apps/users/serializers/v1.py +++ b/apps/users/serializers/v1.py @@ -19,12 +19,21 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): list_serializer_class = AdaptedBulkListSerializer fields = [ 'id', 'name', 'username', 'email', 'groups', 'groups_display', - 'role', 'role_display', 'avatar_url', 'wechat', 'phone', - 'otp_level', 'comment', 'source', 'source_display', - 'is_valid', 'is_expired', 'is_active', - 'created_by', 'is_first_login', - 'date_password_last_updated', 'date_expired', + 'role', 'role_display', 'wechat', 'phone', 'otp_level', + 'comment', 'source', 'source_display', 'is_valid', 'is_expired', + 'is_active', 'created_by', 'is_first_login', + 'date_password_last_updated', 'date_expired', 'avatar_url', ] + extra_kwargs = { + 'groups_display': {'label': _('Groups name')}, + 'source_display': {'label': _('Source name')}, + 'is_first_login': {'label': _('Is first login'), 'read_only': True}, + 'role_display': {'label': _('Role name')}, + 'is_valid': {'label': _('Is valid')}, + 'is_expired': {'label': _('Is expired')}, + 'avatar_url': {'label': _('Avatar url')}, + 'created_by': {'read_only': True}, 'source': {'read_only': True} + } class UserPKUpdateSerializer(serializers.ModelSerializer): @@ -48,17 +57,20 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer): class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): - users = serializers.SerializerMethodField() + users = serializers.PrimaryKeyRelatedField( + required=False, many=True, queryset=User.objects.all(), label=_('User') + ) class Meta: model = UserGroup list_serializer_class = AdaptedBulkListSerializer - fields = '__all__' - read_only_fields = ['created_by'] - - @staticmethod - def get_users(obj): - return [user.name for user in obj.users.all()] + fields = [ + 'id', 'org_id', 'name', 'users', 'comment', 'date_created', + 'created_by', + ] + extra_kwargs = { + 'created_by': {'label': _('Created by'), 'read_only': True} + } class UserGroupUpdateMemberSerializer(serializers.ModelSerializer): diff --git a/apps/users/signals_handler.py b/apps/users/signals_handler.py index 1bc3ef430..4c6afc663 100644 --- a/apps/users/signals_handler.py +++ b/apps/users/signals_handler.py @@ -28,4 +28,3 @@ def on_user_create(sender, user=None, **kwargs): logger.info(" - Sending welcome mail ...".format(user.name)) if user.email: send_user_created_mail(user) - diff --git a/apps/users/templates/users/_user.html b/apps/users/templates/users/_user.html index 1db09db39..0e8764f6a 100644 --- a/apps/users/templates/users/_user.html +++ b/apps/users/templates/users/_user.html @@ -74,6 +74,9 @@ $(document).ready(function () { $('.select2').select2(); $('#id_date_expired').daterangepicker(dateOptions); + var mfa_radio = $('#id_otp_level'); + mfa_radio.addClass("form-inline"); + mfa_radio.children().css("margin-right","15px") }) {% endblock %} diff --git a/apps/users/templates/users/_user_groups_import_modal.html b/apps/users/templates/users/_user_groups_import_modal.html new file mode 100644 index 000000000..63d057215 --- /dev/null +++ b/apps/users/templates/users/_user_groups_import_modal.html @@ -0,0 +1,6 @@ +{% extends '_import_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Import user groups" %}{% endblock %} + +{% block import_modal_download_template_url %}{% url "api-users:user-group-list" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/_user_groups_update_modal.html b/apps/users/templates/users/_user_groups_update_modal.html new file mode 100644 index 000000000..a07c0f82c --- /dev/null +++ b/apps/users/templates/users/_user_groups_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update user group" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/_user_import_modal.html b/apps/users/templates/users/_user_import_modal.html index 678023cfc..e53d67fa7 100644 --- a/apps/users/templates/users/_user_import_modal.html +++ b/apps/users/templates/users/_user_import_modal.html @@ -1,28 +1,6 @@ -{% extends '_modal.html' %} +{% extends '_import_modal.html' %} {% load i18n %} -{% block modal_id %}user_import_modal{% endblock %} -{% block modal_title%}{% trans "Import user" %}{% endblock %} -{% block modal_body %} -

    {% trans "Download template or use export csv format" %}

    -
    - {% csrf_token %} -
    - - {% trans 'Download' %} -
    -
    - - - {% trans 'If set id, will use this id update user existed' %} -
    -
    -

    -

    -

    -

    -

    -

    -

    -

    -{% endblock %} -{% block modal_confirm_id %}btn_user_import{% endblock %} + +{% block modal_title%}{% trans "Import users" %}{% endblock %} + +{% block import_modal_download_template_url %}{% url "api-users:user-list" %}{% endblock %} diff --git a/apps/users/templates/users/_user_update_modal.html b/apps/users/templates/users/_user_update_modal.html new file mode 100644 index 000000000..9dfe60c96 --- /dev/null +++ b/apps/users/templates/users/_user_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update user" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/user_create.html b/apps/users/templates/users/user_create.html index 5760571e1..13bb26cbc 100644 --- a/apps/users/templates/users/user_create.html +++ b/apps/users/templates/users/user_create.html @@ -2,15 +2,83 @@ {% load i18n %} {% load bootstrap3 %} {% block user_template_title %}{% trans "Create user" %}{% endblock %} -{#{% block username %}#} -{# {% bootstrap_field form.username layout="horizontal" %}#} -{#{% endblock %}#} {% block password %} -
    - -
    - {% trans 'Reset link will be generated and sent to the user. ' %} + {% bootstrap_field form.password_strategy layout="horizontal" %} +
    + {% bootstrap_field form.password layout="horizontal" %} +
    + {# 密码popover #} +
    +
    + {% endblock %} diff --git a/apps/users/templates/users/user_group_list.html b/apps/users/templates/users/user_group_list.html index 6f6c6fc72..c2839cad9 100644 --- a/apps/users/templates/users/user_group_list.html +++ b/apps/users/templates/users/user_group_list.html @@ -1,6 +1,29 @@ {% extends '_base_list.html' %} {% load i18n static %} -{% block table_search %}{% endblock %} +{% block table_search %} + +{% endblock %} {% block table_container %} @@ -16,13 +39,15 @@
    - +{% include "users/_user_groups_import_modal.html" %} +{% include "users/_user_groups_update_modal.html" %} {% endblock %} {% block content_bottom_left %}{% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index 15b5fb2f9..cf2a764b4 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -1,16 +1,28 @@ {% extends '_base_list.html' %} {% load i18n static %} {% block table_search %} - + {% endblock %} {% block table_container %} @@ -48,12 +60,13 @@
    {% include "users/_user_import_modal.html" %} +{% include "users/_user_update_modal.html" %} {% endblock %} {% block content_bottom_left %}{% endblock %} {% block custom_foot_js %}