[Update] Merge branch dev_cloud_manager (baijiangjie_local) to dev

This commit is contained in:
BaiJiangJie 2018-10-12 18:41:01 +08:00
commit 21c71aba93
42 changed files with 1310 additions and 329 deletions

View File

@ -2,4 +2,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
__version__ = "1.4.1" __version__ = "1.4.3"

View File

@ -4,3 +4,4 @@ from .label import *
from .system_user import * from .system_user import *
from .node import * from .node import *
from .domain import * from .domain import *
from .cmd_filter import *

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
#
from rest_framework_bulk import BulkModelViewSet
from django.shortcuts import get_object_or_404
from ..hands import IsOrgAdmin
from ..models import CommandFilter, CommandFilterRule
from .. import serializers
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
class CommandFilterViewSet(BulkModelViewSet):
permission_classes = (IsOrgAdmin,)
queryset = CommandFilter.objects.all()
serializer_class = serializers.CommandFilterSerializer
class CommandFilterRuleViewSet(BulkModelViewSet):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterRuleSerializer
def get_queryset(self):
fpk = self.kwargs.get('filter_pk')
if not fpk:
return CommandFilterRule.objects.none()
group = get_object_or_404(CommandFilter, pk=fpk)
return group.rules.all().order_by('priority')

View File

@ -33,7 +33,8 @@ __all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserViewSet', 'SystemUserAuthInfoApi',
'SystemUserPushApi', 'SystemUserTestConnectiveApi', 'SystemUserPushApi', 'SystemUserTestConnectiveApi',
'SystemUserAssetsListView', 'SystemUserPushToAssetApi', 'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
'SystemUserTestAssetConnectabilityApi', 'SystemUserTestAssetConnectabilityApi', 'SystemUserCommandFilterRuleListApi',
] ]
@ -127,3 +128,16 @@ class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView):
asset = get_object_or_404(Asset, id=asset_id) asset = get_object_or_404(Asset, id=asset_id)
task = test_system_user_connectability_a_asset.delay(system_user, asset) task = test_system_user_connectability_a_asset.delay(system_user, asset)
return Response({"task": task.id}) return Response({"task": task.id})
class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
def get_serializer_class(self):
from ..serializers import CommandFilterRuleSerializer
return CommandFilterRuleSerializer
def get_queryset(self):
pk = self.kwargs.get('pk', None)
system_user = get_object_or_404(SystemUser, pk=pk)
return system_user.cmd_filter_rules

View File

@ -4,3 +4,4 @@ from .asset import *
from .label import * from .label import *
from .user import * from .user import *
from .domain import * from .domain import *
from .cmd_filter import *

View File

@ -48,7 +48,7 @@ class AssetCreateForm(OrgModelForm):
'root or other NOPASSWD sudo privilege user existed in asset,' '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' 'If asset is windows or other set any one, more see admin user left menu'
), ),
# 'platform': _("* required Must set exact system platform, Windows, Linux ..."), '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") 'domain': _("If your have some network not connect with each other, you can set domain")
} }
@ -88,7 +88,7 @@ class AssetUpdateForm(OrgModelForm):
'root or other NOPASSWD sudo privilege user existed in asset,' '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' 'If asset is windows or other set any one, more see admin user left menu'
), ),
# 'platform': _("* required Must set exact system platform, Windows, Linux ..."), '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") 'domain': _("If your have some network not connect with each other, you can set domain")
} }

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
#
from django import forms
from orgs.mixins import OrgModelForm
from ..models import CommandFilter, CommandFilterRule
__all__ = ['CommandFilterForm', 'CommandFilterRuleForm']
class CommandFilterForm(OrgModelForm):
class Meta:
model = CommandFilter
fields = ['name', 'comment']
class CommandFilterRuleForm(OrgModelForm):
class Meta:
model = CommandFilterRule
fields = [
'filter', 'type', 'content', 'priority', 'action', 'comment'
]
widgets = {
'content': forms.Textarea(attrs={
'placeholder': 'eg:\r\nreboot\r\nrm -rf'
}),
}

View File

@ -3,8 +3,9 @@
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from ..models import AdminUser, SystemUser
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger
from orgs.mixins import OrgModelForm
from ..models import AdminUser, SystemUser
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
@ -85,7 +86,7 @@ class AdminUserForm(PasswordAndKeyAuthForm):
} }
class SystemUserForm(PasswordAndKeyAuthForm): class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
# Admin user assets define, let user select, save it in form not in view # Admin user assets define, let user select, save it in form not in view
auto_generate_key = forms.BooleanField(initial=True, required=False) auto_generate_key = forms.BooleanField(initial=True, required=False)
@ -136,11 +137,14 @@ class SystemUserForm(PasswordAndKeyAuthForm):
fields = [ fields = [
'name', 'username', 'protocol', 'auto_generate_key', 'name', 'username', 'protocol', 'auto_generate_key',
'password', 'private_key_file', 'auto_push', 'sudo', 'password', 'private_key_file', 'auto_push', 'sudo',
'comment', 'shell', 'priority', 'login_mode', 'comment', 'shell', 'priority', 'login_mode', 'cmd_filters',
] ]
widgets = { widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}),
'cmd_filters': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Command filter')
}),
} }
help_texts = { help_texts = {
'name': '* required', 'name': '* required',

View File

@ -1,11 +1,12 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from .user import AdminUser, SystemUser from .user import *
from .label import Label from .label import Label
from .cluster import * from .cluster import *
from .group import * from .group import *
from .domain import * from .domain import *
from .node import * from .node import *
from .asset import * from .asset import *
from .cmd_filter import *
from .utils import * from .utils import *

View File

@ -15,7 +15,7 @@ from django.core.cache import cache
from ..const import ASSET_ADMIN_CONN_CACHE_KEY from ..const import ASSET_ADMIN_CONN_CACHE_KEY
from .user import AdminUser, SystemUser from .user import AdminUser, SystemUser
from orgs.mixins import OrgModelMixin,OrgManager from orgs.mixins import OrgModelMixin, OrgManager
__all__ = ['Asset'] __all__ = ['Asset']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
__all__ = [
'CommandFilter', 'CommandFilterRule'
]
class CommandFilter(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=64, verbose_name=_("Name"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
comment = models.TextField(blank=True, default='', verbose_name=_("Comment"))
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
def __str__(self):
return self.name
class CommandFilterRule(OrgModelMixin):
TYPE_REGEX = 'regex'
TYPE_COMMAND = 'command'
TYPE_CHOICES = (
(TYPE_REGEX, _('Regex')),
(TYPE_COMMAND, _('Command')),
)
ACTION_DENY, ACTION_ALLOW = range(2)
ACTION_CHOICES = (
(ACTION_DENY, _('Deny')),
(ACTION_ALLOW, _('Allow')),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
filter = models.ForeignKey('CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules')
type = models.CharField(max_length=16, default=TYPE_COMMAND, choices=TYPE_CHOICES, verbose_name=_("Type"))
priority = models.IntegerField(default=50, verbose_name=_("Priority"), help_text=_("1-100, the lower will be match first"),
validators=[MinValueValidator(1), MaxValueValidator(100)])
content = models.TextField(max_length=1024, verbose_name=_("Content"), help_text=_("One line one command"))
action = models.IntegerField(default=ACTION_DENY, choices=ACTION_CHOICES, verbose_name=_("Action"))
comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment"))
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
class Meta:
ordering = ('priority', 'action')
def __str__(self):
return '{} % {}'.format(self.type, self.content)

View File

@ -40,12 +40,10 @@ class Node(OrgModelMixin):
return True return True
self_key = [int(k) for k in self.key.split(':')] self_key = [int(k) for k in self.key.split(':')]
other_key = [int(k) for k in other.key.split(':')] other_key = [int(k) for k in other.key.split(':')]
if len(self_key) < len(other_key): return self_key.__lt__(other_key)
return True
elif len(self_key) > len(other_key): def __lt__(self, other):
return False return not self.__gt__(other)
else:
return self_key[-1] < other_key[-1]
@property @property
def name(self): def name(self):

View File

@ -3,7 +3,6 @@
# #
import logging import logging
import uuid
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models
@ -118,6 +117,7 @@ class SystemUser(AssetUser):
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode')) login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode'))
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
cache_key = "__SYSTEM_USER_CACHED_{}" cache_key = "__SYSTEM_USER_CACHED_{}"
@ -163,6 +163,14 @@ class SystemUser(AssetUser):
def expire_cache(self): def expire_cache(self):
cache.delete(self.cache_key.format(self.id)) cache.delete(self.cache_key.format(self.id))
@property
def cmd_filter_rules(self):
from .cmd_filter import CommandFilterRule
rules = CommandFilterRule.objects.filter(
filter__in=self.cmd_filters.all()
).order_by('priority').distinct()
return rules
@classmethod @classmethod
def get_system_user_by_id_or_cached(cls, sid): def get_system_user_by_id_or_cached(cls, sid):
cached = cache.get(cls.cache_key.format(sid)) cached = cache.get(cls.cache_key.format(sid))

View File

@ -7,3 +7,4 @@ from .label import *
from .system_user import * from .system_user import *
from .node import * from .node import *
from .domain import * from .domain import *
from .cmd_filter import *

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from common.fields import ChoiceDisplayField
from ..models import CommandFilter, CommandFilterRule, SystemUser
class CommandFilterSerializer(serializers.ModelSerializer):
rules = serializers.PrimaryKeyRelatedField(queryset=CommandFilterRule.objects.all(), many=True)
system_users = serializers.PrimaryKeyRelatedField(queryset=SystemUser.objects.all(), many=True)
class Meta:
model = CommandFilter
fields = '__all__'
class CommandFilterRuleSerializer(serializers.ModelSerializer):
serializer_choice_field = ChoiceDisplayField
class Meta:
model = CommandFilterRule
fields = '__all__'

View File

@ -62,6 +62,10 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
<div id="command-filter-block">
<h3>{% trans 'Command filter' %}</h3>
{% bootstrap_field form.cmd_filters layout="horizontal" %}
</div>
<h3>{% trans 'Other' %}</h3> <h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.sudo layout="horizontal" %} {% bootstrap_field form.sudo layout="horizontal" %}
{% bootstrap_field form.shell layout="horizontal" %} {% bootstrap_field form.shell layout="horizontal" %}
@ -101,6 +105,7 @@ var need_change_field_login_mode = [
function protocolChange() { function protocolChange() {
if ($(protocol_id + " option:selected").text() === 'rdp') { if ($(protocol_id + " option:selected").text() === 'rdp') {
$('.auth-fields').removeClass('hidden'); $('.auth-fields').removeClass('hidden');
$('#command-filter-block').addClass('hidden');
$.each(need_change_field, function (index, value) { $.each(need_change_field, function (index, value) {
$(value).closest('.form-group').addClass('hidden') $(value).closest('.form-group').addClass('hidden')
}); });

View File

@ -90,7 +90,7 @@
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="select2" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes %} {% for node in nodes %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.value }}</option> <option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>

View File

@ -0,0 +1,20 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% block form %}
<form id="groupForm" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% 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 %}

View File

@ -0,0 +1,168 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
{% endblock %}
{% 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:cmd-filter-detail' pk=object.id %}" class="text-center">
<i class="fa fa-laptop"></i> {% trans 'Detail' %}
</a>
</li>
<li>
<li>
<a href="{% url 'assets:cmd-filter-rule-list' pk=object.id %}" class="text-center">
<i class="fa fa-laptop"></i> {% trans 'Rules' %}
</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:cmd-filter-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-del">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</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 'Comment' %}:</td>
<td><b>{{ object.comment }}</b></td>
</tr>
<tr>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ object.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Date updated' %}:</td>
<td><b>{{ object.date_updated }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
<td><b>{{ object.created_by }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-4" style="padding-left: 0; padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'System users' %}
</div>
<div class="panel-body">
<table class="table group_edit" id="table-clusters">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Binding to system user' %}" id="system_users_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for system_user in system_users_remain %}
<option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-primary btn-sm" id="btn-binding-system-users">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for system_user in object.system_users.all %}
<tr>
<td><b class="bdg-system-users" data-gid={{ system_user.id }}>{{ system_user }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-unbound-system-user" data-gid={{ system_user.id }} type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
function updateCMDFilterSystemUsers(system_users) {
var the_url = "{% url 'api-assets:cmd-filter-detail' pk=object.id %}";
var body = {
system_users: Object.assign([], system_users)
};
var success = function(data) {
location.reload();
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PATCH',
success: success
});
}
$(document).ready(function () {
$(".select2").select2();
}).on('click', '#btn-binding-system-users', function () {
var origin_system_users = $.map($(".bdg-system-users"), function (s) {
return $(s).data('gid')
});
var new_selected_system_users_id = $.map($("#system_users_selected").select2('data'), function (s) {
return s.id;
});
var system_users = origin_system_users.concat(new_selected_system_users_id);
updateCMDFilterSystemUsers(system_users)
}).on('click', '.btn-unbound-system-user', function () {
var unbound_system_user = $(this).data('gid');
var origin_system_users = $.map($(".bdg-system-users"), function (s) {
return $(s).data('gid')
});
var system_users = $.grep(origin_system_users, function (n, i) {
return n !== unbound_system_user
});
updateCMDFilterSystemUsers(system_users)
})
</script>
{% endblock %}

View File

@ -0,0 +1,89 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}{% endblock %}
{% block help_message %}
<div class="alert alert-info help-message">
{% trans 'System user bound some command filter, each command filter has some rules,'%}
{% trans 'When user login asset with this system user, then run a command,' %}
{% trans 'The command will be filter by rules, higher priority(lower number) rule run first,' %}
{% trans 'When a rule matched, if rule action is allow, then allow command execute,' %}
{% trans 'else if action is deny, then command with be deny,' %}
{% trans 'else match next rule, if none matched, allowed' %}
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:cmd-filter-create' %}" class="btn btn-sm btn-primary"> {% trans "Create command filter" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="cmd_filter_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 'Rules' %}</th>
<th class="text-center">{% trans 'System users' %}</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>
function initTable() {
var options = {
ele: $('#cmd_filter_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url 'assets:cmd-filter-detail' pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 2, createdCell: function (td, cellData, rowData) {
var filters_list_btn = '<a href="{% url "assets:cmd-filter-rule-list" pk=DEFAULT_PK %}">' + cellData.length + '</a>';
filters_list_btn = filters_list_btn.replace("{{ DEFAULT_PK }}", rowData.id);
$(td).html(filters_list_btn);
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var system_users_list_btn = '<a href="{% url "assets:cmd-filter-detail" pk=DEFAULT_PK %}">' + cellData.length + '</a>';
system_users_list_btn = system_users_list_btn.replace("{{ DEFAULT_PK }}", rowData.id);
$(td).html(system_users_list_btn);
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:cmd-filter-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:cmd-filter-list" %}',
columns: [
{data: "id"}, {data: "name" }, {data: "rules" },
{data: "system_users" }, {data: "comment"}, {data: "id"}
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#cmd_filter_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:cmd-filter-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
});
</script>
{% endblock %}

View File

@ -0,0 +1,74 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% 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="ibox-title">
<h5>{{ action }}</h5>
<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>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% bootstrap_form form layout="horizontal" %}
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var content_origin_placeholder = '';
var content_origin_help_text = '';
var content_ref = '';
var content_help_ref = '';
$(document).ready(function(){
content_ref = $('#id_content');
content_help_ref = content_ref.next();
content_origin_placeholder = content_ref.attr('placeholder');
content_origin_help_text = content_help_ref.html();
}).on('change', '#id_type', function () {
if ($('#id_type :selected').val() === 'regex') {
content_ref.attr('placeholder', 'rm.*|reboot|shutdown');
content_help_ref.html("");
} else {
content_ref.attr('placeholder', content_origin_placeholder);
content_help_ref.html(content_origin_help_text);
}
})
</script>
{% endblock %}

View File

@ -0,0 +1,115 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
{% endblock %}
{% 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>
<a href="{% url 'assets:cmd-filter-detail' pk=object.id %}" class="text-center">
<i class="fa fa-laptop"></i> {% trans 'Detail' %}
</a>
</li>
<li class="active">
<a href="{% url 'assets:cmd-filter-rule-list' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Rules' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left: 0;">
<div class="" id="content_start">
</div>
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left"><b>{% trans 'Command filter rule list' %}</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">
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:cmd-filter-rule-create' filter_pk=object.id %}" class="btn btn-sm btn-primary"> {% trans "Create rule" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="cmd_filter_rule_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'Content' %}</th>
<th class="text-center">{% trans 'Priority' %}</th>
<th class="text-center">{% trans 'Strategy' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#cmd_filter_rule_list_table'),
columnDefs: [
{targets: 6, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:cmd-filter-rule-update" filter_pk=object.id pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:cmd-filter-rule-list" filter_pk=object.id %}',
columns: [
{data: "id"}, {data: "type.display" }, {data: 'content'}, {data: 'priority'},
{data: 'action.display'}, {data: "comment" }, {data: "id"}
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#cmd_filter_rule_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:cmd-filter-rule-detail" filter_pk=object.id pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
})
</script>
{% endblock %}

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load common_tags %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
@ -113,7 +114,7 @@
</tr> </tr>
</form> </form>
{% for node in system_user.nodes.all %} {% for node in system_user.nodes.all|sort %}
<tr> <tr>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td> <td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
<td> <td>

View File

@ -156,44 +156,47 @@
</table> </table>
</div> </div>
</div> </div>
{# <div class="panel panel-info">#}
{# <div class="panel-heading">#}
{# <i class="fa fa-info-circle"></i> {% trans 'Nodes' %}#}
{# </div>#}
{# <div class="panel-body">#}
{# <table class="table node_edit" id="add-asset2group">#}
{# <tbody>#}
{# <form>#}
{# <tr>#}
{# <td colspan="2" class="no-borders">#}
{# <select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">#}
{# {% for node in nodes_remain %}#}
{# <option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>#}
{# {% endfor %}#}
{# </select>#}
{# </td>#}
{# </tr>#}
{# <tr>#}
{# <td colspan="2" class="no-borders">#}
{# <button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>#}
{# </td>#}
{# </tr>#}
{# </form>#}
{##}
{# {% for node in system_user.nodes.all %}#}
{# <tr>#}
{# <td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>#}
{# <td>#}
{# <button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>#}
{# </td>#}
{# </tr>#}
{# {% endfor %}#}
{# </tbody>#}
{# </table>#}
{# </div>#}
{# </div>#}
</div> </div>
</div> </div>
{% if system_user.protocol != 'rdp' %}
<div class="col-sm-4" style="padding-left: 0; padding-right: 0">
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Command filter' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Binding command filters' %}" id="command_filters_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for cf in cmd_filters_remain %}
<option value="{{ cf.id }}" id="opt_{{ cf.id }}" >{{ cf }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn-binding-command-filters">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for cf in object.cmd_filters.all %}
<tr>
<td><b class="bdg-command-filters" data-gid={{ cf.id }}>{{ cf }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-unbound-command-filter" data-gid={{ cf.id }} type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -201,27 +204,13 @@
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
function updateSystemUserNode(nodes) { function updateCommandFilters(command_filters) {
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}"; var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
var body = { var body = {
nodes: Object.assign([], nodes) cmd_filters: Object.assign([], command_filters)
}; };
var success = function(data) { var success = function(data) {
// remove all the selected groups from select > option and rendered ul element; location.reload();
$('.select2-selection__rendered').empty();
$('#node_selected').val('');
$.map(jumpserver.nodes_selected, function(node_name, index) {
$('#opt_' + index).remove();
// change tr html of user groups.
$('.node_edit tbody').append(
'<tr>' +
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
}; };
APIUpdateAttr({ APIUpdateAttr({
url: the_url, url: the_url,
@ -229,21 +218,12 @@ function updateSystemUserNode(nodes) {
success: success success: success
}); });
} }
jumpserver.nodes_selected = {};
$(document).ready(function () { $(document).ready(function () {
if($('#id_protocol_type').text() === 'rdp'){ if($('#id_protocol_type').text() === 'rdp'){
$('.only-ssh').addClass('hidden') $('.only-ssh').addClass('hidden')
} }
$(".panel-body .table tr:visible:first").addClass('no-borders-tr'); $(".panel-body .table tr:visible:first").addClass('no-borders-tr');
$('.select2').select2() $('.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];
});
}) })
.on('click', '#btn-auto-push', function () { .on('click', '#btn-auto-push', function () {
var checked = $(this).prop('checked'); var checked = $(this).prop('checked');
@ -255,33 +235,6 @@ $(document).ready(function () {
url: the_url, url: the_url,
body: JSON.stringify(body) body: JSON.stringify(body)
}); });
})
.on('click', '#btn-add-to-node', function() {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var nodes = $('.bdg_node').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
});
updateSystemUserNode(nodes);
})
.on('click', '.btn-remove-from-node', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_node');
var gid = $badge.data('gid');
var node_name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
);
$tr.remove();
var nodes = $('.bdg_node').map(function () {
return $(this).data('gid');
}).get();
updateSystemUserNode(nodes);
}).on('click', '.btn-del', function () { }).on('click', '.btn-del', function () {
var $this = $(this); var $this = $(this);
var name = "{{ system_user.name}}"; var name = "{{ system_user.name}}";
@ -317,25 +270,24 @@ $(document).ready(function () {
success: success, success: success,
flash_message: false flash_message: false
}); });
}).on('click', '.btn-clear-auth', function () { }).on('click', '#btn-binding-command-filters', function () {
var the_url = '{% url "api-assets:system-user-auth-info" pk=system_user.id %}'; var new_selected_cmd_filters = $.map($('#command_filters_selected').select2('data'), function (i) {
var name = '{{ system_user.name }}'; return i.id;
swal({
title: "{% trans 'Are you sure to remove authentication information for the system user ?' %}",
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#ed5565",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: true
}, function () {
APIUpdateAttr({
url: the_url,
method: 'DELETE',
success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}"
});
}); });
var origin_cmd_filters = $.map($(".bdg-command-filters"), function (s) {
return $(s).data('gid')
});
var command_filters = new_selected_cmd_filters.concat(origin_cmd_filters);
updateCommandFilters(command_filters)
}).on('click', '.btn-unbound-command-filter', function () {
var unbound_command_filter = $(this).data('gid');
var origin_command_filters = $.map($(".bdg-command-filters"), function (s) {
return $(s).data('gid')
});
var command_filters = $.grep(origin_command_filters, function (n, i) {
return n !== unbound_command_filter
});
updateCommandFilters(command_filters)
}) })
</script> </script>
{% endblock %} {% endblock %}

View File

@ -1,8 +1,11 @@
# coding:utf-8 # coding:utf-8
from django.urls import path from django.urls import path
from .. import api from rest_framework_nested import routers
# from rest_framework.routers import DefaultRouter
from rest_framework_bulk.routes import BulkRouter from rest_framework_bulk.routes import BulkRouter
from .. import api
app_name = 'assets' app_name = 'assets'
router = BulkRouter() router = BulkRouter()
@ -13,6 +16,11 @@ router.register(r'labels', api.LabelViewSet, 'label')
router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'nodes', api.NodeViewSet, 'node')
router.register(r'domain', api.DomainViewSet, 'domain') router.register(r'domain', api.DomainViewSet, 'domain')
router.register(r'gateway', api.GatewayViewSet, 'gateway') router.register(r'gateway', api.GatewayViewSet, 'gateway')
router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter')
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
urlpatterns = [ urlpatterns = [
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
@ -42,6 +50,9 @@ urlpatterns = [
path('system-user/<uuid:pk>/connective/', path('system-user/<uuid:pk>/connective/',
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'), api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
path('system-user/<uuid:pk>/cmd-filter-rules/',
api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
path('nodes/<uuid:pk>/children/', path('nodes/<uuid:pk>/children/',
api.NodeChildrenApi.as_view(), name='node-children'), api.NodeChildrenApi.as_view(), name='node-children'),
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'), path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
@ -64,5 +75,5 @@ urlpatterns = [
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
] ]
urlpatterns += router.urls urlpatterns += router.urls + cmd_filter_router.urls

View File

@ -49,4 +49,12 @@ urlpatterns = [
path('domain/<uuid:pk>/gateway/create/', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'), path('domain/<uuid:pk>/gateway/create/', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
path('domain/gateway/<uuid:pk>/update/', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'), path('domain/gateway/<uuid:pk>/update/', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
path('cmd-filter/', views.CommandFilterListView.as_view(), name='cmd-filter-list'),
path('cmd-filter/create/', views.CommandFilterCreateView.as_view(), name='cmd-filter-create'),
path('cmd-filter/<uuid:pk>/update/', views.CommandFilterUpdateView.as_view(), name='cmd-filter-update'),
path('cmd-filter/<uuid:pk>/', views.CommandFilterDetailView.as_view(), name='cmd-filter-detail'),
path('cmd-filter/<uuid:pk>/rule/', views.CommandFilterRuleListView.as_view(), name='cmd-filter-rule-list'),
path('cmd-filter/<uuid:filter_pk>/rule/create/', views.CommandFilterRuleCreateView.as_view(), name='cmd-filter-rule-create'),
path('cmd-filter/<uuid:filter_pk>/rule/<uuid:pk>/update/', views.CommandFilterRuleUpdateView.as_view(), name='cmd-filter-rule-update'),
] ]

View File

@ -4,3 +4,4 @@ from .system_user import *
from .admin_user import * from .admin_user import *
from .label import * from .label import *
from .domain import * from .domain import *
from .cmd_filter import *

View File

@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
#
from django.views.generic import TemplateView, CreateView, \
UpdateView, DeleteView, DetailView
from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404, reverse
from common.permissions import AdminUserRequiredMixin
from common.const import create_success_msg, update_success_msg
from ..models import CommandFilter, CommandFilterRule, SystemUser
from ..forms import CommandFilterForm, CommandFilterRuleForm
__all__ = (
"CommandFilterListView", "CommandFilterCreateView",
"CommandFilterUpdateView",
"CommandFilterRuleListView", "CommandFilterRuleCreateView",
"CommandFilterRuleUpdateView", "CommandFilterDetailView",
)
class CommandFilterListView(AdminUserRequiredMixin, TemplateView):
template_name = 'assets/cmd_filter_list.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Command filter list'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class CommandFilterCreateView(AdminUserRequiredMixin, CreateView):
model = CommandFilter
template_name = 'assets/cmd_filter_create_update.html'
form_class = CommandFilterForm
success_url = reverse_lazy('assets:cmd-filter-list')
success_message = create_success_msg
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Create command filter'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class CommandFilterUpdateView(AdminUserRequiredMixin, UpdateView):
model = CommandFilter
template_name = 'assets/cmd_filter_create_update.html'
form_class = CommandFilterForm
success_url = reverse_lazy('assets:cmd-filter-list')
success_message = update_success_msg
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Update command filter'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class CommandFilterDetailView(AdminUserRequiredMixin, DetailView):
model = CommandFilter
template_name = 'assets/cmd_filter_detail.html'
def get_context_data(self, **kwargs):
system_users_remain = SystemUser.objects\
.exclude(cmd_filters=self.object)\
.exclude(protocol='rdp')
context = {
'app': _('Assets'),
'action': _('Command filter detail'),
'system_users_remain': system_users_remain
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class CommandFilterRuleListView(AdminUserRequiredMixin, SingleObjectMixin, TemplateView):
template_name = 'assets/cmd_filter_rule_list.html'
model = CommandFilter
object = None
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=self.model.objects.all())
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Command filter rule list'),
'object': self.get_object()
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class CommandFilterRuleCreateView(AdminUserRequiredMixin, CreateView):
template_name = 'assets/cmd_filter_rule_create_update.html'
model = CommandFilterRule
form_class = CommandFilterRuleForm
success_message = create_success_msg
cmd_filter = None
def get_success_url(self):
return reverse('assets:cmd-filter-rule-list', kwargs={
'pk': self.cmd_filter.id
})
def get_form(self, form_class=None):
form = super().get_form(form_class=form_class)
form['filter'].initial = self.cmd_filter
form['filter'].field.widget.attrs['readonly'] = 1
return form
def dispatch(self, request, *args, **kwargs):
filter_pk = self.kwargs.get('filter_pk')
self.cmd_filter = get_object_or_404(CommandFilter, pk=filter_pk)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Create command filter rule'),
'object': self.cmd_filter,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class CommandFilterRuleUpdateView(AdminUserRequiredMixin, UpdateView):
template_name = 'assets/cmd_filter_rule_create_update.html'
model = CommandFilterRule
form_class = CommandFilterRuleForm
success_message = create_success_msg
cmd_filter = None
def get_success_url(self):
return reverse('assets:cmd-filter-rule-list', kwargs={
'pk': self.cmd_filter.id
})
def get_form(self, form_class=None):
form = super().get_form(form_class=form_class)
form['filter'].initial = self.cmd_filter
form['filter'].field.widget.attrs['readonly'] = 1
return form
def dispatch(self, request, *args, **kwargs):
filter_pk = self.kwargs.get('filter_pk')
self.cmd_filter = get_object_or_404(CommandFilter, pk=filter_pk)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Update command filter rule'),
'object': self.cmd_filter,
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -9,7 +9,7 @@ from django.views.generic.detail import DetailView
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from ..forms import SystemUserForm from ..forms import SystemUserForm
from ..models import SystemUser, Node from ..models import SystemUser, Node, CommandFilter
from common.permissions import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
@ -73,7 +73,7 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('System user detail'), 'action': _('System user detail'),
'nodes_remain': Node.objects.exclude(systemuser=self.object) 'cmd_filters_remain': CommandFilter.objects.exclude(system_users=self.object)
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
@ -91,10 +91,11 @@ class SystemUserAssetView(AdminUserRequiredMixin, DetailView):
context_object_name = 'system_user' context_object_name = 'system_user'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
nodes_remain = sorted(Node.objects.exclude(systemuser=self.object), reverse=True)
context = { context = {
'app': _('assets'), 'app': _('assets'),
'action': _('System user asset'), 'action': _('System user asset'),
'nodes_remain': Node.objects.exclude(systemuser=self.object) 'nodes_remain': nodes_remain
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -86,3 +86,21 @@ class FormEncryptCharField(FormEncryptMixin, forms.CharField):
class FormEncryptDictField(FormEncryptMixin, FormDictField): class FormEncryptDictField(FormEncryptMixin, FormDictField):
pass pass
class ChoiceDisplayField(serializers.ChoiceField):
def __init__(self, *args, **kwargs):
super(ChoiceDisplayField, self).__init__(*args, **kwargs)
self.choice_strings_to_display = {
six.text_type(key): value for key, value in self.choices.items()
}
def to_representation(self, value):
if value is None:
return value
return {
'value': self.choice_strings_to_values.get(six.text_type(value),
value),
'display': self.choice_strings_to_display.get(six.text_type(value),
value),
}

View File

@ -100,3 +100,9 @@ def is_bool_field(field):
@register.filter @register.filter
def to_dict(data): def to_dict(data):
return dict(data) return dict(data)
@register.filter
def sort(data):
print(data)
return sorted(data)

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,8 @@ class SwitchOrgView(DetailView):
pk = kwargs.get('pk') pk = kwargs.get('pk')
self.object = Organization.get_instance(pk) self.object = Organization.get_instance(pk)
request.session['oid'] = self.object.id.__str__() request.session['oid'] = self.object.id.__str__()
return redirect('index') referer = request.META.get('HTTP_REFERER', reverse('index'))
return redirect(referer)
class SwitchToAOrgView(View): class SwitchToAOrgView(View):

View File

@ -1528,9 +1528,15 @@ a.ui-button:active .ui-icon,
.std42-dialog .ui-dialog-titlebar .elfinder-titlebar-minimize:hover .ui-icon { .std42-dialog .ui-dialog-titlebar .elfinder-titlebar-minimize:hover .ui-icon {
background-color: #ff9800; background-color: #ff9800;
} }
.elfinder-dialog-title { .elfinder-dialog-title {
color: #f1f1f1; color: #f1f1f1;
} }
.ui-dialog-buttonpane {
background: #fff;
}
.std42-dialog .ui-dialog-content { .std42-dialog .ui-dialog-content {
background: #fff; background: #fff;
} }

View File

@ -375,7 +375,7 @@
'pass' : '密码', // added 18.04.2012 'pass' : '密码', // added 18.04.2012
'confirmUnmount' : '确实要卸载 $1?', // from v2.1 added 30.04.2012 'confirmUnmount' : '确实要卸载 $1?', // from v2.1 added 30.04.2012
'dropFilesBrowser': '从浏览器中拖放或粘贴文件', // from v2.1 added 30.05.2012 'dropFilesBrowser': '从浏览器中拖放或粘贴文件', // from v2.1 added 30.05.2012
'dropPasteFiles' : '拖放文件,粘贴网址或剪贴板图像', // from v2.1 added 07.04.2014 'dropPasteFiles' : '拖放文件,粘贴剪贴板图像', // from v2.1 added 07.04.2014
'encoding' : '编码', // from v2.1 added 19.12.2014 'encoding' : '编码', // from v2.1 added 19.12.2014
'locale' : '语言环境', // from v2.1 added 19.12.2014 'locale' : '语言环境', // from v2.1 added 19.12.2014
'searchTarget' : '目标: $1', // from v2.1 added 22.5.2015 'searchTarget' : '目标: $1', // from v2.1 added 22.5.2015

View File

@ -1,7 +1,7 @@
{% load i18n %} {% load i18n %}
<div class="footer fixed"> <div class="footer fixed">
<div class="pull-right"> <div class="pull-right">
Version <strong>1.4.1-{% include '_build.html' %}</strong> GPLv2. Version <strong>1.4.3-{% include '_build.html' %}</strong> GPLv2.
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">--> <!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div> </div>
<div> <div>

View File

@ -24,6 +24,7 @@
<li id="admin-user"><a href="{% url 'assets:admin-user-list' %}">{% trans 'Admin user' %}</a></li> <li id="admin-user"><a href="{% url 'assets:admin-user-list' %}">{% trans 'Admin user' %}</a></li>
<li id="system-user"><a href="{% url 'assets:system-user-list' %}">{% trans 'System user' %}</a></li> <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="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>
</ul> </ul>
</li> </li>
<li id="perms"> <li id="perms">
@ -47,6 +48,11 @@
<span class="nav-label">{% trans 'Web terminal' %}</span> <span class="nav-label">{% trans 'Web terminal' %}</span>
</a> </a>
</li> </li>
<li>
<a href="{% url 'terminal:web-sftp' %}" target="_blank">
<span class="nav-label">{% trans 'File manager' %}</span>
</a>
</li>
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
<li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li> <li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li>
{% endif %} {% endif %}
@ -91,10 +97,8 @@
{% for plugin in XPACK_PLUGINS %} {% for plugin in XPACK_PLUGINS %}
<li id="{{ plugin.name }}"><a href="{{ plugin.endpoint }}">{% trans plugin.verbose_name %}</a></li> <li id="{{ plugin.name }}"><a href="{{ plugin.endpoint }}">{% trans plugin.verbose_name %}</a></li>
{% endfor %} {% endfor %}
<li id="id_cloud_account"><a href="{% url 'xpack:cloud:account-list' %}">{% trans 'Account list' %}</a></li> <li id="id_cloud_account"><a href="{% url 'xpack:cloud:account-list' %}">{% trans 'Account list' %}</a></li>
<li id="id_cloud_sync_task"><a href="{% url 'xpack:cloud:sync-task-list' %}">{% trans 'Sync task list' %}</a></li> <li id="id_cloud_sync_task"><a href="{% url 'xpack:cloud:sync-task-list' %}">{% trans 'Sync task list' %}</a></li>
</ul> </ul>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.core.cache import cache
from django.utils import timezone from django.utils import timezone
from rest_framework import serializers from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer from rest_framework_bulk.serializers import BulkListSerializer
@ -28,19 +29,8 @@ class TerminalSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_is_alive(obj): def get_is_alive(obj):
try: key = StatusSerializer.CACHE_KEY_PREFIX + str(obj.id)
status = obj.status_set.latest() return cache.get(key)
except Status.DoesNotExist:
status = None
if not status:
return False
delta = timezone.now() - status.date_created
if delta < timezone.timedelta(seconds=600):
return True
else:
return False
class SessionSerializer(BulkSerializerMixin, serializers.ModelSerializer): class SessionSerializer(BulkSerializerMixin, serializers.ModelSerializer):
@ -57,11 +47,18 @@ class SessionSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class StatusSerializer(serializers.ModelSerializer): class StatusSerializer(serializers.ModelSerializer):
CACHE_KEY_PREFIX = 'terminal_status_'
class Meta: class Meta:
fields = '__all__' fields = '__all__'
model = Status model = Status
def create(self, validated_data):
terminal_id = str(validated_data['terminal'].id)
key = self.CACHE_KEY_PREFIX + terminal_id
cache.set(key, 1, 60)
return validated_data
class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer): class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer):

View File

@ -16,6 +16,7 @@ urlpatterns = [
path('terminal/<uuid:pk>/update/', views.TerminalUpdateView.as_view(), name='terminal-update'), path('terminal/<uuid:pk>/update/', views.TerminalUpdateView.as_view(), name='terminal-update'),
path('<uuid:pk>/accept/', views.TerminalAcceptView.as_view(), name='terminal-accept'), path('<uuid:pk>/accept/', views.TerminalAcceptView.as_view(), name='terminal-accept'),
path('web-terminal/', views.WebTerminalView.as_view(), name='web-terminal'), path('web-terminal/', views.WebTerminalView.as_view(), name='web-terminal'),
path('web-sftp/', views.WebSFTPView.as_view(), name='web-sftp'),
# Session view # Session view
path('session-online/', views.SessionOnlineListView.as_view(), name='session-online-list'), path('session-online/', views.SessionOnlineListView.as_view(), name='session-online-list'),

View File

@ -16,7 +16,7 @@ from common.permissions import SuperUserRequiredMixin
__all__ = [ __all__ = [
"TerminalListView", "TerminalUpdateView", "TerminalDetailView", "TerminalListView", "TerminalUpdateView", "TerminalDetailView",
"TerminalDeleteView", "TerminalConnectView", "TerminalAcceptView", "TerminalDeleteView", "TerminalConnectView", "TerminalAcceptView",
"WebTerminalView", "WebTerminalView", 'WebSFTPView',
] ]
@ -93,6 +93,9 @@ class TerminalAcceptView(SuperUserRequiredMixin, JSONResponseMixin, UpdateView):
class TerminalConnectView(LoginRequiredMixin, SuperUserRequiredMixin, DetailView): class TerminalConnectView(LoginRequiredMixin, SuperUserRequiredMixin, DetailView):
"""
Abandon
"""
template_name = 'flash_message_standalone.html' template_name = 'flash_message_standalone.html'
model = Terminal model = Terminal
@ -121,3 +124,8 @@ class TerminalConnectView(LoginRequiredMixin, SuperUserRequiredMixin, DetailView
class WebTerminalView(LoginRequiredMixin, View): class WebTerminalView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return redirect('/luna/?' + request.GET.urlencode()) return redirect('/luna/?' + request.GET.urlencode())
class WebSFTPView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
return redirect('/coco/elfinder/sftp/?' + request.GET.urlencode())

View File

@ -71,3 +71,4 @@ urllib3==1.22
vine==1.1.4 vine==1.1.4
drf-yasg==1.9.1 drf-yasg==1.9.1
Werkzeug==0.14.1 Werkzeug==0.14.1
drf-nested-routers==0.90.2