mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-09-04 21:06:20 -05:00
Enhancement: websocket status
This commit is contained in:
@@ -185,7 +185,8 @@ export class SettingsComponent
|
|||||||
this.systemStatus.tasks.classifier_status ===
|
this.systemStatus.tasks.classifier_status ===
|
||||||
SystemStatusItemStatus.ERROR ||
|
SystemStatusItemStatus.ERROR ||
|
||||||
this.systemStatus.tasks.sanity_check_status ===
|
this.systemStatus.tasks.sanity_check_status ===
|
||||||
SystemStatusItemStatus.ERROR
|
SystemStatusItemStatus.ERROR ||
|
||||||
|
this.systemStatus.websocket_connected === SystemStatusItemStatus.ERROR
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -254,6 +254,18 @@
|
|||||||
<h6><ng-container i18n>Error</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.sanity_check_error}}</span>
|
<h6><ng-container i18n>Error</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.sanity_check_error}}</span>
|
||||||
}
|
}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
<dt i18n>WebSocket Connection</dt>
|
||||||
|
<dd>
|
||||||
|
<span class="btn btn-sm pe-none align-items-center btn-dark text-uppercase small">
|
||||||
|
@if (status.websocket_connected === 'OK') {
|
||||||
|
<ng-container i18n>OK</ng-container>
|
||||||
|
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
|
||||||
|
} @else {
|
||||||
|
<ng-container i18n>Error</ng-container>
|
||||||
|
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1"></i-bs>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -24,7 +24,7 @@ import {
|
|||||||
} from '@angular/core/testing'
|
} from '@angular/core/testing'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
import { of, throwError } from 'rxjs'
|
import { Subject, of, throwError } from 'rxjs'
|
||||||
import { PaperlessTaskName } from 'src/app/data/paperless-task'
|
import { PaperlessTaskName } from 'src/app/data/paperless-task'
|
||||||
import {
|
import {
|
||||||
InstallType,
|
InstallType,
|
||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
import { SystemStatusService } from 'src/app/services/system-status.service'
|
import { SystemStatusService } from 'src/app/services/system-status.service'
|
||||||
import { TasksService } from 'src/app/services/tasks.service'
|
import { TasksService } from 'src/app/services/tasks.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
|
import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
|
||||||
import { SystemStatusDialogComponent } from './system-status-dialog.component'
|
import { SystemStatusDialogComponent } from './system-status-dialog.component'
|
||||||
|
|
||||||
const status: SystemStatus = {
|
const status: SystemStatus = {
|
||||||
@@ -77,6 +78,8 @@ describe('SystemStatusDialogComponent', () => {
|
|||||||
let tasksService: TasksService
|
let tasksService: TasksService
|
||||||
let systemStatusService: SystemStatusService
|
let systemStatusService: SystemStatusService
|
||||||
let toastService: ToastService
|
let toastService: ToastService
|
||||||
|
let websocketStatusService: WebsocketStatusService
|
||||||
|
let websocketSubject: Subject<boolean> = new Subject<boolean>()
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
@@ -98,6 +101,12 @@ describe('SystemStatusDialogComponent', () => {
|
|||||||
tasksService = TestBed.inject(TasksService)
|
tasksService = TestBed.inject(TasksService)
|
||||||
systemStatusService = TestBed.inject(SystemStatusService)
|
systemStatusService = TestBed.inject(SystemStatusService)
|
||||||
toastService = TestBed.inject(ToastService)
|
toastService = TestBed.inject(ToastService)
|
||||||
|
websocketStatusService = TestBed.inject(WebsocketStatusService)
|
||||||
|
jest
|
||||||
|
.spyOn(websocketStatusService, 'onConnectionStatus')
|
||||||
|
.mockImplementation(() => {
|
||||||
|
return websocketSubject.asObservable()
|
||||||
|
})
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -168,4 +177,19 @@ describe('SystemStatusDialogComponent', () => {
|
|||||||
component.ngOnInit()
|
component.ngOnInit()
|
||||||
expect(component.versionMismatch).toBeFalsy()
|
expect(component.versionMismatch).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should update websocket connection status', () => {
|
||||||
|
websocketSubject.next(true)
|
||||||
|
expect(component.status.websocket_connected).toEqual(
|
||||||
|
SystemStatusItemStatus.OK
|
||||||
|
)
|
||||||
|
websocketSubject.next(false)
|
||||||
|
expect(component.status.websocket_connected).toEqual(
|
||||||
|
SystemStatusItemStatus.ERROR
|
||||||
|
)
|
||||||
|
websocketSubject.next(true)
|
||||||
|
expect(component.status.websocket_connected).toEqual(
|
||||||
|
SystemStatusItemStatus.OK
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'
|
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'
|
||||||
import { Component, OnInit, inject } from '@angular/core'
|
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
NgbActiveModal,
|
NgbActiveModal,
|
||||||
NgbModalModule,
|
NgbModalModule,
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
NgbProgressbarModule,
|
NgbProgressbarModule,
|
||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
|
import { Subject, takeUntil } from 'rxjs'
|
||||||
import { PaperlessTaskName } from 'src/app/data/paperless-task'
|
import { PaperlessTaskName } from 'src/app/data/paperless-task'
|
||||||
import {
|
import {
|
||||||
SystemStatus,
|
SystemStatus,
|
||||||
@@ -18,6 +19,7 @@ import { PermissionsService } from 'src/app/services/permissions.service'
|
|||||||
import { SystemStatusService } from 'src/app/services/system-status.service'
|
import { SystemStatusService } from 'src/app/services/system-status.service'
|
||||||
import { TasksService } from 'src/app/services/tasks.service'
|
import { TasksService } from 'src/app/services/tasks.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
|
import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -34,13 +36,14 @@ import { environment } from 'src/environments/environment'
|
|||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class SystemStatusDialogComponent implements OnInit {
|
export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||||
activeModal = inject(NgbActiveModal)
|
activeModal = inject(NgbActiveModal)
|
||||||
private clipboard = inject(Clipboard)
|
private clipboard = inject(Clipboard)
|
||||||
private systemStatusService = inject(SystemStatusService)
|
private systemStatusService = inject(SystemStatusService)
|
||||||
private tasksService = inject(TasksService)
|
private tasksService = inject(TasksService)
|
||||||
private toastService = inject(ToastService)
|
private toastService = inject(ToastService)
|
||||||
private permissionsService = inject(PermissionsService)
|
private permissionsService = inject(PermissionsService)
|
||||||
|
private websocketStatusService = inject(WebsocketStatusService)
|
||||||
|
|
||||||
public SystemStatusItemStatus = SystemStatusItemStatus
|
public SystemStatusItemStatus = SystemStatusItemStatus
|
||||||
public PaperlessTaskName = PaperlessTaskName
|
public PaperlessTaskName = PaperlessTaskName
|
||||||
@@ -51,6 +54,7 @@ export class SystemStatusDialogComponent implements OnInit {
|
|||||||
public copied: boolean = false
|
public copied: boolean = false
|
||||||
|
|
||||||
private runningTasks: Set<PaperlessTaskName> = new Set()
|
private runningTasks: Set<PaperlessTaskName> = new Set()
|
||||||
|
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
get currentUserIsSuperUser(): boolean {
|
get currentUserIsSuperUser(): boolean {
|
||||||
return this.permissionsService.isSuperUser()
|
return this.permissionsService.isSuperUser()
|
||||||
@@ -65,6 +69,17 @@ export class SystemStatusDialogComponent implements OnInit {
|
|||||||
if (this.versionMismatch) {
|
if (this.versionMismatch) {
|
||||||
this.status.pngx_version = `${this.status.pngx_version} (frontend: ${this.frontendVersion})`
|
this.status.pngx_version = `${this.status.pngx_version} (frontend: ${this.frontendVersion})`
|
||||||
}
|
}
|
||||||
|
this.status.websocket_connected = this.websocketStatusService.isConnected()
|
||||||
|
? SystemStatusItemStatus.OK
|
||||||
|
: SystemStatusItemStatus.ERROR
|
||||||
|
this.websocketStatusService
|
||||||
|
.onConnectionStatus()
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((connected) => {
|
||||||
|
this.status.websocket_connected = connected
|
||||||
|
? SystemStatusItemStatus.OK
|
||||||
|
: SystemStatusItemStatus.ERROR
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public close() {
|
public close() {
|
||||||
@@ -97,7 +112,7 @@ export class SystemStatusDialogComponent implements OnInit {
|
|||||||
this.runningTasks.delete(taskName)
|
this.runningTasks.delete(taskName)
|
||||||
this.systemStatusService.get().subscribe({
|
this.systemStatusService.get().subscribe({
|
||||||
next: (status) => {
|
next: (status) => {
|
||||||
this.status = status
|
Object.assign(this.status, status)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -110,4 +125,9 @@ export class SystemStatusDialogComponent implements OnInit {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.unsubscribeNotifier.next(this)
|
||||||
|
this.unsubscribeNotifier.complete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -44,4 +44,5 @@ export interface SystemStatus {
|
|||||||
sanity_check_last_run: string // ISO date string
|
sanity_check_last_run: string // ISO date string
|
||||||
sanity_check_error: string
|
sanity_check_error: string
|
||||||
}
|
}
|
||||||
|
websocket_connected?: SystemStatusItemStatus // added client-side
|
||||||
}
|
}
|
||||||
|
@@ -103,6 +103,7 @@ export class WebsocketStatusService {
|
|||||||
private documentConsumptionFinishedSubject = new Subject<FileStatus>()
|
private documentConsumptionFinishedSubject = new Subject<FileStatus>()
|
||||||
private documentConsumptionFailedSubject = new Subject<FileStatus>()
|
private documentConsumptionFailedSubject = new Subject<FileStatus>()
|
||||||
private documentDeletedSubject = new Subject<boolean>()
|
private documentDeletedSubject = new Subject<boolean>()
|
||||||
|
private connectionStatusSubject = new Subject<boolean>()
|
||||||
|
|
||||||
private get(taskId: string, filename?: string) {
|
private get(taskId: string, filename?: string) {
|
||||||
let status =
|
let status =
|
||||||
@@ -153,6 +154,15 @@ export class WebsocketStatusService {
|
|||||||
this.statusWebSocket = new WebSocket(
|
this.statusWebSocket = new WebSocket(
|
||||||
`${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`
|
`${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`
|
||||||
)
|
)
|
||||||
|
this.statusWebSocket.onopen = () => {
|
||||||
|
this.connectionStatusSubject.next(true)
|
||||||
|
}
|
||||||
|
this.statusWebSocket.onclose = () => {
|
||||||
|
this.connectionStatusSubject.next(false)
|
||||||
|
}
|
||||||
|
this.statusWebSocket.onerror = () => {
|
||||||
|
this.connectionStatusSubject.next(false)
|
||||||
|
}
|
||||||
this.statusWebSocket.onmessage = (ev: MessageEvent) => {
|
this.statusWebSocket.onmessage = (ev: MessageEvent) => {
|
||||||
const {
|
const {
|
||||||
type,
|
type,
|
||||||
@@ -286,4 +296,12 @@ export class WebsocketStatusService {
|
|||||||
onDocumentDeleted() {
|
onDocumentDeleted() {
|
||||||
return this.documentDeletedSubject
|
return this.documentDeletedSubject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onConnectionStatus() {
|
||||||
|
return this.connectionStatusSubject.asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnected(): boolean {
|
||||||
|
return this.statusWebSocket?.readyState === WebSocket.OPEN
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user