diff --git a/src-ui/src/app/app.component.ts b/src-ui/src/app/app.component.ts index 8ca276dd4..43c23f2af 100644 --- a/src-ui/src/app/app.component.ts +++ b/src-ui/src/app/app.component.ts @@ -32,7 +32,7 @@ export class AppComponent implements OnInit, OnDestroy { this.successSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => { this.toastService.show({title: "Document added", delay: 10000, content: `Document ${status.filename} was added to paperless.`, actionName: "Open document", action: () => { - this.router.navigate(['documents', status.document_id]) + this.router.navigate(['documents', status.documentId]) }}) }) @@ -42,6 +42,4 @@ export class AppComponent implements OnInit, OnDestroy { } - - } diff --git a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html index aa317bd52..59afb74f9 100644 --- a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html +++ b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html @@ -9,10 +9,16 @@ -
-

{uploadStatus.length, plural, =1 {Uploading file...} =other {Uploading {{uploadStatus.length}} files...}}

- +
+

{{status.filename}}: {{status.message}}

+ + +
+ + +
+
\ No newline at end of file diff --git a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts index 19d9909a9..ac270fe30 100644 --- a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts @@ -1,15 +1,10 @@ import { HttpEventType } from '@angular/common/http'; import { Component, OnInit } from '@angular/core'; import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'; +import { ConsumerStatusService, FileStatus, FileStatusPhase } from 'src/app/services/consumer-status.service'; import { DocumentService } from 'src/app/services/rest/document.service'; -import { ToastService } from 'src/app/services/toast.service'; -interface UploadStatus { - loaded: number - total: number -} - @Component({ selector: 'app-upload-file-widget', templateUrl: './upload-file-widget.component.html', @@ -17,8 +12,35 @@ interface UploadStatus { }) export class UploadFileWidgetComponent implements OnInit { - constructor(private documentService: DocumentService, private toastService: ToastService) { } + constructor( + private documentService: DocumentService, + private consumerStatusService: ConsumerStatusService + ) { } + getStatus() { + return this.consumerStatusService.consumerStatus + } + + isFinished(status: FileStatus) { + return status.phase == FileStatusPhase.FAILED || status.phase == FileStatusPhase.SUCCESS + } + + getType(status: FileStatus) { + switch (status.phase) { + case FileStatusPhase.PROCESSING: + case FileStatusPhase.UPLOADING: + return "primary" + case FileStatusPhase.FAILED: + return "danger" + case FileStatusPhase.SUCCESS: + return "success" + } + } + + dismiss(status: FileStatus) { + this.consumerStatusService.dismiss(status) + } + ngOnInit(): void { } @@ -28,54 +50,37 @@ export class UploadFileWidgetComponent implements OnInit { public fileLeave(event){ } - uploadStatus: UploadStatus[] = [] - completedFiles = 0 - - uploadVisible = false - - get loadedSum() { - return this.uploadStatus.map(s => s.loaded).reduce((a,b) => a+b, this.completedFiles > 0 ? 1 : 0) - } - - get totalSum() { - return this.uploadStatus.map(s => s.total).reduce((a,b) => a+b, 1) - } - public dropped(files: NgxFileDropEntry[]) { for (const droppedFile of files) { if (droppedFile.fileEntry.isFile) { - let uploadStatusObject: UploadStatus = {loaded: 0, total: 1} - this.uploadStatus.push(uploadStatusObject) - this.uploadVisible = true const fileEntry = droppedFile.fileEntry as FileSystemFileEntry; fileEntry.file((file: File) => { let formData = new FormData() formData.append('document', file, file.name) + let status = this.consumerStatusService.newFileUpload() + status.filename = file.name this.documentService.uploadDocument(formData).subscribe(event => { if (event.type == HttpEventType.UploadProgress) { - uploadStatusObject.loaded = event.loaded - uploadStatusObject.total = event.total + status.updateProgress(FileStatusPhase.UPLOADING, event.loaded, event.total) } else if (event.type == HttpEventType.Response) { - this.uploadStatus.splice(this.uploadStatus.indexOf(uploadStatusObject), 1) - this.completedFiles += 1 - this.toastService.showInfo($localize`The document has been uploaded and will be processed by the consumer shortly.`) + status.taskId = event.body["task_id"] } }, error => { - this.uploadStatus.splice(this.uploadStatus.indexOf(uploadStatusObject), 1) - this.completedFiles += 1 + status.updateProgress(FileStatusPhase.FAILED) switch (error.status) { case 400: { - this.toastService.showInfo($localize`There was an error while uploading the document: ${error.error.document}`) + status.message = error.error.document break; } default: { - this.toastService.showInfo($localize`An error has occurred while uploading the document. Sorry!`) + status.message = $localize`An error has occurred while uploading the document. Sorry!` break; } } + }) }); } diff --git a/src-ui/src/app/data/websocket-consumer-status-message.ts b/src-ui/src/app/data/websocket-consumer-status-message.ts new file mode 100644 index 000000000..49117b101 --- /dev/null +++ b/src-ui/src/app/data/websocket-consumer-status-message.ts @@ -0,0 +1,11 @@ +export interface WebsocketConsumerStatusMessage { + + filename?: string + task_id?: string + current_progress?: number + max_progress?: number + status?: string + message?: string + document_id: number + +} \ No newline at end of file diff --git a/src-ui/src/app/services/consumer-status.service.ts b/src-ui/src/app/services/consumer-status.service.ts index 070420b0f..8aaae1e93 100644 --- a/src-ui/src/app/services/consumer-status.service.ts +++ b/src-ui/src/app/services/consumer-status.service.ts @@ -1,13 +1,57 @@ import { Injectable } from '@angular/core'; import { Subject } from 'rxjs'; +import { WebsocketConsumerStatusMessage } from '../data/websocket-consumer-status-message'; + +export enum FileStatusPhase { + STARTED = 0, + UPLOADING = 1, + PROCESSING = 2, + SUCCESS = 3, + FAILED = 4 +} + +export class FileStatus { + + filename: string + + taskId: string + + phase: FileStatusPhase = FileStatusPhase.STARTED + + currentPhaseProgress: number + + currentPhaseMaxProgress: number + + message: string + + documentId: number + + getProgress(): number { + switch (this.phase) { + case FileStatusPhase.STARTED: + return 0.0 + case FileStatusPhase.UPLOADING: + return this.currentPhaseProgress / this.currentPhaseMaxProgress * 0.2 + case FileStatusPhase.PROCESSING: + return this.currentPhaseProgress / this.currentPhaseMaxProgress * 0.8 + 0.2 + case FileStatusPhase.SUCCESS: + case FileStatusPhase.FAILED: + return 1.0 + } + } + + updateProgress(status: FileStatusPhase, currentProgress?: number, maxProgress?: number) { + if (status >= this.phase) { + this.phase = status + if (currentProgress) { + this.currentPhaseProgress = currentProgress + } + if (maxProgress) { + this.currentPhaseMaxProgress = maxProgress + } + } + } -export interface FileStatus { - filename?: string - current_progress?: number - max_progress?: number - status?: string - message?: string - document_id?: number } @Injectable({ @@ -23,24 +67,41 @@ export class ConsumerStatusService { private documentConsumptionFinishedSubject = new Subject() private documentConsumptionFailedSubject = new Subject() + private get(taskId: string, filename?: string) { + let status = this.consumerStatus.find(e => e.taskId == taskId) || this.consumerStatus.find(e => e.filename == filename) + if (!status) { + status = new FileStatus() + this.consumerStatus.push(status) + } + status.taskId = taskId + status.filename = filename + return status + } + + newFileUpload(): FileStatus { + let status = new FileStatus() + this.consumerStatus.push(status) + return status + } + connect() { this.disconnect() this.statusWebSocked = new WebSocket("ws://localhost:8000/ws/status/"); this.statusWebSocked.onmessage = (ev) => { - let statusUpdate: FileStatus = JSON.parse(ev['data']) + let statusMessage: WebsocketConsumerStatusMessage = JSON.parse(ev['data']) - let index = this.consumerStatus.findIndex(fs => fs.filename == statusUpdate.filename) - if (index > -1) { - this.consumerStatus[index] = statusUpdate - } else { - this.consumerStatus.push(statusUpdate) - } + let status = this.get(statusMessage.task_id, statusMessage.filename) + status.updateProgress(FileStatusPhase.PROCESSING, statusMessage.current_progress, statusMessage.max_progress) + status.message = statusMessage.message + status.documentId = statusMessage.document_id - if (statusUpdate.status == "SUCCESS") { - this.documentConsumptionFinishedSubject.next(statusUpdate) + if (statusMessage.status == "SUCCESS") { + status.phase = FileStatusPhase.SUCCESS + this.documentConsumptionFinishedSubject.next(status) } - if (statusUpdate.status == "FAILED") { - this.documentConsumptionFailedSubject.next(statusUpdate) + if (statusMessage.status == "FAILED") { + status.phase = FileStatusPhase.FAILED + this.documentConsumptionFailedSubject.next(status) } } }