Unverified Commit 84b3e29d authored by BaiJiangJie's avatar BaiJiangJie Committed by GitHub

Merge pull request #3366 from jumpserver/bugfix

Bugfix
parents 334e3bef a33d5cc5
......@@ -7,6 +7,7 @@ from rest_framework import filters
from rest_framework_bulk import BulkModelViewSet
from django.shortcuts import get_object_or_404
from django.http import Http404
from django.conf import settings
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
from common.utils import get_object_or_none, get_logger
......@@ -110,12 +111,22 @@ class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
class AssetUserExportViewSet(AssetUserViewSet):
serializer_class = serializers.AssetUserExportSerializer
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):
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):
query_params = self.request.query_params
......
......@@ -41,8 +41,8 @@ class AssetUserManager:
instances_map = {}
instances = []
for name, backend in self.backends:
if name != "db" and self._prefer != name:
continue
# if name != "db":
# continue
_instances = backend.filter(
username=username, assets=assets, latest=latest,
prefer=self._prefer, prefer_id=prefer_id,
......
......@@ -40,6 +40,7 @@ var prefer = null;
var lastMFATime = "{{ request.session.MFA_VERIFY_TIME }}";
var testDatetime = "{% trans 'Test datetime: ' %}";
var mfaVerifyTTL = "{{ SECURITY_MFA_VERIFY_TTL }}";
var mfaNeedCheck = "{{ SECURITY_VIEW_AUTH_NEED_MFA }}" === "True";
function initAssetUserTable() {
var options = {
......@@ -112,6 +113,10 @@ $(document).ready(function(){
authAssetId = $(this).data("asset") ;
authHostname = $(this).data("hostname");
authUsername = $(this).data('user');
if (!mfaNeedCheck){
$("#asset_user_auth_view").modal('show');
return
}
var now = new Date();
var nowTime = now.getTime() / 1000;
if ( !lastMFATime || nowTime - lastMFATime > mfaVerifyTTL ) {
......
......@@ -70,43 +70,6 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
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) {
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);
......@@ -116,7 +79,7 @@ function initTable() {
columns: [
{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount", orderable: false},
{#{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);
......
......@@ -177,7 +177,7 @@ function initTable() {
data: "connectivity",
orderable: false,
width: '60px'
}, {data: "id", orderable: false}
}, {data: "id", orderable: false, width: "100px"}
],
op_html: $('#actions').html()
};
......
......@@ -63,7 +63,8 @@ function initTable() {
ajax_url: '{% url "api-assets:cmd-filter-list" %}',
columns: [
{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()
};
......
......@@ -59,7 +59,7 @@ function initTable() {
ajax_url: '{% url "api-assets:domain-list" %}',
columns: [
{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()
};
......
......@@ -44,7 +44,8 @@ function initTable() {
ajax_url: '{% url "api-assets:label-list" %}?sort=name',
columns: [
{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()
};
......
......@@ -3,10 +3,6 @@
{% block 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 '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.' %}
......@@ -91,7 +87,7 @@ function initTable() {
columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"},
{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()
};
......
......@@ -182,3 +182,7 @@ def encrypt_password(password, salt=None):
def get_signer():
signer = Signer(settings.SECRET_KEY)
return signer
def ensure_last_char_is_ascii(data):
remain = ''
......@@ -361,6 +361,7 @@ defaults = {
'TERMINAL_COMMAND_STORAGE': {},
'SECURITY_MFA_AUTH': False,
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
'SECURITY_VIEW_AUTH_NEED_MFA': True,
'SECURITY_LOGIN_LIMIT_COUNT': 7,
'SECURITY_LOGIN_LIMIT_TIME': 30,
'SECURITY_MAX_IDLE_TIME': 30,
......
......@@ -18,6 +18,7 @@ def jumpserver_processor(request):
'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2019',
'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION,
'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL,
'SECURITY_VIEW_AUTH_NEED_MFA': settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA,
}
return context
......
This diff is collapsed.
......@@ -4,6 +4,7 @@
<title>{% trans 'Task log' %}</title>
<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/addons/fit/fit.js' %}"></script>
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" />
<style>
body {
......@@ -40,7 +41,7 @@
disableStdin: true
});
term.open(document.getElementById('term'));
term.resize(120, 30);
window.fit.fit(term);
ws = new WebSocket(wsURL);
ws.onmessage = function(e) {
......@@ -61,5 +62,7 @@
term.write("Connect websocket server error")
}
}
}).on('resize', window, function () {
window.fit.fit(term);
});
</script>
......@@ -80,9 +80,7 @@
<select class="select2 form-control"
id="system-users-select">
{% for s in system_users %}
{% if s.protocol == 'ssh' and s.login_mode == 'auto' %}
<option value="{{ s.id }}">{{ s }}</option>
{% endif %}
<option value="{{ s.id }}" {% if s.protocol != 'ssh' or s.login_mode != 'auto' %}disabled{% endif %}>{{ s }}</option>
{% endfor %}
</select>
<button type="button"
......@@ -104,41 +102,6 @@
var url = null;
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() {
$('#assetTree').html("{% trans 'Loading' %}" + '..');
if (systemUserId) {
......@@ -159,12 +122,6 @@
enable: true
}
},
async: {
enable: true,
url: url,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
edit: {
enable: true,
showRemoveBtn: false,
......@@ -248,13 +205,15 @@
lineHeight: 1,
rightClickSelectsWord: true,
disableStdin: true,
cursorBlink: false,
theme: {
background: '#1f1b1b'
}
});
term.open(document.getElementById('term'));
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);
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
......
......@@ -70,7 +70,7 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView):
user = self.request.user
with tmp_to_root_org():
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
def get_context_data(self, **kwargs):
......
......@@ -3,11 +3,13 @@ import os
import threading
import json
from celery.result import AsyncResult
from common.utils import get_logger
from .celery.utils import get_celery_task_log_path
from channels.generic.websocket import JsonWebsocketConsumer
logger = get_logger(__name__)
class CeleryLogWebsocket(JsonWebsocketConsumer):
disconnected = False
......@@ -22,6 +24,7 @@ class CeleryLogWebsocket(JsonWebsocketConsumer):
self.handle_task(task_id)
def handle_task(self, task_id):
logger.info("Task id: {}".format(task_id))
log_path = get_celery_task_log_path(task_id)
def func():
......@@ -34,19 +37,24 @@ class CeleryLogWebsocket(JsonWebsocketConsumer):
continue
self.send_json({'message': '\r\n'})
try:
task_log_f = open(log_path)
logger.debug('Task log path: {}'.format(log_path))
task_log_f = open(log_path, 'rb')
break
except OSError:
return
if not task_log_f:
return
while not self.disconnected:
data = task_log_f.readline()
if data:
data = data.replace('\n', '\r\n')
self.send_json({'message': data, 'task': task_id})
if data.startswith('Task') and data.find('succeeded'):
data = data.replace(b'\n', b'\r\n')
self.send_json({'message': data.decode(errors='ignore'), 'task': task_id})
if data.startswith(b'Task') and data.find(b'succeeded'):
break
time.sleep(0.2)
time.sleep(0.1)
task_log_f.close()
thread = threading.Thread(target=func)
thread.start()
......
......@@ -190,7 +190,7 @@ function initTable() {
{data: "id"}, {data: "name"}, {data: "users", orderable: false},
{data: "user_groups", orderable: false}, {data: "assets", 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: {},
op_html: $('#actions').html()
......
......@@ -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('asset-permissions/<uuid:pk>/user/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>/asset/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>/users/remove/', api.AssetPermissionRemoveUserApi.as_view(), name='asset-permission-remove-user'),
path('asset-permissions/<uuid:pk>/users/add/', api.AssetPermissionAddUserApi.as_view(), name='asset-permission-add-user'),
path('asset-permissions/<uuid:pk>/assets/remove/', api.AssetPermissionRemoveAssetApi.as_view(), name='asset-permission-remove-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'),
......@@ -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/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 = [
path('remote-app-permissions/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'),
# 用户和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>/user/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-app/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'),
path('remote-app-permissions/<uuid:pk>/users/add/', api.RemoteAppPermissionAddUserApi.as_view(), name='remote-app-permission-add-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-apps/remove/', api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), name='remote-app-permission-remove-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 = [
......
......@@ -234,6 +234,9 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
if user_tree.contains(key):
nodes_single_assets.pop(key)
if not nodes_single_assets:
return
# 如果要设置到ungroup中
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
node_key = Node.ungrouped_key
......@@ -336,8 +339,8 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self.add_direct_nodes_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.add_empty_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_local(user_tree)
return user_tree
......@@ -487,6 +490,7 @@ class ParserNode:
'ip': asset.ip,
'protocols': asset.protocols_as_list,
'platform': asset.platform,
"org_name": asset.org_name,
},
}
}
......
......@@ -5,6 +5,7 @@ import os
import json
import jms_storage
from smtplib import SMTPSenderRefused
from rest_framework import generics
from rest_framework.views import Response, APIView
from django.conf import settings
......@@ -41,9 +42,20 @@ class MailTestingAPI(APIView):
email_from = email_from or email_host_user
email_recipient = email_recipient or email_from
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:
print(e)
return Response({"error": str(e)}, status=401)
return Response({"msg": self.success_message.format(email_recipient)})
else:
return Response({"error": str(serializer.errors)}, status=401)
......
......@@ -201,7 +201,7 @@ function formSubmit(props) {
var errors = jqXHR.responseJSON;
var noneFieldErrorRef = props.form.children('.alert-danger');
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');
}
var noneFieldErrorMsg = "";
......@@ -247,6 +247,7 @@ function formSubmit(props) {
noneFieldErrorRef.css('display', 'block');
noneFieldErrorRef.html(noneFieldErrorMsg);
}
$('.has-error').get(0).scrollIntoView();
}
})
......@@ -453,6 +454,7 @@ jumpserver.initDataTable = function (options) {
{
targets: 0,
orderable: false,
width: "20px",
createdCell: function (td, cellData) {
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
}
......@@ -550,6 +552,7 @@ jumpserver.initServerSideDataTable = function (options) {
{
targets: 0,
orderable: false,
width: "20px",
createdCell: function (td, cellData) {
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
}
......
......@@ -82,7 +82,7 @@ function initTable() {
],
ajax_url: '{% url "api-users:user-group-list" %}?display=1',
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: [],
op_html: $('#actions').html()
};
......
......@@ -125,7 +125,7 @@ function initTable() {
{data: "groups_display", orderable: false},
{data: "source"},
{data: "is_valid", orderable: false},
{data: "id", orderable: false}
{data: "id", orderable: false, width: "100px"}
],
op_html: $('#actions').html()
};
......
......@@ -22,21 +22,20 @@ logger = logging.getLogger('jumpserver')
def construct_user_created_email_body(user):
default_body = _("""
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<p style="text-indent:2em;">
<span>
Username: %(username)s.
</span>
<span>
<a href="%(rest_password_url)s?token=%(rest_password_token)s">click here to set your password</a>
</span>
<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>
<span>
<div>
<p>Your account has been created successfully</p>
<div>
Username: %(username)s
<br/>
Password: <a href="%(rest_password_url)s?token=%(rest_password_token)s">
click here to set your password</a>
(This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>)
</div>
<div>
<p>---</p>
<a href="%(login_url)s">Login direct</a>
</span>
</p>
</div>
</div>
""") % {
'username': user.username,
'rest_password_url': reverse('users:reset-password', external=True),
......
......@@ -65,6 +65,7 @@ logging.basicConfig(
EXIT_EVENT = threading.Event()
LOCK = threading.Lock()
files_preserve = []
STOP_TIMEOUT = 10
logger = logging.getLogger()
......@@ -120,7 +121,7 @@ def check_pid(pid):
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):
......@@ -433,9 +434,19 @@ def stop_service(srv, sig=15):
if not is_running(s):
show_service_status(s)
continue
logging.info("Stop service: {}".format(s))
print("Stop service: {}".format(s), end='')
pid = get_pid(s)
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:
processes.pop(s, None)
......@@ -472,9 +483,9 @@ def show_service_status(s):
for ns in services_set:
if is_running(ns):
pid = get_pid(ns)
logging.info("{} is running: {}".format(ns, pid))
print("{} is running: {}".format(ns, pid))
else:
logging.info("{} is stopped".format(ns))
print("{} is stopped".format(ns))
if __name__ == '__main__':
......@@ -499,6 +510,7 @@ if __name__ == '__main__':
)
parser.add_argument('-d', '--daemon', nargs="?", const=1)
parser.add_argument('-w', '--worker', type=int, nargs="?", const=4)
parser.add_argument('-f', '--force', nargs="?", const=1)
args = parser.parse_args()
if args.daemon:
DAEMON = True
......@@ -513,7 +525,10 @@ if __name__ == '__main__':
start_services_and_watch(srv)
os._exit(0)
elif action == "stop":
stop_service(srv)
if args.force:
stop_service_force(srv)
else:
stop_service(srv)
elif action == "restart":
DAEMON = True
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