Unverified Commit e1919d0a authored by 老广's avatar 老广 Committed by GitHub

Asset meta (#3539)

- 更改了资产表单,影响
  - 资产创建和更新
- 增加了资产平台数据库,影响
  - 平台创建更新和删除
- 更改了资产的platform字段,又一个字符字段,改为一个外键,影响 
  - 资产创建和更新
  - 资产连接 [windows,linux]
  - 测试连接等ansible任务
  - 自动化云导入
- 更改了资产的序列化器,影响
  - 资产创建更新列表
- 统一了树列表基础模板,影响
  - 资产列表页,权限列表页,vault页,资产收集页
- 统一了导入导出组件,影响
  - 资产导入导出
  - 用户导入导出
  - 用户组导入导出
  - 系统用户导入导出
  - 管理用户导入导出
  - vault导出导出
  - 收集用户列表导入导出
- 修改用户更新密码信号,影响
  - 修改用户密码产生的改密日志

- 新增Model instance序列化工具函数,影响
  - 操作日志生成
- 修改api mixin,新增 serializer_classes字段,serializer_classes = {"default": "", "display": "", "list": .., "other_action": ""}, 根据用户请求的方式返回不同的serializer_class,影响

  - 用户的viewset
  - 资产权限的viewset
- 统一系统配置中的tab切换
- 统一没有nav的页面,影响
  - 重置密码
  - 忘记密码
  - 重置中设置密码
  - 独立的message页面
- 修改用户组列表页,不再返还用户组下的用户,仅有数量
- 组织的一些方法变为layzproperty,避免重复计算
- 修改用户组详情页,影响
  - 用户组增加删除用户

  
parent 4ac4b517
......@@ -4,24 +4,27 @@
import random
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from rest_framework.generics import RetrieveAPIView
from django.shortcuts import get_object_or_404
from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsSuperUser
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from ..models import Asset, Node
from ..models import Asset, Node, Platform
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectivity_manual
from ..tasks import (
update_asset_hardware_info_manual, test_asset_connectivity_manual
)
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
logger = get_logger(__file__)
__all__ = [
'AssetViewSet',
'AssetViewSet', 'AssetPlatformRetrieveApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi',
'AssetGatewayApi', 'AssetPlatformViewSet',
]
......@@ -53,6 +56,34 @@ class AssetViewSet(OrgBulkModelViewSet):
self.set_assets_node(assets)
class AssetPlatformRetrieveApi(RetrieveAPIView):
queryset = Platform.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.PlatformSerializer
def get_object(self):
asset_pk = self.kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_pk)
return asset.platform
class AssetPlatformViewSet(ModelViewSet):
queryset = Platform.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = serializers.PlatformSerializer
filterset_fields = ['name', 'base']
search_fields = ['name']
def check_object_permissions(self, request, obj):
if request.method.lower() in ['delete', 'put', 'patch'] and \
obj.internal:
self.permission_denied(
request, message={"detail": "Internal platform"}
)
return super().check_object_permissions(request, obj)
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
"""
Refresh asset hardware info
......
......@@ -177,7 +177,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi):
if not include_assets:
return queryset
assets = self.instance.get_assets().only(
"id", "hostname", "ip", 'platform', "os",
"id", "hostname", "ip", "os",
"org_id", "protocols",
)
for asset in assets:
......
......@@ -5,3 +5,4 @@ from .label import *
from .user import *
from .domain import *
from .cmd_filter import *
from .platform import *
......@@ -6,13 +6,13 @@ from django.utils.translation import gettext_lazy as _
from common.utils import get_logger
from orgs.mixins.forms import OrgModelForm
from ..models import Asset, Node
from ..models import Asset
from ..const import GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT
logger = get_logger(__file__)
__all__ = [
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
'AssetCreateUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
]
......@@ -27,17 +27,27 @@ class ProtocolForm(forms.Form):
)
class AssetCreateForm(OrgModelForm):
class AssetCreateUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.data:
return
self.set_platform_to_name()
self.set_fields_queryset()
def set_fields_queryset(self):
nodes_field = self.fields['nodes']
nodes_choices = []
if self.instance:
nodes_field.choices = [(n.id, n.full_value) for n in
self.instance.nodes.all()]
else:
nodes_field.choices = []
nodes_choices = [
(n.id, n.full_value) for n in
self.instance.nodes.all()
]
nodes_field.choices = nodes_choices
def set_platform_to_name(self):
platform_field = self.fields['platform']
platform_field.to_field_name = 'name'
if self.instance:
self.initial['platform'] = self.instance.platform.name
def add_nodes_initial(self, node):
nodes_field = self.fields['nodes']
......@@ -49,7 +59,7 @@ class AssetCreateForm(OrgModelForm):
fields = [
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain',
'domain', 'number',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
......@@ -64,52 +74,8 @@ class AssetCreateForm(OrgModelForm):
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
}
labels = {
'nodes': _("Node"),
}
help_texts = {
'hostname': GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT,
'admin_user': _(
'root or other NOPASSWD sudo privilege user existed in asset,'
'If asset is windows or other set any one, more see admin user left menu'
),
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
'domain': _("If your have some network not connect with each other, you can set domain")
}
class AssetUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.data:
return
nodes_field = self.fields['nodes']
if self.instance:
nodes_field.choices = ((n.id, n.full_value) for n in
self.instance.nodes.all())
else:
nodes_field.choices = []
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
'class': 'nodes-select2', 'data-placeholder': _('Node')
}),
'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user')
}),
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
'platform': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Platform')
}),
}
labels = {
......
# -*- coding: utf-8 -*-
from django import forms
from django.utils.translation import ugettext_lazy as _
from ..models import Platform
__all__ = ['PlatformForm', 'PlatformMetaForm']
class PlatformMetaForm(forms.Form):
SECURITY_CHOICES = (
('rdp', "RDP"),
('nla', "NLA"),
('tls', 'TLS'),
('any', "Any"),
)
CONSOLE_CHOICES = (
(True, _('Yes')),
(False, _('No')),
)
security = forms.ChoiceField(
choices=SECURITY_CHOICES, initial='any', label=_("RDP security"),
required=False,
)
console = forms.ChoiceField(
choices=CONSOLE_CHOICES, initial=False, label=_("RDP console"),
required=False,
)
class PlatformForm(forms.ModelForm):
class Meta:
model = Platform
fields = [
'name', 'base', 'comment',
]
labels = {
'base': _("Base platform")
}
# Generated by Django 2.2.7 on 2019-12-06 07:26
import common.fields.model
from django.db import migrations, models
def create_internal_platform(apps, schema_editor):
model = apps.get_model("assets", "Platform")
db_alias = schema_editor.connection.alias
type_platforms = (
('Linux', 'Linux', None),
('Unix', 'Unix', None),
('MacOS', 'MacOS', None),
('BSD', 'BSD', None),
('Windows', 'Windows', None),
('Windows2016', 'Windows', {'security': 'tls'}),
('Other', 'Other', None),
)
for name, base, meta in type_platforms:
model.objects.using(db_alias).create(
name=name, base=base, internal=True, meta=meta
)
class Migration(migrations.Migration):
dependencies = [
('assets', '0043_auto_20191114_1111'),
]
operations = [
migrations.CreateModel(
name='Platform',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.SlugField(allow_unicode=True, unique=True, verbose_name='Name')),
('base', models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=16, verbose_name='Base')),
('charset', models.CharField(choices=[('utf8', 'UTF-8'), ('gbk', 'GBK')], default='utf8', max_length=8, verbose_name='Charset')),
('meta', common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Meta')),
('internal', models.BooleanField(default=False, verbose_name='Internal')),
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
],
),
migrations.RunPython(create_internal_platform)
]
# Generated by Django 2.2.7 on 2019-12-06 08:07
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
def migrate_platform_to_asset_type(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
platform_model = apps.get_model("assets", "Platform")
db_alias = schema_editor.connection.alias
platforms = platform_model.objects.using(db_alias).all()
platforms_map = {p.name: p for p in platforms}
for name, p in platforms_map.items():
asset_model.objects.using(db_alias)\
.filter(_platform=name)\
.update(platform=p)
class Migration(migrations.Migration):
dependencies = [
('assets', '0044_platform'),
]
operations = [
migrations.RenameField(
model_name='asset',
old_name='platform',
new_name='_platform',
),
migrations.AddField(
model_name='asset',
name='platform',
field=models.ForeignKey(
default=assets.models.asset.Platform.default,
on_delete=django.db.models.deletion.PROTECT,
related_name='assets', to='assets.Platform',
verbose_name='Platform'),
),
migrations.RunPython(migrate_platform_to_asset_type),
migrations.RemoveField(
model_name='asset',
name='_platform',
),
]
......@@ -11,10 +11,12 @@ from collections import OrderedDict
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .utils import Connectivity
from common.fields.model import JsonDictTextField
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager
from .utils import Connectivity
__all__ = ['Asset', 'ProtocolsMixin']
__all__ = ['Asset', 'ProtocolsMixin', 'Platform']
logger = logging.getLogger(__name__)
......@@ -37,6 +39,13 @@ def default_node():
return None
class AssetManager(OrgManager):
def get_queryset(self):
return super().get_queryset().annotate(
platform_base=models.F('platform__base')
)
class AssetQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
......@@ -119,6 +128,41 @@ class NodesRelationMixin:
return nodes
class Platform(models.Model):
CHARSET_CHOICES = (
('utf8', 'UTF-8'),
('gbk', 'GBK'),
)
BASE_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Other', 'Other'),
)
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
@classmethod
def default(cls):
linux, created = cls.objects.get_or_create(
defaults={'name': 'Linux'}, name='Linux'
)
return linux.id
def __str__(self):
return self.name
class Meta:
verbose_name = _("Platform")
# ordering = ('name',)
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
# Important
PLATFORM_CHOICES = (
......@@ -138,9 +182,8 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
choices=ProtocolsMixin.PROTOCOL_CHOICES,
verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
......@@ -175,7 +218,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
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'))
objects = OrgManager.from_queryset(AssetQuerySet)()
objects = AssetManager.from_queryset(AssetQuerySet)()
_connectivity = None
def __str__(self):
......@@ -191,19 +234,20 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
return True, warning
def is_windows(self):
if self.platform in ("Windows", "Windows2016"):
return True
else:
return False
return self.platform_base == "Windows"
def is_unixlike(self):
if self.platform not in ("Windows", "Windows2016", "Other"):
if self.platform_base not in ("Windows", "Windows2016", "Other"):
return True
else:
return False
def is_support_ansible(self):
return self.has_protocol('ssh') and self.platform not in ("Other",)
return self.has_protocol('ssh') and self.platform_base not in ("Other",)
@lazyproperty
def platform_base(self):
return self.platform.base
@property
def cpu_info(self):
......@@ -264,9 +308,9 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
def as_tree_node(self, parent_node):
from common.tree import TreeNode
icon_skin = 'file'
if self.platform.lower() == 'windows':
if self.platform_base.lower() == 'windows':
icon_skin = 'windows'
elif self.platform.lower() == 'linux':
elif self.platform_base.lower() == 'linux':
icon_skin = 'linux'
data = {
'id': str(self.id),
......@@ -283,7 +327,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
'hostname': self.hostname,
'ip': self.ip,
'protocols': self.protocols_as_list,
'platform': self.platform,
'platform': self.platform_base,
}
}
}
......
......@@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.serializers import AdaptedBulkListSerializer
from ..models import Asset, Node, Label
from ..models import Asset, Node, Label, Platform
from ..const import (
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN,
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG
......@@ -16,7 +16,8 @@ from .base import ConnectivitySerializer
__all__ = [
'AssetSerializer', 'AssetSimpleSerializer',
'ProtocolsField',
'ProtocolsField', 'PlatformSerializer',
'AssetDetailSerializer',
]
......@@ -65,6 +66,9 @@ class ProtocolsField(serializers.ListField):
class AssetSerializer(BulkOrgResourceModelSerializer):
platform = serializers.SlugRelatedField(
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
)
protocols = ProtocolsField(label=_('Protocols'), required=False)
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
......@@ -111,7 +115,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
queryset = queryset.prefetch_related(
Prefetch('nodes', queryset=Node.objects.all().only('id')),
Prefetch('labels', queryset=Label.objects.all().only('id')),
).select_related('admin_user', 'domain')
).select_related('admin_user', 'domain', 'platform')
return queryset
def compatible_with_old_protocol(self, validated_data):
......@@ -139,6 +143,21 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
return super().update(instance, validated_data)
class PlatformSerializer(serializers.ModelSerializer):
meta = serializers.DictField(required=False, allow_null=True)
class Meta:
model = Platform
fields = [
'id', 'name', 'base', 'charset',
'internal', 'meta', 'comment'
]
class AssetDetailSerializer(AssetSerializer):
platform = PlatformSerializer(read_only=True)
class AssetSimpleSerializer(serializers.ModelSerializer):
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
......
{% 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 %}
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update admin user" %}{% endblock %}
\ No newline at end of file
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import assets" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-assets:asset-list" %}{% endblock %}
......@@ -25,7 +25,7 @@
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px;overflow: auto;max-height: 500px">
<div class="col-sm-3" id="split-left" style="padding-left: 3px;overflow: auto;max-height: 500px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager ">
......@@ -37,7 +37,7 @@
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="col-sm-9 animated fadeInRight" id="split-right">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover " id="asset_list_modal_table" style="width: 100%">
<thead>
......
{% 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 %}
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update system user" %}{% endblock %}
\ No newline at end of file
......@@ -5,28 +5,7 @@
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
{% endblock %}
{% block table_search %}
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% include '_csv_import_export.html' %}
{% endblock %}
{% block table_container %}
......@@ -42,9 +21,6 @@
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
{# <th class="text-center">{% trans 'Reachable' %}</th>#}
{# <th class="text-center">{% trans 'Unreachable' %}</th>#}
{# <th class="text-center">{% trans 'Ratio' %}</th>#}
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
......@@ -52,8 +28,6 @@
<tbody>
</tbody>
</table>
{% include 'assets/_admin_user_import_modal.html' %}
{% include 'assets/_admin_user_update_modal.html' %}
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
......@@ -80,14 +54,13 @@ function initTable() {
{data: "comment"}, {data: "id", orderable: false, width: "100px"}
]
};
admin_user_table = jumpserver.initServerSideDataTable(options);
return admin_user_table
return jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
admin_user_table = initTable();
initCsvImportExport(admin_user_table, "{% trans "Admin user" %}")
})
.on('click', '.btn_admin_user_delete', function () {
var $this = $(this);
var $data_table = $("#admin_user_list_table").DataTable();
......@@ -100,69 +73,5 @@ $(document).ready(function(){
}, 3000);
})
.on('click', '.btn_export', function(){
var admin_users = admin_user_table.selected;
var data = {
'resources': admin_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:admin-user-list' %}",
format: "csv",
params: {
search: search
}
};
APIExportData(props);
}).on('click', '#btn_import_confirm',function () {
var url = "{% url 'api-assets:admin-user-list' %}";
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var data_table = $('#admin_user_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
})
.on('click', '#download_update_template', function () {
var admin_users = admin_user_table.selected;
var data = {
'resources': admin_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:admin-user-list' %}?format=csv&template=update",
format: 'csv',
params: {
search: search
}
};
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
}
var url = "{% url 'api-assets:admin-user-list' %}";
var data_table = $('#admin_user_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
});
})
</script>
{% endblock %}
{% extends 'base.html' %}
{% extends '_base_asset_tree_list.html' %}
{% load static %}
{% load i18n %}
{% block help_message %}
{# <div class="alert alert-info help-message">#}
{# <button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>#}
{# 左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产#}
{% trans 'The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node' %}
{# </div>#}
{% endblock %}
{% block custom_head_css_js %}
{# <link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">#}
{# <script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>#}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<style type="text/css">
div#rMenu {
position:absolute;
visibility:hidden;
text-align: left;
{#top: 100%;#}
top: 0;
left: 0;
z-index: 1000;
{#float: left;#}
padding: 0 0;
margin: 2px 0 0;
list-style: none;
background-clip: padding-box;
}
.dataTables_wrapper .dataTables_processing {
opacity: .9;
border: none;
}
div#rMenu li{
margin: 1px 0;
cursor: pointer;
list-style: none outside none;
}
.dropdown a:hover {
background-color: #f1f1f1
}
</style>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px;padding-right: 0">
{% include 'assets/_node_tree.html' %}
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="tree-toggle" style="z-index: 9999">
<div class="btn btn-sm btn-primary tree-toggle-btn">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header">
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
<div class="btn-group" style="float: right">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels">
{% for label in labels %}
<li><a style="font-weight: bolder">{{ label.name }}#{{ label.value }}</a></li>
{% endfor %}
</ul>
</div>
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
<option value="remove">{% trans 'Remove from this node' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
{% block table_container %}
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
{% include '_csv_import_export.html' %}
<div class="btn-group" style="float: right">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels">
{% for label in labels %}
<li><a style="font-weight: bolder">{{ label.name }}#{{ label.value }}</a></li>
{% endfor %}
</ul>
</div>
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
<option value="remove">{% trans 'Remove from this node' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
</div>
{% include 'assets/_asset_update_modal.html' %}
{% include 'assets/_asset_import_modal.html' %}
{% include 'assets/_asset_list_modal.html' %}
{% include 'assets/_node_detail_modal.html' %}
{% endblock %}
......@@ -205,22 +126,6 @@ function initTree() {
})
}
function toggle() {
if (show === 0) {
$("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-lg-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
show = 1;
});
} else {
$("#split-right").attr("class", "col-lg-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500);
show = 0;
}
}
function onNodeSelected(event, treeNode) {
current_node = treeNode;
current_node_id = treeNode.meta.node.id;
......@@ -261,7 +166,8 @@ function onAssetModalConfirmAddAssetToNode(table) {
}
$(document).ready(function(){
initTable();
asset_table = initTable();
initCsvImportExport(asset_table, "{% trans "Asset" %}");
initTree();
if(getCookie('show_current_asset') === '1'){
......@@ -282,81 +188,6 @@ $(document).ready(function(){
$("#asset_list_table_filter input").val(val);
asset_table.search(val).draw();
})
.on('click', '.btn_export', 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',
params: {
search: search,
node_id: current_node_id || '',
show_current_asset: getCookie('show_current_asset')
}
};
APIExportData(props);
})
.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){
url = setUrlParam(url, 'node_id', current_node_id);
}
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
}
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' %}";
if (current_node_id) {
......
{% extends '_base_create_update.html' %} {% load static %} {% load bootstrap3 %}
{% load i18n %}
{% block form %}
<form id="platformForm" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.base layout="horizontal" %}
<div class="meta-config">
<div hidden class="windows-config">
{% bootstrap_field meta_form.security layout="horizontal" %}
{% bootstrap_field meta_form.console layout="horizontal" %}
</div>
</div>
{% bootstrap_field form.comment layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script type="text/javascript">
function toggleWindowConfig() {
var baseRef = $("#id_base");
var windowConfigRef = $(".windows-config");
var windowConfigInputRef = windowConfigRef.find(".form-control");
if (baseRef.val().toLowerCase() === 'windows') {
windowConfigInputRef.attr('disabled', false);
windowConfigRef.show();
} else {
windowConfigInputRef.attr('disabled', true);
windowConfigRef.hide();
}
}
$(document).ready(function () {
toggleWindowConfig()
})
.on("change", "#id_base", function () {
toggleWindowConfig()
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var form = $("form");
var data = form.serializeObject();
var method = "POST";
var theUrl = '{% url "api-assets:platform-list" %}';
var redirectTo = '{% url "assets:platform-list" %}';
{% if type == "update" %}
theUrl = '{% url 'api-assets:platform-detail' pk=object.id %}';
method = "PUT";
{% endif %}
var metaData = $(".meta-config .form-control").serializeObject();
objectAttrsIsBool(metaData, ['console']);
var metaKeys = Object.keys(metaData);
metaKeys.forEach(function (k, v) {
delete data[k]
});
data.meta = metaData;
console.log(data);
var props = {
url: theUrl,
data: data,
method: method,
form: form,
redirect_to: redirectTo
};
formSubmit(props);
})
</script>
{% endblock %}
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'assets:platform-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:platform-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td>{% trans 'Name' %}:</td>
<td><b>{{ object.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Base platform' %}:</td>
<td><b>{{ object.base }}</b></td>
</tr>
<tr>
<td>{% trans 'Charset' %}:</td>
<td><b>{{ object.charset }}</b></td>
</tr>
<tr>
<td>{% trans 'Meta' %}:</td>
<td><b>{{ object.meta }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ object.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
{% endblock %}
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url "assets:platform-create" %}" class="btn btn-sm btn-primary"> {% trans "Create platform" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="platform_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Base platform' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
var platformTable = 0;
function initTable() {
var options = {
ele: $('#platform_list_table'),
columnDefs: [
{targets: 1, render: function (cellData, tp, rowData, meta) {
cellData = htmlEscape(cellData);
var detailBtn = '<a href="{% url "assets:platform-detail" pk=999 %}">' + cellData + '</a>';
return detailBtn.replace('999', rowData.id);
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var updateBtn = '<a href="{% url "assets:platform-update" pk=9999 %}" class="btn btn-xs m-l-xs btn-info" disabled>{% trans "Update" %}</a>'.replace('9999', cellData);
var delBtn = '<a class="btn btn-xs btn-danger m-l-xs btn-object-delete" data-uid="{{ DEFAULT_PK }}" disabled>{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
if (!rowData.internal) {
updateBtn = updateBtn.replace('disabled', '');
delBtn = delBtn.replace('disabled', '');
}
$(td).html(updateBtn + delBtn)
}}
],
ajax_url: '{% url "api-assets:platform-list" %}',
columns: [
{data: "id"}, {data: "name"}, {data: "base" },
{data: "comment"}, {data: "id", orderable: false, width: "100px"}
]
};
platformTable = jumpserver.initServerSideDataTable(options);
return platformTable
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-object-delete', function () {
var $this = $(this);
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var theUrl = '{% url "api-assets:platform-detail" pk=0 %}'.replace('0', uid);
objectDelete($this, name, theUrl);
setTimeout( function () {
platformTable.ajax.reload();
}, 3000);
})
</script>
{% endblock %}
......@@ -8,28 +8,7 @@
{% endblock %}
{% block table_search %}
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% include '_csv_import_export.html' %}
{% endblock %}
{% block table_container %}
......@@ -57,8 +36,6 @@
<tbody>
</tbody>
</table>
{% include 'assets/_system_user_import_modal.html' %}
{% include 'assets/_system_user_update_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
......@@ -89,14 +66,13 @@ function initTable() {
],
op_html: $('#actions').html()
};
system_user_table = jumpserver.initServerSideDataTable(options);
return system_user_table
return jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
system_user_table = initTable();
initCsvImportExport(system_user_table, "{% trans 'System user' %}")
})
.on('click', '.btn_admin_user_delete', function () {
var $this = $(this);
var $data_table = $('#cluster_list_table').DataTable();
......@@ -108,125 +84,6 @@ $(document).ready(function(){
$data_table.ajax.reload();
}, 3000);
})
.on('click', '#btn_bulk_update', function () {
var action = $('#slct_bulk_update').val();
var $data_table = $('#system_user_list_table').DataTable();
var id_list = [];
var plain_id_list = [];
$data_table.rows({selected: true}).every(function(){
id_list.push({id: this.data().id});
plain_id_list.push(this.data().id);
});
if (id_list === []) {
return false;
}
var the_url = "{% url 'api-assets:system-user-list' %}";
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected System Users !!!' %}",
type: "warning",
showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'System Users Deleted.' %}";
swal("{% trans 'System Users Delete' %}", msg, "success");
$('#system_user_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'System Users Deleting failed.' %}";
swal("{% trans 'System Users Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
requestApi({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload();
jumpserver.checked = false;
});
}
function doUpdate() {
{# TODO: bulk update the System Users #}
}
switch (action) {
case 'delete':
doDelete();
break;
case 'update':
doUpdate();
break;
default:
break;
}
})
.on('click', '.btn_export', function () {
var system_users = system_user_table.selected;
var data = {
'resources': system_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:system-user-list' %}",
format: "csv",
params:{
search:search
}
};
APIExportData(props);
})
.on('click', '#btn_import_confirm', function () {
var url = "{% url 'api-assets:system-user-list' %}";
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans 'Please select file' %}");
return
}
var data_table = $('#system_user_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
})
.on('click', '#download_update_template', function () {
var system_users = system_user_table.selected;
var data = {
'resources': system_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:system-user-list' %}?format=csv&template=update",
format: "csv",
params:{
search:search
}
};
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
}
var url = "{% url 'api-assets:system-user-list' %}";
var data_table = $('#system_user_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
});
})
</script>
{% endblock %}
......
......@@ -12,6 +12,7 @@ app_name = 'assets'
router = BulkRouter()
router.register(r'assets', api.AssetViewSet, 'asset')
router.register(r'platforms', api.AssetPlatformViewSet, 'platform')
router.register(r'admin-users', api.AdminUserViewSet, 'admin-user')
router.register(r'system-users', api.SystemUserViewSet, 'system-user')
router.register(r'labels', api.LabelViewSet, 'label')
......@@ -37,6 +38,8 @@ urlpatterns = [
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
path('assets/<uuid:pk>/gateway/',
api.AssetGatewayApi.as_view(), name='asset-gateway'),
path('assets/<uuid:pk>/platform/',
api.AssetPlatformRetrieveApi.as_view(), name='asset-platform-detail'),
path('asset-users/auth-info/',
api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'),
......
......@@ -16,6 +16,11 @@ urlpatterns = [
# Asset user view
path('asset/<uuid:pk>/asset-user/', views.AssetUserListView.as_view(), name='asset-user-list'),
path('platform/', views.PlatformListView.as_view(), name='platform-list'),
path('platform/create/', views.PlatformCreateView.as_view(), name='platform-create'),
path('platform/<int:pk>/', views.PlatformDetailView.as_view(), name='platform-detail'),
path('platform/<int:pk>/update/', views.PlatformUpdateView.as_view(), name='platform-update'),
# User asset view
path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'),
......
# coding:utf-8
from .asset import *
from .platform import *
from .system_user import *
from .admin_user import *
from .label import *
......
......@@ -74,7 +74,7 @@ class UserAssetListView(PermissionsMixin, TemplateView):
class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
model = Asset
form_class = forms.AssetCreateForm
form_class = forms.AssetCreateUpdateForm
template_name = 'assets/asset_create.html'
success_url = reverse_lazy('assets:asset-list')
permission_classes = [IsOrgAdmin]
......@@ -110,7 +110,7 @@ class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
class AssetUpdateView(PermissionsMixin, UpdateView):
model = Asset
form_class = forms.AssetUpdateForm
form_class = forms.AssetCreateUpdateForm
template_name = 'assets/asset_update.html'
success_url = reverse_lazy('assets:asset-list')
permission_classes = [IsOrgAdmin]
......
# -*- coding: utf-8 -*-
#
from django.views.generic import TemplateView, CreateView, \
UpdateView, DeleteView, DetailView
from django.views.generic import (
TemplateView, CreateView, UpdateView, DeleteView, DetailView
)
from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy, reverse
......
# -*- coding: utf-8 -*-
from django.views import generic
from django.utils.translation import ugettext as _
from common.permissions import PermissionsMixin, IsSuperUser
from ..models import Platform
from ..forms import PlatformForm, PlatformMetaForm
__all__ = [
'PlatformListView', 'PlatformUpdateView', 'PlatformCreateView',
'PlatformDetailView',
]
class PlatformListView(PermissionsMixin, generic.TemplateView):
template_name = 'assets/platform_list.html'
permission_classes = (IsSuperUser,)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'app': _('Assets'),
'action': _("Platform list"),
})
return context
class PlatformCreateView(PermissionsMixin, generic.CreateView):
form_class = PlatformForm
permission_classes = (IsSuperUser,)
template_name = 'assets/platform_create_update.html'
model = Platform
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
meta_form = PlatformMetaForm()
context.update({
'app': _('Assets'),
'action': _("Create platform"),
'meta_form': meta_form,
})
return context
class PlatformUpdateView(generic.UpdateView):
form_class = PlatformForm
permission_classes = (IsSuperUser,)
model = Platform
template_name = 'assets/platform_create_update.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
meta_form = PlatformMetaForm(initial=self.object.meta)
context.update({
'app': _('Assets'),
'action': _("Update platform"),
'type': 'update',
'meta_form': meta_form,
})
return context
class PlatformDetailView(generic.DetailView):
permission_classes = (IsSuperUser,)
model = Platform
template_name = 'assets/platform_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'app': _('Assets'),
'action': _("Platform detail"),
})
return context
......@@ -11,6 +11,7 @@ from rest_framework.request import Request
from jumpserver.utils import current_request
from common.utils import get_request_ip, get_logger, get_syslogger
from users.models import User
from users.signals import post_user_change_password
from authentication.signals import post_auth_failed, post_auth_success
from terminal.models import Session, Command
from common.utils.encode import model_to_json
......@@ -18,7 +19,7 @@ from . import models
from .tasks import write_login_log_async
logger = get_logger(__name__)
sys_logger = get_syslogger("audits")
sys_logger = get_syslogger(__name__)
json_render = JSONRenderer()
......@@ -50,7 +51,7 @@ def create_operate_log(action, sender, resource):
logger.error("Create operate log error: {}".format(e))
@receiver(post_save, dispatch_uid="my_unique_identifier")
@receiver(post_save)
def on_object_created_or_update(sender, instance=None, created=False, update_fields=None, **kwargs):
if instance._meta.object_name == 'User' and \
update_fields and 'last_login' in update_fields:
......@@ -62,21 +63,27 @@ def on_object_created_or_update(sender, instance=None, created=False, update_fie
create_operate_log(action, sender, instance)
@receiver(post_delete, dispatch_uid="my_unique_identifier")
@receiver(post_delete)
def on_object_delete(sender, instance=None, **kwargs):
create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance)
@receiver(post_save, sender=User, dispatch_uid="my_unique_identifier")
def on_user_change_password(sender, instance=None, **kwargs):
if hasattr(instance, '_set_password'):
if not current_request or not current_request.user.is_authenticated:
return
with transaction.atomic():
models.PasswordChangeLog.objects.create(
user=instance, change_by=current_request.user,
remote_addr=get_request_ip(current_request),
)
@receiver(post_user_change_password, sender=User)
def on_user_change_password(sender, user=None, **kwargs):
if not current_request:
remote_addr = '127.0.0.1'
change_by = 'System'
else:
remote_addr = get_request_ip(current_request)
if not current_request.user.is_authenticated:
change_by = str(user)
else:
change_by = str(current_request.user)
with transaction.atomic():
models.PasswordChangeLog.objects.create(
user=str(user), change_by=change_by,
remote_addr=remote_addr,
)
def on_audits_log_create(sender, instance=None, **kwargs):
......@@ -95,7 +102,7 @@ def on_audits_log_create(sender, instance=None, **kwargs):
else:
return
data = model_to_json(instance)
data = model_to_json(instance, indent=None)
msg = "{} - {}".format(category, data)
sys_logger.info(msg)
......
......@@ -102,47 +102,47 @@
{% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function() {
jumpserver.initStaticTable('#login_log_table');
$('#date .input-daterange').datepicker({
format: "yyyy-mm-dd",
todayBtn: "linked",
keyboardNavigation: false,
forceParse: false,
calendarWeeks: true,
autoclose: true
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function() {
jumpserver.initStaticTable('#login_log_table');
$('#date .input-daterange').datepicker({
format: "yyyy-mm-dd",
todayBtn: "linked",
keyboardNavigation: false,
forceParse: false,
calendarWeeks: true,
autoclose: true
});
$('.select2').select2({
dropdownAutoWidth: true,
width: 'auto'
});
});
$('.select2').select2({
dropdownAutoWidth: true,
width: 'auto'
});
})
.on('click', '.btn_export', function () {
var date_from = $('#id_date_from').val();
var date_to = $('#id_date_to').val();
var user = $('.select2 option:selected').val();
var keyword = $('#search').val();
$.ajax({
url: "{% url "audits:login-log-export" %}",
method: 'POST',
data: JSON.stringify({
'date_from':date_from,
'date_to':date_to,
'user':user,
'keyword':keyword
}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
.on('click', '.btn_export', function () {
var date_from = $('#id_date_from').val();
var date_to = $('#id_date_to').val();
var user = $('.select2 option:selected').val();
var keyword = $('#search').val();
$.ajax({
url: "{% url "audits:login-log-export" %}",
method: 'POST',
data: JSON.stringify({
'date_from':date_from,
'date_to':date_to,
'user':user,
'keyword':keyword
}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
})
</script>
})
</script>
{% endblock %}
......@@ -2,11 +2,8 @@
#
from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import gettext_lazy as _
from captcha.fields import CaptchaField
from django.conf import settings
from users.utils import get_login_failed_count
class UserLoginForm(forms.Form):
......
......@@ -27,10 +27,17 @@ class IDSpmFilterMixin:
class SerializerMixin:
def get_serializer_class(self):
if self.request.method.lower() == 'get' and\
self.request.query_params.get('draw') \
and hasattr(self, 'serializer_display_class'):
return self.serializer_display_class
serializer_class = None
if hasattr(self, 'serializer_classes') and \
isinstance(self.serializer_classes, dict):
if self.action == 'list' and self.request.query_params.get('draw'):
serializer_class = self.serializer_classes.get('display')
if serializer_class is None:
serializer_class = self.serializer_classes.get(
self.action, self.serializer_classes.get('default')
)
if serializer_class:
return serializer_class
return super().get_serializer_class()
......
......@@ -2,6 +2,7 @@
#
import re
import os
import logging
from collections import defaultdict
from django.conf import settings
from django.dispatch import receiver
......@@ -10,13 +11,13 @@ from django.db import connection
from django.conf import LazySettings
from django.db.utils import ProgrammingError, OperationalError
from jumpserver.utils import get_current_request
from common.utils import get_logger
from .local import thread_local
from .signals import django_ready
pattern = re.compile(r'FROM `(\w+)`')
logger = get_logger(__name__)
logger = logging.getLogger("jumpserver.common")
DEBUG_DB = os.environ.get('DEBUG_DB', '0') == '1'
......@@ -50,19 +51,29 @@ def on_request_finished_logging_db_query(sender, **kwargs):
counters['total'].time += float(time)
counters = sorted(counters.items(), key=lambda x: x[1])
if not counters:
return
method = 'GET'
path = '/Unknown'
current_request = get_current_request()
if current_request:
method = current_request.method
path = current_request.get_full_path()
logger.debug(">>> [{}] {}".format(method, path))
for name, counter in counters:
logger.debug("Query {:3} times using {:.2f}s {}".format(
counter.counter, counter.time, name)
)
@receiver(request_finished)
def on_request_finished_release_local(sender, **kwargs):
thread_local.__release_local__()
if settings.DEBUG and DEBUG_DB:
request_finished.connect(on_request_finished_logging_db_query)
else:
request_finished.connect(on_request_finished_release_local)
@receiver(django_ready)
......
......@@ -27,12 +27,12 @@ def combine_seq(s1, s2, callback=None):
return seq
def get_logger(name=None):
def get_logger(name=''):
return logging.getLogger('jumpserver.%s' % name)
def get_syslogger(name=None):
return logging.getLogger('jms.%s' % name)
def get_syslogger(name=''):
return logging.getLogger('syslog.%s' % name)
def timesince(dt, since='', default="just now"):
......
......@@ -184,6 +184,7 @@ class Config(dict):
'ASSETS_PERM_CACHE_ENABLE': False,
'SYSLOG_ADDR': '', # '192.168.0.1:514'
'SYSLOG_FACILITY': 'user',
'SYSLOG_SOCKTYPE': 2,
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
'WINDOWS_SSH_DEFAULT_SHELL': 'cmd',
'FLOWER_URL': "127.0.0.1:5555",
......@@ -282,10 +283,10 @@ class DynamicConfig:
]
if self.get('AUTH_LDAP'):
backends.insert(0, 'authentication.backends.ldap.LDAPAuthorizationBackend')
if self.get('AUTH_OPENID'):
if self.static_config.get('AUTH_OPENID'):
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend')
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend')
if self.get('AUTH_RADIUS'):
if self.static_config.get('AUTH_RADIUS'):
backends.insert(0, 'authentication.backends.radius.RadiusBackend')
return backends
......
......@@ -81,7 +81,7 @@ LOGGING = {
'propagate': False,
},
'jumpserver': {
'handlers': ['console', 'file', 'syslog'],
'handlers': ['console', 'file'],
'level': LOG_LEVEL,
},
'ops.ansible_api': {
......@@ -92,7 +92,7 @@ LOGGING = {
'handlers': ['console', 'file'],
'level': "INFO",
},
'jms.audits': {
'syslog': {
'handlers': ['syslog'],
'level': 'INFO'
},
......@@ -110,6 +110,7 @@ if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2:
'class': 'logging.handlers.SysLogHandler',
'facility': CONFIG.SYSLOG_FACILITY,
'address': (host, int(port)),
'socktype': CONFIG.SYSLOG_SOCKTYPE,
})
if not os.path.isdir(LOG_DIR):
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-06 19:03+0800\n"
"POT-Creation-Date: 2019-12-09 15:41+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
......@@ -76,7 +76,7 @@ msgstr "运行参数"
#: applications/templates/applications/remote_app_list.html:20
#: applications/templates/applications/user_remote_app_list.html:18
#: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:295 assets/models/authbook.py:24
#: assets/models/asset.py:330 assets/models/authbook.py:24
#: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32
#: assets/serializers/asset_user.py:82 assets/serializers/system_user.py:45
#: assets/templates/assets/admin_user_list.html:44
......@@ -100,12 +100,12 @@ msgstr "运行参数"
#: users/templates/users/user_asset_permission.html:90
#: xpack/plugins/change_auth_plan/forms.py:73
#: xpack/plugins/change_auth_plan/models.py:419
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:44
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:42
#: 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:307
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:60
#: xpack/plugins/orgs/templates/orgs/org_list.html:17
#: xpack/plugins/vault/forms.py:13 xpack/plugins/vault/forms.py:15
msgid "Asset"
......@@ -116,11 +116,10 @@ msgstr "资产"
#: applications/templates/applications/remote_app_list.html:18
#: applications/templates/applications/user_remote_app_list.html:16
#: assets/forms/asset.py:21 assets/forms/domain.py:77 assets/forms/user.py:75
#: assets/forms/user.py:95 assets/models/base.py:28 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:21 assets/models/domain.py:20
#: assets/models/group.py:20 assets/models/label.py:18
#: assets/templates/assets/_node_detail_modal.html:27
#: assets/templates/assets/admin_user_detail.html:51
#: assets/forms/user.py:95 assets/models/asset.py:136 assets/models/base.py:28
#: assets/models/cluster.py:18 assets/models/cmd_filter.py:21
#: assets/models/domain.py:20 assets/models/group.py:20
#: assets/models/label.py:18 assets/templates/assets/admin_user_detail.html:51
#: assets/templates/assets/admin_user_list.html:42
#: assets/templates/assets/cmd_filter_detail.html:56
#: assets/templates/assets/cmd_filter_list.html:22
......@@ -128,6 +127,7 @@ msgstr "资产"
#: assets/templates/assets/domain_gateway_list.html:62
#: assets/templates/assets/domain_list.html:21
#: assets/templates/assets/label_list.html:14
#: assets/templates/assets/platform_list.html:16
#: assets/templates/assets/system_user_detail.html:53
#: assets/templates/assets/system_user_list.html:45 ops/models/adhoc.py:37
#: ops/templates/ops/task_detail.html:58 ops/templates/ops/task_list.html:11
......@@ -146,7 +146,7 @@ msgstr "资产"
#: terminal/models.py:350 terminal/templates/terminal/base_storage_list.html:32
#: terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:30 users/forms.py:162
#: users/models/group.py:14 users/models/user.py:443
#: users/models/group.py:14 users/models/user.py:433
#: users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_asset_permission.html:54
#: users/templates/users/user_asset_permission.html:174
......@@ -158,16 +158,16 @@ msgstr "资产"
#: users/templates/users/user_pubkey_update.html:57
#: xpack/plugins/change_auth_plan/forms.py:56
#: xpack/plugins/change_auth_plan/models.py:64
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:59
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:144
#: xpack/plugins/cloud/templates/cloud/account_detail.html:50
#: xpack/plugins/cloud/templates/cloud/account_detail.html:47
#: xpack/plugins/cloud/templates/cloud/account_list.html:12
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:56
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12
#: xpack/plugins/gathered_user/models.py:28
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:16
#: xpack/plugins/orgs/templates/orgs/org_detail.html:52
#: xpack/plugins/orgs/templates/orgs/org_detail.html:47
#: xpack/plugins/orgs/templates/orgs/org_list.html:12
msgid "Name"
msgstr "名称"
......@@ -190,7 +190,7 @@ msgstr "参数"
#: applications/models/remote_app.py:39
#: applications/templates/applications/remote_app_detail.html:68
#: assets/models/asset.py:174 assets/models/base.py:36
#: assets/models/asset.py:209 assets/models/base.py:36
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26
#: assets/models/cmd_filter.py:59 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:63
......@@ -202,10 +202,10 @@ msgstr "参数"
#: orgs/models.py:16 perms/models/base.py:54
#: perms/templates/perms/asset_permission_detail.html:93
#: perms/templates/perms/remote_app_permission_detail.html:85
#: users/models/user.py:484 users/serializers/group.py:32
#: users/models/user.py:474 users/serializers/group.py:32
#: users/templates/users/user_detail.html:112
#: xpack/plugins/change_auth_plan/models.py:109
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:111
#: xpack/plugins/cloud/models.py:80 xpack/plugins/cloud/models.py:179
#: xpack/plugins/gathered_user/models.py:46
msgid "Created by"
......@@ -215,7 +215,7 @@ msgstr "创建者"
# msgstr "创建者"
#: applications/models/remote_app.py:42
#: applications/templates/applications/remote_app_detail.html:64
#: assets/models/asset.py:175 assets/models/base.py:34
#: assets/models/asset.py:210 assets/models/base.py:34
#: assets/models/cluster.py:26 assets/models/domain.py:23
#: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:59
......@@ -230,11 +230,11 @@ msgstr "创建者"
#: terminal/templates/terminal/terminal_detail.html:59
#: tickets/templates/tickets/ticket_detail.html:52 users/models/group.py:17
#: users/templates/users/user_group_detail.html:58
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:103
#: xpack/plugins/cloud/models.py:83 xpack/plugins/cloud/models.py:182
#: xpack/plugins/cloud/templates/cloud/account_detail.html:66
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:101
#: xpack/plugins/orgs/templates/orgs/org_detail.html:60
#: xpack/plugins/cloud/templates/cloud/account_detail.html:63
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:98
#: xpack/plugins/orgs/templates/orgs/org_detail.html:55
msgid "Date created"
msgstr "创建日期"
......@@ -244,11 +244,12 @@ msgstr "创建日期"
#: applications/templates/applications/remote_app_detail.html:72
#: applications/templates/applications/remote_app_list.html:21
#: applications/templates/applications/user_remote_app_list.html:19
#: assets/models/asset.py:176 assets/models/base.py:33
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:23
#: assets/models/cmd_filter.py:56 assets/models/domain.py:21
#: assets/models/domain.py:53 assets/models/group.py:23
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:67
#: assets/models/asset.py:141 assets/models/asset.py:211
#: assets/models/base.py:33 assets/models/cluster.py:29
#: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:56
#: assets/models/domain.py:21 assets/models/domain.py:53
#: assets/models/group.py:23 assets/models/label.py:23
#: assets/templates/assets/admin_user_detail.html:67
#: assets/templates/assets/admin_user_list.html:48
#: assets/templates/assets/asset_detail.html:128
#: assets/templates/assets/cmd_filter_detail.html:60
......@@ -257,6 +258,7 @@ msgstr "创建日期"
#: assets/templates/assets/domain_detail.html:71
#: assets/templates/assets/domain_gateway_list.html:67
#: assets/templates/assets/domain_list.html:24
#: assets/templates/assets/platform_list.html:18
#: assets/templates/assets/system_user_detail.html:99
#: assets/templates/assets/system_user_list.html:53 ops/models/adhoc.py:43
#: orgs/models.py:18 perms/models/base.py:56
......@@ -266,20 +268,20 @@ msgstr "创建日期"
#: terminal/models.py:357 terminal/templates/terminal/base_storage_list.html:34
#: terminal/templates/terminal/terminal_detail.html:63
#: tickets/templates/tickets/ticket_detail.html:104 users/models/group.py:15
#: users/models/user.py:476 users/templates/users/user_detail.html:130
#: users/models/user.py:466 users/templates/users/user_detail.html:130
#: users/templates/users/user_group_detail.html:62
#: users/templates/users/user_group_list.html:37
#: users/templates/users/user_profile.html:138
#: xpack/plugins/change_auth_plan/models.py:105
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:115
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
#: xpack/plugins/cloud/models.py:77 xpack/plugins/cloud/models.py:173
#: xpack/plugins/cloud/templates/cloud/account_detail.html:70
#: xpack/plugins/cloud/templates/cloud/account_detail.html:67
#: xpack/plugins/cloud/templates/cloud/account_list.html:15
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:105
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:102
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18
#: xpack/plugins/gathered_user/models.py:42
#: xpack/plugins/orgs/templates/orgs/org_detail.html:64
#: xpack/plugins/orgs/templates/orgs/org_detail.html:59
#: xpack/plugins/orgs/templates/orgs/org_list.html:23
msgid "Comment"
msgstr "备注"
......@@ -305,14 +307,15 @@ msgstr "远程应用"
#: assets/templates/assets/domain_create_update.html:16
#: assets/templates/assets/gateway_create_update.html:54
#: assets/templates/assets/label_create_update.html:18
#: assets/templates/assets/platform_create_update.html:16
#: perms/templates/perms/asset_permission_create_update.html:81
#: perms/templates/perms/remote_app_permission_create_update.html:82
#: settings/templates/settings/basic_setting.html:64
#: 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/security_setting.html:73
#: settings/templates/settings/terminal_setting.html:76
#: settings/templates/settings/basic_setting.html:45
#: settings/templates/settings/email_content_setting.html:35
#: settings/templates/settings/email_setting.html:46
#: settings/templates/settings/ldap_setting.html:45
#: settings/templates/settings/security_setting.html:54
#: settings/templates/settings/terminal_setting.html:53
#: terminal/templates/terminal/base_storage_create_update.html:12
#: terminal/templates/terminal/terminal_update.html:43
#: users/templates/users/_user.html:51
......@@ -324,13 +327,13 @@ msgstr "远程应用"
#: users/templates/users/user_profile_update.html:67
#: users/templates/users/user_pubkey_update.html:74
#: users/templates/users/user_pubkey_update.html:80
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:69
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:53
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:44
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:67
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:29
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:49
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:40
#: xpack/plugins/interface/templates/interface/interface.html:72
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
#: xpack/plugins/vault/templates/vault/vault_create.html:45
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:29
#: xpack/plugins/vault/templates/vault/vault_create.html:41
msgid "Reset"
msgstr "重置"
......@@ -345,15 +348,16 @@ msgstr "重置"
#: assets/templates/assets/domain_create_update.html:17
#: assets/templates/assets/gateway_create_update.html:55
#: assets/templates/assets/label_create_update.html:19
#: assets/templates/assets/platform_create_update.html:17
#: audits/templates/audits/login_log_list.html:95
#: perms/templates/perms/asset_permission_create_update.html:82
#: perms/templates/perms/remote_app_permission_create_update.html:83
#: settings/templates/settings/basic_setting.html:65
#: 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/security_setting.html:74
#: settings/templates/settings/terminal_setting.html:78
#: settings/templates/settings/basic_setting.html:46
#: settings/templates/settings/email_content_setting.html:36
#: settings/templates/settings/email_setting.html:47
#: settings/templates/settings/ldap_setting.html:48
#: settings/templates/settings/security_setting.html:55
#: settings/templates/settings/terminal_setting.html:55
#: terminal/templates/terminal/base_storage_create_update.html:13
#: terminal/templates/terminal/command_list.html:47
#: terminal/templates/terminal/session_list.html:50
......@@ -361,13 +365,13 @@ msgstr "重置"
#: users/templates/users/_user.html:52
#: users/templates/users/forgot_password.html:42
#: users/templates/users/user_bulk_update.html:24
#: users/templates/users/user_list.html:61
#: users/templates/users/user_list.html:57
#: users/templates/users/user_password_update.html:76
#: users/templates/users/user_profile_update.html:68
#: users/templates/users/user_pubkey_update.html:81
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:70
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:68
#: xpack/plugins/interface/templates/interface/interface.html:74
#: xpack/plugins/vault/templates/vault/vault_create.html:46
#: xpack/plugins/vault/templates/vault/vault_create.html:42
msgid "Submit"
msgstr "提交"
......@@ -389,8 +393,8 @@ msgstr "提交"
#: perms/templates/perms/remote_app_permission_detail.html:13
#: perms/templates/perms/remote_app_permission_remote_app.html:13
#: perms/templates/perms/remote_app_permission_user.html:13
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:17
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:20
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:13
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:18
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:17
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:106
#: xpack/plugins/change_auth_plan/views.py:91
......@@ -405,7 +409,7 @@ msgstr "详情"
#: assets/templates/assets/admin_user_list.html:72
#: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_list.html:78
#: assets/templates/assets/asset_list.html:168
#: assets/templates/assets/asset_list.html:167
#: assets/templates/assets/cmd_filter_detail.html:24
#: assets/templates/assets/cmd_filter_list.html:56
#: assets/templates/assets/cmd_filter_rule_list.html:81
......@@ -414,6 +418,7 @@ msgstr "详情"
#: assets/templates/assets/domain_gateway_list.html:92
#: assets/templates/assets/domain_list.html:50
#: assets/templates/assets/label_list.html:39
#: assets/templates/assets/platform_list.html:40
#: assets/templates/assets/system_user_detail.html:21
#: assets/templates/assets/system_user_list.html:27
#: assets/templates/assets/system_user_list.html:79 audits/models.py:34
......@@ -431,19 +436,19 @@ msgstr "详情"
#: users/templates/users/user_group_list.html:20
#: users/templates/users/user_group_list.html:71
#: users/templates/users/user_list.html:20
#: users/templates/users/user_list.html:107
#: users/templates/users/user_list.html:110
#: users/templates/users/user_list.html:103
#: users/templates/users/user_list.html:106
#: users/templates/users/user_profile.html:181
#: users/templates/users/user_profile.html:191
#: users/templates/users/user_profile.html:201
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:27
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:56
#: xpack/plugins/cloud/templates/cloud/account_detail.html:23
#: xpack/plugins/cloud/templates/cloud/account_detail.html:20
#: xpack/plugins/cloud/templates/cloud/account_list.html:40
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:29
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:57
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:46
#: xpack/plugins/orgs/templates/orgs/org_detail.html:25
#: xpack/plugins/orgs/templates/orgs/org_detail.html:20
#: xpack/plugins/orgs/templates/orgs/org_list.html:93
msgid "Update"
msgstr "更新"
......@@ -453,7 +458,7 @@ msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:23
#: assets/templates/assets/admin_user_list.html:73
#: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_list.html:169
#: assets/templates/assets/asset_list.html:168
#: assets/templates/assets/cmd_filter_detail.html:28
#: assets/templates/assets/cmd_filter_list.html:57
#: assets/templates/assets/cmd_filter_rule_list.html:82
......@@ -462,6 +467,7 @@ msgstr "更新"
#: assets/templates/assets/domain_gateway_list.html:93
#: assets/templates/assets/domain_list.html:51
#: assets/templates/assets/label_list.html:40
#: assets/templates/assets/platform_list.html:41
#: assets/templates/assets/system_user_detail.html:25
#: assets/templates/assets/system_user_list.html:80 audits/models.py:35
#: authentication/templates/authentication/_access_key_modal.html:65
......@@ -477,16 +483,16 @@ msgstr "更新"
#: users/templates/users/user_detail.html:31
#: users/templates/users/user_group_detail.html:27
#: users/templates/users/user_group_list.html:73
#: users/templates/users/user_list.html:117
#: users/templates/users/user_list.html:121
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33
#: users/templates/users/user_list.html:111
#: users/templates/users/user_list.html:115
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:31
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:58
#: xpack/plugins/cloud/templates/cloud/account_detail.html:27
#: xpack/plugins/cloud/templates/cloud/account_detail.html:24
#: xpack/plugins/cloud/templates/cloud/account_list.html:42
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:33
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:58
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:47
#: xpack/plugins/orgs/templates/orgs/org_detail.html:29
#: xpack/plugins/orgs/templates/orgs/org_detail.html:24
#: xpack/plugins/orgs/templates/orgs/org_list.html:95
msgid "Delete"
msgstr "删除"
......@@ -520,6 +526,7 @@ msgstr "创建远程应用"
#: assets/templates/assets/domain_gateway_list.html:68
#: assets/templates/assets/domain_list.html:25
#: assets/templates/assets/label_list.html:17
#: assets/templates/assets/platform_list.html:19
#: assets/templates/assets/system_user_list.html:54 audits/models.py:39
#: audits/templates/audits/operate_log_list.html:45
#: audits/templates/audits/operate_log_list.html:71
......@@ -546,7 +553,7 @@ msgstr "创建远程应用"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20
#: xpack/plugins/cloud/templates/cloud/account_list.html:16
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:72
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:67
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:19
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:20
#: xpack/plugins/orgs/templates/orgs/org_list.html:24
......@@ -604,13 +611,13 @@ msgstr "不能包含特殊字符:[ {} ]"
msgid "* The contains characters that are not allowed"
msgstr "* 包含不被允许的字符"
#: assets/forms/asset.py:25 assets/models/asset.py:140
#: assets/forms/asset.py:25 assets/models/asset.py:176
#: assets/models/domain.py:50
#: assets/templates/assets/domain_gateway_list.html:64
msgid "Port"
msgstr "端口"
#: assets/forms/asset.py:56 assets/models/asset.py:145
#: assets/forms/asset.py:56 assets/models/asset.py:180
#: assets/models/user.py:110 assets/templates/assets/asset_detail.html:186
#: assets/templates/assets/asset_detail.html:194
#: assets/templates/assets/system_user_assets.html:87
......@@ -622,10 +629,10 @@ msgid "Nodes"
msgstr "节点"
#: assets/forms/asset.py:59 assets/forms/asset.py:106
#: assets/models/asset.py:149 assets/models/cluster.py:19
#: assets/models/asset.py:184 assets/models/cluster.py:19
#: assets/models/user.py:68 assets/templates/assets/asset_detail.html:72
#: templates/_nav.html:44 xpack/plugins/cloud/models.py:161
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:68
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65
#: xpack/plugins/orgs/templates/orgs/org_list.html:19
msgid "Admin user"
msgstr "管理用户"
......@@ -639,7 +646,7 @@ msgid "Label"
msgstr "标签"
#: assets/forms/asset.py:65 assets/forms/asset.py:112
#: assets/models/asset.py:144 assets/models/domain.py:26
#: assets/models/asset.py:179 assets/models/domain.py:26
#: assets/models/domain.py:52 assets/templates/assets/asset_detail.html:76
#: assets/templates/assets/user_asset_list.html:80
#: xpack/plugins/orgs/templates/orgs/org_list.html:18
......@@ -661,8 +668,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:157
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:64
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:61
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:61
msgid "Node"
msgstr "节点"
......@@ -687,7 +694,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
#: assets/forms/domain.py:17 assets/forms/label.py:15
#: perms/templates/perms/asset_permission_asset.html:74
#: xpack/plugins/change_auth_plan/forms.py:64
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:74
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:70
msgid "Select assets"
msgstr "选择资产"
......@@ -719,7 +726,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:205
#: perms/templates/perms/remote_app_permission_user.html:50
#: settings/templates/settings/_ldap_list_users_modal.html:31 users/forms.py:14
#: users/forms.py:161 users/models/user.py:441
#: users/forms.py:161 users/models/user.py:431
#: users/templates/users/_select_user_modal.html:14
#: users/templates/users/user_detail.html:68
#: users/templates/users/user_list.html:36
......@@ -727,14 +734,18 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: xpack/plugins/change_auth_plan/forms.py:58
#: xpack/plugins/change_auth_plan/models.py:66
#: xpack/plugins/change_auth_plan/models.py:415
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:63
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:13
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:64
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:69
msgid "Username"
msgstr "用户名"
#: assets/forms/platform.py:19 assets/templates/assets/platform_list.html:17
msgid "Base platform"
msgstr "基础平台"
#: assets/forms/user.py:26
msgid "Password or private key passphrase"
msgstr "密码或密钥密码"
......@@ -760,7 +771,7 @@ msgstr "密码"
#: assets/forms/user.py:30 assets/serializers/asset_user.py:71
#: assets/templates/assets/_asset_user_auth_update_modal.html:27
#: users/models/user.py:470
#: users/models/user.py:460
msgid "Private key"
msgstr "ssh私钥"
......@@ -800,7 +811,23 @@ msgstr "如果选择手动登录模式,用户名和密码可以不填写"
msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig"
msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
#: assets/models/asset.py:135 assets/models/domain.py:49
#: assets/models/asset.py:137
msgid "Base"
msgstr "基础"
#: assets/models/asset.py:138
msgid "Charset"
msgstr "编码"
#: assets/models/asset.py:139 tickets/models/ticket.py:38
msgid "Meta"
msgstr "元数据"
#: assets/models/asset.py:140
msgid "Internal"
msgstr "内部的"
#: assets/models/asset.py:171 assets/models/domain.py:49
#: assets/serializers/asset_user.py:28
#: assets/templates/assets/_asset_list_modal.html:47
#: assets/templates/assets/_asset_user_list.html:20
......@@ -812,12 +839,12 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
#: perms/templates/perms/asset_permission_list.html:207
#: settings/forms/terminal.py:16 users/templates/users/_granted_assets.html:31
#: users/templates/users/user_asset_permission.html:176
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:54
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:63
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:50
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:68
msgid "IP"
msgstr "IP"
#: assets/models/asset.py:136 assets/serializers/asset_user.py:27
#: assets/models/asset.py:172 assets/serializers/asset_user.py:27
#: assets/serializers/gathered_user.py:20
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/_asset_user_auth_update_modal.html:9
......@@ -829,12 +856,12 @@ msgstr "IP"
#: perms/templates/perms/asset_permission_list.html:208
#: settings/forms/terminal.py:15 users/templates/users/_granted_assets.html:30
#: users/templates/users/user_asset_permission.html:177
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:53
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:62
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:49
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:67
msgid "Hostname"
msgstr "主机名"
#: assets/models/asset.py:139 assets/models/domain.py:51
#: assets/models/asset.py:175 assets/models/domain.py:51
#: assets/models/user.py:113 assets/templates/assets/asset_detail.html:68
#: assets/templates/assets/domain_gateway_list.html:65
#: assets/templates/assets/system_user_detail.html:65
......@@ -844,91 +871,91 @@ msgstr "主机名"
msgid "Protocol"
msgstr "协议"
#: assets/models/asset.py:142 assets/serializers/asset.py:68
#: assets/models/asset.py:177 assets/serializers/asset.py:69
#: assets/templates/assets/asset_create.html:24
#: assets/templates/assets/user_asset_list.html:77
#: perms/serializers/user_permission.py:48
msgid "Protocols"
msgstr "协议组"
#: assets/models/asset.py:143 assets/templates/assets/asset_detail.html:100
#: assets/models/asset.py:178 assets/templates/assets/asset_detail.html:100
#: assets/templates/assets/user_asset_list.html:78
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:146 assets/models/authbook.py:27
#: assets/models/asset.py:181 assets/models/authbook.py:27
#: assets/models/cmd_filter.py:22 assets/models/domain.py:54
#: assets/models/label.py:22 assets/templates/assets/asset_detail.html:108
#: authentication/models.py:45
msgid "Is active"
msgstr "激活"
#: assets/models/asset.py:152 assets/templates/assets/asset_detail.html:64
#: assets/models/asset.py:187 assets/templates/assets/asset_detail.html:64
msgid "Public IP"
msgstr "公网IP"
#: assets/models/asset.py:153 assets/templates/assets/asset_detail.html:116
#: assets/models/asset.py:188 assets/templates/assets/asset_detail.html:116
msgid "Asset number"
msgstr "资产编号"
#: assets/models/asset.py:156 assets/templates/assets/asset_detail.html:80
#: assets/models/asset.py:191 assets/templates/assets/asset_detail.html:80
msgid "Vendor"
msgstr "制造商"
#: assets/models/asset.py:157 assets/templates/assets/asset_detail.html:84
#: assets/models/asset.py:192 assets/templates/assets/asset_detail.html:84
msgid "Model"
msgstr "型号"
#: assets/models/asset.py:158 assets/templates/assets/asset_detail.html:112
#: assets/models/asset.py:193 assets/templates/assets/asset_detail.html:112
msgid "Serial number"
msgstr "序列号"
#: assets/models/asset.py:160
#: assets/models/asset.py:195
msgid "CPU model"
msgstr "CPU型号"
#: assets/models/asset.py:161
#: xpack/plugins/license/templates/license/license_detail.html:71
#: assets/models/asset.py:196
#: xpack/plugins/license/templates/license/license_detail.html:80
msgid "CPU count"
msgstr "CPU数量"
#: assets/models/asset.py:162
#: assets/models/asset.py:197
msgid "CPU cores"
msgstr "CPU核数"
#: assets/models/asset.py:163
#: assets/models/asset.py:198
msgid "CPU vcpus"
msgstr "CPU总数"
#: assets/models/asset.py:164 assets/templates/assets/asset_detail.html:92
#: assets/models/asset.py:199 assets/templates/assets/asset_detail.html:92
msgid "Memory"
msgstr "内存"
#: assets/models/asset.py:165
#: assets/models/asset.py:200
msgid "Disk total"
msgstr "硬盘大小"
#: assets/models/asset.py:166
#: assets/models/asset.py:201
msgid "Disk info"
msgstr "硬盘信息"
#: assets/models/asset.py:168 assets/templates/assets/asset_detail.html:104
#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:104
msgid "OS"
msgstr "操作系统"
#: assets/models/asset.py:169
#: assets/models/asset.py:204
msgid "OS version"
msgstr "系统版本"
#: assets/models/asset.py:170
#: assets/models/asset.py:205
msgid "OS arch"
msgstr "系统架构"
#: assets/models/asset.py:171
#: assets/models/asset.py:206
msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:173 assets/templates/assets/asset_create.html:46
#: assets/models/asset.py:208 assets/templates/assets/asset_create.html:46
#: assets/templates/assets/asset_detail.html:220 templates/_nav.html:46
msgid "Labels"
msgstr "标签管理"
......@@ -962,8 +989,8 @@ msgstr "ssh公钥"
#: assets/models/base.py:35 assets/models/gathered_user.py:20
#: assets/templates/assets/cmd_filter_detail.html:68 common/mixins/models.py:52
#: ops/models/adhoc.py:46
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:68
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:107
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:71
msgid "Date updated"
msgstr "更新日期"
......@@ -975,7 +1002,7 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:462
#: assets/models/cluster.py:22 users/models/user.py:452
#: users/templates/users/user_detail.html:77
msgid "Phone"
msgstr "手机"
......@@ -1001,7 +1028,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:585
#: users/models/user.py:575
msgid "System"
msgstr "系统"
......@@ -1092,17 +1119,15 @@ msgid "Gateway"
msgstr "网关"
#: assets/models/gathered_user.py:16
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:67
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:70
msgid "Present"
msgstr "存在"
#: assets/models/gathered_user.py:17
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:65
msgid "Date last login"
msgstr "最后登录日期"
#: assets/models/gathered_user.py:18
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:66
msgid "IP last login"
msgstr "最后登录IP"
......@@ -1142,14 +1167,14 @@ msgstr "默认资产组"
#: tickets/models/ticket.py:128 tickets/templates/tickets/ticket_detail.html:32
#: tickets/templates/tickets/ticket_list.html:34
#: tickets/templates/tickets/ticket_list.html:103 users/forms.py:339
#: users/models/user.py:148 users/models/user.py:164 users/models/user.py:573
#: users/models/user.py:148 users/models/user.py:164 users/models/user.py:563
#: users/serializers/group.py:21
#: users/templates/users/user_asset_permission.html:55
#: users/templates/users/user_asset_permission.html:84
#: users/templates/users/user_group_detail.html:73
#: users/templates/users/user_group_list.html:36 users/views/profile.py:68
#: xpack/plugins/orgs/forms.py:28
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113
#: xpack/plugins/orgs/templates/orgs/org_detail.html:108
#: xpack/plugins/orgs/templates/orgs/org_list.html:15
msgid "User"
msgstr "用户"
......@@ -1179,7 +1204,7 @@ msgstr "空"
msgid "favorite"
msgstr "收藏夹"
#: assets/models/node.py:452 assets/templates/assets/_node_detail_modal.html:39
#: assets/models/node.py:452
msgid "Key"
msgstr "键"
......@@ -1203,14 +1228,16 @@ msgstr "手动登录"
#: assets/views/cmd_filter.py:31 assets/views/cmd_filter.py:48
#: assets/views/cmd_filter.py:66 assets/views/cmd_filter.py:84
#: assets/views/cmd_filter.py:104 assets/views/cmd_filter.py:138
#: assets/views/cmd_filter.py:173 assets/views/domain.py:30
#: assets/views/domain.py:47 assets/views/domain.py:65
#: assets/views/domain.py:80 assets/views/domain.py:106
#: assets/views/domain.py:135 assets/views/domain.py:156
#: assets/views/cmd_filter.py:173 assets/views/domain.py:31
#: assets/views/domain.py:48 assets/views/domain.py:66
#: assets/views/domain.py:81 assets/views/domain.py:107
#: assets/views/domain.py:136 assets/views/domain.py:157
#: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:73
#: assets/views/system_user.py:29 assets/views/system_user.py:46
#: assets/views/system_user.py:63 assets/views/system_user.py:79
#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:71
#: assets/views/platform.py:17 assets/views/platform.py:32
#: assets/views/platform.py:47 assets/views/system_user.py:29
#: assets/views/system_user.py:46 assets/views/system_user.py:63
#: assets/views/system_user.py:79 templates/_nav.html:39
#: xpack/plugins/change_auth_plan/models.py:71
msgid "Assets"
msgstr "资产管理"
......@@ -1284,17 +1311,17 @@ msgstr "协议格式 {}/{}"
msgid "Protocol duplicate: {}"
msgstr "协议重复: {}"
#: assets/serializers/asset.py:69 assets/serializers/asset.py:143
#: assets/serializers/asset.py:70 assets/serializers/asset.py:155
#: assets/serializers/asset_user.py:29
#: assets/templates/assets/_asset_user_list.html:23
msgid "Connectivity"
msgstr "连接"
#: assets/serializers/asset.py:95
#: assets/serializers/asset.py:96
msgid "Hardware info"
msgstr "硬件信息"
#: assets/serializers/asset.py:96 orgs/mixins/serializers.py:27
#: assets/serializers/asset.py:97 orgs/mixins/serializers.py:27
msgid "Org name"
msgstr "组织名称"
......@@ -1303,7 +1330,7 @@ msgid "Backend"
msgstr "后端"
#: assets/serializers/asset_user.py:67 users/forms.py:282
#: users/models/user.py:473 users/templates/users/first_login.html:42
#: users/models/user.py:463 users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:49
#: users/templates/users/user_profile.html:69
#: users/templates/users/user_profile_update.html:46
......@@ -1478,7 +1505,7 @@ msgstr "资产列表"
#: ops/templates/ops/command_execution_create.html:124
#: settings/templates/settings/_ldap_list_users_modal.html:41
#: users/templates/users/_granted_assets.html:7
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:62
msgid "Loading"
msgstr "加载中"
......@@ -1509,7 +1536,6 @@ msgid "Asset user auth"
msgstr "资产用户信息"
#: assets/templates/assets/_asset_user_auth_view_modal.html:54
#: assets/templates/assets/_node_detail_modal.html:56
#: authentication/templates/authentication/login_wait_confirm.html:114
msgid "Copy success"
msgstr "复制成功"
......@@ -1519,7 +1545,6 @@ msgid "Get auth info error"
msgstr "获取认证信息错误"
#: assets/templates/assets/_asset_user_auth_view_modal.html:97
#: assets/templates/assets/_node_detail_modal.html:67
#: assets/templates/assets/_user_asset_detail_modal.html:23
#: authentication/templates/authentication/_access_key_modal.html:142
#: authentication/templates/authentication/_mfa_confirm_modal.html:53
......@@ -1540,7 +1565,7 @@ msgid "Datetime"
msgstr "日期"
#: assets/templates/assets/_asset_user_list.html:41
#: assets/templates/assets/asset_list.html:138
#: assets/templates/assets/asset_list.html:137
msgid "Test datetime: "
msgstr "测试日期: "
......@@ -1576,27 +1601,6 @@ msgstr "SSH端口"
msgid "If use nat, set the ssh real port"
msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口"
#: assets/templates/assets/_node_detail_modal.html:11
#: assets/templates/assets/asset_list.html:203
msgid "Node detail"
msgstr "节点详情"
#: assets/templates/assets/_node_detail_modal.html:18
#: audits/templates/audits/login_log_list.html:56
#: authentication/templates/authentication/_access_key_modal.html:30
#: ops/templates/ops/adhoc_detail.html:47
#: ops/templates/ops/adhoc_history_detail.html:47
#: ops/templates/ops/task_detail.html:54
#: terminal/templates/terminal/session_list.html:24
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:64
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:60
msgid "ID"
msgstr "ID"
#: assets/templates/assets/_node_detail_modal.html:33
msgid "Full name"
msgstr "全名"
#: assets/templates/assets/_node_tree.html:49
msgid "Add node"
msgstr "新建节点"
......@@ -1630,9 +1634,9 @@ msgstr "重命名成功"
#: assets/templates/assets/gateway_create_update.html:33
#: perms/templates/perms/asset_permission_create_update.html:36
#: perms/templates/perms/remote_app_permission_create_update.html:37
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:41
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:27
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:27
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:39
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:23
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:23
msgid "Basic"
msgstr "基本"
......@@ -1653,9 +1657,9 @@ msgstr "自动生成密钥"
#: perms/templates/perms/asset_permission_create_update.html:51
#: perms/templates/perms/remote_app_permission_create_update.html:51
#: terminal/templates/terminal/terminal_update.html:38
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:65
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:48
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:39
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:63
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:44
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:35
msgid "Other"
msgstr "其它"
......@@ -1681,7 +1685,7 @@ msgstr "资产列表"
#: assets/templates/assets/admin_user_assets.html:24
#: perms/templates/perms/asset_permission_asset.html:31
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:31
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:27
msgid "Asset list of "
msgstr "资产列表"
......@@ -1706,14 +1710,14 @@ msgstr "替换资产的管理员"
#: assets/templates/assets/admin_user_detail.html:86
#: perms/templates/perms/asset_permission_asset.html:99
#: xpack/plugins/change_auth_plan/forms.py:68
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:99
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:95
#: xpack/plugins/gathered_user/forms.py:36
msgid "Select nodes"
msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:95
#: assets/templates/assets/asset_detail.html:200
#: assets/templates/assets/asset_list.html:427
#: assets/templates/assets/asset_list.html:424
#: assets/templates/assets/cmd_filter_detail.html:101
#: assets/templates/assets/system_user_assets.html:101
#: assets/templates/assets/system_user_detail.html:177
......@@ -1727,12 +1731,12 @@ msgstr "选择节点"
#: users/templates/users/user_detail.html:548
#: users/templates/users/user_group_create_update.html:28
#: users/templates/users/user_group_list.html:120
#: users/templates/users/user_list.html:276
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:54
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:45
#: users/templates/users/user_list.html:256
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:30
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:50
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:41
#: xpack/plugins/interface/templates/interface/interface.html:103
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:34
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:30
msgid "Confirm"
msgstr "确认"
......@@ -1755,8 +1759,8 @@ msgstr "Jumpserver 使用该用户来 `推送系统用户`、`获取资产硬件
#: audits/templates/audits/login_log_list.html:91
#: users/templates/users/user_group_list.html:10
#: users/templates/users/user_list.html:10
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:49
#: xpack/plugins/vault/templates/vault/vault.html:47
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:54
#: xpack/plugins/vault/templates/vault/vault.html:53
msgid "Export"
msgstr "导出"
......@@ -1766,8 +1770,8 @@ msgstr "导出"
#: settings/templates/settings/_ldap_list_users_modal.html:172
#: users/templates/users/user_group_list.html:15
#: users/templates/users/user_list.html:15
#: xpack/plugins/license/templates/license/license_detail.html:101
#: xpack/plugins/vault/templates/vault/vault.html:52
#: xpack/plugins/license/templates/license/license_detail.html:110
#: xpack/plugins/vault/templates/vault/vault.html:58
msgid "Import"
msgstr "导入"
......@@ -1778,15 +1782,15 @@ msgstr "创建管理用户"
#: assets/templates/assets/admin_user_list.html:123
#: assets/templates/assets/admin_user_list.html:154
#: assets/templates/assets/asset_list.html:307
#: assets/templates/assets/asset_list.html:344
#: assets/templates/assets/asset_list.html:304
#: assets/templates/assets/asset_list.html:341
#: assets/templates/assets/system_user_list.html:186
#: assets/templates/assets/system_user_list.html:217
#: users/templates/users/user_group_list.html:164
#: users/templates/users/user_group_list.html:195
#: users/templates/users/user_list.html:184
#: users/templates/users/user_list.html:216
#: xpack/plugins/vault/templates/vault/vault.html:200
#: users/templates/users/user_list.html:165
#: users/templates/users/user_list.html:197
#: xpack/plugins/vault/templates/vault/vault.html:222
msgid "Please select file"
msgstr "选择文件"
......@@ -1804,9 +1808,9 @@ msgstr "资产用户"
#: terminal/templates/terminal/session_detail.html:85
#: users/templates/users/user_detail.html:141
#: users/templates/users/user_profile.html:150
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:128
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:132
#: xpack/plugins/license/templates/license/license_detail.html:93
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:126
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:129
#: xpack/plugins/license/templates/license/license_detail.html:102
msgid "Quick modify"
msgstr "快速修改"
......@@ -1875,12 +1879,12 @@ msgid "Hardware"
msgstr "硬件"
#: assets/templates/assets/asset_list.html:109
#: users/templates/users/user_list.html:51
#: users/templates/users/user_list.html:50
msgid "Delete selected"
msgstr "批量删除"
#: assets/templates/assets/asset_list.html:110
#: users/templates/users/user_list.html:55
#: users/templates/users/user_list.html:51
msgid "Update selected"
msgstr "批量更新"
......@@ -1889,75 +1893,75 @@ msgid "Remove from this node"
msgstr "从节点移除"
#: assets/templates/assets/asset_list.html:112
#: users/templates/users/user_list.html:56
#: users/templates/users/user_list.html:52
msgid "Deactive selected"
msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:113
#: users/templates/users/user_list.html:57
#: users/templates/users/user_list.html:53
msgid "Active selected"
msgstr "激活所选"
#: assets/templates/assets/asset_list.html:194
#: assets/templates/assets/asset_list.html:193
msgid "Add assets to node"
msgstr "添加资产到节点"
#: assets/templates/assets/asset_list.html:195
#: assets/templates/assets/asset_list.html:194
msgid "Move assets to node"
msgstr "移动资产到节点"
#: assets/templates/assets/asset_list.html:197
#: assets/templates/assets/asset_list.html:196
msgid "Refresh node hardware info"
msgstr "更新节点资产硬件信息"
#: assets/templates/assets/asset_list.html:198
#: assets/templates/assets/asset_list.html:197
msgid "Test node connective"
msgstr "测试节点资产可连接性"
#: assets/templates/assets/asset_list.html:200
#: assets/templates/assets/asset_list.html:199
msgid "Display only current node assets"
msgstr "仅显示当前节点资产"
#: assets/templates/assets/asset_list.html:201
#: assets/templates/assets/asset_list.html:200
msgid "Displays all child node assets"
msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:421
#: assets/templates/assets/asset_list.html:418
#: assets/templates/assets/system_user_list.html:127
#: users/templates/users/user_detail.html:448
#: users/templates/users/user_detail.html:474
#: users/templates/users/user_detail.html:542
#: users/templates/users/user_group_list.html:114
#: users/templates/users/user_list.html:270
#: users/templates/users/user_list.html:250
#: xpack/plugins/interface/templates/interface/interface.html:97
msgid "Are you sure?"
msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:422
#: assets/templates/assets/asset_list.html:419
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:425
#: assets/templates/assets/asset_list.html:422
#: assets/templates/assets/system_user_list.html:131
#: users/templates/users/user_detail.html:452
#: users/templates/users/user_detail.html:478
#: users/templates/users/user_detail.html:546
#: users/templates/users/user_group_list.html:118
#: users/templates/users/user_list.html:274
#: users/templates/users/user_list.html:254
#: xpack/plugins/interface/templates/interface/interface.html:101
msgid "Cancel"
msgstr "取消"
#: assets/templates/assets/asset_list.html:436
#: assets/templates/assets/asset_list.html:433
msgid "Asset Deleted."
msgstr "已被删除"
#: assets/templates/assets/asset_list.html:437
#: assets/templates/assets/asset_list.html:445
#: assets/templates/assets/asset_list.html:434
#: assets/templates/assets/asset_list.html:442
msgid "Asset Delete"
msgstr "删除"
#: assets/templates/assets/asset_list.html:444
#: assets/templates/assets/asset_list.html:441
msgid "Asset Deleting failed."
msgstr "删除失败"
......@@ -2034,14 +2038,14 @@ msgid "Gateway list"
msgstr "网关列表"
#: assets/templates/assets/domain_gateway_list.html:51
#: assets/views/domain.py:136
#: assets/views/domain.py:137
msgid "Create gateway"
msgstr "创建网关"
#: assets/templates/assets/domain_gateway_list.html:94
#: assets/templates/assets/domain_gateway_list.html:96
#: settings/templates/settings/email_setting.html:64
#: settings/templates/settings/ldap_setting.html:65
#: settings/templates/settings/email_setting.html:45
#: settings/templates/settings/ldap_setting.html:46
msgid "Test connection"
msgstr "测试连接"
......@@ -2062,7 +2066,7 @@ msgstr ""
msgid "JMS => Domain gateway => Target assets"
msgstr "JMS => 网域网关 => 目标资产"
#: assets/templates/assets/domain_list.html:13 assets/views/domain.py:48
#: assets/templates/assets/domain_list.html:13 assets/views/domain.py:49
msgid "Create domain"
msgstr "创建网域"
......@@ -2070,6 +2074,10 @@ msgstr "创建网域"
msgid "Create label"
msgstr "创建标签"
#: assets/templates/assets/platform_list.html:8 assets/views/platform.py:33
msgid "Create platform"
msgstr "创建系统平台"
#: assets/templates/assets/system_user_assets.html:35
msgid "Assets of "
msgstr "资产"
......@@ -2194,23 +2202,23 @@ msgstr "创建命令过滤器规则"
msgid "Update command filter rule"
msgstr "更新命令过滤器规则"
#: assets/views/domain.py:31 templates/_nav.html:43
#: assets/views/domain.py:32 templates/_nav.html:43
msgid "Domain list"
msgstr "网域列表"
#: assets/views/domain.py:66
#: assets/views/domain.py:67
msgid "Update domain"
msgstr "更新网域"
#: assets/views/domain.py:81
#: assets/views/domain.py:82
msgid "Domain detail"
msgstr "网域详情"
#: assets/views/domain.py:107
#: assets/views/domain.py:108
msgid "Domain gateway list"
msgstr "域网关列表"
#: assets/views/domain.py:157
#: assets/views/domain.py:158
msgid "Update gateway"
msgstr "创建网关"
......@@ -2226,6 +2234,14 @@ msgstr "提示: 请避免使用内部预留标签名: {}"
msgid "Update label"
msgstr "更新标签"
#: assets/views/platform.py:18
msgid "Platform list"
msgstr "平台列表"
#: assets/views/platform.py:48
msgid "Update platform"
msgstr "更新系统平台"
#: assets/views/system_user.py:30
msgid "System user list"
msgstr "系统用户列表"
......@@ -2273,7 +2289,7 @@ msgstr "成功"
#: audits/models.py:33
#: authentication/templates/authentication/_access_key_modal.html:22
#: xpack/plugins/vault/templates/vault/vault.html:38
#: xpack/plugins/vault/templates/vault/vault.html:44
msgid "Create"
msgstr "创建"
......@@ -2326,7 +2342,7 @@ msgstr "Agent"
#: audits/models.py:86 audits/templates/audits/login_log_list.html:62
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: users/forms.py:194 users/models/user.py:465
#: users/forms.py:194 users/models/user.py:455
#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
......@@ -2335,7 +2351,7 @@ msgstr "MFA"
#: xpack/plugins/change_auth_plan/models.py:423
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
#: xpack/plugins/cloud/models.py:278
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:64
msgid "Reason"
msgstr "原因"
......@@ -2344,8 +2360,8 @@ msgstr "原因"
#: tickets/templates/tickets/ticket_list.html:36
#: tickets/templates/tickets/ticket_list.html:104
#: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:65
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62
msgid "Status"
msgstr "状态"
......@@ -2365,7 +2381,7 @@ msgstr "登录日期"
#: xpack/plugins/change_auth_plan/models.py:426
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17
#: xpack/plugins/gathered_user/models.py:140
#: xpack/plugins/gathered_user/models.py:143
msgid "Date start"
msgstr "开始日期"
......@@ -2382,11 +2398,22 @@ msgstr "选择用户"
#: ops/templates/ops/command_execution_list.html:49
#: ops/templates/ops/command_execution_list.html:54
#: templates/_base_list.html:37 templates/_user_profile.html:23
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:47
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:45
msgid "Search"
msgstr "搜索"
#: audits/templates/audits/login_log_list.html:56
#: authentication/templates/authentication/_access_key_modal.html:30
#: ops/templates/ops/adhoc_detail.html:47
#: ops/templates/ops/adhoc_history_detail.html:47
#: ops/templates/ops/task_detail.html:54
#: terminal/templates/terminal/session_list.html:24
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:59
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:57
msgid "ID"
msgstr "ID"
#: audits/templates/audits/login_log_list.html:59
msgid "UA"
msgstr "Agent"
......@@ -2581,14 +2608,14 @@ msgid "Show"
msgstr "显示"
#: authentication/templates/authentication/_access_key_modal.html:66
#: users/models/user.py:365 users/templates/users/user_profile.html:94
#: users/models/user.py:355 users/templates/users/user_profile.html:94
#: users/templates/users/user_profile.html:163
#: users/templates/users/user_profile.html:166
msgid "Disable"
msgstr "禁用"
#: authentication/templates/authentication/_access_key_modal.html:67
#: users/models/user.py:366 users/templates/users/user_profile.html:92
#: users/models/user.py:356 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:170
msgid "Enable"
msgstr "启用"
......@@ -2804,8 +2831,10 @@ msgid "discard time"
msgstr ""
#: common/utils/ipip/utils.py:15
#, fuzzy
#| msgid "Invalid file."
msgid "Invalid ip"
msgstr "无效 IP"
msgstr "文件不合法"
#: common/validators.py:11
msgid "Special char not allowed"
......@@ -2908,8 +2937,8 @@ msgid "Become"
msgstr "Become"
#: ops/models/adhoc.py:191 users/templates/users/user_group_detail.html:54
#: xpack/plugins/cloud/templates/cloud/account_detail.html:62
#: xpack/plugins/orgs/templates/orgs/org_detail.html:56
#: xpack/plugins/cloud/templates/cloud/account_detail.html:59
#: xpack/plugins/orgs/templates/orgs/org_detail.html:51
msgid "Create by"
msgstr "创建者"
......@@ -2935,7 +2964,7 @@ msgstr "完成时间"
#: xpack/plugins/change_auth_plan/models.py:429
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16
#: xpack/plugins/gathered_user/models.py:143
#: xpack/plugins/gathered_user/models.py:146
msgid "Time"
msgstr "时间"
......@@ -2978,8 +3007,10 @@ msgid "Task end"
msgstr "任务结束"
#: ops/tasks.py:63
#, fuzzy
#| msgid "Sync task history"
msgid "Clean task history period"
msgstr "定期清除任务历史"
msgstr "同步历史列表"
#: ops/tasks.py:76
msgid "Clean celery log period"
......@@ -3168,9 +3199,9 @@ msgid "Versions"
msgstr "版本"
#: ops/templates/ops/task_list.html:68
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:137
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:135
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:54
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:141
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:138
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:44
msgid "Run"
......@@ -3235,7 +3266,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件"
#: perms/templates/perms/asset_permission_list.html:206
#: perms/templates/perms/remote_app_permission_list.html:16
#: templates/_nav.html:21 users/forms.py:313 users/models/group.py:26
#: users/models/user.py:449 users/templates/users/_select_user_modal.html:16
#: users/models/user.py:439 users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_asset_permission.html:56
#: users/templates/users/user_asset_permission.html:87
#: users/templates/users/user_detail.html:222
......@@ -3283,7 +3314,7 @@ msgstr "资产授权"
#: perms/models/base.py:53
#: perms/templates/perms/asset_permission_detail.html:85
#: perms/templates/perms/remote_app_permission_detail.html:77
#: users/models/user.py:481 users/templates/users/user_detail.html:108
#: users/models/user.py:471 users/templates/users/user_detail.html:108
#: users/templates/users/user_profile.html:120
msgid "Date expired"
msgstr "失效日期"
......@@ -3304,8 +3335,8 @@ msgstr "用户或用户组"
#: perms/templates/perms/asset_permission_asset.html:23
#: perms/templates/perms/asset_permission_detail.html:22
#: perms/templates/perms/asset_permission_user.html:23
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:20
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:23
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:16
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:21
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:20
msgid "Assets and node"
msgstr "资产或节点"
......@@ -3323,9 +3354,9 @@ msgstr "添加资产"
#: perms/templates/perms/remote_app_permission_user.html:92
#: perms/templates/perms/remote_app_permission_user.html:120
#: users/templates/users/user_group_detail.html:87
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:80
#: xpack/plugins/orgs/templates/orgs/org_detail.html:93
#: xpack/plugins/orgs/templates/orgs/org_detail.html:130
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:76
#: xpack/plugins/orgs/templates/orgs/org_detail.html:88
#: xpack/plugins/orgs/templates/orgs/org_detail.html:125
msgid "Add"
msgstr "添加"
......@@ -3335,7 +3366,7 @@ msgstr "添加节点"
#: perms/templates/perms/asset_permission_asset.html:105
#: users/templates/users/user_detail.html:239
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:105
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:101
msgid "Join"
msgstr "加入"
......@@ -3351,7 +3382,7 @@ msgstr "有效期"
#: perms/templates/perms/asset_permission_detail.html:61
#: perms/templates/perms/remote_app_permission_detail.html:61
#: xpack/plugins/license/templates/license/license_detail.html:67
#: xpack/plugins/license/templates/license/license_detail.html:76
msgid "User count"
msgstr "用户数量"
......@@ -3361,7 +3392,7 @@ msgid "User group count"
msgstr "用户组数量"
#: perms/templates/perms/asset_permission_detail.html:69
#: xpack/plugins/license/templates/license/license_detail.html:63
#: xpack/plugins/license/templates/license/license_detail.html:72
msgid "Asset count"
msgstr "资产数量"
......@@ -3388,7 +3419,7 @@ msgstr "刷新授权缓存"
#: users/templates/users/user_asset_permission.html:60
#: users/templates/users/user_asset_permission.html:175
#: users/templates/users/user_list.html:40 xpack/plugins/cloud/models.py:74
#: xpack/plugins/cloud/templates/cloud/account_detail.html:58
#: xpack/plugins/cloud/templates/cloud/account_detail.html:55
#: xpack/plugins/cloud/templates/cloud/account_list.html:14
msgid "Validity"
msgstr "有效"
......@@ -3858,7 +3889,7 @@ msgid "Refresh cache"
msgstr "刷新缓存"
#: settings/templates/settings/_ldap_list_users_modal.html:33
#: users/models/user.py:445 users/templates/users/user_detail.html:72
#: users/models/user.py:435 users/templates/users/user_detail.html:72
#: users/templates/users/user_profile.html:59
msgid "Email"
msgstr "邮件"
......@@ -3872,71 +3903,41 @@ msgid ""
"User is not currently selected, please check the user you want to import"
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:21
#: settings/templates/settings/terminal_setting.html:54 settings/views.py:20
#: settings/templates/settings/_setting_tabs.html:4
#: settings/templates/settings/terminal_setting.html:31 settings/views.py:20
msgid "Basic setting"
msgstr "基本设置"
#: settings/templates/settings/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
#: settings/templates/settings/terminal_setting.html:25 settings/views.py:47
#: settings/templates/settings/_setting_tabs.html:7 settings/views.py:47
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:28 settings/views.py:162
#: settings/templates/settings/_setting_tabs.html:10 settings/views.py:162
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:32 settings/views.py:74
#: settings/templates/settings/_setting_tabs.html:13 settings/views.py:74
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/terminal_setting.html:36 settings/views.py:106
#: settings/templates/settings/_setting_tabs.html:16 settings/views.py:106
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:39 settings/views.py:135
#: settings/templates/settings/_setting_tabs.html:19
#: settings/templates/settings/security_setting.html:26 settings/views.py:135
msgid "Security setting"
msgstr "安全设置"
#: settings/templates/settings/email_content_setting.html:45
#: settings/templates/settings/email_content_setting.html:26
msgid "Create User setting"
msgstr "创建用户设置"
#: settings/templates/settings/ldap_setting.html:66
#: settings/templates/settings/ldap_setting.html:47
msgid "Bulk import"
msgstr "一键导入"
#: settings/templates/settings/security_setting.html:49
#: settings/templates/settings/security_setting.html:30
msgid "Password check rule"
msgstr "密码校验规则"
......@@ -4428,8 +4429,8 @@ msgid ""
msgstr ""
#: terminal/forms/storage.py:143 xpack/plugins/cloud/models.py:304
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:109
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:106
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:59
msgid "Region"
msgstr "地域"
......@@ -4710,10 +4711,6 @@ msgstr "标题"
msgid "Body"
msgstr "内容"
#: tickets/models/ticket.py:38
msgid "Meta"
msgstr ""
#: tickets/models/ticket.py:39 tickets/templates/tickets/ticket_detail.html:51
msgid "Assignee"
msgstr "处理人"
......@@ -4827,11 +4824,11 @@ msgstr "工单列表"
msgid "Ticket detail"
msgstr "工单详情"
#: users/api/user.py:180
#: users/api/user.py:174
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
#: users/forms.py:47 users/models/user.py:453
#: users/forms.py:47 users/models/user.py:443
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:88
#: users/templates/users/user_list.html:37
......@@ -4839,7 +4836,7 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
msgid "Role"
msgstr "角色"
#: users/forms.py:51 users/models/user.py:488
#: users/forms.py:51 users/models/user.py:478
#: users/templates/users/user_detail.html:104
#: users/templates/users/user_list.html:39
#: users/templates/users/user_profile.html:102
......@@ -4884,8 +4881,8 @@ msgid "Set password"
msgstr "设置密码"
#: users/forms.py:152 xpack/plugins/change_auth_plan/models.py:89
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:49
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:47
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:67
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:16
msgid "Password strategy"
......@@ -4957,7 +4954,7 @@ msgstr "选择用户"
msgid "User auth from {}, go there change password"
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
#: users/models/user.py:147 users/models/user.py:581
#: users/models/user.py:147 users/models/user.py:571
msgid "Administrator"
msgstr "管理员"
......@@ -4978,27 +4975,27 @@ msgstr "组织管理员"
msgid "Org auditor"
msgstr "组织审计员"
#: users/models/user.py:367 users/templates/users/user_profile.html:90
#: users/models/user.py:357 users/templates/users/user_profile.html:90
msgid "Force enable"
msgstr "强制启用"
#: users/models/user.py:433
#: users/models/user.py:423
msgid "Local"
msgstr "数据库"
#: users/models/user.py:456
#: users/models/user.py:446
msgid "Avatar"
msgstr "头像"
#: users/models/user.py:459 users/templates/users/user_detail.html:83
#: users/models/user.py:449 users/templates/users/user_detail.html:83
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:492
#: users/models/user.py:482
msgid "Date password last updated"
msgstr "最后更新密码日期"
#: users/models/user.py:584
#: users/models/user.py:574
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
......@@ -5057,7 +5054,7 @@ msgstr "安全令牌验证"
#: users/templates/users/_base_otp.html:44 users/templates/users/_user.html:13
#: users/templates/users/user_profile_update.html:55
#: xpack/plugins/cloud/models.py:147
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:60
#: 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 "账户"
......@@ -5347,7 +5344,7 @@ msgid "User group detail"
msgstr "用户组详情"
#: users/templates/users/user_group_detail.html:81
#: xpack/plugins/orgs/templates/orgs/org_detail.html:121
#: xpack/plugins/orgs/templates/orgs/org_detail.html:116
msgid "Add user"
msgstr "添加用户"
......@@ -5372,52 +5369,28 @@ msgstr "用户组删除"
msgid "UserGroup Deleting failed."
msgstr "用户组删除失败"
#: users/templates/users/user_list.html:53
msgid "Remove selected"
msgstr "批量移除"
#: users/templates/users/user_list.html:129
#: users/templates/users/user_list.html:133
msgid "Remove"
msgstr "移除"
#: users/templates/users/user_list.html:271
#: users/templates/users/user_list.html:251
msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!"
#: users/templates/users/user_list.html:282
msgid "User Deleting failed."
msgstr "用户删除失败"
#: users/templates/users/user_list.html:262
msgid "User Deleted."
msgstr "已被删除"
#: users/templates/users/user_list.html:283
#: users/templates/users/user_list.html:263
#: users/templates/users/user_list.html:267
msgid "User Delete"
msgstr "删除"
#: users/templates/users/user_list.html:305
msgid "This will remove the selected users !!"
msgstr "移除选中用户 !!!"
#: users/templates/users/user_list.html:307
msgid "User Removing failed."
msgstr "用户移除失败"
#: users/templates/users/user_list.html:308
msgid "User Remove"
msgstr "用户移除"
#: users/templates/users/user_list.html:357
msgid "Are you sure about removing it?"
msgstr "您确定移除吗?"
#: users/templates/users/user_list.html:358
msgid "Remove the success"
msgstr "移除成功"
#: users/templates/users/user_list.html:266
msgid "User Deleting failed."
msgstr "用户删除失败"
#: users/templates/users/user_list.html:363
#: users/templates/users/user_list.html:327
msgid "User is expired"
msgstr "用户已失效"
#: users/templates/users/user_list.html:366
#: users/templates/users/user_list.html:330
msgid "User is inactive"
msgstr "用户已禁用"
......@@ -5804,16 +5777,16 @@ msgid "Password length"
msgstr "密码长度"
#: xpack/plugins/change_auth_plan/forms.py:75
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:58
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:81
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:56
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:79
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17
#: xpack/plugins/cloud/forms.py:33 xpack/plugins/cloud/forms.py:87
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:41
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:72
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:37
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16
#: xpack/plugins/gathered_user/forms.py:13
#: xpack/plugins/gathered_user/forms.py:41
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:32
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:28
msgid "Periodic perform"
msgstr "定时执行"
......@@ -5869,9 +5842,9 @@ msgstr "所有资产使用不同的随机密码"
#: xpack/plugins/change_auth_plan/models.py:79
#: xpack/plugins/change_auth_plan/models.py:148
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:98
#: xpack/plugins/cloud/models.py:165 xpack/plugins/cloud/models.py:219
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:88
#: xpack/plugins/gathered_user/models.py:35
#: xpack/plugins/gathered_user/models.py:72
msgid "Cycle perform"
......@@ -5879,16 +5852,16 @@ msgstr "周期执行"
#: xpack/plugins/change_auth_plan/models.py:84
#: xpack/plugins/change_auth_plan/models.py:146
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:90
#: xpack/plugins/cloud/models.py:170 xpack/plugins/cloud/models.py:217
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:80
#: xpack/plugins/gathered_user/models.py:40
#: xpack/plugins/gathered_user/models.py:70
msgid "Regularly perform"
msgstr "定期执行"
#: xpack/plugins/change_auth_plan/models.py:93
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:72
msgid "Password rules"
msgstr "密码规则"
......@@ -5942,46 +5915,46 @@ msgstr "* 密码长度范围 6-30 位"
msgid "* Please enter a valid crontab expression"
msgstr "* 请输入有效的 crontab 表达式"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:23
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:26
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:19
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:24
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:23
#: xpack/plugins/change_auth_plan/views.py:133
msgid "Plan execution list"
msgstr "执行列表"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:66
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:62
msgid "Add asset to this plan"
msgstr "添加资产"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:91
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:87
msgid "Add node to this plan"
msgstr "添加节点"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:11
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:8
msgid ""
"When the user password on the asset is changed, the action is performed "
"using the admin user associated with the asset"
msgstr "更改资产上的用户密码时,将会使用与该资产关联的管理用户进行操作"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:76
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74
msgid "Length"
msgstr "长度"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:84
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:75
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:82
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:72
msgid "Yes"
msgstr "是"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:86
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:84
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:74
msgid "No"
msgstr "否"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:134
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:132
msgid "Run plan manually"
msgstr "手动执行计划"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:178
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:176
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:102
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:90
msgid "Execute failed"
......@@ -5992,7 +5965,7 @@ msgid "Execution list of plan"
msgstr "执行列表"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:104
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:89
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:84
msgid "Log"
msgstr "日志"
......@@ -6071,7 +6044,7 @@ msgid "Unavailable"
msgstr "无效"
#: xpack/plugins/cloud/models.py:63
#: xpack/plugins/cloud/templates/cloud/account_detail.html:54
#: xpack/plugins/cloud/templates/cloud/account_detail.html:51
#: xpack/plugins/cloud/templates/cloud/account_list.html:13
msgid "Provider"
msgstr "云服务商"
......@@ -6085,7 +6058,7 @@ msgid "Access key secret"
msgstr ""
#: xpack/plugins/cloud/models.py:88
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:30
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:26
msgid "Cloud account"
msgstr "云账号"
......@@ -6098,7 +6071,7 @@ msgid "Instances"
msgstr "实例"
#: xpack/plugins/cloud/models.py:176
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:97
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:94
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:17
msgid "Date last sync"
msgstr "最后同步日期"
......@@ -6116,8 +6089,8 @@ msgid "Partial succeed"
msgstr ""
#: xpack/plugins/cloud/models.py:281 xpack/plugins/cloud/models.py:313
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:71
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:66
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63
msgid "Date sync"
msgstr "同步日期"
......@@ -6134,8 +6107,8 @@ msgid "Sync instance task history"
msgstr "同步实例任务历史"
#: xpack/plugins/cloud/models.py:301
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:117
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:61
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:114
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:58
msgid "Instance"
msgstr "实例"
......@@ -6155,7 +6128,7 @@ msgstr "AWS (国际)"
msgid "Qcloud"
msgstr "腾讯云"
#: xpack/plugins/cloud/templates/cloud/account_detail.html:20
#: xpack/plugins/cloud/templates/cloud/account_detail.html:17
#: xpack/plugins/cloud/views.py:79
msgid "Account detail"
msgstr "账户详情"
......@@ -6165,61 +6138,61 @@ msgstr "账户详情"
msgid "Create account"
msgstr "创建账户"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:33
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:29
msgid "Region & Instance"
msgstr "地域 & 实例"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:37
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:33
msgid "Node & AdminUser"
msgstr "节点 & 管理用户"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:67
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:63
msgid "Load failed"
msgstr "加载失败"
#: 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:21
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:17
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:20
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:18
#: xpack/plugins/cloud/views.py:144
msgid "Sync task detail"
msgstr "同步任务详情"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:28
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:20
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:23
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21
#: xpack/plugins/cloud/views.py:160
msgid "Sync task history"
msgstr "同步历史列表"
#: 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:27
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:26
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24
#: xpack/plugins/cloud/views.py:212
msgid "Sync instance list"
msgstr "同步实例列表"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:138
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:135
msgid "Run task manually"
msgstr "手动执行任务"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:181
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:178
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:99
msgid "Sync success"
msgstr "同步成功"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:65
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:60
msgid "Total count"
msgstr "总数"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:66
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:61
msgid "Succeed count"
msgstr "成功"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:67
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:62
msgid "Failed count"
msgstr "失败"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:68
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:63
msgid "Exist count"
msgstr "存在"
......@@ -6267,19 +6240,19 @@ msgid "Periodic"
msgstr "定时执行"
#: xpack/plugins/gathered_user/models.py:57
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:38
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:43
msgid "Gather user task"
msgstr "收集用户任务"
#: xpack/plugins/gathered_user/models.py:137
#: xpack/plugins/gathered_user/models.py:140
msgid "Task"
msgstr "任务"
#: xpack/plugins/gathered_user/models.py:149
#: xpack/plugins/gathered_user/models.py:152
msgid "gather user task execution"
msgstr "收集用户执行"
#: xpack/plugins/gathered_user/models.py:155
#: xpack/plugins/gathered_user/models.py:158
msgid "Assets is empty, please change nodes"
msgstr "资产为空,请更改节点"
......@@ -6377,8 +6350,8 @@ msgid "It is already in the default setting state!"
msgstr "当前已经是初始化状态!"
#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94
#: xpack/plugins/license/templates/license/license_detail.html:41
#: xpack/plugins/license/templates/license/license_detail.html:46
#: xpack/plugins/license/templates/license/license_detail.html:50
#: xpack/plugins/license/templates/license/license_detail.html:55
#: xpack/plugins/license/views.py:32
msgid "License"
msgstr "许可证"
......@@ -6392,7 +6365,7 @@ msgid "Enterprise edition"
msgstr "企业版"
#: xpack/plugins/license/templates/license/_license_import_modal.html:4
#: xpack/plugins/license/templates/license/license_detail.html:99
#: xpack/plugins/license/templates/license/license_detail.html:108
msgid "Import license"
msgstr "导入许可证"
......@@ -6400,64 +6373,64 @@ msgstr "导入许可证"
msgid "License file"
msgstr "许可证文件"
#: xpack/plugins/license/templates/license/license_detail.html:11
#: xpack/plugins/license/templates/license/license_detail.html:12
msgid "Please Import License"
msgstr "请导入许可证"
#: xpack/plugins/license/templates/license/license_detail.html:13
#: xpack/plugins/license/templates/license/license_detail.html:47
#: xpack/plugins/license/templates/license/license_detail.html:17
#: xpack/plugins/license/templates/license/license_detail.html:56
msgid "License has expired"
msgstr "许可证已经过期"
#: xpack/plugins/license/templates/license/license_detail.html:15
#: xpack/plugins/license/templates/license/license_detail.html:22
msgid "The license will at "
msgstr "许可证将在 "
#: xpack/plugins/license/templates/license/license_detail.html:15
#: xpack/plugins/license/templates/license/license_detail.html:22
msgid " expired."
msgstr " 过期。"
#: xpack/plugins/license/templates/license/license_detail.html:28
#: xpack/plugins/license/templates/license/license_detail.html:37
#: xpack/plugins/license/views.py:33
msgid "License detail"
msgstr "许可证详情"
#: xpack/plugins/license/templates/license/license_detail.html:42
#: xpack/plugins/license/templates/license/license_detail.html:51
msgid "No license"
msgstr "暂无许可证"
#: xpack/plugins/license/templates/license/license_detail.html:51
#: xpack/plugins/license/templates/license/license_detail.html:60
msgid "Subscription ID"
msgstr "订阅授权ID"
#: xpack/plugins/license/templates/license/license_detail.html:55
#: xpack/plugins/license/templates/license/license_detail.html:64
msgid "Corporation"
msgstr "公司"
#: xpack/plugins/license/templates/license/license_detail.html:59
#: xpack/plugins/license/templates/license/license_detail.html:68
msgid "Expired"
msgstr "过期时间"
#: xpack/plugins/license/templates/license/license_detail.html:64
#: xpack/plugins/license/templates/license/license_detail.html:68
#: xpack/plugins/license/templates/license/license_detail.html:72
#: xpack/plugins/license/templates/license/license_detail.html:76
#: xpack/plugins/license/templates/license/license_detail.html:73
#: xpack/plugins/license/templates/license/license_detail.html:77
#: xpack/plugins/license/templates/license/license_detail.html:81
#: xpack/plugins/license/templates/license/license_detail.html:85
msgid "Unlimited"
msgstr "无限制"
#: xpack/plugins/license/templates/license/license_detail.html:75
#: xpack/plugins/license/templates/license/license_detail.html:84
msgid "Concurrent connections"
msgstr "并发连接"
#: xpack/plugins/license/templates/license/license_detail.html:80
#: xpack/plugins/license/templates/license/license_detail.html:89
msgid "Edition"
msgstr "版本"
#: xpack/plugins/license/templates/license/license_detail.html:106
#: xpack/plugins/license/templates/license/license_detail.html:115
msgid "Technology consulting"
msgstr "技术咨询"
#: xpack/plugins/license/templates/license/license_detail.html:109
#: xpack/plugins/license/templates/license/license_detail.html:118
msgid "Consult"
msgstr "咨询"
......@@ -6474,7 +6447,7 @@ msgid "Select auditor"
msgstr "选择审计员"
#: xpack/plugins/orgs/forms.py:29
#: xpack/plugins/orgs/templates/orgs/org_detail.html:76
#: xpack/plugins/orgs/templates/orgs/org_detail.html:71
#: xpack/plugins/orgs/templates/orgs/org_list.html:13
msgid "Admin"
msgstr "管理员"
......@@ -6485,12 +6458,12 @@ msgstr "管理员"
msgid "Organizations"
msgstr "组织管理"
#: xpack/plugins/orgs/templates/orgs/org_detail.html:22
#: xpack/plugins/orgs/templates/orgs/org_detail.html:17
#: xpack/plugins/orgs/views.py:80
msgid "Org detail"
msgstr "组织详情"
#: xpack/plugins/orgs/templates/orgs/org_detail.html:84
#: xpack/plugins/orgs/templates/orgs/org_detail.html:79
msgid "Add admin"
msgstr "添加管理员"
......@@ -6527,8 +6500,15 @@ msgstr "密码匣子"
msgid "vault create"
msgstr "创建"
#~ msgid "User Deleted."
#~ msgstr "已被删除"
#, fuzzy
#~| msgid "Platform list"
#~ msgid "Platform create"
#~ msgstr "平台列表"
#, fuzzy
#~| msgid "Password update"
#~ msgid "Platform update"
#~ msgstr "密码更新"
#~ msgid "Search no entry matched in ou {}"
#~ msgstr "在ou:{}中没有匹配条目"
......@@ -6583,6 +6563,9 @@ msgstr "创建"
#~ msgid "Delete failed"
#~ msgstr "删除失败"
#~ msgid "Are you sure about deleting it?"
#~ msgstr "您确定删除吗?"
#~ msgid "The connection fails"
#~ msgstr "连接失败"
......@@ -6612,6 +6595,9 @@ msgstr "创建"
#~ msgid "Approve selected"
#~ msgstr "同意所选"
#~ msgid "Reject selected"
#~ msgstr "拒绝所选"
#~ msgid ""
#~ "\n"
#~ " <div>\n"
......
......@@ -58,7 +58,7 @@
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="col-sm-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content"
style="padding-top: 0;padding-left: 1px">
......@@ -71,7 +71,7 @@
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="col-sm-9 animated fadeInRight" id="split-right">
<div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn"
onclick="toggle()">
......@@ -87,14 +87,14 @@
style="height: 100%;width: 100%"></div>
</div>
<div class="row">
<div class="col-lg-10">
<div class="col-sm-10">
<div class="input-group"
style="height: 100%; width: 100%">
<textarea class="form-control"
id="command-text"></textarea>
</div>
</div>
<div class="col-lg-2">
<div class="col-sm-2">
<select class="select2 form-control"
id="system-users-select">
{% for s in system_users %}
......@@ -199,12 +199,12 @@
function toggle() {
if (show === 0) {
$("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-lg-12");
$("#split-right").attr("class", "col-sm-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
show = 1;
});
} else {
$("#split-right").attr("class", "col-lg-9");
$("#split-right").attr("class", "col-sm-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500);
show = 0;
......
......@@ -23,8 +23,10 @@ class AssetPermissionViewSet(OrgModelViewSet):
资产授权列表的增删改查api
"""
model = AssetPermission
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
serializer_display_class = serializers.AssetPermissionListSerializer
serializer_classes = {
'default': serializers.AssetPermissionCreateUpdateSerializer,
'display': serializers.AssetPermissionListSerializer
}
filter_fields = ['name']
permission_classes = (IsOrgAdmin,)
......
{% extends 'base.html' %}
{% extends '_base_asset_tree_list.html' %}
{% load static %}
{% load i18n %}
......@@ -12,56 +12,36 @@
.toggle {
cursor: pointer;
}
.detail-key {
width: 70px;
}
</style>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px;padding-right: 0">
{% include 'assets/_node_tree.html' %}
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header">
<div class="btn-group uc pull-left m-r-5">
<button class="btn btn-sm btn-primary btn-create-permission">
{% trans "Create permission" %}
</button>
<button data-toggle="dropdown" class="btn btn-primary btn-sm dropdown-toggle"><span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a class="refresh-asset-permission-cache" href="#">{% trans 'Refresh permission cache' %}</a></li>
</ul>
</div>
<table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%">
<thead>
<tr>
<th></th>
<th>{% trans 'Name' %}</th>
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Node'%}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Validity' %}</th>
<th class="text-center" >{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% block table_container %}
<div class="btn-group uc pull-left m-r-5">
<button class="btn btn-sm btn-primary btn-create-permission">
{% trans "Create permission" %}
</button>
<button data-toggle="dropdown" class="btn btn-primary btn-sm dropdown-toggle"><span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a class="refresh-asset-permission-cache" href="#">{% trans 'Refresh permission cache' %}</a></li>
</ul>
</div>
<table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%">
<thead>
<tr>
<th></th>
<th>{% trans 'Name' %}</th>
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Node'%}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Validity' %}</th>
<th class="text-center" >{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% include '_filter_dropdown.html' %}
{% endblock %}
......
......@@ -13,71 +13,74 @@ router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRela
router.register('asset-permissions-system-users-relations', api.AssetPermissionSystemUserRelationViewSet, 'asset-permissions-system-users-relation')
user_permission_urlpatterns = [
path('users/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
path('users/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'),
path('<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
path('assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'),
# Assets as tree
path('users/<uuid:pk>/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
path('users/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
path('<uuid:pk>/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
path('assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
# Nodes
path('users/<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
path('users/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'),
path('<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
path('nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'),
# Node children
path('users/<uuid:pk>/nodes/children/', api.UserGrantedNodesApi.as_view(), name='user-nodes-children'),
path('users/nodes/children/', api.UserGrantedNodesApi.as_view(), name='my-nodes-children'),
path('<uuid:pk>/nodes/children/', api.UserGrantedNodesApi.as_view(), name='user-nodes-children'),
path('nodes/children/', api.UserGrantedNodesApi.as_view(), name='my-nodes-children'),
# Node as tree
path('users/<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
path('users/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
path('<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
path('nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
# Node with assets as tree
path('users/<uuid:pk>/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-with-assets-as-tree'),
path('users/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
path('<uuid:pk>/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-with-assets-as-tree'),
path('nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
# Node children as tree
path('users/<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'),
path('users/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
path('<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'),
path('nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
# Node children with assets as tree
path('users/<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'),
path('users/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
path('<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'),
path('nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
# Node assets
path('users/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
path('users/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
path('nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
# Asset System users
path('users/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='user-asset-system-users'),
path('users/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'),
path('assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'),
]
user_group_permission_urlpatterns = [
# 查询某个用户组授权的资产和资产组
path('user-groups/<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
path('user-groups/<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
path('user-groups/<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
path('user-groups/<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'),
path('user-groups/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
path('user-groups/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'),
path('<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
path('<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
path('<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
path('<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'),
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
path('<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'),
]
asset_permission_urlpatterns = [
# Assets
path('', include(user_permission_urlpatterns)),
permission_urlpatterns = [
# 授权规则中授权的资产
path('asset-permissions/<uuid:pk>/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'),
path('asset-permissions/<uuid:pk>/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'),
path('<uuid:pk>/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'),
path('<uuid:pk>/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'),
# 验证用户是否有某个资产和系统用户的权限
path('asset-permissions/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'),
path('asset-permissions/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'),
path('user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'),
path('user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'),
# 刷新缓存
path('asset-permissions/cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'),
path('cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'),
]
asset_permission_urlpatterns = [
# Assets
path('users/', include(user_permission_urlpatterns)),
path('user-groups/', include(user_group_permission_urlpatterns)),
path('asset-permissions/', include(permission_urlpatterns)),
]
asset_permission_urlpatterns += router.urls
{% load i18n %}
<ul class="nav nav-tabs">
<li id="tab-basic">
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li id="tab-email" >
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li id="tab-email-content" >
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li id="tab-ldap">
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li id="tab-terminal">
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li id="tab-security">
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
<script>
$(document).ready(function () {
var path = location.pathname;
if (path.endsWith('/')) {
path = path.substring(0, path.length-1)
}
var pathList = path.split('/');
var tabId = pathList[pathList.length-1];
if (tabId === "settings") {
tabId = "basic"
}
tabId = "#tab-" + tabId;
$(tabId).addClass("active")
})
</script>
......@@ -10,26 +10,7 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
{% include 'settings/_setting_tabs.html' %}
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
......
......@@ -10,53 +10,33 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
{% include 'settings/_setting_tabs.html' %}
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
<form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
<form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
<h3>{% trans "Create User setting" %}</h3>
{% 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" %}
<div class="hr-line-dashed"></div>
<h3>{% trans "Create User setting" %}</h3>
{% 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" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</form>
</div>
</form>
</div>
</div>
</div>
......
......@@ -10,26 +10,7 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li class="active">
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
{% include 'settings/_setting_tabs.html' %}
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
......
......@@ -10,26 +10,7 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
{% include 'settings/_setting_tabs.html' %}
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
......
......@@ -10,26 +10,7 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
{% include 'settings/_setting_tabs.html' %}
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
......
......@@ -15,30 +15,7 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i
class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i
class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i
class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i
class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
{% include 'settings/_setting_tabs.html' %}
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
......
/*!
* Ladda
* http://lab.hakim.se/ladda
* MIT licensed
*
* Copyright (C) 2015 Hakim El Hattab, http://hakim.se
*/.ladda-button{position:relative}.ladda-button .ladda-spinner{position:absolute;z-index:2;display:inline-block;width:32px;height:32px;top:50%;margin-top:0;opacity:0;pointer-events:none}.ladda-button .ladda-label{position:relative;z-index:3}.ladda-button .ladda-progress{position:absolute;width:0;height:100%;left:0;top:0;background:rgba(0,0,0,0.2);visibility:hidden;opacity:0;-webkit-transition:0.1s linear all !important;-moz-transition:0.1s linear all !important;-ms-transition:0.1s linear all !important;-o-transition:0.1s linear all !important;transition:0.1s linear all !important}.ladda-button[data-loading] .ladda-progress{opacity:1;visibility:visible}.ladda-button,.ladda-button .ladda-spinner,.ladda-button .ladda-label{-webkit-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-moz-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-ms-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-o-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important}.ladda-button[data-style=zoom-in],.ladda-button[data-style=zoom-in] .ladda-spinner,.ladda-button[data-style=zoom-in] .ladda-label,.ladda-button[data-style=zoom-out],.ladda-button[data-style=zoom-out] .ladda-spinner,.ladda-button[data-style=zoom-out] .ladda-label{-webkit-transition:0.3s ease all !important;-moz-transition:0.3s ease all !important;-ms-transition:0.3s ease all !important;-o-transition:0.3s ease all !important;transition:0.3s ease all !important}.ladda-button[data-style=expand-right] .ladda-spinner{right:-6px}.ladda-button[data-style=expand-right][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-right][data-size="xs"] .ladda-spinner{right:-12px}.ladda-button[data-style=expand-right][data-loading]{padding-right:56px}.ladda-button[data-style=expand-right][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-right][data-loading][data-size="s"],.ladda-button[data-style=expand-right][data-loading][data-size="xs"]{padding-right:40px}.ladda-button[data-style=expand-left] .ladda-spinner{left:26px}.ladda-button[data-style=expand-left][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-left][data-size="xs"] .ladda-spinner{left:4px}.ladda-button[data-style=expand-left][data-loading]{padding-left:56px}.ladda-button[data-style=expand-left][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-left][data-loading][data-size="s"],.ladda-button[data-style=expand-left][data-loading][data-size="xs"]{padding-left:40px}.ladda-button[data-style=expand-up]{overflow:hidden}.ladda-button[data-style=expand-up] .ladda-spinner{top:-32px;left:50%;margin-left:0}.ladda-button[data-style=expand-up][data-loading]{padding-top:54px}.ladda-button[data-style=expand-up][data-loading] .ladda-spinner{opacity:1;top:26px;margin-top:0}.ladda-button[data-style=expand-up][data-loading][data-size="s"],.ladda-button[data-style=expand-up][data-loading][data-size="xs"]{padding-top:32px}.ladda-button[data-style=expand-up][data-loading][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-up][data-loading][data-size="xs"] .ladda-spinner{top:4px}.ladda-button[data-style=expand-down]{overflow:hidden}.ladda-button[data-style=expand-down] .ladda-spinner{top:62px;left:50%;margin-left:0}.ladda-button[data-style=expand-down][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-down][data-size="xs"] .ladda-spinner{top:40px}.ladda-button[data-style=expand-down][data-loading]{padding-bottom:54px}.ladda-button[data-style=expand-down][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-down][data-loading][data-size="s"],.ladda-button[data-style=expand-down][data-loading][data-size="xs"]{padding-bottom:32px}.ladda-button[data-style=slide-left]{overflow:hidden}.ladda-button[data-style=slide-left] .ladda-label{position:relative}.ladda-button[data-style=slide-left] .ladda-spinner{left:100%;margin-left:0}.ladda-button[data-style=slide-left][data-loading] .ladda-label{opacity:0;left:-100%}.ladda-button[data-style=slide-left][data-loading] .ladda-spinner{opacity:1;left:50%}.ladda-button[data-style=slide-right]{overflow:hidden}.ladda-button[data-style=slide-right] .ladda-label{position:relative}.ladda-button[data-style=slide-right] .ladda-spinner{right:100%;margin-left:0;left:16px}.ladda-button[data-style=slide-right][data-loading] .ladda-label{opacity:0;left:100%}.ladda-button[data-style=slide-right][data-loading] .ladda-spinner{opacity:1;left:50%}.ladda-button[data-style=slide-up]{overflow:hidden}.ladda-button[data-style=slide-up] .ladda-label{position:relative}.ladda-button[data-style=slide-up] .ladda-spinner{left:50%;margin-left:0;margin-top:1em}.ladda-button[data-style=slide-up][data-loading] .ladda-label{opacity:0;top:-1em}.ladda-button[data-style=slide-up][data-loading] .ladda-spinner{opacity:1;margin-top:0}.ladda-button[data-style=slide-down]{overflow:hidden}.ladda-button[data-style=slide-down] .ladda-label{position:relative}.ladda-button[data-style=slide-down] .ladda-spinner{left:50%;margin-left:0;margin-top:-2em}.ladda-button[data-style=slide-down][data-loading] .ladda-label{opacity:0;top:1em}.ladda-button[data-style=slide-down][data-loading] .ladda-spinner{opacity:1;margin-top:0}.ladda-button[data-style=zoom-out]{overflow:hidden}.ladda-button[data-style=zoom-out] .ladda-spinner{left:50%;margin-left:32px;-webkit-transform:scale(2.5);-moz-transform:scale(2.5);-ms-transform:scale(2.5);-o-transform:scale(2.5);transform:scale(2.5)}.ladda-button[data-style=zoom-out] .ladda-label{position:relative;display:inline-block}.ladda-button[data-style=zoom-out][data-loading] .ladda-label{opacity:0;-webkit-transform:scale(0.5);-moz-transform:scale(0.5);-ms-transform:scale(0.5);-o-transform:scale(0.5);transform:scale(0.5)}.ladda-button[data-style=zoom-out][data-loading] .ladda-spinner{opacity:1;margin-left:0;-webkit-transform:none;-moz-transform:none;-ms-transform:none;-o-transform:none;transform:none}.ladda-button[data-style=zoom-in]{overflow:hidden}.ladda-button[data-style=zoom-in] .ladda-spinner{left:50%;margin-left:-16px;-webkit-transform:scale(0.2);-moz-transform:scale(0.2);-ms-transform:scale(0.2);-o-transform:scale(0.2);transform:scale(0.2)}.ladda-button[data-style=zoom-in] .ladda-label{position:relative;display:inline-block}.ladda-button[data-style=zoom-in][data-loading] .ladda-label{opacity:0;-webkit-transform:scale(2.2);-moz-transform:scale(2.2);-ms-transform:scale(2.2);-o-transform:scale(2.2);transform:scale(2.2)}.ladda-button[data-style=zoom-in][data-loading] .ladda-spinner{opacity:1;margin-left:0;-webkit-transform:none;-moz-transform:none;-ms-transform:none;-o-transform:none;transform:none}.ladda-button[data-style=contract]{overflow:hidden;width:100px}.ladda-button[data-style=contract] .ladda-spinner{left:50%;margin-left:0}.ladda-button[data-style=contract][data-loading]{border-radius:50%;width:52px}.ladda-button[data-style=contract][data-loading] .ladda-label{opacity:0}.ladda-button[data-style=contract][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=contract-overlay]{overflow:hidden;width:100px;box-shadow:0px 0px 0px 2000px transparent}.ladda-button[data-style=contract-overlay] .ladda-spinner{left:50%;margin-left:0}.ladda-button[data-style=contract-overlay][data-loading]{border-radius:50%;width:52px;box-shadow:0px 0px 0px 2000px rgba(0,0,0,0.8)}.ladda-button[data-style=contract-overlay][data-loading] .ladda-label{opacity:0}.ladda-button[data-style=contract-overlay][data-loading] .ladda-spinner{opacity:1}
/*!
* Ladda including the default theme.
*//*!
* Ladda
* http://lab.hakim.se/ladda
* MIT licensed
*
* Copyright (C) 2015 Hakim El Hattab, http://hakim.se
*/.ladda-button{position:relative}.ladda-button .ladda-spinner{position:absolute;z-index:2;display:inline-block;width:32px;height:32px;top:50%;margin-top:0;opacity:0;pointer-events:none}.ladda-button .ladda-label{position:relative;z-index:3}.ladda-button .ladda-progress{position:absolute;width:0;height:100%;left:0;top:0;background:rgba(0,0,0,0.2);visibility:hidden;opacity:0;-webkit-transition:0.1s linear all !important;-moz-transition:0.1s linear all !important;-ms-transition:0.1s linear all !important;-o-transition:0.1s linear all !important;transition:0.1s linear all !important}.ladda-button[data-loading] .ladda-progress{opacity:1;visibility:visible}.ladda-button,.ladda-button .ladda-spinner,.ladda-button .ladda-label{-webkit-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-moz-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-ms-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-o-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important}.ladda-button[data-style=zoom-in],.ladda-button[data-style=zoom-in] .ladda-spinner,.ladda-button[data-style=zoom-in] .ladda-label,.ladda-button[data-style=zoom-out],.ladda-button[data-style=zoom-out] .ladda-spinner,.ladda-button[data-style=zoom-out] .ladda-label{-webkit-transition:0.3s ease all !important;-moz-transition:0.3s ease all !important;-ms-transition:0.3s ease all !important;-o-transition:0.3s ease all !important;transition:0.3s ease all !important}.ladda-button[data-style=expand-right] .ladda-spinner{right:-6px}.ladda-button[data-style=expand-right][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-right][data-size="xs"] .ladda-spinner{right:-12px}.ladda-button[data-style=expand-right][data-loading]{padding-right:56px}.ladda-button[data-style=expand-right][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-right][data-loading][data-size="s"],.ladda-button[data-style=expand-right][data-loading][data-size="xs"]{padding-right:40px}.ladda-button[data-style=expand-left] .ladda-spinner{left:26px}.ladda-button[data-style=expand-left][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-left][data-size="xs"] .ladda-spinner{left:4px}.ladda-button[data-style=expand-left][data-loading]{padding-left:56px}.ladda-button[data-style=expand-left][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-left][data-loading][data-size="s"],.ladda-button[data-style=expand-left][data-loading][data-size="xs"]{padding-left:40px}.ladda-button[data-style=expand-up]{overflow:hidden}.ladda-button[data-style=expand-up] .ladda-spinner{top:-32px;left:50%;margin-left:0}.ladda-button[data-style=expand-up][data-loading]{padding-top:54px}.ladda-button[data-style=expand-up][data-loading] .ladda-spinner{opacity:1;top:26px;margin-top:0}.ladda-button[data-style=expand-up][data-loading][data-size="s"],.ladda-button[data-style=expand-up][data-loading][data-size="xs"]{padding-top:32px}.ladda-button[data-style=expand-up][data-loading][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-up][data-loading][data-size="xs"] .ladda-spinner{top:4px}.ladda-button[data-style=expand-down]{overflow:hidden}.ladda-button[data-style=expand-down] .ladda-spinner{top:62px;left:50%;margin-left:0}.ladda-button[data-style=expand-down][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-down][data-size="xs"] .ladda-spinner{top:40px}.ladda-button[data-style=expand-down][data-loading]{padding-bottom:54px}.ladda-button[data-style=expand-down][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-down][data-loading][data-size="s"],.ladda-button[data-style=expand-down][data-loading][data-size="xs"]{padding-bottom:32px}.ladda-button[data-style=slide-left]{overflow:hidden}.ladda-button[data-style=slide-left] .ladda-label{position:relative}.ladda-button[data-style=slide-left] .ladda-spinner{left:100%;margin-left:0}.ladda-button[data-style=slide-left][data-loading] .ladda-label{opacity:0;left:-100%}.ladda-button[data-style=slide-left][data-loading] .ladda-spinner{opacity:1;left:50%}.ladda-button[data-style=slide-right]{overflow:hidden}.ladda-button[data-style=slide-right] .ladda-label{position:relative}.ladda-button[data-style=slide-right] .ladda-spinner{right:100%;margin-left:0;left:16px}.ladda-button[data-style=slide-right][data-loading] .ladda-label{opacity:0;left:100%}.ladda-button[data-style=slide-right][data-loading] .ladda-spinner{opacity:1;left:50%}.ladda-button[data-style=slide-up]{overflow:hidden}.ladda-button[data-style=slide-up] .ladda-label{position:relative}.ladda-button[data-style=slide-up] .ladda-spinner{left:50%;margin-left:0;margin-top:1em}.ladda-button[data-style=slide-up][data-loading] .ladda-label{opacity:0;top:-1em}.ladda-button[data-style=slide-up][data-loading] .ladda-spinner{opacity:1;margin-top:0}.ladda-button[data-style=slide-down]{overflow:hidden}.ladda-button[data-style=slide-down] .ladda-label{position:relative}.ladda-button[data-style=slide-down] .ladda-spinner{left:50%;margin-left:0;margin-top:-2em}.ladda-button[data-style=slide-down][data-loading] .ladda-label{opacity:0;top:1em}.ladda-button[data-style=slide-down][data-loading] .ladda-spinner{opacity:1;margin-top:0}.ladda-button[data-style=zoom-out]{overflow:hidden}.ladda-button[data-style=zoom-out] .ladda-spinner{left:50%;margin-left:32px;-webkit-transform:scale(2.5);-moz-transform:scale(2.5);-ms-transform:scale(2.5);-o-transform:scale(2.5);transform:scale(2.5)}.ladda-button[data-style=zoom-out] .ladda-label{position:relative;display:inline-block}.ladda-button[data-style=zoom-out][data-loading] .ladda-label{opacity:0;-webkit-transform:scale(0.5);-moz-transform:scale(0.5);-ms-transform:scale(0.5);-o-transform:scale(0.5);transform:scale(0.5)}.ladda-button[data-style=zoom-out][data-loading] .ladda-spinner{opacity:1;margin-left:0;-webkit-transform:none;-moz-transform:none;-ms-transform:none;-o-transform:none;transform:none}.ladda-button[data-style=zoom-in]{overflow:hidden}.ladda-button[data-style=zoom-in] .ladda-spinner{left:50%;margin-left:-16px;-webkit-transform:scale(0.2);-moz-transform:scale(0.2);-ms-transform:scale(0.2);-o-transform:scale(0.2);transform:scale(0.2)}.ladda-button[data-style=zoom-in] .ladda-label{position:relative;display:inline-block}.ladda-button[data-style=zoom-in][data-loading] .ladda-label{opacity:0;-webkit-transform:scale(2.2);-moz-transform:scale(2.2);-ms-transform:scale(2.2);-o-transform:scale(2.2);transform:scale(2.2)}.ladda-button[data-style=zoom-in][data-loading] .ladda-spinner{opacity:1;margin-left:0;-webkit-transform:none;-moz-transform:none;-ms-transform:none;-o-transform:none;transform:none}.ladda-button[data-style=contract]{overflow:hidden;width:100px}.ladda-button[data-style=contract] .ladda-spinner{left:50%;margin-left:0}.ladda-button[data-style=contract][data-loading]{border-radius:50%;width:52px}.ladda-button[data-style=contract][data-loading] .ladda-label{opacity:0}.ladda-button[data-style=contract][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=contract-overlay]{overflow:hidden;width:100px;box-shadow:0px 0px 0px 2000px transparent}.ladda-button[data-style=contract-overlay] .ladda-spinner{left:50%;margin-left:0}.ladda-button[data-style=contract-overlay][data-loading]{border-radius:50%;width:52px;box-shadow:0px 0px 0px 2000px rgba(0,0,0,0.8)}.ladda-button[data-style=contract-overlay][data-loading] .ladda-label{opacity:0}.ladda-button[data-style=contract-overlay][data-loading] .ladda-spinner{opacity:1}.ladda-button{background:#666;border:0;padding:14px 18px;font-size:18px;cursor:pointer;color:#fff;border-radius:2px;border:1px solid transparent;-webkit-appearance:none;-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:transparent}.ladda-button:hover{border-color:rgba(0,0,0,0.07);background-color:#888}.ladda-button[data-color=green]{background:#2aca76}.ladda-button[data-color=green]:hover{background-color:#38d683}.ladda-button[data-color=blue]{background:#53b5e6}.ladda-button[data-color=blue]:hover{background-color:#69bfe9}.ladda-button[data-color=red]{background:#ea8557}.ladda-button[data-color=red]:hover{background-color:#ed956e}.ladda-button[data-color=purple]{background:#9973C2}.ladda-button[data-color=purple]:hover{background-color:#a685ca}.ladda-button[data-color=mint]{background:#16a085}.ladda-button[data-color=mint]:hover{background-color:#19b698}.ladda-button[disabled],.ladda-button[data-loading]{border-color:rgba(0,0,0,0.07)}.ladda-button[disabled],.ladda-button[disabled]:hover,.ladda-button[data-loading],.ladda-button[data-loading]:hover{cursor:default;background-color:#999}.ladda-button[data-size=xs]{padding:4px 8px}.ladda-button[data-size=xs] .ladda-label{font-size:0.7em}.ladda-button[data-size=s]{padding:6px 10px}.ladda-button[data-size=s] .ladda-label{font-size:0.9em}.ladda-button[data-size=l] .ladda-label{font-size:1.2em}.ladda-button[data-size=xl] .ladda-label{font-size:1.5em}
......@@ -177,7 +177,7 @@ function formSubmit(props) {
*/
props = props || {};
var data = props.data || props.form.serializeObject();
var redirect_to = props.redirect_to;
var redirectTo = props.redirect_to || props.redirectTo;
$.ajax({
url: props.url,
type: props.method || 'POST',
......@@ -185,12 +185,8 @@ function formSubmit(props) {
contentType: props.content_type || "application/json; charset=utf-8",
dataType: props.data_type || "json"
}).done(function (data, textState, jqXHR) {
if (redirect_to) {
if (props.message) {
var messages = "ed65330a45559c87345a0eb6ac7812d18d0d8976$[[\"__json_message\"\0540\05425\054\"asdfasdf \\u521b\\u5efa\\u6210\\u529f\"]]"
setCookie("messages", messages)
}
location.href = redirect_to;
if (redirectTo) {
location.href = redirectTo;
} else if (typeof props.success === 'function') {
return props.success(data, textState, jqXHR);
}
......@@ -254,7 +250,6 @@ function formSubmit(props) {
}
$('.has-error').get(0).scrollIntoView();
}
})
}
......@@ -1032,6 +1027,62 @@ function rootNodeAddDom(ztree, callback) {
})
}
function APIExportCSV(props) {
/*
{
listUrl:
objectsId:
template:
table:
params:
}
*/
var _listUrl = props.listUrl;
var _objectsId = props.objectsId;
var _template = props.template;
var _table = props.table;
var _params = props.params || {};
var tableParams = _table.ajax.params();
var exportUrl = setUrlParam(_listUrl, 'format', 'csv');
if (_template) {
exportUrl = setUrlParam(exportUrl, 'template', _template)
}
for (var k in tableParams) {
if (datatableInternalParams.includes(k)) {
continue
}
if (!tableParams[k]) {
continue
}
exportUrl = setUrlParam(exportUrl, k, tableParams[k])
}
for (var k in _params) {
exportUrl = setUrlParam(exportUrl, k, tableParams[k])
}
if (!_objectsId) {
console.log(exportUrl);
window.open(exportUrl);
return
}
requestApi({
url: '/api/v1/common/resources/cache/',
data: JSON.stringify({resources: _objectsId}),
method: "POST",
flash_message: false,
success: function (data) {
exportUrl = setUrlParam(exportUrl, 'spm', data.spm);
console.log(exportUrl);
window.open(exportUrl);
},
failed: function () {
toastr.error(gettext('Export failed'));
}
});
}
function APIExportData(props) {
props = props || {};
$.ajax({
......@@ -1081,6 +1132,7 @@ function APIImportData(props) {
},
error: function (error) {
var data = error.responseJSON;
console.log(data);
if (data instanceof Array) {
var html = '';
var li = '';
......@@ -1141,8 +1193,8 @@ function objectAttrsIsBool(obj, attrs) {
attrs.forEach(function (attr) {
if (!obj[attr]) {
obj[attr] = false
} else if (['on', '1'].includes(obj[attr])) {
obj[attr] = true
} else {
obj[attr] = ['on', '1', 'true', 'True'].includes(obj[attr]);
}
})
}
......
/*!
* Ladda for jQuery
* http://lab.hakim.se/ladda
* MIT licensed
*
* Copyright (C) 2015 Hakim El Hattab, http://hakim.se
*/
!function(a,b){if(void 0===b)return console.error("jQuery required for Ladda.jQuery");var c=[];b=b.extend(b,{ladda:function(b){"stopAll"===b&&a.stopAll()}}),b.fn=b.extend(b.fn,{ladda:function(d){var e=c.slice.call(arguments,1);return"bind"===d?(e.unshift(b(this).selector),a.bind.apply(a,e)):b(this).each(function(){var c,f=b(this);void 0===d?f.data("ladda",a.create(this)):(c=f.data("ladda"),c[d].apply(c,e))}),this}})}(this.Ladda,this.jQuery);
\ No newline at end of file
/*!
* Ladda 1.0.0 (2016-03-08, 09:31)
* http://lab.hakim.se/ladda
* MIT licensed
*
* Copyright (C) 2016 Hakim El Hattab, http://hakim.se
*/
!function(a,b){"object"==typeof exports?module.exports=b(require("spin.js")):"function"==typeof define&&define.amd?define(["spin"],b):a.Ladda=b(a.Spinner)}(this,function(a){"use strict";function b(a){if("undefined"==typeof a)return void console.warn("Ladda button target must be defined.");if(/ladda-button/i.test(a.className)||(a.className+=" ladda-button"),a.hasAttribute("data-style")||a.setAttribute("data-style","expand-right"),!a.querySelector(".ladda-label")){var b=document.createElement("span");b.className="ladda-label",i(a,b)}var c,d=a.querySelector(".ladda-spinner");d||(d=document.createElement("span"),d.className="ladda-spinner"),a.appendChild(d);var e,f={start:function(){return c||(c=g(a)),a.setAttribute("disabled",""),a.setAttribute("data-loading",""),clearTimeout(e),c.spin(d),this.setProgress(0),this},startAfter:function(a){return clearTimeout(e),e=setTimeout(function(){f.start()},a),this},stop:function(){return a.removeAttribute("disabled"),a.removeAttribute("data-loading"),clearTimeout(e),c&&(e=setTimeout(function(){c.stop()},1e3)),this},toggle:function(){return this.isLoading()?this.stop():this.start(),this},setProgress:function(b){b=Math.max(Math.min(b,1),0);var c=a.querySelector(".ladda-progress");0===b&&c&&c.parentNode?c.parentNode.removeChild(c):(c||(c=document.createElement("div"),c.className="ladda-progress",a.appendChild(c)),c.style.width=(b||0)*a.offsetWidth+"px")},enable:function(){return this.stop(),this},disable:function(){return this.stop(),a.setAttribute("disabled",""),this},isLoading:function(){return a.hasAttribute("data-loading")},remove:function(){clearTimeout(e),a.removeAttribute("disabled",""),a.removeAttribute("data-loading",""),c&&(c.stop(),c=null);for(var b=0,d=j.length;d>b;b++)if(f===j[b]){j.splice(b,1);break}}};return j.push(f),f}function c(a,b){for(;a.parentNode&&a.tagName!==b;)a=a.parentNode;return b===a.tagName?a:void 0}function d(a){for(var b=["input","textarea","select"],c=[],d=0;d<b.length;d++)for(var e=a.getElementsByTagName(b[d]),f=0;f<e.length;f++)e[f].hasAttribute("required")&&c.push(e[f]);return c}function e(a,e){e=e||{};var f=[];"string"==typeof a?f=h(document.querySelectorAll(a)):"object"==typeof a&&"string"==typeof a.nodeName&&(f=[a]);for(var g=0,i=f.length;i>g;g++)!function(){var a=f[g];if("function"==typeof a.addEventListener){var h=b(a),i=-1;a.addEventListener("click",function(b){var f=!0,g=c(a,"FORM");if("undefined"!=typeof g)if("function"==typeof g.checkValidity)f=g.checkValidity();else for(var j=d(g),k=0;k<j.length;k++)""===j[k].value.replace(/^\s+|\s+$/g,"")&&(f=!1),"checkbox"!==j[k].type&&"radio"!==j[k].type||j[k].checked||(f=!1),"email"===j[k].type&&(f=/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(j[k].value));f&&(h.startAfter(1),"number"==typeof e.timeout&&(clearTimeout(i),i=setTimeout(h.stop,e.timeout)),"function"==typeof e.callback&&e.callback.apply(null,[h]))},!1)}}()}function f(){for(var a=0,b=j.length;b>a;a++)j[a].stop()}function g(b){var c,d,e=b.offsetHeight;0===e&&(e=parseFloat(window.getComputedStyle(b).height)),e>32&&(e*=.8),b.hasAttribute("data-spinner-size")&&(e=parseInt(b.getAttribute("data-spinner-size"),10)),b.hasAttribute("data-spinner-color")&&(c=b.getAttribute("data-spinner-color")),b.hasAttribute("data-spinner-lines")&&(d=parseInt(b.getAttribute("data-spinner-lines"),10));var f=.2*e,g=.6*f,h=7>f?2:3;return new a({color:c||"#fff",lines:d||12,radius:f,length:g,width:h,zIndex:"auto",top:"auto",left:"auto",className:""})}function h(a){for(var b=[],c=0;c<a.length;c++)b.push(a[c]);return b}function i(a,b){var c=document.createRange();c.selectNodeContents(a),c.surroundContents(b),a.appendChild(b)}var j=[];return{bind:e,create:b,stopAll:f}});
\ No newline at end of file
!function(a,b){"object"==typeof exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return l[e]||(m.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",m.cssRules.length),l[e]=1),e}function d(a,b){var c,d,e=a.style;for(b=b.charAt(0).toUpperCase()+b.slice(1),d=0;d<k.length;d++)if(c=k[d]+b,void 0!==e[c])return c;return void 0!==e[b]?b:void 0}function e(a,b){for(var c in b)a.style[d(a,c)||c]=b[c];return a}function f(a){for(var b=1;b<arguments.length;b++){var c=arguments[b];for(var d in c)void 0===a[d]&&(a[d]=c[d])}return a}function g(a,b){return"string"==typeof a?a:a[b%a.length]}function h(a){this.opts=f(a||{},h.defaults,n)}function i(){function c(b,c){return a("<"+b+' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">',c)}m.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.width,left:d.radius,top:-d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.length+d.width,k=2*j,l=2*-(d.width+d.length)+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d<e.childNodes.length&&(e=e.childNodes[b+d],e=e&&e.firstChild,e=e&&e.firstChild,e&&(e.opacity=c))}}var j,k=["webkit","Moz","ms","O"],l={},m=function(){var c=a("style",{type:"text/css"});return b(document.getElementsByTagName("head")[0],c),c.sheet||c.styleSheet}(),n={lines:12,length:7,width:5,radius:10,rotate:0,corners:1,color:"#000",direction:1,speed:1,trail:100,opacity:.25,fps:20,zIndex:2e9,className:"spinner",top:"50%",left:"50%",position:"absolute"};h.defaults={},f(h.prototype,{spin:function(b){this.stop();var c=this,d=c.opts,f=c.el=e(a(0,{className:d.className}),{position:d.position,width:0,zIndex:d.zIndex});d.radius+d.length+d.width;if(e(f,{left:d.left,top:d.top}),b&&b.insertBefore(f,b.firstChild||null),f.setAttribute("role","progressbar"),c.lines(f,c.opts),!j){var g,h=0,i=(d.lines-1)*(1-d.direction)/2,k=d.fps,l=k/d.speed,m=(1-d.opacity)/(l*d.trail/100),n=l/d.lines;!function o(){h++;for(var a=0;a<d.lines;a++)g=Math.max(1-(h+(d.lines-a)*n)%l*m,d.opacity),c.opacity(f,a*d.direction+i,g,d);c.timeout=c.el&&setTimeout(o,~~(1e3/k))}()}return c},stop:function(){var a=this.el;return a&&(clearTimeout(this.timeout),a.parentNode&&a.parentNode.removeChild(a),this.el=void 0),this},lines:function(d,f){function h(b,c){return e(a(),{position:"absolute",width:f.length+f.width+"px",height:f.width+"px",background:b,boxShadow:c,transformOrigin:"left",transform:"rotate("+~~(360/f.lines*k+f.rotate)+"deg) translate("+f.radius+"px,0)",borderRadius:(f.corners*f.width>>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k<f.lines;k++)i=e(a(),{position:"absolute",top:1+~(f.width/2)+"px",transform:f.hwaccel?"translate3d(0,0,0)":"",opacity:f.opacity,animation:j&&c(f.opacity,f.trail,l+k*f.direction,f.lines)+" "+1/f.speed+"s linear infinite"}),f.shadow&&b(i,e(h("#000","0 0 4px #000"),{top:"2px"})),b(d,b(i,h(g(f.color,k),"0 0 1px rgba(0,0,0,.1)")));return d},opacity:function(a,b,c){b<a.childNodes.length&&(a.childNodes[b].style.opacity=c)}});var o=e(a("group"),{behavior:"url(#default#VML)"});return!d(o,"transform")&&o.adj?i():j=d(o,"animation"),h});
\ No newline at end of file
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block help_message %}
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-sm-3" id="split-left" style="padding-left: 3px;padding-right: 0">
{% include 'assets/_node_tree.html' %}
</div>
<div class="col-sm-9 animated fadeInRight" id="split-right">
<div class="tree-toggle" style="z-index: 10">
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggleSpliter()">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header">
{% block table_container %}
<table class="table table-striped table-bordered table-hover" id="{% block table_id %}editable{% endblock %}" >
<thead>
<tr>
{% block table_head %} {% endblock %}
</tr>
</thead>
<tbody>
{% block table_body %} {% endblock %}
</tbody>
</table>
{% endblock %}
</div>
</div>
</div>
</div>
<script>
var showTree = 1;
function toggleSpliter() {
if (showTree === 1) {
$("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-sm-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
showTree = 1;
});
} else {
console.log("hide")
$("#split-right").attr("class", "col-sm-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500);
showTree = 0;
}
}
</script>
{% endblock %}
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
<title>{% block html_title %}{% endblock %}</title>
{% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script>
<style>
.passwordBox {
max-width: 560px;
margin: 0 auto;
padding: 100px 20px 20px 20px;
}
</style>
{% block custom_head_css_js %} {% endblock %}
</head>
<body class="gray-bg">
<div class="passwordBox animated fadeInDown">
<div class="row">
<div class="col-md-12">
<div class="ibox-content">
<img src="{{ LOGO_URL }}" style="margin: auto" width="50" height="50">
<h2 class="font-bold" style="display: inline">{% block title %}{% endblock %}</h2>
<h1></h1>
{% block content %} {% endblock %}
</div>
</div>
</div>
<hr/>
<div class="row">
<div class="col-md-12">
{% include '_copyright.html' %}
</div>
</div>
</div>
</body>
{% block custom_foot_js %} {% endblock %}
</html>
{% load i18n %}
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li id="li_csv_export">
<a id="btn_csv_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li id="li_csv_import">
<a id="btn_csv_import" data-toggle="modal" data-target="#csv_import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li id="li_csv_update">
<a id="btn_csv_update" data-toggle="modal" data-target="#csv_update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% include '_csv_import_modal.html' %}
{% include '_csv_update_modal.html' %}
<script>
var csvTable = null;
var csvListUrl = null;
var csvExportCallback = null;
function initCsvImportExport(table, objectType, listUrl, hide) {
csvTable = table;
$(".csv_object_type").html(objectType);
csvListUrl = listUrl ? listUrl : csvTable.ajax.url();
if (hide && hide.length > 0) {
hide.forEach(function (v) {
$("#li_csv_" + v).hide();
})
}
}
var datatableInternalParams = ['draw', 'limit', 'order', 'offset'];
$(document).ready(function () {
}).on('click', '#btn_csv_export', function () {
var selectedObjects = csvTable.selected;
function _export() {
APIExportCSV({
listUrl: csvListUrl,
objectsId: selectedObjects,
table: csvTable
});
}
if (csvExportCallback) {
csvExportCallback(_export)
} else {
_export();
}
})
</script>
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}csv_import_modal{% endblock %}
{% block modal_title%}<span class="csv_object_type">csv</span> {% trans 'Import' %}{% endblock %}
{% block modal_confirm_id %}btn_csv_import_confirm{% endblock %}
{% block modal_body %}
<form method="post" id="fm_import">
{% csrf_token %}
<div class="form-group">
<label class="control-label">{% trans "Download the imported template or use the exported CSV file format" %}</label>
<a id="csv_download_template" style="display: block">{% trans 'Download the import template' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="id_file">{% trans "Select the CSV file to import" %}</label>
<input id="id_csv_file" type="file" name="file" />
</div>
</form>
<div style="max-height: 300px;overflow: auto">
<p class="text-success" id="success_created"></p>
<p id="success_created_detail"></p>
<p class="text-danger" id="created_failed"></p>
<p id="created_failed_detail"></p>
</div>
<script>
$(document).ready(function () {
}).on("click", '#csv_download_template', function () {
var theUrl ="csvImportURL?format=csv&template=import&limit=1" ;
theUrl = theUrl.replace("csvImportURL", csvListUrl);
window.open(theUrl)
}).on('click', '#btn_csv_import_confirm', function () {
var file = document.getElementById('id_csv_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
APIImportData({
url: csvListUrl,
method: "POST",
body: file,
data_table: csvTable
});
})
</script>
{% endblock %}
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}csv_update_modal{% endblock %}
{% block modal_confirm_id %}btn_csv_update_confirm{% endblock %}
{% block modal_title%}<span class="csv_object_type">csv</span> {% trans 'Update' %}{% endblock %}
{% block modal_body %}
<form method="post" id="fm_import">
{% csrf_token %}
<div class="form-group">
<label class="control-label">{% trans "Download the update template or use the exported CSV file format" %}</label>
<a id="csv_download_update_template" style="display: block">{% trans 'Download the update template' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="update_file">{% trans "Select the CSV file to import" %}</label>
<input id="csv_update_file" type="file" name="file" />
</div>
</form>
<div>
<p class="text-warning" id="success_updated"></p>
<p id="success_updated_detail"></p>
<p class="text-danger" id="updated_failed"></p>
<p id="updated_failed_detail"></p>
</div>
<script>
$(document).ready(function () {
}).on('click', '#csv_download_update_template', function () {
var objectsId = csvTable.selected;
APIExportCSV({
listUrl: csvListUrl,
objectsId: objectsId,
template: 'update',
table: csvTable
});
}).on('click', '#btn_csv_update_confirm', function () {
var file = document.getElementById('csv_update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
APIImportData({
url: csvListUrl,
method: "PUT",
body: file,
data_table: csvTable
});
})
</script>
{% endblock %}
......@@ -45,6 +45,9 @@
<li id="system-user"><a href="{% url 'assets:system-user-list' %}">{% trans 'System user' %}</a></li>
<li id="label"><a href="{% url 'assets:label-list' %}">{% trans 'Labels' %}</a></li>
<li id="cmd-filter"><a href="{% url 'assets:cmd-filter-list' %}">{% trans 'Command filters' %}</a></li>
{% if request.user.is_superuser %}
<li id="platform"><a href="{% url 'assets:platform-list' %}">{% trans 'Platform list' %}</a></li>
{% endif %}
</ul>
</li>
{% endif %}
......
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> {{ JMS_TITLE }} </title>
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
{# <link rel="stylesheet" href="{% static 'fonts/font_otp/iconfont.css' %}" />#}
<link rel="stylesheet" href="{% static 'css/otp.css' %}" />
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
</head>
<body>
<header>
<div class="logo">
<a href="{% url 'index' %}">
<img src="{{ LOGO_URL }}" alt="" width="50px" height="50px"/>
</a>
<a href="{% url 'index' %}">{{ JMS_TITLE }}</a>
</div>
<div>
<a href="{% url 'index' %}">{% trans 'Home page' %}</a>
<b></b>
<a href="http://docs.jumpserver.org/zh/docs/">{% trans 'Docs' %}</a>
<b></b>
<a href="https://www.github.com/jumpserver/">GitHub</a>
</div>
</header>
<body>
{% block body %}
{% endblock %}
</body>
<footer>
<div class="" style="margin-top: 100px;">
{% include '_copyright.html' %}
</div>
</footer>
</body>
</html>
{% load i18n %}
{% extends '_base_only_content.html' %}
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
{% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script>
{% load i18n %}
{% block html_title %} {{ title }} {% endblock %}
{% block title %} {{ title }}{% endblock %}
</head>
{% block custom_head_css_js %}
<style>
.passwordBox {
max-width: 660px;
}
</style>
{% endblock %}
<body class="gray-bg">
<div class="passwordBox2 animated fadeInDown">
<div class="row">
<div class="col-md-12">
<div class="ibox-content">
<div>
<img src="{{ LOGO_URL }}" style="margin: auto" width="82" height="82">
<h2 style="display: inline">
{{ JMS_TITLE }}
</h2>
</div>
{% if errors %}
<p>
<div class="alert alert-danger">
{{ errors }}
</div>
</p>
{% endif %}
{% block content %}
<div>
{% if errors %}
<p>
<div class="alert alert-danger">
{{ errors }}
</div>
</p>
{% endif %}
{% if messages %}
<p>
<div class="alert alert-success" id="messages">
{{ messages|safe }}
</div>
</p>
{% endif %}
<div class="row">
<div class="col-lg-3">
<a href="{{ redirect_url }}" class="btn btn-primary block full-width m-b">{% trans 'Return' %}</a>
</div>
</div>
</div>
{% if messages %}
<p>
<div class="alert alert-success" id="messages">
{{ messages|safe }}
</div>
</div>
<hr/>
</p>
{% endif %}
<div class="row">
<div class="col-md-6">
{% include '_copyright.html' %}
<div class="col-lg-3">
<a href="{{ redirect_url }}" class="btn btn-primary block full-width m-b">{% trans 'Return' %}</a>
</div>
</div>
</div>
</body>
{% endblock %}
{% block custom_foot_js %}
<script>
var time = '{{ interval }}';
if (!time){
if (!time) {
time = 5;
} else {
time = parseInt(time);
}
function redirect_page() {
if (time >= 0) {
var messages = '{{ messages|safe }}, <b>' + time +'</b> ...';
var messages = '{{ messages|safe }}, <b>' + time + '</b> ...';
$('#messages').html(messages);
time--;
setTimeout(redirect_page, 1000);
}
else {
} else {
window.location.href = "{{ redirect_url }}";
}
}
{% if auto_redirect %}
window.onload = redirect_page;
window.onload = redirect_page;
{% endif %}
</script>
</html>
{% endblock %}
# Generated by Django 2.2.7 on 2019-12-06 02:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('terminal', '0018_auto_20191202_1010'),
]
operations = [
migrations.AlterField(
model_name='replaystorage',
name='type',
field=models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('s3', 'S3'), ('ceph', 'Ceph'), ('swift', 'Swift'), ('oss', 'OSS'), ('azure', 'Azure')], default='server', max_length=16, verbose_name='Type'),
),
]
# -*- coding: utf-8 -*-
#
from ..serializers import (
UserGroupSerializer,
UserGroupListSerializer,
UserGroupUpdateMemberSerializer,
)
from ..serializers import UserGroupSerializer
from ..models import UserGroup
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from common.permissions import IsOrgAdmin
__all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi']
__all__ = ['UserGroupViewSet']
class UserGroupViewSet(OrgBulkModelViewSet):
model = UserGroup
filter_fields = ("name",)
search_fields = filter_fields
serializer_class = UserGroupSerializer
permission_classes = (IsOrgAdmin,)
serializer_class = UserGroupSerializer
def get_serializer_class(self):
if self.action in ("list", 'retrieve') and \
self.request.query_params.get("display"):
return UserGroupListSerializer
return self.serializer_class
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
model = UserGroup
serializer_class = UserGroupUpdateMemberSerializer
permission_classes = (IsOrgAdmin,)
......@@ -24,7 +24,7 @@ from ..signals import post_user_create
logger = get_logger(__name__)
__all__ = [
'UserViewSet', 'UserChangePasswordApi', 'UserUpdateGroupApi',
'UserViewSet', 'UserChangePasswordApi',
'UserResetPasswordApi', 'UserResetPKApi', 'UserUpdatePKApi',
'UserUnblockPKApi', 'UserProfileApi', 'UserResetOTPApi',
]
......@@ -39,8 +39,10 @@ class UserQuerysetMixin:
class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
filter_fields = ('username', 'email', 'name', 'id')
search_fields = filter_fields
serializer_class = serializers.UserSerializer
serializer_display_class = serializers.UserDisplaySerializer
serializer_classes = {
'default': serializers.UserSerializer,
'display': serializers.UserDisplaySerializer
}
permission_classes = (IsOrgAdmin, CanUpdateDeleteUser)
def get_queryset(self):
......@@ -99,11 +101,6 @@ class UserChangePasswordApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
user.save()
class UserUpdateGroupApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
serializer_class = serializers.UserUpdateGroupSerializer
permission_classes = (IsOrgAdmin,)
class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = serializers.UserSerializer
......
# -*- coding: utf-8 -*-
#
from .user import *
from .group import *
from .profile import *
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from orgs.mixins.forms import OrgModelForm
from ..models import User, UserGroup
__all__ = ['UserGroupForm']
class UserGroupForm(OrgModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.none(),
label=_("User"),
widget=forms.SelectMultiple(
attrs={
'class': 'users-select2',
'data-placeholder': _('Select users')
}
),
required=False,
)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_fields_queryset()
def set_fields_queryset(self):
users_field = self.fields.get('users')
if self.instance:
users_field.initial = self.instance.users.all()
users_field.queryset = self.instance.users.all()
else:
users_field.queryset = User.objects.none()
def save(self, commit=True):
raise Exception("Save by restful api")
class Meta:
model = UserGroup
fields = [
'name', 'users', 'comment',
]
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from captcha.fields import CaptchaField
from common.utils import validate_ssh_public_key
from ..models import User
__all__ = [
'UserProfileForm', 'UserMFAForm', 'UserFirstLoginFinishForm',
'UserPasswordForm', 'UserPublicKeyForm', 'FileForm',
'UserTokenResetPasswordForm', 'UserForgotPasswordForm',
]
class UserProfileForm(forms.ModelForm):
username = forms.CharField(disabled=True, label=_("Username"))
name = forms.CharField(disabled=True, label=_("Name"))
email = forms.CharField(disabled=True)
class Meta:
model = User
fields = [
'username', 'name', 'email',
'wechat', 'phone',
]
UserProfileForm.verbose_name = _("Profile")
class UserMFAForm(forms.ModelForm):
mfa_description = _(
'When enabled, '
'you will enter the MFA binding process the next time you log in. '
'you can also directly bind in '
'"personal information -> quick modification -> change MFA Settings"!')
class Meta:
model = User
fields = ['mfa_level']
widgets = {'mfa_level': forms.RadioSelect()}
help_texts = {
'mfa_level': _('* Enable MFA authentication '
'to make the account more secure.'),
}
UserMFAForm.verbose_name = _("MFA")
class UserFirstLoginFinishForm(forms.Form):
finish_description = _(
'In order to protect you and your company, '
'please keep your account, '
'password and key sensitive information properly. '
'(for example: setting complex password, enabling MFA authentication)'
)
UserFirstLoginFinishForm.verbose_name = _("Finish")
class UserTokenResetPasswordForm(forms.Form):
new_password = forms.CharField(
min_length=5, max_length=128,
widget=forms.PasswordInput,
label=_("New password")
)
confirm_password = forms.CharField(
min_length=5, max_length=128,
widget=forms.PasswordInput,
label=_("Confirm password")
)
def clean_confirm_password(self):
new_password = self.cleaned_data['new_password']
confirm_password = self.cleaned_data['confirm_password']
if new_password != confirm_password:
raise forms.ValidationError(_('Password does not match'))
return confirm_password
class UserForgotPasswordForm(forms.Form):
email = forms.EmailField(label=_("Email"))
captcha = CaptchaField(label=_("Captcha"))
class UserPasswordForm(UserTokenResetPasswordForm):
old_password = forms.CharField(
max_length=128, widget=forms.PasswordInput,
label=_("Old password")
)
def __init__(self, *args, **kwargs):
self.instance = kwargs.pop('instance')
super().__init__(*args, **kwargs)
def clean_old_password(self):
old_password = self.cleaned_data['old_password']
if not self.instance.check_password(old_password):
raise forms.ValidationError(_('Old password error'))
return old_password
def save(self):
password = self.cleaned_data['new_password']
self.instance.reset_password(new_password=password)
return self.instance
class UserPublicKeyForm(forms.Form):
pubkey_description = _('Automatically configure and download the SSH key')
public_key = forms.CharField(
label=_('ssh public key'), max_length=5000, required=False,
widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}),
help_text=_('Paste your id_rsa.pub here.')
)
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
self.instance = kwargs.pop('instance')
else:
self.instance = None
super().__init__(*args, **kwargs)
def clean_public_key(self):
public_key = self.cleaned_data['public_key']
if self.instance.public_key and public_key == self.instance.public_key:
msg = _('Public key should not be the same as your old one.')
raise forms.ValidationError(msg)
if public_key and not validate_ssh_public_key(public_key):
raise forms.ValidationError(_('Not a valid ssh public key'))
return public_key
def save(self):
public_key = self.cleaned_data['public_key']
if public_key:
self.instance.public_key = public_key
self.instance.save()
return self.instance
UserPublicKeyForm.verbose_name = _("Public key")
class FileForm(forms.Form):
file = forms.FileField()
# ~*~ coding: utf-8 ~*~
from django import forms
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from common.utils import validate_ssh_public_key
from orgs.mixins.forms import OrgModelForm
from .models import User, UserGroup
from .utils import check_password_rules, get_current_org_members
from ..models import User
from ..utils import (
check_password_rules, get_current_org_members, get_source_choices
)
class UserCheckPasswordForm(forms.Form):
username = forms.CharField(label=_('Username'), max_length=100)
password = forms.CharField(
label=_('Password'), widget=forms.PasswordInput,
max_length=128, strip=False
)
class UserCheckOtpCodeForm(forms.Form):
otp_code = forms.CharField(label=_('MFA code'), max_length=6)
def get_source_choices():
choices_all = dict(User.SOURCE_CHOICES)
choices = [
(User.SOURCE_LOCAL, choices_all[User.SOURCE_LOCAL]),
]
if settings.AUTH_LDAP:
choices.append((User.SOURCE_LDAP, choices_all[User.SOURCE_LDAP]))
if settings.AUTH_OPENID:
choices.append((User.SOURCE_OPENID, choices_all[User.SOURCE_OPENID]))
if settings.AUTH_RADIUS:
choices.append((User.SOURCE_RADIUS, choices_all[User.SOURCE_RADIUS]))
return choices
__all__ = [
'UserCreateForm', 'UserUpdateForm', 'UserBulkUpdateForm',
'UserCheckOtpCodeForm', 'UserCheckPasswordForm'
]
class UserCreateUpdateFormMixin(OrgModelForm):
......@@ -157,131 +137,6 @@ class UserUpdateForm(UserCreateUpdateFormMixin):
pass
class UserProfileForm(forms.ModelForm):
username = forms.CharField(disabled=True, label=_("Username"))
name = forms.CharField(disabled=True, label=_("Name"))
email = forms.CharField(disabled=True)
class Meta:
model = User
fields = [
'username', 'name', 'email',
'wechat', 'phone',
]
UserProfileForm.verbose_name = _("Profile")
class UserMFAForm(forms.ModelForm):
mfa_description = _(
'When enabled, '
'you will enter the MFA binding process the next time you log in. '
'you can also directly bind in '
'"personal information -> quick modification -> change MFA Settings"!')
class Meta:
model = User
fields = ['mfa_level']
widgets = {'mfa_level': forms.RadioSelect()}
help_texts = {
'mfa_level': _('* Enable MFA authentication '
'to make the account more secure.'),
}
UserMFAForm.verbose_name = _("MFA")
class UserFirstLoginFinishForm(forms.Form):
finish_description = _(
'In order to protect you and your company, '
'please keep your account, '
'password and key sensitive information properly. '
'(for example: setting complex password, enabling MFA authentication)'
)
UserFirstLoginFinishForm.verbose_name = _("Finish")
class UserPasswordForm(forms.Form):
old_password = forms.CharField(
max_length=128, widget=forms.PasswordInput,
label=_("Old password")
)
new_password = forms.CharField(
min_length=5, max_length=128,
widget=forms.PasswordInput,
label=_("New password")
)
confirm_password = forms.CharField(
min_length=5, max_length=128,
widget=forms.PasswordInput,
label=_("Confirm password")
)
def __init__(self, *args, **kwargs):
self.instance = kwargs.pop('instance')
super().__init__(*args, **kwargs)
def clean_old_password(self):
old_password = self.cleaned_data['old_password']
if not self.instance.check_password(old_password):
raise forms.ValidationError(_('Old password error'))
return old_password
def clean_confirm_password(self):
new_password = self.cleaned_data['new_password']
confirm_password = self.cleaned_data['confirm_password']
if new_password != confirm_password:
raise forms.ValidationError(_('Password does not match'))
return confirm_password
def save(self):
password = self.cleaned_data['new_password']
self.instance.reset_password(new_password=password)
return self.instance
class UserPublicKeyForm(forms.Form):
pubkey_description = _('Automatically configure and download the SSH key')
public_key = forms.CharField(
label=_('ssh public key'), max_length=5000, required=False,
widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}),
help_text=_('Paste your id_rsa.pub here.')
)
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
self.instance = kwargs.pop('instance')
else:
self.instance = None
super().__init__(*args, **kwargs)
def clean_public_key(self):
public_key = self.cleaned_data['public_key']
if self.instance.public_key and public_key == self.instance.public_key:
msg = _('Public key should not be the same as your old one.')
raise forms.ValidationError(msg)
if public_key and not validate_ssh_public_key(public_key):
raise forms.ValidationError(_('Not a valid ssh public key'))
return public_key
def save(self):
public_key = self.cleaned_data['public_key']
if public_key:
self.instance.public_key = public_key
self.instance.save()
return self.instance
UserPublicKeyForm.verbose_name = _("Public key")
class UserBulkUpdateForm(OrgModelForm):
users = forms.ModelMultipleChoiceField(
required=True,
......@@ -333,40 +188,12 @@ class UserBulkUpdateForm(OrgModelForm):
return users
class UserGroupForm(OrgModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.none(),
label=_("User"),
widget=forms.SelectMultiple(
attrs={
'class': 'users-select2',
'data-placeholder': _('Select users')
}
),
required=False,
class UserCheckPasswordForm(forms.Form):
password = forms.CharField(
label=_('Password'), widget=forms.PasswordInput,
max_length=128, strip=False
)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_fields_queryset()
def set_fields_queryset(self):
users_field = self.fields.get('users')
if self.instance:
users_field.initial = self.instance.users.all()
users_field.queryset = self.instance.users.all()
else:
users_field.queryset = User.objects.none()
def save(self, commit=True):
raise Exception("Save by restful api")
class Meta:
model = UserGroup
fields = [
'name', 'users', 'comment',
]
class FileForm(forms.Form):
file = forms.FileField()
class UserCheckOtpCodeForm(forms.Form):
otp_code = forms.CharField(label=_('MFA code'), max_length=6)
......@@ -4,6 +4,7 @@ import uuid
from django.db import models, IntegrityError
from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin
__all__ = ['UserGroup']
......@@ -20,6 +21,10 @@ class UserGroup(OrgModelMixin):
def __str__(self):
return self.name
@lazyproperty
def users_amount(self):
return self.users.count()
class Meta:
ordering = ['name']
unique_together = [('org_id', 'name'),]
......
......@@ -19,6 +19,7 @@ from django.shortcuts import reverse
from orgs.utils import current_org
from common.utils import get_signer, date_expired_default, get_logger, lazyproperty
from common import fields
from ..signals import post_user_change_password
__all__ = ['User']
......@@ -43,14 +44,10 @@ class AuthMixin:
self.set_password(password_raw_)
def set_password(self, raw_password):
self._set_password = True
if self.can_update_password():
self.date_password_last_updated = timezone.now()
post_user_change_password.send(self.__class__, user=self)
super().set_password(raw_password)
else:
error = _("User auth from {}, go there change password").format(
self.source)
raise PermissionError(error)
def can_update_password(self):
return self.is_local
......@@ -196,22 +193,22 @@ class RoleMixin:
def is_app(self):
return self.role == 'App'
@property
@lazyproperty
def user_orgs(self):
from orgs.models import Organization
return Organization.get_user_user_orgs(self)
@property
@lazyproperty
def admin_orgs(self):
from orgs.models import Organization
return Organization.get_user_admin_orgs(self)
@property
@lazyproperty
def audit_orgs(self):
from orgs.models import Organization
return Organization.get_user_audit_orgs(self)
@property
@lazyproperty
def admin_or_audit_orgs(self):
from orgs.models import Organization
return Organization.get_user_admin_or_audit_orgs(self)
......@@ -223,26 +220,26 @@ class RoleMixin:
else:
return False
@property
@lazyproperty
def is_org_auditor(self):
if self.is_super_auditor or self.related_audit_orgs.exists():
return True
else:
return False
@property
@lazyproperty
def can_admin_current_org(self):
return current_org.can_admin_by(self)
@property
@lazyproperty
def can_audit_current_org(self):
return current_org.can_audit_by(self)
@property
@lazyproperty
def can_user_current_org(self):
return current_org.can_user_by(self)
@property
@lazyproperty
def can_admin_or_audit_current_org(self):
return self.can_admin_current_org or self.can_audit_current_org
......
......@@ -4,29 +4,29 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.fields import StringManyToManyField
from common.serializers import AdaptedBulkListSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from django.db.models import Count
from ..models import User, UserGroup
from .. import utils
__all__ = [
'UserGroupSerializer', 'UserGroupListSerializer',
'UserGroupUpdateMemberSerializer',
'UserGroupSerializer',
]
class UserGroupSerializer(BulkOrgResourceModelSerializer):
users = serializers.PrimaryKeyRelatedField(
required=False, many=True, queryset=User.objects, label=_('User')
required=False, many=True, queryset=User.objects, label=_('User'),
write_only=True
)
class Meta:
model = UserGroup
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'users', 'comment', 'date_created',
'created_by',
'id', 'name', 'users', 'users_amount', 'comment',
'date_created', 'created_by',
]
extra_kwargs = {
'created_by': {'label': _('Created by'), 'read_only': True}
......@@ -47,23 +47,8 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer):
raise serializers.ValidationError(msg)
return users
class UserGroupListSerializer(UserGroupSerializer):
users = StringManyToManyField(many=True, read_only=True)
class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects)
class Meta:
model = UserGroup
fields = ['id', 'users']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_fields_queryset()
def set_fields_queryset(self):
users_field = self.fields['users']
users_field.child_relation.queryset = utils.get_current_org_members()
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.annotate(users_amount=Count('users'))
return queryset
......@@ -7,11 +7,11 @@ from common.utils import validate_ssh_public_key
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from common.permissions import CanUpdateDeleteUser
from ..models import User, UserGroup
from ..models import User
__all__ = [
'UserSerializer', 'UserPKUpdateSerializer', 'UserUpdateGroupSerializer',
'UserSerializer', 'UserPKUpdateSerializer',
'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
'UserProfileSerializer', 'UserDisplaySerializer',
]
......@@ -123,16 +123,6 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
return value
class UserUpdateGroupSerializer(serializers.ModelSerializer):
groups = serializers.PrimaryKeyRelatedField(
many=True, queryset=UserGroup.objects
)
class Meta:
model = User
fields = ['id', 'groups']
class ChangeUserPasswordSerializer(serializers.ModelSerializer):
class Meta:
......
......@@ -2,3 +2,4 @@ from django.dispatch import Signal
post_user_create = Signal(providing_args=('user',))
post_user_change_password = Signal(providing_args=('user',))
......@@ -2,7 +2,7 @@
#
from django.dispatch import receiver
from django.db.models.signals import post_save, m2m_changed
from django.db.models.signals import m2m_changed
from common.utils import get_logger
from .signals import post_user_create
......
{% extends '_without_nav_base.html' %}
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> {{ JMS_TITLE }} </title>
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
<link rel="stylesheet" href="{% static 'fonts/font_otp/iconfont.css' %}" />
<link rel="stylesheet" href="{% static 'css/otp.css' %}" />
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
<script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script>
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
</head>
<body>
<!--头部-->
<header>
<div class="logo">
<a href="{% url 'index' %}">
<img src="{{ LOGO_URL }}" alt="" width="50px" height="50px"/>
</a>
<a href="{% url 'index' %}">{{ JMS_TITLE }}</a>
</div>
<div>
<a href="{% url 'index' %}">{% trans 'Home page' %}</a>
<b></b>
<a href="http://docs.jumpserver.org/zh/docs/">{% trans 'Docs' %}</a>
<b></b>
<a href="https://www.github.com/jumpserver/">GitHub</a>
</div>
</header>
<!--内容-->
<article>
<div class="" style="text-align: center; margin-bottom: 50px">
<h2>
{% block small_title %}
{% endblock %}
</h2>
</div>
<div >
<div class="verify">{% trans 'Security token validation' %}&nbsp;&nbsp;{% trans 'Account' %}&nbsp;<span>{{ user.username }}</span>&nbsp;&nbsp;{% trans 'Follow these steps to complete the binding operation' %}</div>
<hr style="width: 500px; margin: auto; margin-top: 10px;">
{% block content %}
{% block body %}
<article>
<div class="" style="text-align: center; margin-bottom: 50px">
<h2>
{% block small_title %}
{% endblock %}
</div>
</article>
<footer>
<div class="" style="margin-top: 100px;">
{% include '_copyright.html' %}
</div>
</footer>
</body>
</html>
</h2>
</div>
<div >
<div class="verify">{% trans 'Security token validation' %}&nbsp;&nbsp;{% trans 'Account' %}&nbsp;<span>{{ user.username }}</span>&nbsp;&nbsp;{% trans 'Follow these steps to complete the binding operation' %}</div>
<hr style="width: 500px; margin: auto; margin-top: 10px;">
{% block content %}
{% endblock %}
</div>
</article>
{% endblock %}
{% 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
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update user group" %}{% endblock %}
\ No newline at end of file
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import users" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-users:user-list" %}{% endblock %}
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update user" %}{% endblock %}
\ No newline at end of file
......@@ -13,7 +13,7 @@
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="col-sm-12">
<div class="ibox">
<div class="ibox-title">
<h5>{% trans 'First Login' %}</h5>
......@@ -55,7 +55,7 @@
</div>
<div class="content clearfix" style="background-color: #eee; border-radius:5px;">
<div class="row">
<form action="" method="post" class="form col-lg-8 p-m" id="fl_form" style="padding-left: 40px;">
<form action="" method="post" class="form col-sm-8 p-m" id="fl_form" style="padding-left: 40px;">
{% csrf_token %}
{{ wizard.management_form }}
{#{% if wizard.form.forms %}#}
......@@ -88,7 +88,7 @@
{% endif %}
</form>
<div class="col-lg-4">
<div class="col-sm-4">
<div class="text-center">
<div style="margin-top: 20px">
<i class="fa fa-sign-in" style="font-size: 180px;color: #e5e5e5 "></i>
......
......@@ -13,7 +13,7 @@
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="col-sm-12">
<div class="ibox">
<div class="ibox-title">
<h5>{% trans 'First Login' %}</h5>
......
{% extends '_base_only_content.html' %}
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
<title>{% trans 'Forgot password' %}</title>
{% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script>
</head>
<body class="gray-bg">
<div class="passwordBox animated fadeInDown">
<div class="row">
<div class="col-md-12">
<div class="ibox-content">
<img src="{{ LOGO_URL }}" style="margin: auto" width="82" height="82">
<h2 class="font-bold" style="display: inline">{% trans 'Forgot password' %} ?</h2>
<h1></h1>
{% if errors %}
<p class="red-fonts">{{ errors }}</p>
{% endif %}
<p>
{% trans 'Input your email, that will send a mail to your' %}
</p>
<div class="row">
<div class="col-lg-12">
<form class="m-t" role="form" action="" method="post">
{% csrf_token %}
<div class="form-group">
<input type="email" name="email" class="form-control" placeholder="Email address" required="">
</div>
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Submit' %}</button>
</form>
</div>
</div>
</div>
</div>
</div>
<hr/>
<div class="row">
<div class="col-md-12">
{% include '_copyright.html' %}
</div>
{% load bootstrap3 %}
{% block custom_head_css_js %}
<style>
.captcha {
float: right;
}
</style>
{% endblock %}
{% block html_title %}{% trans 'Forgot password' %}{% endblock %}
{% block title %} {% trans 'Forgot password' %}?{% endblock %}
{% block content %}
{% if errors %}
<p class="red-fonts">{{ errors }}</p>
{% endif %}
<p>
{% trans 'Input your email, that will send a mail to your' %}
</p>
<div class="row">
<div class="col-sm-12">
<form role="form" class="form-horizontal" action="" method="post">
{% csrf_token %}
{% bootstrap_field form.email layout="horizontal" %}
{% bootstrap_field form.captcha layout="horizontal" %}
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Submit' %}</button>
</form>
</div>
</div>
{% endblock %}
</body>
</html>
{% extends '_base_only_content.html' %}
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> {{ JMS_TITLE }} </title>
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
{% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script>
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
</head>
<body class="gray-bg">
<div class="loginColumns animated fadeInDown">
<div class="row">
<div class="col-md-6">
<h2 class="font-bold">{% trans 'Welcome to the Jumpserver open source fortress' %}</h2>
<p>
{% trans 'Jumpserver is an open source desktop system developed using Python and Django that helps Internet businesses with efficient users, assets, permissions, and audit management' %}
</p>
<p>
{% trans 'We are from all over the world, we have great admiration and worship for the spirit of open source, we have endless pursuit for perfection, neatness and elegance' %}
</p>
<p>
{% trans 'We focus on automatic operation and maintenance, and strive to build an easy-to-use, stable, safe and automatic board hopping machine, which is our unremitting pursuit and power' %}
</p>
<p>
<small>{% trans 'Always young, always with tears in my eyes. Stay foolish Stay hungry' %}</small>
</p>
</div>
<div class="col-md-6">
<div class="ibox-content">
<div><img src="{{ LOGO_URL }}" width="82" height="82"> <span class="font-bold text-center" style="font-size: 32px; font-family: inherit">{% trans 'Reset password' %}</span></div>
<form class="m-t" role="form" method="post" action="">
{% csrf_token %}
{% if errors %}
<p class="red-fonts">{{ errors }}</p>
{% endif %}
<div class="form-group">
<input type="password" id="id_new_password" class="form-control" name="password" placeholder="{% trans 'Password' %}" required="">
{# 密码popover #}
<div id="container">
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
<div class="arrow" style="left: 50%;"></div>
<h3 class="popover-title" style="display: none;"></h3>
<h4>{% trans 'Your password must satisfy' %}</h4><div id="id_password_rules" style="color: #908a8a; margin-left:20px; font-size:15px;"></div>
<h4 style="margin-top: 10px;">{% trans 'Password strength' %}</h4><div id="id_progress"></div>
<div class="popover-content"></div>
</div>
</div>
</div>
<div class="form-group">
<input type="password" id="id_confirm_password" class="form-control" name="password-confirm" placeholder="{% trans 'Password again' %}" required="">
</div>
<button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button>
<a href="#">
<small>Forgot password?</small>
</a>
<p class="text-muted text-center">
</p>
</form>
<p class="m-t">
</p>
{% load bootstrap3 %}
{% block html_title %}{% trans 'Reset password' %}{% endblock %}
{% block title %}{% trans 'Reset password' %}{% endblock %}
{% block content %}
<form class="m-t" role="form" method="post" action="">
{% csrf_token %}
{% if errors %}
<p class="red-fonts">{{ errors }}</p>
{% endif %}
{% if not token_invalid %}
<div class="form-group">
{% bootstrap_field form.new_password %}
{% bootstrap_field form.confirm_password %}
{# 密码popover #}
<div id="container">
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
<div class="arrow" style="left: 50%;"></div>
<h3 class="popover-title" style="display: none;"></h3>
<h4>{% trans 'Your password must satisfy' %}</h4><div id="id_password_rules" style="color: #908a8a; margin-left:20px; font-size:15px;"></div>
<h4 style="margin-top: 10px;">{% trans 'Password strength' %}</h4><div id="id_progress"></div>
<div class="popover-content"></div>
</div>
</div>
</div>
<hr/>
<div class="row">
<div class="col-md-12">
{% include '_copyright.html' %}
</div>
</div>
</div>
</body>
</html>
<script>
$(document).ready(function () {
// 密码强度校验
var el = $('#id_password_rules'),
idPassword = $('#id_new_password'),
idPopover = $('#popover777'),
container = $('#container'),
progress = $('#id_progress'),
password_check_rules = {{ password_check_rules|safe }},
minLength = 6,
top = 146, left = 170,
i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}",
"weak": "{% trans 'Weak' %}",
"normal": "{% trans 'Normal' %}",
"medium": "{% trans 'Medium' %}",
"strong": "{% trans 'Strong' %}",
"veryStrong": "{% trans 'Very strong' %}"
};
jQuery.each(password_check_rules, function (idx, rules) {
if(rules.key === 'id_security_password_min_length'){
minLength = rules.value
}
});
// 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
// 监听事件
idPassword.on('focus', function () {
idPopover.css('top', top);
idPopover.css('left', left);
idPopover.css('display', 'block');
});
idPassword.on('blur', function () {
idPopover.css('display', 'none');
});
idPassword.on('keyup', function(){
var password = idPassword.val();
checkPasswordRules(password, minLength);
})
})
<button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button>
{% endif %}
</form>
{% endblock %}
{% block custom_foot_js %}
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
<script>
$(document).ready(function () {
// 密码强度校验
var el = $('#id_password_rules'),
idPassword = $('#id_new_password'),
idPopover = $('#popover777'),
container = $('#container'),
progress = $('#id_progress'),
password_check_rules = {{ password_check_rules|safe }},
minLength = 6,
top = 146, left = 170,
i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}",
"weak": "{% trans 'Weak' %}",
"normal": "{% trans 'Normal' %}",
"medium": "{% trans 'Medium' %}",
"strong": "{% trans 'Strong' %}",
"veryStrong": "{% trans 'Very strong' %}"
};
jQuery.each(password_check_rules, function (idx, rules) {
if(rules.key === 'id_security_password_min_length'){
minLength = rules.value
}
});
// 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
// 监听事件
idPassword.on('focus', function () {
idPopover.css('top', top);
idPopover.css('left', left);
idPopover.css('display', 'block');
});
idPassword.on('blur', function () {
idPopover.css('display', 'none');
});
idPassword.on('keyup', function(){
var password = idPassword.val();
checkPasswordRules(password, minLength);
})
})
</script>
{% endblock %}
......@@ -244,10 +244,10 @@
{% for group in user_object.groups.all %}
<tr>
<td >
<b class="bdg_group" data-gid={{ group.id }}>{{ group.name }}</b>
<b class="bdg_group" >{{ group.name }}</b>
</td>
<td>
<button class="btn btn-danger pull-right btn-xs btn_leave_group" type="button"><i class="fa fa-minus"></i></button>
<button class="btn btn-danger pull-right btn-xs btn_leave_group" data-uid={{ group.id }} type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
......@@ -307,38 +307,8 @@
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.nodes_selected = {};
var usersSelect2;
function updateUserGroups(groups) {
var the_url = "{% url 'api-users:user-update-group' pk=user_object.id %}";
var body = {
groups: Object.assign([], groups)
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#groups_selected').val('');
$.map(jumpserver.nodes_selected, function(group_name, index) {
$('#opt_' + index).remove();
// change tr html of user groups.
$('.group_edit tbody').append(
'<tr>' +
'<td><b class="bdg_group" data-gid="' + index + '">' + group_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn_leave_group" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
function updateUserLoginReviewer(reviewers) {
var url = "{% url 'api-auth:login-confirm-setting-update' user_id=user_object.id %}";
var data = {reviewers: reviewers};
......@@ -352,16 +322,44 @@ function updateUserLoginReviewer(reviewers) {
})
}
var usersGroupsRelationUrl = "{% url 'api-users:users-groups-relation-list' %}";
function addObjects(objectsId) {
if (!objectsId || objectsId.length === 0) {
return
}
var theUrl = usersGroupsRelationUrl;
var body = [];
objectsId.forEach(function (v) {
var data = {user: "{{ object.id }}"};
data["usergroup"] = v;
body.push(data)
});
requestApi({
url: theUrl,
body: JSON.stringify(body),
method: "POST",
success: reloadPage
});
}
function removeObject(objectId) {
if (!objectId) {
return
}
var theUrl = usersGroupsRelationUrl;
theUrl = setUrlParam(theUrl, 'user', "{{ object.id }}");
theUrl = setUrlParam(theUrl, 'usergroup', objectId);
requestApi({
url: theUrl,
method: "DELETE",
success: reloadPage
});
}
$(document).ready(function() {
$('.select2').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.nodes_selected[data.id];
});
$('.select2').select2();
usersSelect2 = usersSelect2Init('.users-select2')
})
.on('click', '#is_active', function() {
......@@ -405,31 +403,11 @@ $(document).ready(function() {
});
})
.on('click', '#btn_join_group', function() {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var groups = $('.bdg_group').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.nodes_selected, function(value, index) {
groups.push(index);
$('#opt_' + index).remove();
});
updateUserGroups(groups)
var objectsId = $("#groups_selected").val();
addObjects(objectsId);
}).on('click', '.btn_leave_group', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_group');
var gid = $badge.data('gid');
var group_name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
);
$tr.remove();
var groups = $('.bdg_group').map(function() {
return $(this).data('gid');
}).get();
updateUserGroups(groups)
var objectId = $(this).data('uid');
removeObject(objectId)
}).on('click', '#btn-reset-password', function() {
function doReset() {
var the_url = '{% url "api-users:user-reset-password" pk=user_object.id %}';
......
......@@ -91,9 +91,9 @@
{% for user in user_group.users.all %}
<tr>
<td ><b class="bdg_user" data-uid={{ user.id }}>{{ user.name }}</b></td>
<td ><b class="bdg_user" >{{ user.name }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-remove-user" type="button"><i class="fa fa-minus"></i></button>
<button class="btn btn-danger pull-right btn-xs btn-remove-user" data-uid={{ user.id }} type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
......@@ -110,73 +110,51 @@
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.users_selected = {};
function updateGroupMember(users) {
var the_url = "{% url 'api-users:user-group-update-user' pk=user_group.id %}";
var body = {
users: Object.assign([], users)
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#slct_users').val('');
$.map(jumpserver.users_selected, function(user_name, index) {
$('#opt_' + index).remove();
// change tr html of users
$('.user_edit tbody').append(
'<tr>' +
'<td><b class="bdg_user" data-uid="' + index + '">' + user_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-user" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.selected_groups
jumpserver.users_selected = {};
};
var usersGroupsRelationUrl = "{% url 'api-users:users-groups-relation-list' %}";
function addObjects(objectsId) {
if (!objectsId || objectsId.length === 0) {
return
}
var theUrl = usersGroupsRelationUrl;
var body = [];
objectsId.forEach(function (v) {
var data = {usergroup: "{{ object.id }}"};
data["user"] = v;
body.push(data)
});
requestApi({
url: the_url,
url: theUrl,
body: JSON.stringify(body),
success: success
method: "POST",
success: reloadPage
});
}
function removeObject(objectId, type) {
if (!objectId) {
return
}
var theUrl = usersGroupsRelationUrl;
theUrl = setUrlParam(theUrl, 'usergroup', "{{ object.id }}");
theUrl = setUrlParam(theUrl, 'user', objectId);
requestApi({
url: theUrl,
method: "DELETE",
success: reloadPage
});
}
$(document).ready(function () {
$('.select2').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.users_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.users_selected[data.id]
});
$('.select2').select2();
usersSelect2Init('#slct_users')
}).on('click', '.btn-remove-user', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_user');
var uid = $badge.data('uid');
var user_name = $badge.html() || $badge.text();
$('#slct_users').append(
'<option value="' + uid + '" id="opt_' + uid + '">' + user_name + '</option>'
);
$tr.remove();
var users = $('.bdg_user').map(function() {
return $(this).data('uid');
}).get();
updateGroupMember(users)
var objectId = $(this).data("uid");
removeObject(objectId);
}).on('click', '#btn_add_user', function() {
if (Object.keys(jumpserver.users_selected).length === 0) {
return false;
}
var users = $('.bdg_user').map(function() {
return $(this).data('uid');
}).get();
$.map(jumpserver.users_selected, function(value, index) {
users.push(index);
$('#opt_' + index).remove();
});
updateGroupMember(users)
var objectsId = $("#slct_users").val();
addObjects(objectsId);
}).on('click', '.btn-delete-user-group', function () {
var $this = $(this);
var name = "{{ user_group.name }}";
......@@ -185,5 +163,6 @@ $(document).ready(function () {
var redirect_url = "{% url 'users:user-group-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
</script>
{% endblock %}
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% include '_csv_import_export.html' %}
{% endblock %}
{% block table_container %}
<div class="pull-left m-r-5"><a href="{% url 'users:user-group-create' %}" class="btn btn-sm btn-primary ">{% trans "Create user group" %}</a></div>
......@@ -39,14 +18,13 @@
</tr>
</thead>
</table>
{% include "users/_user_groups_import_modal.html" %}
{% include "users/_user_groups_update_modal.html" %}
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
var groups_table = 0;
var groupsTable = 0;
var usersAmountTpl = '<a class="group-users-amount" data-uid="ID">NUM</a>';
function initTable() {
var options = {
ele: $('#group_list_table'),
......@@ -58,14 +36,16 @@ function initTable() {
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 2, createdCell: function (td, cellData, rowData) {
var html = createPopover(cellData);
$(td).html(html);
var data = usersAmountTpl
.replace("ID", rowData.id)
.replace('NUM', cellData);
$(td).html(data);
}},
{targets: 3, createdCell: function (td, cellData) {
cellData = htmlEscape(cellData);
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(rowData.name);
var update_btn = '<a href="{% url "users:user-group-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
......@@ -80,129 +60,53 @@ function initTable() {
}
}}
],
ajax_url: '{% url "api-users:user-group-list" %}?display=1',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "users", orderable: false},
ajax_url: '{% url "api-users:user-group-list" %}',
columns: [{data: "id"}, {data: "name" }, {data: "users_amount", orderable: false},
{data: "comment"}, {data: "id", orderable: false, width:"100px"}],
order: [],
op_html: $('#actions').html()
};
groups_table = jumpserver.initServerSideDataTable(options);
return groups_table
groupsTable = jumpserver.initServerSideDataTable(options);
return groupsTable
}
$(document).ready(function() {
initTable()
var usersGroupsRelationUrl = "{% url 'api-users:users-groups-relation-list' %}";
function getGroupUsers(groupId, callback) {
var theUrl = setUrlParam(usersGroupsRelationUrl, "usergroup", groupId);
if (!callback) {
callback = function (data) {
console.log(data)
}
}
requestApi({
url: theUrl,
method: "GET",
success: callback,
flash_message: false,
})
}
$(document).ready(function() {
groupsTable = initTable();
initCsvImportExport(groupsTable, "{% trans 'User groups' %}")
}).on('click', '.btn_delete_user_group', function(){
var $this = $(this);
var group_id = $this.data('gid');
var name = $this.data('name');
var the_url = "{% url 'api-users:user-group-detail' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', group_id);
objectDelete($this, name, the_url)
}).on('click', '#btn_bulk_update', function(){
var action = $('#slct_bulk_update').val();
var $data_table = $('#group_list_table').DataTable()
var plain_id_list = [];
$data_table.rows({selected: true}).every(function(){
plain_id_list.push(this.data().id);
});
if (plain_id_list === []) {
return false;
}
var the_url = "{% url 'api-users:user-group-list' %}";
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected groups !!!' %}",
type: "warning",
showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'UserGroups Deleted.' %}";
swal("{% trans 'UserGroups Delete' %}", msg, "success");
$data_table.ajax.reload();
};
var fail = function() {
var msg = "{% trans 'UserGroup Deleting failed.' %}";
swal("{% trans 'UserGroups Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
requestApi({url: url_delete, method: 'DELETE', success: success, error: fail});
jumpserver.checked = false;
});
}
switch(action) {
case 'delete':
doDelete();
break;
default:
break;
}
}).on('click', '.btn_export', function(){
var groups = groups_table.selected;
var data = {
'resources': groups
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-users:user-group-list' %}",
format: "csv",
params: {
search: search
}
};
APIExportData(props);
}).on('click', '#btn_import_confirm',function () {
var url = "{% url 'api-users:user-group-list' %}";
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var data_table = $('#group_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
})
.on('click', '#download_update_template', function(){
var groups = groups_table.selected;
var data = {
'resources': groups
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-users:user-group-list' %}?format=csv&template=update",
format: "csv",
params: {
search: search
}
};
APIExportData(props);
}).on('click', '#btn_update_confirm',function () {
var url = "{% url 'api-users:user-group-list' %}";
var file = document.getElementById('update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var data_table = $('#group_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
.on('click', '.group-users-amount', function () {
var $this = $(this);
var groupId = $(this).data("uid");
getGroupUsers(groupId, function (data) {
var groups = [];
data.forEach(function (v) {
groups.push(v.user_display);
});
})
var popover = createPopover(groups);
$this.parent().html(popover);
$(popover).trigger('click');
})
});
</script>
{% endblock %}
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}
<div class="pull-right" >
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% include '_csv_import_export.html' %}
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>
......@@ -63,14 +42,12 @@
</div>
</div>
</div>
{% include "users/_user_import_modal.html" %}
{% include "users/_user_update_modal.html" %}
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script>
var users_table = 0;
var usersTable = 0;
function initTable() {
var options = {
ele: $('#user_list_table'),
......@@ -148,86 +125,17 @@ function initTable() {
],
op_html: $('#actions').html()
};
users_table = jumpserver.initServerSideDataTable(options);
return users_table
usersTable = jumpserver.initServerSideDataTable(options);
return usersTable
}
$(document).ready(function(){
initTable();
var fields = $('#fm_user_bulk_update .form-group');
$.each(fields, function (index, value) {
console.log(value)
});
$('.btn_export').click(function () {
var users = users_table.selected;
var data = {
'resources': users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-users:user-list' %}",
format: 'csv',
params: {
search: search
}
};
APIExportData(props);
});
$('#btn_import_confirm').click(function() {
var url = "{% url 'api-users:user-list' %}";
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var data_table = $('#user_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
});
$('#download_update_template').click(function () {
var users = users_table.selected;
var data = {
'resources': users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-users:user-list' %}?format=csv&template=update",
format: 'csv',
params: {
search: search
}
};
APIExportData(props);
});
$('#btn_update_confirm').click(function() {
var url = "{% url 'api-users:user-list' %}";
var file = document.getElementById('update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var data_table = $('#user_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
});
});
usersTable = initTable();
initCsvImportExport(usersTable, "{% trans 'User groups' %}")
}).on('click', '#btn_bulk_update', function(){
var action = $('#slct_bulk_update').val();
var id_list = users_table.selected;
var id_list = usersTable.selected;
if (id_list.length === 0) {
return false;
}
......
......@@ -9,11 +9,6 @@
{% block content %}
<form class="" role="form" method="post" action="">
{% csrf_token %}
<div class="form-input">
<input type="text" class="" name="{{ form.username.html_name }}" value="{{ form.username.value }}" readonly="readonly" required="">
</div>
<div class="form-input">
<input type="password" class="" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
</div>
......
......@@ -14,7 +14,7 @@ app_name = 'users'
router = BulkRouter()
router.register(r'users', api.UserViewSet, 'user')
router.register(r'groups', api.UserGroupViewSet, 'user-group')
router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'user-group-relation')
router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'users-groups-relation')
urlpatterns = [
......@@ -28,8 +28,6 @@ urlpatterns = [
path('users/<uuid:pk>/pubkey/reset/', api.UserResetPKApi.as_view(), name='user-public-key-reset'),
path('users/<uuid:pk>/pubkey/update/', api.UserUpdatePKApi.as_view(), name='user-public-key-update'),
path('users/<uuid:pk>/unblock/', api.UserUnblockPKApi.as_view(), name='user-unblock'),
path('users/<uuid:pk>/groups/', api.UserUpdateGroupApi.as_view(), name='user-update-group'),
path('groups/<uuid:pk>/users/', api.UserGroupUpdateUserApi.as_view(), name='user-group-update-user'),
]
urlpatterns += router.urls
......
......@@ -20,10 +20,10 @@ urlpatterns = [
path('profile/password/update/', views.UserPasswordUpdateView.as_view(), name='user-password-update'),
path('profile/pubkey/update/', views.UserPublicKeyUpdateView.as_view(), name='user-pubkey-update'),
path('profile/pubkey/generate/', views.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'),
path('profile/otp/enable/authentication/', views.UserOtpEnableAuthenticationView.as_view(), name='user-otp-enable-authentication'),
path('profile/otp/enable/authentication/', views.UserCheckPasswordView.as_view(), name='user-otp-enable-authentication'),
path('profile/otp/enable/install-app/', views.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'),
path('profile/otp/enable/bind/', views.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'),
path('profile/otp/disable/authentication/', views.UserOtpDisableAuthenticationView.as_view(), name='user-otp-disable-authentication'),
path('profile/otp/disable/authentication/', views.UserDisableMFAView.as_view(), name='user-otp-disable-authentication'),
path('profile/otp/update/', views.UserOtpUpdateView.as_view(), name='user-otp-update'),
path('profile/otp/settings-success/', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'),
......
......@@ -329,3 +329,18 @@ def construct_user_email(username, email):
def get_current_org_members(exclude=()):
from orgs.utils import current_org
return current_org.get_org_members(exclude=exclude)
def get_source_choices():
from .models import User
choices_all = dict(User.SOURCE_CHOICES)
choices = [
(User.SOURCE_LOCAL, choices_all[User.SOURCE_LOCAL]),
]
if settings.AUTH_LDAP:
choices.append((User.SOURCE_LDAP, choices_all[User.SOURCE_LDAP]))
if settings.AUTH_OPENID:
choices.append((User.SOURCE_OPENID, choices_all[User.SOURCE_OPENID]))
if settings.AUTH_RADIUS:
choices.append((User.SOURCE_RADIUS, choices_all[User.SOURCE_RADIUS]))
return choices
......@@ -4,13 +4,13 @@ from __future__ import unicode_literals
from django.shortcuts import render
from django.views.generic import RedirectView
from django.core.files.storage import default_storage
from django.http import HttpResponseRedirect
from django.shortcuts import reverse, redirect
from django.utils.translation import ugettext as _
from django.views.generic.base import TemplateView
from django.conf import settings
from django.urls import reverse_lazy
from formtools.wizard.views import SessionWizardView
from django.views.generic import FormView
from common.utils import get_object_or_none
from common.permissions import PermissionsMixin, IsValidUser
......@@ -33,22 +33,24 @@ class UserLoginView(RedirectView):
query_string = True
class UserForgotPasswordView(TemplateView):
class UserForgotPasswordView(FormView):
template_name = 'users/forgot_password.html'
form_class = forms.UserForgotPasswordForm
def post(self, request):
email = request.POST.get('email')
def form_valid(self, form):
request = self.request
email = form.cleaned_data['email']
user = get_object_or_none(User, email=email)
if not user:
error = _('Email address invalid, please input again')
return self.get(request, errors=error)
elif not user.can_update_password():
error = _('User auth from {}, go there change password'.format(user.source))
error = _('User auth from {}, go there change password'.format(
user.source))
return self.get(request, errors=error)
else:
send_reset_password_mail(user)
return HttpResponseRedirect(
reverse('users:forgot-password-sendmail-success'))
return redirect('users:forgot-password-sendmail-success')
class UserForgotPasswordSendmailSuccessView(TemplateView):
......@@ -79,44 +81,47 @@ class UserResetPasswordSuccessView(TemplateView):
return super().get_context_data(**kwargs)
class UserResetPasswordView(TemplateView):
class UserResetPasswordView(FormView):
template_name = 'users/reset_password.html'
form_class = forms.UserTokenResetPasswordForm
def get(self, request, *args, **kwargs):
token = request.GET.get('token', '')
context = self.get_context_data(**kwargs)
errors = kwargs.get('errors')
if errors:
context['errors'] = errors
return self.render_to_response(context)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
token = self.request.GET.get('token', '')
user = User.validate_reset_password_token(token)
if not user:
kwargs.update({'errors': _('Token invalid or expired')})
else:
check_rules = get_password_check_rules()
kwargs.update({'password_check_rules': check_rules})
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
password = request.POST.get('password')
password_confirm = request.POST.get('password-confirm')
token = request.GET.get('token')
if password != password_confirm:
return self.get(request, errors=_('Password not same'))
context['errors'] = _('Token invalid or expired')
context['token_invalid'] = True
check_rules = get_password_check_rules()
context['password_check_rules'] = check_rules
return context
def form_valid(self, form):
token = self.request.GET.get('token')
user = User.validate_reset_password_token(token)
if not user:
return self.get(request, errors=_('Token invalid or expired'))
return self.get(self.request, errors=_('Token invalid or expired'))
if not user.can_update_password():
error = _('User auth from {}, go there change password'.format(user.source))
return self.get(request, errors=error)
errors = _('User auth from {}, go there change password'.format(user.source))
return self.get(self.request, errors=errors)
password = form.cleaned_data['new_password']
is_ok = check_password_rules(password)
if not is_ok:
return self.get(
request,
errors=_('* Your password does not meet the requirements')
)
errors = _('* Your password does not meet the requirements')
return self.get(self.request, errors=errors)
user.reset_password(password)
User.expired_reset_password_token(token)
return HttpResponseRedirect(reverse('users:reset-password-success'))
return redirect('users:reset-password-success')
class UserFirstLoginView(PermissionsMixin, SessionWizardView):
......@@ -177,5 +182,4 @@ class UserFirstLoginView(PermissionsMixin, SessionWizardView):
choices = [(k, v) for k, v in choices if k in [0, 1]]
form.fields["mfa_level"].choices = choices
form.fields["mfa_level"].initial = self.request.user.mfa_level
return form
......@@ -31,9 +31,9 @@ __all__ = [
'UserProfileView',
'UserProfileUpdateView', 'UserPasswordUpdateView',
'UserPublicKeyUpdateView', 'UserPublicKeyGenerateView',
'UserOtpEnableAuthenticationView', 'UserOtpEnableInstallAppView',
'UserCheckPasswordView', 'UserOtpEnableInstallAppView',
'UserOtpEnableBindView', 'UserOtpSettingsSuccessView',
'UserOtpDisableAuthenticationView', 'UserOtpUpdateView',
'UserDisableMFAView', 'UserOtpUpdateView',
]
logger = get_logger(__name__)
......@@ -140,24 +140,10 @@ class UserPublicKeyGenerateView(PermissionsMixin, View):
return response
class UserOtpEnableAuthenticationView(FormView):
template_name = 'users/user_password_authentication.html'
class UserCheckPasswordView(FormView):
template_name = 'users/user_password_check.html'
form_class = forms.UserCheckPasswordForm
def get_form(self, form_class=None):
user = get_user_or_tmp_user(self.request)
form = super().get_form(form_class=form_class)
form['username'].initial = user.username
return form
def get_context_data(self, **kwargs):
user = get_user_or_tmp_user(self.request)
context = {
'user': user
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def form_valid(self, form):
user = get_user_or_tmp_user(self.request)
password = form.cleaned_data.get('password')
......@@ -165,10 +151,17 @@ class UserOtpEnableAuthenticationView(FormView):
if not user:
form.add_error("password", _("Password invalid"))
return self.form_invalid(form)
if not user.mfa_is_otp():
user.enable_mfa()
user.save()
return redirect(self.get_success_url())
def get_success_url(self):
return reverse('users:user-otp-enable-install-app')
if settings.OTP_IN_RADIUS:
success_url = reverse_lazy('users:user-otp-settings-success')
else:
success_url = reverse('users:user-otp-enable-install-app')
return success_url
class UserOtpEnableInstallAppView(TemplateView):
......@@ -213,23 +206,23 @@ class UserOtpEnableBindView(TemplateView, FormView):
def save_otp(self, otp_secret_key):
user = get_user_or_tmp_user(self.request)
user.enable_otp()
user.enable_mfa()
user.otp_secret_key = otp_secret_key
user.save()
class UserOtpDisableAuthenticationView(FormView):
template_name = 'users/user_otp_authentication.html'
class UserDisableMFAView(FormView):
template_name = 'users/user_disable_mfa.html'
form_class = forms.UserCheckOtpCodeForm
success_url = reverse_lazy('users:user-otp-settings-success')
def form_valid(self, form):
user = self.request.user
otp_code = form.cleaned_data.get('otp_code')
otp_secret_key = user.otp_secret_key
if check_otp_code(otp_secret_key, otp_code):
user.disable_otp()
valid = user.check_mfa(otp_code)
if valid:
user.disable_mfa()
user.save()
return super().form_valid(form)
else:
......@@ -237,16 +230,13 @@ class UserOtpDisableAuthenticationView(FormView):
return super().form_invalid(form)
class UserOtpUpdateView(UserOtpDisableAuthenticationView):
class UserOtpUpdateView(UserDisableMFAView):
success_url = reverse_lazy('users:user-otp-enable-bind')
class UserOtpSettingsSuccessView(TemplateView):
template_name = 'flash_message_standalone.html'
# def get(self, request, *args, **kwargs):
# return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
title, describe = self.get_title_describe()
context = {
......@@ -265,8 +255,9 @@ class UserOtpSettingsSuccessView(TemplateView):
auth_logout(self.request)
title = _('MFA enable success')
describe = _('MFA enable success, return login page')
if not user.otp_enabled:
if not user.mfa_enabled:
title = _('MFA disable success')
describe = _('MFA disable success, return login page')
return title, describe
......@@ -19,7 +19,7 @@ from daemon import pidfile
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, BASE_DIR)
logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
try:
from apps.jumpserver import const
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment