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 %} +
+{% 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 %} +{% 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 }} | +
+ + | +{% trans 'Name' %} | +{% trans 'App type' %} | +{% trans 'Asset' %} | +{% trans 'System user' %} | +{% trans 'Comment' %} | +{% trans 'Action' %} | +
---|
+ + | +{% trans 'Name' %} | +{% trans 'App type' %} | +{% trans 'Asset' %} | +{% trans 'System user' %} | +{% trans 'Comment' %} | +{% trans 'Action' %} | +
---|
-
- - - - - - -{% 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 %} + + + + +{% 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 %} {% endblock %} +{% block table_search %} +\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{% 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 'Active' %} : | +
+
+
+
+
+
+
+ |
+
+ + | +{% trans 'Name' %} | +{% trans 'User' %} | +{% trans 'User group' %} | +{% trans 'RemoteApp' %} | +{% trans 'Validity' %} | +{% trans 'Action' %} | +
---|
{% trans 'Name' %} | +{% trans 'Type' %} | ++ |
---|---|---|
{{ remote_app.name }} | +{{ remote_app.get_type_display }} | ++ + | +
{{ user_group }} | ++ + | +