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__}")