mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-06-17 11:10:23 +00:00
diagnostics: Store results of full run in database
Tests: - Run diagnostics. Restart plinth, and check that the diagnostics results are still available to view. Signed-off-by: James Valleroy <jvalleroy@mailbox.org> Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
parent
3fcd6b9e58
commit
27284fe888
@ -4,22 +4,24 @@ FreedomBox app for system diagnostics.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import json
|
||||
import logging
|
||||
import pathlib
|
||||
import threading
|
||||
from copy import deepcopy
|
||||
|
||||
import psutil
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext_noop
|
||||
|
||||
from plinth import app as app_module
|
||||
from plinth import daemon, glib, menu
|
||||
from plinth import daemon, glib, kvstore, menu
|
||||
from plinth import operation as operation_module
|
||||
from plinth.modules.apache.components import diagnose_url_on_all
|
||||
from plinth.modules.backups.components import BackupRestore
|
||||
|
||||
from . import manifest
|
||||
from .check import Result
|
||||
from .check import CheckJSONDecoder, CheckJSONEncoder, Result, translate_checks
|
||||
|
||||
_description = [
|
||||
_('The system diagnostic test will run a number of checks on your '
|
||||
@ -117,8 +119,7 @@ def _run_on_all_enabled_modules():
|
||||
continue
|
||||
|
||||
apps.append((app.app_id, app))
|
||||
app_name = app.info.name or app.app_id
|
||||
current_results['results'][app.app_id] = {'name': app_name}
|
||||
current_results['results'][app.app_id] = {'id': app.app_id}
|
||||
|
||||
current_results['apps'] = apps
|
||||
|
||||
@ -266,6 +267,9 @@ def _run_diagnostics():
|
||||
_run_on_all_enabled_modules()
|
||||
with results_lock:
|
||||
results = current_results['results']
|
||||
# Store the most recent results in the database.
|
||||
kvstore.set('diagnostics_results',
|
||||
json.dumps(results, cls=CheckJSONEncoder))
|
||||
|
||||
issue_count = 0
|
||||
severity = 'warning'
|
||||
@ -309,3 +313,39 @@ def _run_diagnostics():
|
||||
message=message, actions=actions,
|
||||
data=data, group='admin')
|
||||
note.dismiss(False)
|
||||
|
||||
|
||||
def are_results_available():
|
||||
"""Return whether diagnostic results are available."""
|
||||
with results_lock:
|
||||
results = current_results
|
||||
|
||||
if not results:
|
||||
results = kvstore.get_default('diagnostics_results', '{}')
|
||||
results = json.loads(results)
|
||||
|
||||
return bool(results)
|
||||
|
||||
|
||||
def get_results():
|
||||
"""Return the latest results of full diagnostics."""
|
||||
with results_lock:
|
||||
results = deepcopy(current_results)
|
||||
|
||||
# If no results are available in memory, then load from database.
|
||||
if not results:
|
||||
results = kvstore.get_default('diagnostics_results', '{}')
|
||||
results = json.loads(results, cls=CheckJSONDecoder)
|
||||
results = {'results': results, 'progress_percentage': 100}
|
||||
|
||||
# Translate and format diagnostic check descriptions for each app
|
||||
for app_id in results['results']:
|
||||
app = app_module.App.get(app_id)
|
||||
app_name = app.info.name or app_id
|
||||
results['results'][app_id]['name'] = app_name
|
||||
if 'diagnosis' in results['results'][app_id]:
|
||||
diagnosis = results['results'][app_id]['diagnosis']
|
||||
results['results'][app_id]['diagnosis'] = translate_checks(
|
||||
diagnosis)
|
||||
|
||||
return results
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Diagnostic check data type."""
|
||||
|
||||
import dataclasses
|
||||
from dataclasses import dataclass, field
|
||||
from enum import StrEnum
|
||||
import json
|
||||
|
||||
from django.utils.translation import gettext
|
||||
|
||||
@ -18,9 +20,6 @@ class Result(StrEnum):
|
||||
ERROR = 'error'
|
||||
|
||||
|
||||
# TODO: Description should not be translated until we need to display it.
|
||||
|
||||
|
||||
@dataclass
|
||||
class DiagnosticCheck:
|
||||
"""A diagnostic check and optional result and parameters."""
|
||||
@ -44,3 +43,32 @@ def translate(check: DiagnosticCheck) -> DiagnosticCheck:
|
||||
def translate_checks(checks: list[DiagnosticCheck]) -> list[DiagnosticCheck]:
|
||||
"""Translate and format diagnostic checks."""
|
||||
return [translate(check) for check in checks]
|
||||
|
||||
|
||||
class CheckJSONEncoder(json.JSONEncoder):
|
||||
"""Encode objects that include DiagnosticChecks."""
|
||||
|
||||
def default(self, o):
|
||||
"""Add class tag to DiagnosticChecks."""
|
||||
if isinstance(o, DiagnosticCheck):
|
||||
o = dataclasses.asdict(o)
|
||||
o.update({'__class__': 'DiagnosticCheck'})
|
||||
return o
|
||||
|
||||
return super().default(o)
|
||||
|
||||
|
||||
class CheckJSONDecoder(json.JSONDecoder):
|
||||
"""Decode objects that include DiagnosticChecks."""
|
||||
|
||||
def __init__(self):
|
||||
json.JSONDecoder.__init__(self, object_hook=CheckJSONDecoder.from_dict)
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data):
|
||||
"""Convert tagged data to DiagnosticCheck."""
|
||||
if data.get('__class__') == 'DiagnosticCheck':
|
||||
return DiagnosticCheck(data['check_id'], data['description'],
|
||||
data['result'], data['parameters'])
|
||||
|
||||
return data
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
value="{% trans "Run Diagnostics" %}"/>
|
||||
</form>
|
||||
|
||||
{% if results %}
|
||||
{% if results_available %}
|
||||
<a class="btn btn-default" role="button"href="{% url 'diagnostics:full' %}">
|
||||
{% trans "View Results" %}
|
||||
</a>
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Tests for diagnostic check data type."""
|
||||
|
||||
import json
|
||||
import pytest
|
||||
|
||||
from plinth.modules.diagnostics.check import DiagnosticCheck, Result, translate
|
||||
from plinth.modules.diagnostics.check import (DiagnosticCheck,
|
||||
CheckJSONEncoder,
|
||||
CheckJSONDecoder, Result,
|
||||
translate)
|
||||
|
||||
|
||||
def test_result():
|
||||
@ -57,3 +61,18 @@ def test_translate():
|
||||
Result.PASSED, {'key': 'value'})
|
||||
translated = translate(check)
|
||||
assert translated.description == 'sample check ?missing?'
|
||||
|
||||
|
||||
def test_json_encoder_decoder():
|
||||
"""Test encoding and decoding as JSON."""
|
||||
check = DiagnosticCheck('some-check-id', 'sample check', Result.PASSED)
|
||||
check_json = json.dumps(check, cls=CheckJSONEncoder)
|
||||
for string in [
|
||||
'"check_id": "some-check-id"', '"description": "sample check"',
|
||||
'"result": "passed"', '"parameters": {}',
|
||||
'"__class__": "DiagnosticCheck"'
|
||||
]:
|
||||
assert string in check_json
|
||||
|
||||
decoded_check = json.loads(check_json, cls=CheckJSONDecoder)
|
||||
assert decoded_check == check
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
FreedomBox app for running diagnostics.
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
import logging
|
||||
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
@ -36,12 +35,9 @@ class DiagnosticsView(AppView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Return additional context for rendering the template."""
|
||||
with diagnostics.results_lock:
|
||||
results = diagnostics.current_results
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['has_diagnostics'] = False
|
||||
context['results'] = results
|
||||
context['results_available'] = diagnostics.are_results_available()
|
||||
return context
|
||||
|
||||
|
||||
@ -62,19 +58,9 @@ class DiagnosticsFullView(TemplateView):
|
||||
except KeyError:
|
||||
is_task_running = False
|
||||
|
||||
with diagnostics.results_lock:
|
||||
results = deepcopy(diagnostics.current_results)
|
||||
|
||||
# Translate and format diagnostic check descriptions for each app
|
||||
for app_id in results['results']:
|
||||
if 'diagnosis' in results['results'][app_id]:
|
||||
diagnosis = results['results'][app_id]['diagnosis']
|
||||
results['results'][app_id]['diagnosis'] = translate_checks(
|
||||
diagnosis)
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['is_task_running'] = is_task_running
|
||||
context['results'] = results
|
||||
context['results'] = diagnostics.get_results()
|
||||
context['refresh_page_sec'] = 3 if is_task_running else None
|
||||
return context
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user