Don't restart apps on update-common, use session messages

This commit is contained in:
Disassembler 2018-11-04 20:11:45 +01:00
parent 8f7cb14305
commit 340323a0b5
No known key found for this signature in database
GPG Key ID: 524BD33A0EE29499
7 changed files with 96 additions and 99 deletions

View File

@ -13,7 +13,6 @@ class ActionItem:
class ActionQueue: class ActionQueue:
def __init__(self): def __init__(self):
self.actions = {} self.actions = {}
# Priority 0 = restart/shutdown, 1 = config update, 2 = apps actions
self.queue = deque() self.queue = deque()
self.lock = Lock() self.lock = Lock()
self.is_running = False self.is_running = False

View File

@ -14,6 +14,7 @@ from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_public_key from cryptography.hazmat.primitives.serialization import load_pem_public_key
from . import tools from . import tools
from . import validator
PUB_FILE = '/etc/vmmgr/packages.pub' PUB_FILE = '/etc/vmmgr/packages.pub'
LXC_ROOT = '/var/lib/lxc' LXC_ROOT = '/var/lib/lxc'
@ -234,20 +235,18 @@ class AppMgr:
def update_common_settings(self, email, gmaps_api_key): def update_common_settings(self, email, gmaps_api_key):
# Update common configuration values # Update common configuration values
if email != None: if not validator.is_valid_email(email):
# Update email raise validator.InvalidValueException('email', email)
if not validator.is_valid_email(email): self.conf['common']['email'] = email
raise validator.InvalidValueException('email', email) self.conf['common']['gmaps-api-key'] = gmaps_api_key
self.conf['common']['email'] = email self.conf.save()
if gmaps_api_key != None:
# Update Google Maps API key def update_repo_settings(self, url, user, pwd):
self.conf['common']['gmaps-api-key'] = gmaps_api_key # Update lxc repository configuration
# Save config to file self.conf['repo']['url'] = url
self.conf['repo']['user'] = user
self.conf['repo']['pwd'] = pwd
self.conf.save() self.conf.save()
for app in self.conf['apps'].copy():
# Restart currently running apps in order to update their config
if tools.is_service_started(app):
tools.restart_service(app)
def hash_file(file_path): def hash_file(file_path):
sha512 = hashlib.sha512() sha512 = hashlib.sha512()

View File

@ -31,6 +31,37 @@ class WSGIApp(object):
self.appmgr.clean_pending_packages() self.appmgr.clean_pending_packages()
self.jinja_env = Environment(loader=FileSystemLoader('/usr/share/vmmgr/templates'), autoescape=True, lstrip_blocks=True, trim_blocks=True) self.jinja_env = Environment(loader=FileSystemLoader('/usr/share/vmmgr/templates'), autoescape=True, lstrip_blocks=True, trim_blocks=True)
self.jinja_env.globals.update(is_app_visible=self.is_app_visible) self.jinja_env.globals.update(is_app_visible=self.is_app_visible)
self.url_map = Map((
Rule('/', endpoint='portal_view'),
Rule('/login', methods=['GET'], endpoint='login_view'),
Rule('/login', methods=['POST'], endpoint='login_action'),
Rule('/setup-host', redirect_to='/login?redir=setup-host'),
Rule('/setup-apps', redirect_to='/login?redir=setup-apps')
))
self.admin_url_map = Map((
Rule('/', endpoint='portal_view'),
Rule('/logout', endpoint='logout_action'),
Rule('/setup-host', endpoint='setup_host_view'),
Rule('/setup-apps', endpoint='setup_apps_view'),
Rule('/update-host', endpoint='update_host_action'),
Rule('/verify-dns', endpoint='verify_dns_action'),
Rule('/verify-https', endpoint='verify_http_action', defaults={'proto': 'https'}),
Rule('/verify-http', endpoint='verify_http_action', defaults={'proto': 'http'}),
Rule('/update-cert', endpoint='update_cert_action'),
Rule('/update-common', endpoint='update_common_action'),
Rule('/update-repo', endpoint='update_repo_action'),
Rule('/update-app-visibility', endpoint='update_app_visibility_action'),
Rule('/update-app-autostart', endpoint='update_app_autostart_action'),
Rule('/start-app', endpoint='start_app_action'),
Rule('/stop-app', endpoint='stop_app_action'),
Rule('/install-app', endpoint='install_app_action'),
Rule('/get-app-status', endpoint='get_app_status_action'),
Rule('/clear-app-status', endpoint='clear_app_status_action'),
Rule('/uninstall-app', endpoint='uninstall_app_action'),
Rule('/update-password', endpoint='update_password_action'),
Rule('/shutdown-vm', endpoint='shutdown_vm_action'),
Rule('/reboot-vm', endpoint='reboot_vm_action')
))
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response) return self.wsgi_app(environ, start_response)
@ -42,15 +73,17 @@ class WSGIApp(object):
request.session.lang = WSGILang() request.session.lang = WSGILang()
# Dispatch request # Dispatch request
response = self.dispatch_request(request) response = self.dispatch_request(request)
# Save session if changed
request.session.save(response)
return response(environ, start_response) return response(environ, start_response)
def dispatch_request(self, request): def dispatch_request(self, request):
adapter = self.get_url_map(request.session).bind_to_environ(request.environ) map = self.admin_url_map if request.session['admin'] else self.url_map
adapter = map.bind_to_environ(request.environ)
try: try:
endpoint, values = adapter.match() endpoint, values = adapter.match()
return getattr(self, endpoint)(request, **values) response = getattr(self, endpoint)(request, **values)
# Save session if changed
request.session.save(response)
return response
except NotFound as e: except NotFound as e:
# Return custom 404 page # Return custom 404 page
response = self.render_html('404.html', request) response = self.render_html('404.html', request)
@ -59,43 +92,6 @@ class WSGIApp(object):
except HTTPException as e: except HTTPException as e:
return e return e
def get_url_map(self, session):
rules = [
Rule('/', endpoint='portal_view'),
Rule('/login', methods=['GET'], endpoint='login_view', defaults={'redirect': '/'}),
Rule('/login', methods=['POST'], endpoint='login_action'),
Rule('/logout', endpoint='logout_action')
]
if session['admin']:
rules += [
Rule('/setup-host', endpoint='setup_host_view'),
Rule('/setup-apps', endpoint='setup_apps_view'),
Rule('/update-host', endpoint='update_host_action'),
Rule('/verify-dns', endpoint='verify_dns_action'),
Rule('/verify-https', endpoint='verify_http_action', defaults={'proto': 'https'}),
Rule('/verify-http', endpoint='verify_http_action', defaults={'proto': 'http'}),
Rule('/update-cert', endpoint='update_cert_action'),
Rule('/update-common', endpoint='update_common_action'),
Rule('/update-repo', endpoint='update_repo_action'),
Rule('/update-app-visibility', endpoint='update_app_visibility_action'),
Rule('/update-app-autostart', endpoint='update_app_autostart_action'),
Rule('/start-app', endpoint='start_app_action'),
Rule('/stop-app', endpoint='stop_app_action'),
Rule('/install-app', endpoint='install_app_action'),
Rule('/get-app-status', endpoint='get_app_status_action'),
Rule('/clear-app-status', endpoint='clear_app_status_action'),
Rule('/uninstall-app', endpoint='uninstall_app_action'),
Rule('/update-password', endpoint='update_password_action'),
Rule('/shutdown-vm', endpoint='shutdown_vm_action'),
Rule('/reboot-vm', endpoint='reboot_vm_action'),
]
else:
rules += [
Rule('/setup-host', endpoint='login_view', defaults={'redirect': '/setup-host'}),
Rule('/setup-apps', endpoint='login_view', defaults={'redirect': '/setup-apps'}),
]
return Map(rules)
def render_template(self, template_name, request, **context): def render_template(self, template_name, request, **context):
# Enhance context # Enhance context
context['conf'] = self.conf context['conf'] = self.conf
@ -112,17 +108,28 @@ class WSGIApp(object):
def render_json(self, data): def render_json(self, data):
return Response(json.dumps(data), mimetype='application/json') return Response(json.dumps(data), mimetype='application/json')
def get_session_message(self, request):
# Consume and retrieve message stored in session
if 'msg' not in request.session:
return None
message = request.session['msg']
del request.session['msg']
# Message is in format location:type:text
return message.split(':', 3)
def login_view(self, request, **kwargs): def login_view(self, request, **kwargs):
return self.render_html('login.html', request, redirect=kwargs['redirect']) redir = request.args.get('redir')
message = self.get_session_message(request)
return self.render_html('login.html', request, redir=redir, message=message)
def login_action(self, request): def login_action(self, request):
password = request.form['password'] password = request.form['password']
redir_url = request.form['redirect'] redir = request.form['redir']
if tools.adminpwd_verify(password, self.conf['host']['adminpwd']): if tools.adminpwd_verify(password, self.conf['host']['adminpwd']):
request.session['admin'] = True request.session['admin'] = True
return redirect(redir_url) return redirect('/{}'.format(redir))
else: request.session['msg'] = 'login:error:{}'.format(request.session.lang.bad_password())
return self.render_html('login.html', request, message=request.session.lang.bad_password()) return redirect('/login?redir={}'.format(redir)) if redir else redirect('/login')
def logout_action(self, request): def logout_action(self, request):
request.session.reset() request.session.reset()
@ -152,7 +159,8 @@ class WSGIApp(object):
pass pass
repo_reachable = bool(self.appmgr.online_packages) repo_reachable = bool(self.appmgr.online_packages)
table = self.render_setup_apps_table(request) table = self.render_setup_apps_table(request)
return self.render_html('setup-apps.html', request, repo_reachable=repo_reachable, table=table) message = self.get_session_message(request)
return self.render_html('setup-apps.html', request, repo_reachable=repo_reachable, table=table, message=message)
def render_setup_apps_table(self, request): def render_setup_apps_table(self, request):
lang = request.session.lang lang = request.session.lang
@ -298,20 +306,26 @@ class WSGIApp(object):
def update_common_action(self, request): def update_common_action(self, request):
# Update common settings shared between apps - admin e-mail address, Google Maps API key # Update common settings shared between apps - admin e-mail address, Google Maps API key
try: try:
self.appmgr.update_common_settings(request.form['email'], request.form['gmaps-api-key']) email = request.form['email']
gmaps_api_key = request.form['gmaps-api-key']
self.appmgr.update_common_settings(email, gmaps_api_key)
request.session['msg'] = 'common:info:{}'.format(request.session.lang.common_updated())
except BadRequest: except BadRequest:
return self.render_json({'error': request.session.lang.malformed_request()}) return self.render_json({'error': request.session.lang.malformed_request()})
return self.render_json({'ok': request.session.lang.common_updated()}) except InvalidValueException:
request.session['msg'] = 'common:error:{}'.format(request.session.lang.invalid_email(email))
return redirect('/setup-apps')
def update_repo_action(self, request): def update_repo_action(self, request):
# Update repository URL and credentials # Update repository URL and credentials
try: try:
self.conf['repo']['url'] = request.form['repourl'] url = request.form['repourl']
self.conf['repo']['user'] = request.form['repousername'] user = request.form['repousername']
self.conf['repo']['pwd'] = request.form['repopassword'] pwd = request.form['repopassword']
self.conf.save() self.appmgr.update_repo_settings(url, user, pwd)
except: request.session['msg'] = 'repo:info:{}'.format(request.session.lang.repo_updated())
pass except BadRequest:
return self.render_json({'error': request.session.lang.malformed_request()})
return redirect('/setup-apps') return redirect('/setup-apps')
def update_app_visibility_action(self, request): def update_app_visibility_action(self, request):

View File

@ -17,7 +17,9 @@ class WSGILang:
'key_file_missing': 'Nebyl vybrán soubor se soukromým klíčem.', 'key_file_missing': 'Nebyl vybrán soubor se soukromým klíčem.',
'cert_request_error': 'Došlo k chybě při žádosti o certifikát. Zkontrolujte, zda je virtuální stroj dostupný z internetu na portu 80.', 'cert_request_error': 'Došlo k chybě při žádosti o certifikát. Zkontrolujte, zda je virtuální stroj dostupný z internetu na portu 80.',
'cert_installed': 'Certifikát byl úspěšně nainstalován. Přejděte na URL <a href="{}">{}</a> nebo restartujte webový prohlížeč pro jeho načtení.', 'cert_installed': 'Certifikát byl úspěšně nainstalován. Přejděte na URL <a href="{}">{}</a> nebo restartujte webový prohlížeč pro jeho načtení.',
'common_updated': 'Nastavení aplikací bylo úspěšně změněno.', 'invalid_email': 'Zadaný e-mail "{}" není platný.',
'common_updated': 'Nastavení aplikací bylo úspěšně změněno. Pokud je některá z aplikací spuštěna, změny se projeví po jejím restartu.',
'repo_updated': 'Nastavení distribučního serveru bylo úspěšně změněno.',
'stop_start_error': 'Došlo k chybě při spouštění/zastavování. Zkuste akci opakovat nebo restartuje virtuální stroj.', 'stop_start_error': 'Došlo k chybě při spouštění/zastavování. Zkuste akci opakovat nebo restartuje virtuální stroj.',
'installation_in_progress': 'Probíhá instalace jiného balíku. Vyčkejte na její dokončení.', 'installation_in_progress': 'Probíhá instalace jiného balíku. Vyčkejte na její dokončení.',
'package_manager_error': 'Došlo k chybě při instalaci aplikace. Zkuste akci opakovat nebo restartuje virtuální stroj.', 'package_manager_error': 'Došlo k chybě při instalaci aplikace. Zkuste akci opakovat nebo restartuje virtuální stroj.',

View File

@ -8,7 +8,6 @@ $(function() {
$('#verify-http').on('click', verify_http); $('#verify-http').on('click', verify_http);
$('#cert-method').on('change', toggle_cert_method); $('#cert-method').on('change', toggle_cert_method);
$('#update-cert').on('submit', update_cert); $('#update-cert').on('submit', update_cert);
$('#update-common').on('submit', update_common);
$('#app-manager') $('#app-manager')
.on('click', '.app-visible', update_app_visibility) .on('click', '.app-visible', update_app_visibility)
.on('click', '.app-autostart', update_app_autostart) .on('click', '.app-autostart', update_app_autostart)
@ -107,23 +106,6 @@ function update_cert() {
return false; return false;
} }
function update_common() {
$('#common-submit').hide();
$('#common-message').hide();
$('#common-wait').show();
$.post('/update-common', {'email': $('#email').val(), 'gmaps-api-key': $('#gmaps-api-key').val()}, function(data) {
$('#common-wait').hide();
if (data.error) {
$('#common-message').attr('class','error').html(data.error).show();
$('#common-submit').show();
} else {
$('#common-message').attr('class','info').html(data.ok).show();
$('#common-submit').show();
}
});
return false;
}
function _update_app(item, ev) { function _update_app(item, ev) {
var el = $(ev.target); var el = $(ev.target);
var app = el.closest('tr').data('app'); var app = el.closest('tr').data('app');

View File

@ -3,7 +3,7 @@
{% block body %} {% block body %}
<div class="setup-box"> <div class="setup-box">
<h2>Přihlášení</h2> <h2>Přihlášení</h2>
<form action="/login" method="post"> <form method="post">
<table> <table>
<tr> <tr>
<td>Jméno:</td> <td>Jméno:</td>
@ -14,12 +14,12 @@
<td><input type="password" name="password"></td> <td><input type="password" name="password"></td>
</tr> </tr>
<tr> <tr>
<td><input type="hidden" name="redirect" value="{{ redirect }}"></td> <td><input type="hidden" name="redir" value="{{ redir }}"></td>
<td><input type="submit" value="Přihlásit"></td> <td><input type="submit" value="Přihlásit"></td>
</tr> </tr>
</table> </table>
{% if message is defined %} {% if message %}
<p class="error">{{ message }}</p> <p class="{{ message[1] }}">{{ message[2] }}</p>
{% endif %} {% endif %}
</form> </form>
</div> </div>

View File

@ -40,6 +40,9 @@
<td>&nbsp;</td> <td>&nbsp;</td>
<td colspan="2"> <td colspan="2">
<input type="submit" id="repo-submit" value="Nastavit hodnoty"> <input type="submit" id="repo-submit" value="Nastavit hodnoty">
{% if message and message[0] == 'repo' %}
<div class="{{ message[1] }}">{{ message[2] }}</div>
{% endif %}
</td> </td>
</tr> </tr>
</table> </table>
@ -53,23 +56,21 @@
<table> <table>
<tr> <tr>
<td>E-mail</td> <td>E-mail</td>
<td><input type="text" name="email" id="email" value="{{ conf['common']['email'] }}"></td> <td><input type="text" name="email" value="{{ conf['common']['email'] }}"></td>
<td class="remark">Administrativní e-mail na který budou doručovány zprávy a upozornění z aplikací. Stejná e-mailová adresa bude také využita některými aplikacemi pro odesílání zpráv uživatelům.</td> <td class="remark">Administrativní e-mail na který budou doručovány zprávy a upozornění z aplikací. Stejná e-mailová adresa bude také využita některými aplikacemi pro odesílání zpráv uživatelům.</td>
</tr> </tr>
<tr> <tr>
<td>Google Maps API klíč</td> <td>Google Maps API klíč</td>
<td><input type="text" name="gmaps-api-key" id="gmaps-api-key" value="{{ conf['common']['gmaps-api-key'] }}"></td> <td><input type="text" name="gmaps-api-key" value="{{ conf['common']['gmaps-api-key'] }}"></td>
<td class="remark">API klíč pro službu Google Maps, která je využita některými aplikacemi.</td> <td class="remark">API klíč pro službu Google Maps, která je využita některými aplikacemi.</td>
</tr> </tr>
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<td colspan="2"> <td colspan="2">
<input type="submit" id="common-submit" value="Nastavit hodnoty"> <input type="submit" id="common-submit" value="Nastavit hodnoty">
<div id="common-message"></div> {% if message and message[0] == 'common' %}
<div id="common-wait" class="loader-wrap"> <div class="{{ message[1] }}">{{ message[2] }}</div>
<div class="loader"></div> {% endif %}
<span>Provádí se změna nastavení, prosím čekejte...</span>
</div>
</td> </td>
</tr> </tr>
</table> </table>