Commit d5cb0a2e authored by ibuler's avatar ibuler

[Update] Merge with dev

parents 792fc3b0 f802fb64
...@@ -7,6 +7,7 @@ from rest_framework import filters ...@@ -7,6 +7,7 @@ from rest_framework import filters
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.http import Http404 from django.http import Http404
from django.conf import settings
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
from common.utils import get_object_or_none, get_logger from common.utils import get_object_or_none, get_logger
...@@ -110,12 +111,22 @@ class AssetUserViewSet(CommonApiMixin, BulkModelViewSet): ...@@ -110,12 +111,22 @@ class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
class AssetUserExportViewSet(AssetUserViewSet): class AssetUserExportViewSet(AssetUserViewSet):
serializer_class = serializers.AssetUserExportSerializer serializer_class = serializers.AssetUserExportSerializer
http_method_names = ['get'] http_method_names = ['get']
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self):
if settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions()
class AssetUserAuthInfoApi(generics.RetrieveAPIView): class AssetUserAuthInfoApi(generics.RetrieveAPIView):
serializer_class = serializers.AssetUserAuthInfoSerializer serializer_class = serializers.AssetUserAuthInfoSerializer
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self):
if settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions()
def get_object(self): def get_object(self):
query_params = self.request.query_params query_params = self.request.query_params
......
...@@ -41,8 +41,8 @@ class AssetUserManager: ...@@ -41,8 +41,8 @@ class AssetUserManager:
instances_map = {} instances_map = {}
instances = [] instances = []
for name, backend in self.backends: for name, backend in self.backends:
if name != "db" and self._prefer != name: # if name != "db":
continue # continue
_instances = backend.filter( _instances = backend.filter(
username=username, assets=assets, latest=latest, username=username, assets=assets, latest=latest,
prefer=self._prefer, prefer_id=prefer_id, prefer=self._prefer, prefer_id=prefer_id,
......
...@@ -53,7 +53,7 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend): ...@@ -53,7 +53,7 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend):
return queryset return queryset
if node is None: if node is None:
return queryset.none() return queryset
query_all = self.is_query_all(request) query_all = self.is_query_all(request)
if query_all: if query_all:
pattern = node.get_all_children_pattern(with_self=True) pattern = node.get_all_children_pattern(with_self=True)
......
...@@ -120,6 +120,10 @@ class SystemUser(AssetUser): ...@@ -120,6 +120,10 @@ class SystemUser(AssetUser):
def __str__(self): def __str__(self):
return '{0.name}({0.username})'.format(self) return '{0.name}({0.username})'.format(self)
@property
def nodes_amount(self):
return self.nodes.all().count()
@property @property
def login_mode_display(self): def login_mode_display(self):
return self.get_login_mode_display() return self.get_login_mode_display()
......
...@@ -26,13 +26,14 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ...@@ -26,13 +26,14 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
fields = [ fields = [
'id', 'name', 'username', 'password', 'public_key', 'private_key', 'id', 'name', 'username', 'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display', 'priority', 'protocol', 'login_mode', 'login_mode_display', 'priority', 'protocol',
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes', 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment',
'assets_amount', 'auto_generate_key' 'assets_amount', 'nodes_amount', 'auto_generate_key'
] ]
extra_kwargs = { extra_kwargs = {
'password': {"write_only": True}, 'password': {"write_only": True},
'public_key': {"write_only": True}, 'public_key': {"write_only": True},
'private_key': {"write_only": True}, 'private_key': {"write_only": True},
'nodes_amount': {'label': _('Node')},
'assets_amount': {'label': _('Asset')}, 'assets_amount': {'label': _('Asset')},
'login_mode_display': {'label': _('Login mode display')}, 'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True}, 'created_by': {'read_only': True},
......
...@@ -40,6 +40,7 @@ var prefer = null; ...@@ -40,6 +40,7 @@ var prefer = null;
var lastMFATime = "{{ request.session.MFA_VERIFY_TIME }}"; var lastMFATime = "{{ request.session.MFA_VERIFY_TIME }}";
var testDatetime = "{% trans 'Test datetime: ' %}"; var testDatetime = "{% trans 'Test datetime: ' %}";
var mfaVerifyTTL = "{{ SECURITY_MFA_VERIFY_TTL }}"; var mfaVerifyTTL = "{{ SECURITY_MFA_VERIFY_TTL }}";
var mfaNeedCheck = "{{ SECURITY_VIEW_AUTH_NEED_MFA }}" === "True";
function initAssetUserTable() { function initAssetUserTable() {
var options = { var options = {
...@@ -112,6 +113,10 @@ $(document).ready(function(){ ...@@ -112,6 +113,10 @@ $(document).ready(function(){
authAssetId = $(this).data("asset") ; authAssetId = $(this).data("asset") ;
authHostname = $(this).data("hostname"); authHostname = $(this).data("hostname");
authUsername = $(this).data('user'); authUsername = $(this).data('user');
if (!mfaNeedCheck){
$("#asset_user_auth_view").modal('show');
return
}
var now = new Date(); var now = new Date();
var nowTime = now.getTime() / 1000; var nowTime = now.getTime() / 1000;
if ( !lastMFATime || nowTime - lastMFATime > mfaVerifyTTL ) { if ( !lastMFATime || nowTime - lastMFATime > mfaVerifyTTL ) {
......
...@@ -70,43 +70,6 @@ function initTable() { ...@@ -70,43 +70,6 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id); return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id);
}}, }},
{#{targets: 4, createdCell: function (td, cellData) {#}
{# var innerHtml = "";#}
{# var data = cellData.reachable;#}
{# if (data !== 0) {#}
{# innerHtml = "<span class='text-navy'>" + data + "</span>";#}
{# } else {#}
{# innerHtml = "<span>" + data + "</span>";#}
{# }#}
{# $(td).html(innerHtml)#}
{#}},#}
{#{targets: 5, createdCell: function (td, cellData) {#}
{# var data = cellData.unreachable;#}
{# var innerHtml = "";#}
{# if (data !== 0) {#}
{# innerHtml = "<span class='text-danger'>" + data + "</span>";#}
{# } else {#}
{# innerHtml = "<span>" + data + "</span>";#}
{# }#}
{# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');#}
{#}},#}
{#{targets: 6, createdCell: function (td, cellData, rowData) {#}
{# var val = 0;#}
{# var innerHtml = "";#}
{# var total = rowData.assets_amount;#}
{# var reachable = cellData.reachable;#}
{# if (total !== 0) {#}
{# val = reachable/total * 100;#}
{# }#}
{##}
{# if (val === 100) {#}
{# innerHtml = "<span class='text-navy'>" + val + "% </span>";#}
{# } else {#}
{# var num = new Number(val);#}
{# innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";#}
{# }#}
{# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');#}
{#}},#}
{targets: 5, createdCell: function (td, cellData, rowData) { {targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
...@@ -116,7 +79,7 @@ function initTable() { ...@@ -116,7 +79,7 @@ function initTable() {
columns: [ columns: [
{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount", orderable: false}, {data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount", orderable: false},
{#{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},#} {#{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},#}
{data: "comment"}, {data: "id", orderable: false} {data: "comment"}, {data: "id", orderable: false, width: "100px"}
] ]
}; };
admin_user_table = jumpserver.initServerSideDataTable(options); admin_user_table = jumpserver.initServerSideDataTable(options);
......
...@@ -177,7 +177,7 @@ function initTable() { ...@@ -177,7 +177,7 @@ function initTable() {
data: "connectivity", data: "connectivity",
orderable: false, orderable: false,
width: '60px' width: '60px'
}, {data: "id", orderable: false} }, {data: "id", orderable: false, width: "100px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
...@@ -292,7 +292,8 @@ $(document).ready(function(){ ...@@ -292,7 +292,8 @@ $(document).ready(function(){
format: 'csv', format: 'csv',
params: { params: {
search: search, search: search,
node_id: current_node_id || '' node_id: current_node_id || '',
show_current_asset: getCookie('show_current_asset')
} }
}; };
APIExportData(props); APIExportData(props);
......
...@@ -144,7 +144,9 @@ function updateCMDFilterSystemUsers(system_users) { ...@@ -144,7 +144,9 @@ function updateCMDFilterSystemUsers(system_users) {
}); });
} }
$(document).ready(function () { $(document).ready(function () {
$(".select2").select2(); $(".select2").select2({
closeOnSelect: false
});
}).on('click', '#btn-binding-system-users', function () { }).on('click', '#btn-binding-system-users', function () {
var origin_system_users = $.map($(".bdg-system-users"), function (s) { var origin_system_users = $.map($(".bdg-system-users"), function (s) {
return $(s).data('gid') return $(s).data('gid')
......
...@@ -63,7 +63,8 @@ function initTable() { ...@@ -63,7 +63,8 @@ function initTable() {
ajax_url: '{% url "api-assets:cmd-filter-list" %}', ajax_url: '{% url "api-assets:cmd-filter-list" %}',
columns: [ columns: [
{data: "id"}, {data: "name" }, {data: "rules", orderable: false}, {data: "id"}, {data: "name" }, {data: "rules", orderable: false},
{data: "system_users", orderable: false}, {data: "comment"}, {data: "id", orderable: false} {data: "system_users", orderable: false}, {data: "comment"},
{data: "id", orderable: false, width: "100px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -59,7 +59,7 @@ function initTable() { ...@@ -59,7 +59,7 @@ function initTable() {
ajax_url: '{% url "api-assets:domain-list" %}', ajax_url: '{% url "api-assets:domain-list" %}',
columns: [ columns: [
{data: "id"}, {data: "name" }, {data: "asset_count", orderable: false }, {data: "id"}, {data: "name" }, {data: "asset_count", orderable: false },
{data: "gateway_count", orderable: false }, {data: "comment" }, {data: "id", orderable: false} {data: "gateway_count", orderable: false }, {data: "comment" }, {data: "id", orderable: false, width: "100px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -44,7 +44,8 @@ function initTable() { ...@@ -44,7 +44,8 @@ function initTable() {
ajax_url: '{% url "api-assets:label-list" %}?sort=name', ajax_url: '{% url "api-assets:label-list" %}?sort=name',
columns: [ columns: [
{data: "id"}, {data: "name" }, {data: "value" }, {data: "id"}, {data: "name" }, {data: "value" },
{data: "asset_count", orderable: false}, {data: "id", orderable: false} {data: "asset_count", orderable: false},
{data: "id", orderable: false, width: "100px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -3,10 +3,6 @@ ...@@ -3,10 +3,6 @@
{% block help_message %} {% block help_message %}
<div class="alert alert-info help-message"> <div class="alert alert-info help-message">
{# 系统用户是 Jumpserver跳转登录资产时使用的用户,可以理解为登录资产用户,如 web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`);#}
{# 简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资产。#}
{# 系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。#}
{# 目前还不支持Windows的自动推送#}
{% trans 'System user is Jumpserver jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%} {% trans 'System user is Jumpserver jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%}
{% trans 'In simple terms, users log into Jumpserver using their own username, and Jumpserver uses system users to log into assets. '%} {% trans 'In simple terms, users log into Jumpserver using their own username, and Jumpserver uses system users to log into assets. '%}
{% trans 'When system users are created, if you choose auto push Jumpserver to use Ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.' %} {% trans 'When system users are created, if you choose auto push Jumpserver to use Ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.' %}
...@@ -91,7 +87,7 @@ function initTable() { ...@@ -91,7 +87,7 @@ function initTable() {
columns: [ columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"},
{data: "login_mode"}, {data: "assets_amount", orderable: false }, {data: "login_mode"}, {data: "assets_amount", orderable: false },
{data: "comment" }, {data: "id", orderable: false } {data: "comment" }, {data: "id", orderable: false, width: "100px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -104,6 +104,7 @@ $(document).ready(function () { ...@@ -104,6 +104,7 @@ $(document).ready(function () {
body: JSON.stringify({asset: assetId}), body: JSON.stringify({asset: assetId}),
flash_message: false, flash_message: false,
success: function (data) { success: function (data) {
favoriteAssets.push(assetId);
var btn = disfavorBtnTmpl.replace("ID", assetId); var btn = disfavorBtnTmpl.replace("ID", assetId);
$this.replaceWith(btn) $this.replaceWith(btn)
} }
...@@ -117,6 +118,10 @@ $(document).ready(function () { ...@@ -117,6 +118,10 @@ $(document).ready(function () {
method: "DELETE", method: "DELETE",
flash_message: false, flash_message: false,
success: function (data) { success: function (data) {
var index = favoriteAssets.indexOf(assetId);
if (index !== '-1'){
favoriteAssets.splice(index, 1);
}
var btn = favorBtnTmpl.replace("ID", assetId); var btn = favorBtnTmpl.replace("ID", assetId);
$this.replaceWith(btn) $this.replaceWith(btn)
} }
......
...@@ -88,11 +88,13 @@ class UserLoginLog(models.Model): ...@@ -88,11 +88,13 @@ class UserLoginLog(models.Model):
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login')) datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
@classmethod @classmethod
def get_login_logs(cls, date_form=None, date_to=None, user=None, keyword=None): def get_login_logs(cls, date_from=None, date_to=None, user=None, keyword=None):
login_logs = cls.objects.all() login_logs = cls.objects.all()
if date_form and date_to: if date_from and date_to:
date_from = "{} {}".format(date_from, '00:00:00')
date_to = "{} {}".format(date_to, '23:59:59')
login_logs = login_logs.filter( login_logs = login_logs.filter(
datetime__gt=date_form, datetime__lt=date_to datetime__gte=date_from, datetime__lte=date_to
) )
if user: if user:
login_logs = login_logs.filter(username=user) login_logs = login_logs.filter(username=user)
......
...@@ -121,7 +121,7 @@ ...@@ -121,7 +121,7 @@
}); });
}) })
.on('click', '.btn_export', function () { .on('click', '.btn_export', function () {
var date_form = $('#id_date_from').val(); var date_from = $('#id_date_from').val();
var date_to = $('#id_date_to').val(); var date_to = $('#id_date_to').val();
var user = $('.select2 option:selected').val(); var user = $('.select2 option:selected').val();
var keyword = $('#search').val(); var keyword = $('#search').val();
...@@ -129,7 +129,7 @@ ...@@ -129,7 +129,7 @@
url: "{% url "audits:login-log-export" %}", url: "{% url "audits:login-log-export" %}",
method: 'POST', method: 'POST',
data: JSON.stringify({ data: JSON.stringify({
'date_form':date_form, 'date_from':date_from,
'date_to':date_to, 'date_to':date_to,
'user':user, 'user':user,
'keyword':keyword 'keyword':keyword
......
...@@ -267,19 +267,22 @@ class LoginLogExportView(PermissionsMixin, View): ...@@ -267,19 +267,22 @@ class LoginLogExportView(PermissionsMixin, View):
header = [field.verbose_name for field in fields] header = [field.verbose_name for field in fields]
login_logs = cache.get(request.GET.get('spm', ''), []) login_logs = cache.get(request.GET.get('spm', ''), [])
response = write_content_to_excel(excel_response, login_logs=login_logs, response = write_content_to_excel(
header=header, fields=fields) excel_response, login_logs=login_logs, header=header, fields=fields
)
return response return response
def post(self, request): def post(self, request):
try: try:
date_form = json.loads(request.body).get('date_form', []) date_from = json.loads(request.body).get('date_from', [])
date_to = json.loads(request.body).get('date_to', []) date_to = json.loads(request.body).get('date_to', [])
user = json.loads(request.body).get('user', []) user = json.loads(request.body).get('user', [])
keyword = json.loads(request.body).get('keyword', []) keyword = json.loads(request.body).get('keyword', [])
login_logs = UserLoginLog.get_login_logs( login_logs = UserLoginLog.get_login_logs(
date_form=date_form, date_to=date_to, user=user, keyword=keyword) date_from=date_from, date_to=date_to, user=user,
keyword=keyword,
)
except ValueError: except ValueError:
return HttpResponse('Json object not valid', status=400) return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().hex spm = uuid.uuid4().hex
......
...@@ -63,7 +63,6 @@ class IDSpmFilter(filters.BaseFilterBackend): ...@@ -63,7 +63,6 @@ class IDSpmFilter(filters.BaseFilterBackend):
cache_key = const.KEY_CACHE_RESOURCES_ID.format(spm) cache_key = const.KEY_CACHE_RESOURCES_ID.format(spm)
resources_id = cache.get(cache_key) resources_id = cache.get(cache_key)
if not resources_id or not isinstance(resources_id, list): if not resources_id or not isinstance(resources_id, list):
queryset = queryset.none()
return queryset return queryset
queryset = queryset.filter(id__in=resources_id) queryset = queryset.filter(id__in=resources_id)
return queryset return queryset
......
...@@ -51,7 +51,7 @@ class TreeNode: ...@@ -51,7 +51,7 @@ class TreeNode:
result = True result = True
elif self.pId != other.pId: elif self.pId != other.pId:
result = self.pId > other.pId result = self.pId > other.pId
elif self.id.startswith('-') and not other.id.startswith('-'): elif str(self.id).startswith('-') and not str(other.id).startswith('-'):
result = False result = False
else: else:
result = self.name > other.name result = self.name > other.name
......
...@@ -182,3 +182,7 @@ def encrypt_password(password, salt=None): ...@@ -182,3 +182,7 @@ def encrypt_password(password, salt=None):
def get_signer(): def get_signer():
signer = Signer(settings.SECRET_KEY) signer = Signer(settings.SECRET_KEY)
return signer return signer
def ensure_last_char_is_ascii(data):
remain = ''
...@@ -361,6 +361,7 @@ defaults = { ...@@ -361,6 +361,7 @@ defaults = {
'TERMINAL_COMMAND_STORAGE': {}, 'TERMINAL_COMMAND_STORAGE': {},
'SECURITY_MFA_AUTH': False, 'SECURITY_MFA_AUTH': False,
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True, 'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
'SECURITY_VIEW_AUTH_NEED_MFA': True,
'SECURITY_LOGIN_LIMIT_COUNT': 7, 'SECURITY_LOGIN_LIMIT_COUNT': 7,
'SECURITY_LOGIN_LIMIT_TIME': 30, 'SECURITY_LOGIN_LIMIT_TIME': 30,
'SECURITY_MAX_IDLE_TIME': 30, 'SECURITY_MAX_IDLE_TIME': 30,
......
...@@ -19,6 +19,7 @@ def jumpserver_processor(request): ...@@ -19,6 +19,7 @@ def jumpserver_processor(request):
'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION, 'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION,
'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL, 'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL,
'FORCE_SCRIPT_NAME': settings.FORCE_SCRIPT_NAME, 'FORCE_SCRIPT_NAME': settings.FORCE_SCRIPT_NAME,
'SECURITY_VIEW_AUTH_NEED_MFA': settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA,
} }
return context return context
......
This diff is collapsed.
...@@ -4,16 +4,39 @@ ...@@ -4,16 +4,39 @@
<title>{% trans 'Task log' %}</title> <title>{% trans 'Task log' %}</title>
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script> <script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script> <script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
<script src="{% static 'js/plugins/xterm/addons/fit/fit.js' %}"></script>
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" /> <link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" />
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
<style> <style>
body { body {
background-color: black; background-color: black;
} }
.xterm-rows { .xterm-rows {
{#padding: 15px;#}
font-family: "Bitstream Vera Sans Mono", Monaco, "Consolas", Courier, monospace; font-family: "Bitstream Vera Sans Mono", Monaco, "Consolas", Courier, monospace;
font-size: 13px; font-size: 13px;
} }
.terminal .xterm-viewport {
background-color: #1f1b1b;
overflow: auto;
}
body ::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.3);
background-color: #272323;
border-radius: 6px;
}
body ::-webkit-scrollbar {
width: 8px;
height: 8px;
}
body ::-webkit-scrollbar-thumb {
background-color: #494141;
border-radius: 6px;
}
</style> </style>
</head> </head>
<div id="term" style="height: 100%;width: 100%"> <div id="term" style="height: 100%;width: 100%">
...@@ -35,12 +58,13 @@ ...@@ -35,12 +58,13 @@
cursorBlink: false, cursorBlink: false,
screenKeys: false, screenKeys: false,
fontFamily: '"Monaco", "Consolas", "monospace"', fontFamily: '"Monaco", "Consolas", "monospace"',
fontSize: 12, fontSize: 13,
lineHeight: 1.2,
rightClickSelectsWord: true, rightClickSelectsWord: true,
disableStdin: true disableStdin: true
}); });
term.open(document.getElementById('term')); term.open(document.getElementById('term'));
term.resize(120, 30); window.fit.fit(term);
ws = new WebSocket(wsURL); ws = new WebSocket(wsURL);
ws.onmessage = function(e) { ws.onmessage = function(e) {
...@@ -61,5 +85,7 @@ ...@@ -61,5 +85,7 @@
term.write("Connect websocket server error") term.write("Connect websocket server error")
} }
} }
}).on('resize', window, function () {
window.fit.fit(term);
}); });
</script> </script>
...@@ -34,6 +34,27 @@ ...@@ -34,6 +34,27 @@
.select2-container .select2-selection--single { .select2-container .select2-selection--single {
height: 34px; height: 34px;
} }
.terminal .xterm-viewport {
background-color: #1f1b1b;
overflow: auto;
}
body ::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.3);
background-color: #272323;
border-radius: 6px;
}
body ::-webkit-scrollbar {
width: 8px;
height: 8px;
}
body ::-webkit-scrollbar-thumb {
background-color: #494141;
border-radius: 6px;
}
</style> </style>
{% endblock %} {% endblock %}
...@@ -80,9 +101,7 @@ ...@@ -80,9 +101,7 @@
<select class="select2 form-control" <select class="select2 form-control"
id="system-users-select"> id="system-users-select">
{% for s in system_users %} {% for s in system_users %}
{% if s.protocol == 'ssh' and s.login_mode == 'auto' %} <option value="{{ s.id }}" {% if s.protocol != 'ssh' or s.login_mode != 'auto' %}disabled{% endif %}>{{ s }}</option>
<option value="{{ s.id }}">{{ s }}</option>
{% endif %}
{% endfor %} {% endfor %}
</select> </select>
<button type="button" <button type="button"
...@@ -104,41 +123,6 @@ ...@@ -104,41 +123,6 @@
var url = null; var url = null;
var treeUrl = "{% url 'api-perms:my-nodes-with-assets-as-tree' %}?cache_policy=1"; var treeUrl = "{% url 'api-perms:my-nodes-with-assets-as-tree' %}?cache_policy=1";
function proposeGeometry(term) {
if (!term.element.parentElement) {
return null;
}
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
var elementStyle = window.getComputedStyle(term.element);
var elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
right: parseInt(elementStyle.getPropertyValue('padding-right')),
left: parseInt(elementStyle.getPropertyValue('padding-left'))
};
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
var elementPaddingHor = elementPadding.right + elementPadding.left;
var availableHeight = parentElementHeight - elementPaddingVer;
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
var geometry = {
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
};
return geometry;
}
function fit(term) {
var geometry = proposeGeometry(term);
if (geometry) {
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
term._core.renderer.clear();
term.resize(geometry.cols, geometry.rows);
}
}
}
function initTree() { function initTree() {
$('#assetTree').html("{% trans 'Loading' %}" + '..'); $('#assetTree').html("{% trans 'Loading' %}" + '..');
if (systemUserId) { if (systemUserId) {
...@@ -159,12 +143,6 @@ ...@@ -159,12 +143,6 @@
enable: true enable: true
} }
}, },
async: {
enable: true,
url: url,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
edit: { edit: {
enable: true, enable: true,
showRemoveBtn: false, showRemoveBtn: false,
...@@ -244,17 +222,18 @@ ...@@ -244,17 +222,18 @@
cursorBlink: false, cursorBlink: false,
screenKeys: false, screenKeys: false,
fontFamily: 'monaco, Consolas, "Lucida Console", monospace', fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
fontSize: 14, fontSize: 13,
lineHeight: 1,
rightClickSelectsWord: true, rightClickSelectsWord: true,
disableStdin: true, disableStdin: true,
lineHeight: 1.2,
theme: { theme: {
background: '#1f1b1b' background: '#1f1b1b'
} }
}); });
term.open(document.getElementById('term')); term.open(document.getElementById('term'));
var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n"; var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
fit(term); window.fit.fit(term);
{#fit(term);#}
term.write(msg); term.write(msg);
var scheme = document.location.protocol === "https:" ? "wss" : "ws"; var scheme = document.location.protocol === "https:" ? "wss" : "ws";
......
...@@ -70,7 +70,7 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView): ...@@ -70,7 +70,7 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView):
user = self.request.user user = self.request.user
with tmp_to_root_org(): with tmp_to_root_org():
util = AssetPermissionUtilV2(user) util = AssetPermissionUtilV2(user)
system_users = [s for s in util.get_system_users() if s.protocol == 'ssh'] system_users = util.get_system_users()
return system_users return system_users
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
......
...@@ -3,11 +3,13 @@ import os ...@@ -3,11 +3,13 @@ import os
import threading import threading
import json import json
from celery.result import AsyncResult from common.utils import get_logger
from .celery.utils import get_celery_task_log_path from .celery.utils import get_celery_task_log_path
from channels.generic.websocket import JsonWebsocketConsumer from channels.generic.websocket import JsonWebsocketConsumer
logger = get_logger(__name__)
class CeleryLogWebsocket(JsonWebsocketConsumer): class CeleryLogWebsocket(JsonWebsocketConsumer):
disconnected = False disconnected = False
...@@ -21,34 +23,48 @@ class CeleryLogWebsocket(JsonWebsocketConsumer): ...@@ -21,34 +23,48 @@ class CeleryLogWebsocket(JsonWebsocketConsumer):
if task_id: if task_id:
self.handle_task(task_id) self.handle_task(task_id)
def handle_task(self, task_id): def wait_util_log_path_exist(self, task_id):
log_path = get_celery_task_log_path(task_id) log_path = get_celery_task_log_path(task_id)
while not self.disconnected:
if not os.path.exists(log_path):
self.send_json({'message': '.', 'task': task_id})
time.sleep(0.5)
continue
self.send_json({'message': '\r\n'})
try:
logger.debug('Task log path: {}'.format(log_path))
task_log_f = open(log_path, 'rb')
return task_log_f
except OSError:
return None
def read_log_file(self, task_id):
task_log_f = self.wait_util_log_path_exist(task_id)
if not task_log_f:
logger.debug('Task log file is None: {}'.format(task_id))
return
def func(): task_end_mark = []
task_log_f = None while not self.disconnected:
data = task_log_f.read(4096)
while not self.disconnected: if data:
if not os.path.exists(log_path): data = data.replace(b'\n', b'\r\n')
self.send_json({'message': '.', 'task': task_id}) self.send_json(
time.sleep(0.5) {'message': data.decode(errors='ignore'), 'task': task_id}
continue )
self.send_json({'message': '\r\n'}) if data.find(b'succeeded in') != -1:
try: task_end_mark.append(1)
task_log_f = open(log_path) if data.find(bytes(task_id, 'utf8')) != -1:
break task_end_mark.append(1)
except OSError: elif len(task_end_mark) == 2:
return logger.debug('Task log end: {}'.format(task_id))
break
while not self.disconnected: time.sleep(0.2)
data = task_log_f.readline() task_log_f.close()
if data:
data = data.replace('\n', '\r\n') def handle_task(self, task_id):
self.send_json({'message': data, 'task': task_id}) logger.info("Task id: {}".format(task_id))
if data.startswith('Task') and data.find('succeeded'): thread = threading.Thread(target=self.read_log_file, args=(task_id,))
break
time.sleep(0.2)
task_log_f.close()
thread = threading.Thread(target=func)
thread.start() thread.start()
def disconnect(self, close_code): def disconnect(self, close_code):
......
...@@ -73,14 +73,17 @@ class AssetPermissionViewSet(OrgModelViewSet): ...@@ -73,14 +73,17 @@ class AssetPermissionViewSet(OrgModelViewSet):
node_id = self.request.query_params.get('node_id') node_id = self.request.query_params.get('node_id')
node_name = self.request.query_params.get('node') node_name = self.request.query_params.get('node')
if node_id: if node_id:
node = get_object_or_none(Node, pk=node_id) _nodes = Node.objects.filter(pk=node_id)
elif node_name: elif node_name:
node = get_object_or_none(Node, name=node_name) _nodes = Node.objects.filter(value=node_name)
else: else:
return queryset return queryset
if not node: if not _nodes:
return queryset.none() return queryset.none()
nodes = node.get_ancestors(with_self=True)
nodes = set()
for node in _nodes:
nodes |= set(node.get_ancestors(with_self=True))
queryset = queryset.filter(nodes__in=nodes) queryset = queryset.filter(nodes__in=nodes)
return queryset return queryset
......
...@@ -29,7 +29,7 @@ class BasePermissionQuerySet(models.QuerySet): ...@@ -29,7 +29,7 @@ class BasePermissionQuerySet(models.QuerySet):
return self.filter(is_active=False) return self.filter(is_active=False)
def invalid(self): def invalid(self):
now = timezone.now now = timezone.now()
q = ( q = (
Q(is_active=False) | Q(is_active=False) |
Q(date_start__gt=now) | Q(date_start__gt=now) |
......
...@@ -190,7 +190,7 @@ function initTable() { ...@@ -190,7 +190,7 @@ function initTable() {
{data: "id"}, {data: "name"}, {data: "users", orderable: false}, {data: "id"}, {data: "name"}, {data: "users", orderable: false},
{data: "user_groups", orderable: false}, {data: "assets", orderable: false}, {data: "user_groups", orderable: false}, {data: "assets", orderable: false},
{data: "nodes", orderable: false}, {data: "system_users", orderable: false}, {data: "nodes", orderable: false}, {data: "system_users", orderable: false},
{data: "is_valid", orderable: false}, {data: "id", orderable: false} {data: "is_valid", orderable: false}, {data: "id", orderable: false, width: "100px"}
], ],
select: {}, select: {},
op_html: $('#actions').html() op_html: $('#actions').html()
......
...@@ -62,10 +62,10 @@ asset_permission_urlpatterns = [ ...@@ -62,10 +62,10 @@ asset_permission_urlpatterns = [
path('user-groups/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'), path('user-groups/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'),
# 用户和资产授权变更 # 用户和资产授权变更
path('asset-permissions/<uuid:pk>/user/remove/', api.AssetPermissionRemoveUserApi.as_view(), name='asset-permission-remove-user'), path('asset-permissions/<uuid:pk>/users/remove/', api.AssetPermissionRemoveUserApi.as_view(), name='asset-permission-remove-user'),
path('asset-permissions/<uuid:pk>/user/add/', api.AssetPermissionAddUserApi.as_view(), name='asset-permission-add-user'), path('asset-permissions/<uuid:pk>/users/add/', api.AssetPermissionAddUserApi.as_view(), name='asset-permission-add-user'),
path('asset-permissions/<uuid:pk>/asset/remove/', api.AssetPermissionRemoveAssetApi.as_view(), name='asset-permission-remove-asset'), path('asset-permissions/<uuid:pk>/assets/remove/', api.AssetPermissionRemoveAssetApi.as_view(), name='asset-permission-remove-asset'),
path('asset-permissions/<uuid:pk>/asset/add/', api.AssetPermissionAddAssetApi.as_view(), name='asset-permission-add-asset'), path('asset-permissions/<uuid:pk>/assets/add/', api.AssetPermissionAddAssetApi.as_view(), name='asset-permission-add-asset'),
# 授权规则中授权的资产 # 授权规则中授权的资产
path('asset-permissions/<uuid:pk>/assets/', api.AssetPermissionAssetsApi.as_view(), name='asset-permission-assets'), path('asset-permissions/<uuid:pk>/assets/', api.AssetPermissionAssetsApi.as_view(), name='asset-permission-assets'),
...@@ -75,7 +75,7 @@ asset_permission_urlpatterns = [ ...@@ -75,7 +75,7 @@ asset_permission_urlpatterns = [
path('asset-permissions/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'), path('asset-permissions/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'),
# 刷新缓存 # 刷新缓存
path('asset-permissions/user/cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'), path('asset-permissions/cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'),
] ]
...@@ -99,10 +99,10 @@ remote_app_permission_urlpatterns = [ ...@@ -99,10 +99,10 @@ remote_app_permission_urlpatterns = [
path('remote-app-permissions/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'), path('remote-app-permissions/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'),
# 用户和RemoteApp变更 # 用户和RemoteApp变更
path('remote-app-permissions/<uuid:pk>/user/add/', api.RemoteAppPermissionAddUserApi.as_view(), name='remote-app-permission-add-user'), path('remote-app-permissions/<uuid:pk>/users/add/', api.RemoteAppPermissionAddUserApi.as_view(), name='remote-app-permission-add-user'),
path('remote-app-permissions/<uuid:pk>/user/remove/', api.RemoteAppPermissionRemoveUserApi.as_view(), name='remote-app-permission-remove-user'), path('remote-app-permissions/<uuid:pk>/users/remove/', api.RemoteAppPermissionRemoveUserApi.as_view(), name='remote-app-permission-remove-user'),
path('remote-app-permissions/<uuid:pk>/remote-app/remove/', api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), name='remote-app-permission-remove-remote-app'), path('remote-app-permissions/<uuid:pk>/remote-apps/remove/', api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), name='remote-app-permission-remove-remote-app'),
path('remote-app-permissions/<uuid:pk>/remote-app/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'), path('remote-app-permissions/<uuid:pk>/remote-apps/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'),
] ]
old_version_urlpatterns = [ old_version_urlpatterns = [
......
...@@ -234,6 +234,9 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): ...@@ -234,6 +234,9 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
if user_tree.contains(key): if user_tree.contains(key):
nodes_single_assets.pop(key) nodes_single_assets.pop(key)
if not nodes_single_assets:
return
# 如果要设置到ungroup中 # 如果要设置到ungroup中
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
node_key = Node.ungrouped_key node_key = Node.ungrouped_key
...@@ -336,8 +339,8 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): ...@@ -336,8 +339,8 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self.add_direct_nodes_to_user_tree(user_tree) self.add_direct_nodes_to_user_tree(user_tree)
self.add_single_assets_node_to_user_tree(user_tree) self.add_single_assets_node_to_user_tree(user_tree)
self.parse_user_tree_to_full_tree(user_tree) self.parse_user_tree_to_full_tree(user_tree)
self.add_empty_node_if_need(user_tree)
self.add_favorite_node_if_need(user_tree) self.add_favorite_node_if_need(user_tree)
self.add_empty_node_if_need(user_tree)
self.set_user_tree_to_cache_if_need(user_tree) self.set_user_tree_to_cache_if_need(user_tree)
self.set_user_tree_to_local(user_tree) self.set_user_tree_to_local(user_tree)
return user_tree return user_tree
...@@ -487,6 +490,7 @@ class ParserNode: ...@@ -487,6 +490,7 @@ class ParserNode:
'ip': asset.ip, 'ip': asset.ip,
'protocols': asset.protocols_as_list, 'protocols': asset.protocols_as_list,
'platform': asset.platform, 'platform': asset.platform,
"org_name": asset.org_name,
}, },
} }
} }
......
...@@ -5,6 +5,7 @@ import os ...@@ -5,6 +5,7 @@ import os
import json import json
import jms_storage import jms_storage
from smtplib import SMTPSenderRefused
from rest_framework import generics from rest_framework import generics
from rest_framework.views import Response, APIView from rest_framework.views import Response, APIView
from django.conf import settings from django.conf import settings
...@@ -41,9 +42,20 @@ class MailTestingAPI(APIView): ...@@ -41,9 +42,20 @@ class MailTestingAPI(APIView):
email_from = email_from or email_host_user email_from = email_from or email_host_user
email_recipient = email_recipient or email_from email_recipient = email_recipient or email_from
send_mail(subject, message, email_from, [email_recipient]) send_mail(subject, message, email_from, [email_recipient])
except SMTPSenderRefused as e:
resp = e.smtp_error
if isinstance(resp, bytes):
for coding in ('gbk', 'utf8'):
try:
resp = resp.decode(coding)
except UnicodeDecodeError:
continue
else:
break
return Response({"error": str(resp)}, status=401)
except Exception as e: except Exception as e:
print(e)
return Response({"error": str(e)}, status=401) return Response({"error": str(e)}, status=401)
return Response({"msg": self.success_message.format(email_recipient)}) return Response({"msg": self.success_message.format(email_recipient)})
else: else:
return Response({"error": str(serializer.errors)}, status=401) return Response({"error": str(serializer.errors)}, status=401)
......
apps/static/img/facio.ico

1.7 KB | W: | H:

apps/static/img/facio.ico

3.21 KB | W: | H:

apps/static/img/facio.ico
apps/static/img/facio.ico
apps/static/img/facio.ico
apps/static/img/facio.ico
  • 2-up
  • Swipe
  • Onion skin
...@@ -206,7 +206,7 @@ function formSubmit(props) { ...@@ -206,7 +206,7 @@ function formSubmit(props) {
var errors = jqXHR.responseJSON; var errors = jqXHR.responseJSON;
var noneFieldErrorRef = props.form.children('.alert-danger'); var noneFieldErrorRef = props.form.children('.alert-danger');
if (noneFieldErrorRef.length !== 1) { if (noneFieldErrorRef.length !== 1) {
props.form.prepend('<div class="alert alert-danger" style="display: none"></div>'); props.form.prepend('<div class="alert alert-danger has-error" style="display: none"></div>');
noneFieldErrorRef = props.form.children('.alert-danger'); noneFieldErrorRef = props.form.children('.alert-danger');
} }
var noneFieldErrorMsg = ""; var noneFieldErrorMsg = "";
...@@ -252,6 +252,7 @@ function formSubmit(props) { ...@@ -252,6 +252,7 @@ function formSubmit(props) {
noneFieldErrorRef.css('display', 'block'); noneFieldErrorRef.css('display', 'block');
noneFieldErrorRef.html(noneFieldErrorMsg); noneFieldErrorRef.html(noneFieldErrorMsg);
} }
$('.has-error').get(0).scrollIntoView();
} }
}) })
...@@ -458,6 +459,7 @@ jumpserver.initDataTable = function (options) { ...@@ -458,6 +459,7 @@ jumpserver.initDataTable = function (options) {
{ {
targets: 0, targets: 0,
orderable: false, orderable: false,
width: "20px",
createdCell: function (td, cellData) { createdCell: function (td, cellData) {
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData)); $(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
} }
...@@ -555,6 +557,7 @@ jumpserver.initServerSideDataTable = function (options) { ...@@ -555,6 +557,7 @@ jumpserver.initServerSideDataTable = function (options) {
{ {
targets: 0, targets: 0,
orderable: false, orderable: false,
width: "20px",
createdCell: function (td, cellData) { createdCell: function (td, cellData) {
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData)); $(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
} }
......
...@@ -84,9 +84,6 @@ ...@@ -84,9 +84,6 @@
<tr> <tr>
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Add user' %}" id="slct_users" class="select2" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Add user' %}" id="slct_users" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for user in users %}
<option value="{{ user.id }}" id="opt_{{ user.id }}">{{ user.name }}</option>
{% endfor %}
</select> </select>
</td> </td>
</tr> </tr>
...@@ -157,7 +154,8 @@ $(document).ready(function () { ...@@ -157,7 +154,8 @@ $(document).ready(function () {
}).on('select2:unselect', function(evt) { }).on('select2:unselect', function(evt) {
var data = evt.params.data; var data = evt.params.data;
delete jumpserver.users_selected[data.id] delete jumpserver.users_selected[data.id]
}) });
usersSelect2Init('#slct_users')
}).on('click', '.btn_remove_user', function() { }).on('click', '.btn_remove_user', function() {
var $this = $(this); var $this = $(this);
var $tr = $this.closest('tr'); var $tr = $this.closest('tr');
......
...@@ -82,7 +82,7 @@ function initTable() { ...@@ -82,7 +82,7 @@ function initTable() {
], ],
ajax_url: '{% url "api-users:user-group-list" %}?display=1', ajax_url: '{% url "api-users:user-group-list" %}?display=1',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "users", orderable: false}, columns: [{data: function(){return ""}}, {data: "name" }, {data: "users", orderable: false},
{data: "comment"}, {data: "id", orderable: false }], {data: "comment"}, {data: "id", orderable: false, width:"100px"}],
order: [], order: [],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -125,7 +125,7 @@ function initTable() { ...@@ -125,7 +125,7 @@ function initTable() {
{data: "groups_display", orderable: false}, {data: "groups_display", orderable: false},
{data: "source"}, {data: "source"},
{data: "is_valid", orderable: false}, {data: "is_valid", orderable: false},
{data: "id", orderable: false} {data: "id", orderable: false, width: "100px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -22,21 +22,20 @@ logger = logging.getLogger('jumpserver') ...@@ -22,21 +22,20 @@ logger = logging.getLogger('jumpserver')
def construct_user_created_email_body(user): def construct_user_created_email_body(user):
default_body = _(""" default_body = _("""
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <div>
<p style="text-indent:2em;"> <p>Your account has been created successfully</p>
<span> <div>
Username: %(username)s. Username: %(username)s
</span> <br/>
<span> Password: <a href="%(rest_password_url)s?token=%(rest_password_token)s">
<a href="%(rest_password_url)s?token=%(rest_password_token)s">click here to set your password</a> click here to set your password</a>
</span> (This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>)
<span> </div>
This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a> <div>
</span> <p>---</p>
<span>
<a href="%(login_url)s">Login direct</a> <a href="%(login_url)s">Login direct</a>
</span> </div>
</p> </div>
""") % { """) % {
'username': user.username, 'username': user.username,
'rest_password_url': reverse('users:reset-password', external=True), 'rest_password_url': reverse('users:reset-password', external=True),
......
...@@ -65,6 +65,7 @@ logging.basicConfig( ...@@ -65,6 +65,7 @@ logging.basicConfig(
EXIT_EVENT = threading.Event() EXIT_EVENT = threading.Event()
LOCK = threading.Lock() LOCK = threading.Lock()
files_preserve = [] files_preserve = []
STOP_TIMEOUT = 10
logger = logging.getLogger() logger = logging.getLogger()
...@@ -120,7 +121,7 @@ def check_pid(pid): ...@@ -120,7 +121,7 @@ def check_pid(pid):
def get_pid_file_path(s): def get_pid_file_path(s):
return os.path.join('/tmp', '{}.pid'.format(s)) return os.path.join(TMP_DIR, '{}.pid'.format(s))
def get_log_file_path(s): def get_log_file_path(s):
...@@ -433,9 +434,19 @@ def stop_service(srv, sig=15): ...@@ -433,9 +434,19 @@ def stop_service(srv, sig=15):
if not is_running(s): if not is_running(s):
show_service_status(s) show_service_status(s)
continue continue
logging.info("Stop service: {}".format(s)) print("Stop service: {}".format(s), end='')
pid = get_pid(s) pid = get_pid(s)
os.kill(pid, sig) os.kill(pid, sig)
for i in range(STOP_TIMEOUT):
if i == STOP_TIMEOUT - 1:
print("\033[31m Error\033[0m")
if not is_running(s):
print("\033[32m Ok\033[0m")
break
else:
time.sleep(1)
continue
with LOCK: with LOCK:
processes.pop(s, None) processes.pop(s, None)
...@@ -472,9 +483,9 @@ def show_service_status(s): ...@@ -472,9 +483,9 @@ def show_service_status(s):
for ns in services_set: for ns in services_set:
if is_running(ns): if is_running(ns):
pid = get_pid(ns) pid = get_pid(ns)
logging.info("{} is running: {}".format(ns, pid)) print("{} is running: {}".format(ns, pid))
else: else:
logging.info("{} is stopped".format(ns)) print("{} is stopped".format(ns))
if __name__ == '__main__': if __name__ == '__main__':
...@@ -499,6 +510,7 @@ if __name__ == '__main__': ...@@ -499,6 +510,7 @@ if __name__ == '__main__':
) )
parser.add_argument('-d', '--daemon', nargs="?", const=1) parser.add_argument('-d', '--daemon', nargs="?", const=1)
parser.add_argument('-w', '--worker', type=int, nargs="?", const=4) parser.add_argument('-w', '--worker', type=int, nargs="?", const=4)
parser.add_argument('-f', '--force', nargs="?", const=1)
args = parser.parse_args() args = parser.parse_args()
if args.daemon: if args.daemon:
DAEMON = True DAEMON = True
...@@ -513,7 +525,10 @@ if __name__ == '__main__': ...@@ -513,7 +525,10 @@ if __name__ == '__main__':
start_services_and_watch(srv) start_services_and_watch(srv)
os._exit(0) os._exit(0)
elif action == "stop": elif action == "stop":
stop_service(srv) if args.force:
stop_service_force(srv)
else:
stop_service(srv)
elif action == "restart": elif action == "restart":
DAEMON = True DAEMON = True
stop_service(srv) stop_service(srv)
......
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