diff --git a/openwisp_utils/admin_theme/admin.py b/openwisp_utils/admin_theme/admin.py index 555721c1..6533de54 100644 --- a/openwisp_utils/admin_theme/admin.py +++ b/openwisp_utils/admin_theme/admin.py @@ -2,12 +2,19 @@ from django.conf import settings from django.contrib import admin +from django.shortcuts import render from django.urls import path from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy +from django.utils.translation import gettext_lazy as _ from . import settings as app_settings from .dashboard import get_dashboard_context +from .system_info import ( + get_enabled_openwisp_modules, + get_openwisp_version, + get_os_details, +) logger = logging.getLogger(__name__) @@ -30,6 +37,16 @@ def index(self, request, extra_context=None): context = {'dashboard_enabled': False} return super().index(request, extra_context=context) + def openwisp_info(self, request, *args, **kwargs): + context = { + 'enabled_openwisp_modules': get_enabled_openwisp_modules(), + 'system_info': get_os_details(), + 'openwisp_version': get_openwisp_version(), + 'title': _('System Information'), + 'site_title': self.site_title, + } + return render(request, 'admin/openwisp_info.html', context) + def get_urls(self): autocomplete_view = import_string(app_settings.AUTOCOMPLETE_FILTER_VIEW) return [ @@ -38,6 +55,11 @@ def get_urls(self): self.admin_view(autocomplete_view.as_view(admin_site=self)), name='ow-auto-filter', ), + path( + 'ow-info/', + self.admin_view(self.openwisp_info), + name='ow-info', + ), ] + super().get_urls() diff --git a/openwisp_utils/admin_theme/apps.py b/openwisp_utils/admin_theme/apps.py index 584bb0d0..9793a006 100644 --- a/openwisp_utils/admin_theme/apps.py +++ b/openwisp_utils/admin_theme/apps.py @@ -51,6 +51,14 @@ def register_menu_groups(self): position=10, config={'label': _('Home'), 'url': '/admin', 'icon': 'ow-dashboard-icon'}, ) + register_menu_group( + position=899, + config={ + 'label': _('System info'), + 'url': '/admin/ow-info', + 'icon': 'ow-info-icon', + }, + ) def modify_admin_theme_settings_links(self): link_files = [] diff --git a/openwisp_utils/admin_theme/static/admin/css/openwisp.css b/openwisp_utils/admin_theme/static/admin/css/openwisp.css index 58c15c04..dcf71a07 100644 --- a/openwisp_utils/admin_theme/static/admin/css/openwisp.css +++ b/openwisp_utils/admin_theme/static/admin/css/openwisp.css @@ -1511,6 +1511,10 @@ body::-webkit-scrollbar { mask-image: url(../../ui/openwisp/images/dashboard.svg); -webkit-mask-image: url(../../ui/openwisp/images/dashboard.svg); } +.ow-info-icon { + mask-image: url(../../ui/openwisp/images/info.svg); + -webkit-mask-image: url(../../ui/openwisp/images/info.svg); +} .password { mask-image: url(../../ui/openwisp/images/password.svg); -webkit-mask-image: url(../../ui/openwisp/images/password.svg); diff --git a/openwisp_utils/admin_theme/static/ui/openwisp/images/info.svg b/openwisp_utils/admin_theme/static/ui/openwisp/images/info.svg new file mode 100644 index 00000000..1244ae88 --- /dev/null +++ b/openwisp_utils/admin_theme/static/ui/openwisp/images/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/openwisp_utils/admin_theme/system_info.py b/openwisp_utils/admin_theme/system_info.py new file mode 100644 index 00000000..fda71896 --- /dev/null +++ b/openwisp_utils/admin_theme/system_info.py @@ -0,0 +1,55 @@ +import platform +from collections import OrderedDict + +import pkg_resources +from django.conf import settings +from django.utils.module_loading import import_string + +EXTRA_OPENWISP_PACKAGES = ['netdiff', 'netjsonconfig'] + + +def get_installed_openwisp_packages(): + dists = pkg_resources.working_set + return { + dist.key: dist.version + for dist in dists + if dist.key.startswith('openwisp') or dist.key in EXTRA_OPENWISP_PACKAGES + } + + +def get_openwisp_version(): + try: + return import_string('openwisp2.__openwisp_version__') + except ImportError: + return None + + +def get_enabled_openwisp_modules(): + enabled_packages = {} + installed_packages = get_installed_openwisp_packages() + extra_packages = {} + for package, version in installed_packages.items(): + if package in EXTRA_OPENWISP_PACKAGES: + extra_packages[package] = version + continue + package_name = package.replace("-", "_") + if package_name in settings.INSTALLED_APPS: + enabled_packages[package] = version + else: + # check for sub-apps + for app in settings.INSTALLED_APPS: + if app.startswith(package_name + '.'): + enabled_packages[package] = version + break + enabled_packages = OrderedDict(sorted(enabled_packages.items())) + enabled_packages.update(OrderedDict(sorted(extra_packages.items()))) + return enabled_packages + + +def get_os_details(): + uname = platform.uname() + return { + 'os_version': uname.version, + 'kernel_version': uname.release, + 'hardware_platform': uname.machine, + } diff --git a/openwisp_utils/admin_theme/templates/admin/openwisp_info.html b/openwisp_utils/admin_theme/templates/admin/openwisp_info.html new file mode 100644 index 00000000..b4ad589c --- /dev/null +++ b/openwisp_utils/admin_theme/templates/admin/openwisp_info.html @@ -0,0 +1,18 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block content %} +{% if openwisp_version %} +
{% trans "OS version" %}: {{ system_info.os_version }}
+{% trans "Kernel version" %}: {{ system_info.kernel_version }}
+{% trans "Hardware platform" %}: {{ system_info.hardware_platform }}
+{% endblock content %} diff --git a/runtests.py b/runtests.py index 97bde978..5427a446 100755 --- a/runtests.py +++ b/runtests.py @@ -5,7 +5,7 @@ import sys sys.path.insert(0, "tests") -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "openwisp2.settings") if __name__ == "__main__": from django.core.management import execute_from_command_line diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/manage.py b/tests/manage.py index f9726f9e..33876c7b 100755 --- a/tests/manage.py +++ b/tests/manage.py @@ -3,7 +3,7 @@ import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "openwisp2.settings") from django.core.management import execute_from_command_line diff --git a/tests/openwisp2/__init__.py b/tests/openwisp2/__init__.py new file mode 100644 index 00000000..62bcec8d --- /dev/null +++ b/tests/openwisp2/__init__.py @@ -0,0 +1 @@ +__openwisp_version__ = '23.0.0a' diff --git a/tests/local_settings.example.py b/tests/openwisp2/local_settings.example.py similarity index 100% rename from tests/local_settings.example.py rename to tests/openwisp2/local_settings.example.py diff --git a/tests/settings.py b/tests/openwisp2/settings.py similarity index 99% rename from tests/settings.py rename to tests/openwisp2/settings.py index dffaeec0..7e3493d6 100644 --- a/tests/settings.py +++ b/tests/openwisp2/settings.py @@ -46,7 +46,7 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] -ROOT_URLCONF = 'urls' +ROOT_URLCONF = 'openwisp2.urls' LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' diff --git a/tests/urls.py b/tests/openwisp2/urls.py similarity index 100% rename from tests/urls.py rename to tests/openwisp2/urls.py diff --git a/tests/test_project/tests/test_admin.py b/tests/test_project/tests/test_admin.py index 50e057d4..74eb4e94 100644 --- a/tests/test_project/tests/test_admin.py +++ b/tests/test_project/tests/test_admin.py @@ -569,3 +569,33 @@ def test_organization_radius_settings_admin(self): self.assertEqual( org_rad_settings.get_field_value('extra_config'), 'no data' ) + + @patch( + 'openwisp_utils.admin_theme.system_info.settings.INSTALLED_APPS', + ['openwisp_users', 'openwisp_utils.admin_theme'], + ) + def test_system_information(self, *args): + def _assert_system_information(response): + self.assertContains(response, '