this.systemStatus.tasks.classifier_status ===
SystemStatusItemStatus.ERROR ||
this.systemStatus.tasks.sanity_check_status ===
- SystemStatusItemStatus.ERROR
+ SystemStatusItemStatus.ERROR ||
+ this.systemStatus.websocket_connected === SystemStatusItemStatus.ERROR
)
}
<h6><ng-container i18n>Error</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.sanity_check_error}}</span>
}
</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>
</div>
</div>
} from '@angular/core/testing'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
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 {
InstallType,
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 { WebsocketStatusService } from 'src/app/services/websocket-status.service'
import { SystemStatusDialogComponent } from './system-status-dialog.component'
const status: SystemStatus = {
let tasksService: TasksService
let systemStatusService: SystemStatusService
let toastService: ToastService
+ let websocketStatusService: WebsocketStatusService
+ let websocketSubject: Subject<boolean> = new Subject<boolean>()
beforeEach(async () => {
await TestBed.configureTestingModule({
tasksService = TestBed.inject(TasksService)
systemStatusService = TestBed.inject(SystemStatusService)
toastService = TestBed.inject(ToastService)
+ websocketStatusService = TestBed.inject(WebsocketStatusService)
+ jest
+ .spyOn(websocketStatusService, 'onConnectionStatus')
+ .mockImplementation(() => {
+ return websocketSubject.asObservable()
+ })
fixture.detectChanges()
})
component.ngOnInit()
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
+ )
+ })
})
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'
-import { Component, OnInit, inject } from '@angular/core'
+import { Component, OnDestroy, OnInit, inject } from '@angular/core'
import {
NgbActiveModal,
NgbModalModule,
NgbProgressbarModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
+import { Subject, takeUntil } from 'rxjs'
import { PaperlessTaskName } from 'src/app/data/paperless-task'
import {
SystemStatus,
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 { WebsocketStatusService } from 'src/app/services/websocket-status.service'
import { environment } from 'src/environments/environment'
@Component({
NgxBootstrapIconsModule,
],
})
-export class SystemStatusDialogComponent implements OnInit {
+export class SystemStatusDialogComponent implements OnInit, OnDestroy {
activeModal = inject(NgbActiveModal)
private clipboard = inject(Clipboard)
private systemStatusService = inject(SystemStatusService)
private tasksService = inject(TasksService)
private toastService = inject(ToastService)
private permissionsService = inject(PermissionsService)
+ private websocketStatusService = inject(WebsocketStatusService)
public SystemStatusItemStatus = SystemStatusItemStatus
public PaperlessTaskName = PaperlessTaskName
public copied: boolean = false
private runningTasks: Set<PaperlessTaskName> = new Set()
+ private unsubscribeNotifier: Subject<any> = new Subject()
get currentUserIsSuperUser(): boolean {
return this.permissionsService.isSuperUser()
if (this.versionMismatch) {
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() {
this.runningTasks.delete(taskName)
this.systemStatusService.get().subscribe({
next: (status) => {
- this.status = status
+ Object.assign(this.status, status)
},
})
},
},
})
}
+
+ ngOnDestroy(): void {
+ this.unsubscribeNotifier.next(this)
+ this.unsubscribeNotifier.complete()
+ }
}
sanity_check_last_run: string // ISO date string
sanity_check_error: string
}
+ websocket_connected?: SystemStatusItemStatus // added client-side
}
private documentConsumptionFinishedSubject = new Subject<FileStatus>()
private documentConsumptionFailedSubject = new Subject<FileStatus>()
private documentDeletedSubject = new Subject<boolean>()
+ private connectionStatusSubject = new Subject<boolean>()
private get(taskId: string, filename?: string) {
let status =
this.statusWebSocket = new WebSocket(
`${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) => {
const {
type,
onDocumentDeleted() {
return this.documentDeletedSubject
}
+
+ onConnectionStatus() {
+ return this.connectionStatusSubject.asObservable()
+ }
+
+ isConnected(): boolean {
+ return this.statusWebSocket?.readyState === WebSocket.OPEN
+ }
}