]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
QoL: log version at startup, show backend vs frontend mismatch in system status ...
authorshamoon <4887959+shamoon@users.noreply.github.com>
Thu, 19 Jun 2025 15:29:34 +0000 (08:29 -0700)
committerGitHub <noreply@github.com>
Thu, 19 Jun 2025 15:29:34 +0000 (08:29 -0700)
12 files changed:
Dockerfile
src-ui/src/app/components/app-frame/app-frame.component.ts
src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.html
src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts
src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts
src-ui/src/app/data/ui-settings.ts
src-ui/src/environments/environment.prod.ts
src-ui/src/environments/environment.ts
src/documents/tests/test_api_uisettings.py
src/documents/views.py
src/paperless/asgi.py
src/paperless/wsgi.py

index b4a65f60e4d7a78d0ffbc6d50f1daf90b986ef65..99ee07782df9c991014d2b1e2b80c649932a8c33 100644 (file)
@@ -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
 
index fabcbf7d1eac74867208d41f67c4d4e60acd27f4..09252fe19d684965b5fd0b4cd1e4d81f8eedea02 100644 (file)
@@ -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)
   }
index 94c4ef22d8ad3a8999fefff952f9a172bd54d04f..e3b09ee7e6f540d7c899e71282b373f05939a9bd 100644 (file)
           <div class="card-body">
             <dl class="card-text">
               <dt i18n>Paperless-ngx Version</dt>
-              <dd>{{status.pngx_version}}</dd>
+              <dd>
+                {{status.pngx_version}}
+                @if (versionMismatch) {
+                    <button class="btn btn-sm d-inline align-items-center btn-dark text-uppercase small" [ngbPopover]="versionPopover" triggers="click mouseenter:mouseleave">
+                      <i-bs name="exclamation-triangle-fill" class="text-danger lh-1"></i-bs>
+                    </button>
+                }
+                <ng-template #versionPopover>
+                  Frontend version: {{frontendVersion}}<br>
+                  Backend version: {{status.pngx_version}}
+                </ng-template>
+              </dd>
               <dt i18n>Install Type</dt>
               <dd>{{status.install_type}}</dd>
               <dt i18n>Server OS</dt>
index 28a0889ab72323cba7e19cecfceb805b74e20452..f9d8b4d68354e413ddf028a32f947f2b7ee7c6a8 100644 (file)
@@ -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()
+  })
 })
index c7ba3c57aad78b6aaca3fa732615ac7f881f0b8c..9585aa6b84b2c5e5a78fbddd96b8454f3c846b69 100644 (file)
@@ -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()
   }
index c5164d6e1d7cc93424aec800fc0b790d0cf45a91..e3cdeabae967739aa49ff0e375b138f80b6d0103 100644 (file)
@@ -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',
index 050e6a9072753bdba22bef5d02d1b785c66a6275..9b6d756fd9ffdcb23dc33cbeb5c4c818c034e84e 100644 (file)
@@ -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:',
index 1c1552e7b4a286ed41bb73dacd43e7b18f9af2e5..1097404c3ff1292b446663e4d40e25b1c0bf2ef4 100644 (file)
@@ -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:',
index 2837298983ba6007da6f7f37031007c3030b6f32..26c6f17ae5d957012f9ae753cb2961e8f2b70f78 100644 (file)
@@ -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,
index b66e6381a50c1ab13c328cd77384e2dcfa8511ce..4dc1862607cf332472c26e7566372103fd597062 100644 (file)
@@ -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
index 8d63c347af161deaefcf241eb9dca117bb3a308b..3d74a1edbac84f03153f6e5bffb294496e5a1425 100644 (file)
@@ -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__}")
index 6aab72299ec979120097a1de93eeee3810a42f20..734c8444242179f64ad65b03da17c5794c397fe6 100644 (file)
@@ -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__}")