From 52b95f2b62d5c1b29c49ff1e4841c1b8d7e1ceb9 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 19 Jun 2025 08:29:34 -0700 Subject: [PATCH] QoL: log version at startup, show backend vs frontend mismatch in system status (#10214) --- Dockerfile | 2 +- .../app-frame/app-frame.component.ts | 5 +++- .../system-status-dialog.component.html | 13 +++++++++- .../system-status-dialog.component.spec.ts | 26 +++++++++++++++++++ .../system-status-dialog.component.ts | 18 +++++++++++-- src-ui/src/app/data/ui-settings.ts | 6 +++++ src-ui/src/environments/environment.prod.ts | 1 + src-ui/src/environments/environment.ts | 1 + src/documents/tests/test_api_uisettings.py | 2 ++ src/documents/views.py | 2 ++ src/paperless/asgi.py | 7 +++++ src/paperless/wsgi.py | 7 +++++ 12 files changed, 85 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index b4a65f60e..99ee07782 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ ARG PNGX_TAG_VERSION= RUN set -eux && \ case "${PNGX_TAG_VERSION}" in \ dev|beta|fix*|feature*) \ - sed -i -E "s/version: '([0-9\.]+)'/version: '\1 #${PNGX_TAG_VERSION}'/g" /src/src-ui/src/environments/environment.prod.ts \ + sed -i -E "s/tag: '([a-z\.]+)'/tag: '${PNGX_TAG_VERSION}'/g" /src/src-ui/src/environments/environment.prod.ts \ ;; \ esac diff --git a/src-ui/src/app/components/app-frame/app-frame.component.ts b/src-ui/src/app/components/app-frame/app-frame.component.ts index fabcbf7d1..09252fe19 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.ts +++ b/src-ui/src/app/components/app-frame/app-frame.component.ts @@ -74,7 +74,6 @@ export class AppFrameComponent extends ComponentWithPermissions implements OnInit, ComponentCanDeactivate { - versionString = `${environment.appTitle} ${environment.version}` appRemoteVersion: AppRemoteVersion isMenuCollapsed: boolean = true @@ -142,6 +141,10 @@ export class AppFrameComponent }, 200) // slightly longer than css animation for slim sidebar } + get versionString(): string { + return `${environment.appTitle} v${this.settingsService.get(SETTINGS_KEYS.VERSION)}${environment.production ? '' : ` #${environment.tag}`}` + } + get customAppTitle(): string { return this.settingsService.get(SETTINGS_KEYS.APP_TITLE) } diff --git a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.html b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.html index 94c4ef22d..e3b09ee7e 100644 --- a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.html +++ b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.html @@ -20,7 +20,18 @@
Paperless-ngx Version
-
{{status.pngx_version}}
+
+ {{status.pngx_version}} + @if (versionMismatch) { + + } + + Frontend version: {{frontendVersion}}
+ Backend version: {{status.pngx_version}} +
+
Install Type
{{status.install_type}}
Server OS
diff --git a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts index 28a0889ab..f9d8b4d68 100644 --- a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts +++ b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts @@ -1,3 +1,18 @@ +// Mock production environment for testing +jest.mock('src/environments/environment', () => ({ + environment: { + production: true, + apiBaseUrl: 'http://localhost:8000/api/', + apiVersion: '9', + appTitle: 'Paperless-ngx', + tag: 'prod', + version: '2.4.3', + webSocketHost: 'localhost:8000', + webSocketProtocol: 'ws:', + webSocketBaseUrl: '/ws/', + }, +})) + import { Clipboard } from '@angular/cdk/clipboard' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import { provideHttpClientTesting } from '@angular/common/http/testing' @@ -142,4 +157,15 @@ describe('SystemStatusDialogComponent', () => { `Task ${PaperlessTaskName.IndexOptimize} started` ) }) + + it('shoduld handle version mismatch', () => { + component.frontendVersion = '2.4.2' + component.ngOnInit() + expect(component.versionMismatch).toBeTruthy() + expect(component.status.pngx_version).toContain('(frontend: 2.4.2)') + component.frontendVersion = '2.4.3' + component.status.pngx_version = '2.4.3' + component.ngOnInit() + expect(component.versionMismatch).toBeFalsy() + }) }) diff --git a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts index c7ba3c57a..9585aa6b8 100644 --- a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts +++ b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts @@ -1,5 +1,5 @@ import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard' -import { Component } from '@angular/core' +import { Component, OnInit } from '@angular/core' import { NgbActiveModal, NgbModalModule, @@ -18,6 +18,7 @@ import { PermissionsService } from 'src/app/services/permissions.service' import { SystemStatusService } from 'src/app/services/system-status.service' import { TasksService } from 'src/app/services/tasks.service' import { ToastService } from 'src/app/services/toast.service' +import { environment } from 'src/environments/environment' @Component({ selector: 'pngx-system-status-dialog', @@ -33,10 +34,12 @@ import { ToastService } from 'src/app/services/toast.service' NgxBootstrapIconsModule, ], }) -export class SystemStatusDialogComponent { +export class SystemStatusDialogComponent implements OnInit { public SystemStatusItemStatus = SystemStatusItemStatus public PaperlessTaskName = PaperlessTaskName public status: SystemStatus + public frontendVersion: string = environment.version + public versionMismatch: boolean = false public copied: boolean = false @@ -55,6 +58,17 @@ export class SystemStatusDialogComponent { private permissionsService: PermissionsService ) {} + public ngOnInit() { + this.versionMismatch = + environment.production && + this.status.pngx_version && + this.frontendVersion && + this.status.pngx_version !== this.frontendVersion + if (this.versionMismatch) { + this.status.pngx_version = `${this.status.pngx_version} (frontend: ${this.frontendVersion})` + } + } + public close() { this.activeModal.close() } diff --git a/src-ui/src/app/data/ui-settings.ts b/src-ui/src/app/data/ui-settings.ts index c5164d6e1..e3cdeabae 100644 --- a/src-ui/src/app/data/ui-settings.ts +++ b/src-ui/src/app/data/ui-settings.ts @@ -20,6 +20,7 @@ export enum GlobalSearchType { export const PAPERLESS_GREEN_HEX = '#17541f' export const SETTINGS_KEYS = { + VERSION: 'version', LANGUAGE: 'language', APP_LOGO: 'app_logo', APP_TITLE: 'app_title', @@ -76,6 +77,11 @@ export const SETTINGS_KEYS = { } export const SETTINGS: UiSetting[] = [ + { + key: SETTINGS_KEYS.VERSION, + type: 'string', + default: '', + }, { key: SETTINGS_KEYS.LANGUAGE, type: 'string', diff --git a/src-ui/src/environments/environment.prod.ts b/src-ui/src/environments/environment.prod.ts index 050e6a907..9b6d756fd 100644 --- a/src-ui/src/environments/environment.prod.ts +++ b/src-ui/src/environments/environment.prod.ts @@ -5,6 +5,7 @@ export const environment = { apiBaseUrl: document.baseURI + 'api/', apiVersion: '9', // match src/paperless/settings.py appTitle: 'Paperless-ngx', + tag: 'prod', version: '2.16.3', webSocketHost: window.location.host, webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:', diff --git a/src-ui/src/environments/environment.ts b/src-ui/src/environments/environment.ts index 1c1552e7b..1097404c3 100644 --- a/src-ui/src/environments/environment.ts +++ b/src-ui/src/environments/environment.ts @@ -7,6 +7,7 @@ export const environment = { apiBaseUrl: 'http://localhost:8000/api/', apiVersion: '9', appTitle: 'Paperless-ngx', + tag: 'dev', version: 'DEVELOPMENT', webSocketHost: 'localhost:8000', webSocketProtocol: 'ws:', diff --git a/src/documents/tests/test_api_uisettings.py b/src/documents/tests/test_api_uisettings.py index 283729898..26c6f17ae 100644 --- a/src/documents/tests/test_api_uisettings.py +++ b/src/documents/tests/test_api_uisettings.py @@ -7,6 +7,7 @@ from rest_framework import status from rest_framework.test import APITestCase from documents.tests.utils import DirectoriesMixin +from paperless.version import __full_version_str__ class TestApiUiSettings(DirectoriesMixin, APITestCase): @@ -39,6 +40,7 @@ class TestApiUiSettings(DirectoriesMixin, APITestCase): self.assertDictEqual( response.data["settings"], { + "version": __full_version_str__, "app_title": None, "app_logo": None, "auditlog_enabled": True, diff --git a/src/documents/views.py b/src/documents/views.py index b66e6381a..4dc186260 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -2184,6 +2184,8 @@ class UiSettingsView(GenericAPIView): general_config = GeneralConfig() + ui_settings["version"] = version.__full_version_str__ + ui_settings["app_title"] = settings.APP_TITLE if general_config.app_title is not None and len(general_config.app_title) > 0: ui_settings["app_title"] = general_config.app_title diff --git a/src/paperless/asgi.py b/src/paperless/asgi.py index 8d63c347a..3d74a1edb 100644 --- a/src/paperless/asgi.py +++ b/src/paperless/asgi.py @@ -21,3 +21,10 @@ application = ProtocolTypeRouter( "websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns)), }, ) + +import logging # noqa: E402 + +from paperless.version import __full_version_str__ # noqa: E402 + +logger = logging.getLogger("paperless.asgi") +logger.info(f"[init] Paperless-ngx version: v{__full_version_str__}") diff --git a/src/paperless/wsgi.py b/src/paperless/wsgi.py index 6aab72299..734c84442 100644 --- a/src/paperless/wsgi.py +++ b/src/paperless/wsgi.py @@ -14,3 +14,10 @@ from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "paperless.settings") application = get_wsgi_application() + +import logging # noqa: E402 + +from paperless.version import __full_version_str__ # noqa: E402 + +logger = logging.getLogger("paperless.wsgi") +logger.info(f"[init] Paperless-ngx version: v{__full_version_str__}")