diagnostics: Revamp main diagnostics page

- Run diagnostics on each module separately.

- Run diagnostics in a separate thread.

- Show progressive update while running diagnostics.

- Store and show old diagnostics.

- Prevent CSRF on the expensive operation of running diagnostics.
This commit is contained in:
Sunil Mohan Adapa 2015-08-21 19:50:27 +05:30 committed by James Valleroy
parent 49c4c1dce6
commit eca538169e
4 changed files with 114 additions and 78 deletions

View File

@ -1,19 +0,0 @@
#!/bin/sh
#
# This file is part of Plinth.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
/usr/lib/freedombox/testsuite/check

View File

@ -19,15 +19,24 @@
Plinth module for running diagnostics Plinth module for running diagnostics
""" """
import collections
from django.http import Http404 from django.http import Http404
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.views.decorators.http import require_POST
from gettext import gettext as _ from gettext import gettext as _
import importlib import importlib
import logging
import threading
from plinth import actions
from plinth import cfg from plinth import cfg
from plinth import module_loader from plinth import module_loader
from plinth.errors import ActionError
logger = logging.Logger(__name__)
current_results = {}
_running_task = None
def init(): def init():
@ -39,26 +48,16 @@ def init():
def index(request): def index(request):
"""Serve the index page""" """Serve the index page"""
if request.method == 'POST' and not _running_task:
_start_task()
return TemplateResponse(request, 'diagnostics.html', return TemplateResponse(request, 'diagnostics.html',
{'title': _('System Diagnostics')}) {'title': _('System Diagnostics'),
'is_running': _running_task is not None,
'results': current_results})
def test(request): @require_POST
"""Run diagnostics and the output page"""
output = ''
error = ''
try:
output = actions.superuser_run("diagnostic-test")
except ActionError as exception:
output, error = exception.args[1:]
except Exception as exception:
error = str(exception)
return TemplateResponse(request, 'diagnostics_test.html',
{'title': _('Diagnostic Test'),
'diagnostics_output': output,
'diagnostics_error': error})
def module(request, module_name): def module(request, module_name):
"""Return diagnostics for a particular module.""" """Return diagnostics for a particular module."""
found = False found = False
@ -79,3 +78,49 @@ def module(request, module_name):
{'title': _('Diagnostic Test'), {'title': _('Diagnostic Test'),
'module_name': module_name, 'module_name': module_name,
'results': results}) 'results': results})
def _start_task():
"""Start the run task in a separate thread."""
if _running_task:
raise Exception('Task already running')
global _running_task
_running_task = threading.Thread(target=_run_on_all_modules_wrapper)
_running_task.start()
def _run_on_all_modules_wrapper():
"""Wrapper over actual task to catch exceptions."""
try:
run_on_all_modules()
except Exception as exception:
logger.exception('Error running diagnostics - %s', exception)
current_results['error'] = str(exception)
global _running_task
_running_task = None
def run_on_all_modules():
"""Run diagnostics on all modules and store the result."""
global current_results
current_results = {'modules': [],
'results': collections.OrderedDict(),
'progress_percentage': 0}
modules = []
for module_import_path in module_loader.loaded_modules:
loaded_module = importlib.import_module(module_import_path)
if not hasattr(loaded_module, 'diagnose'):
continue
module_name = module_import_path.split('.')[-1]
modules.append((module_name, loaded_module))
current_results['results'][module_name] = None
current_results['modules'] = modules
for current_index, (module_name, loaded_module) in enumerate(modules):
current_results['results'][module_name] = loaded_module.diagnose()
current_results['progress_percentage'] = \
int((current_index + 1) * 100 / len(modules))

View File

@ -18,16 +18,61 @@
# #
{% endcomment %} {% endcomment %}
{% block page_head %}
{% if is_running %}
<meta http-equiv="refresh" content="3" />
{% endif %}
{% endblock %}
{% block content %} {% block content %}
<h2>{{ title }}</h2> <h2>{{ title }}</h2>
<p>The system diagnostic test will run a number of checks on your <p>The system diagnostic test will run a number of checks on your
system to confirm that network services are running and configured system to confirm that applications and services are working as expected.</p>
properly. It may take a minute to complete.</p>
<p><a class="btn btn-primary btn-lg" href="{% url 'diagnostics:test' %}"> {% if not is_running %}
Run diagnostic test &raquo; <form class="form form-diagnostics-button" method="post"
</a></p> action="{% url 'diagnostics:index' %}">
{% csrf_token %}
<input type="submit" class="btn btn-primary" value="Run Diagnostics"/>
</form>
{% else %}
<p>Diagnotics test is currently running</p>
<div class="progress">
<div class="progress-bar progress-bar-striped active"
role="progressbar" aria-valuemin="0" aria-valuemax="100"
aria-valuenow="{{ results.progress_percentage }}"
style="width: {{ results.progress_percentage }}%">
{{ results.progress_percentage }}%
</div>
</div>
{% endif %}
{% if results %}
<h3>Results</h3>
{% if results.error %}
<div class="alert alert-danger alert-dismissable">
<a class="close" data-dismiss="alert">&times;</a>
{{ results.error }}
</div>
{% else %}
{% for module, module_results in results.results.items %}
<h4>Module: {{ module }}</h4>
{% if module_results %}
{% include "diagnostics_results.html" with results=module_results %}
{% else %}
<p><span class="glyphicon glyphicon-hourglass"></span></p>
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endblock %} {% endblock %}

View File

@ -1,35 +0,0 @@
{% extends 'base.html' %}
{% comment %}
#
# This file is part of Plinth.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
{% endcomment %}
{% block content %}
<h2>{{title}}</h2>
{% if diagnostics_error %}
<p>The diagnostic test encountered an error:<p>
<pre>{{ diagnostics_error }}</pre>
{% endif %}
{% if diagnostics_output %}
<p>Output of diagnostic test:</p>
<pre>{{ diagnostics_output }}</pre>
{% endif %}
{% endblock %}