mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-11-12 17:46:43 -05:00
Manage dialog polling
This commit is contained in:
parent
01e9988bd2
commit
92be4e37ab
@ -16,6 +16,11 @@
|
||||
</div>
|
||||
}
|
||||
@if (!loading && !error) {
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<p class="mb-0 text-muted small">
|
||||
<ng-container i18n>Status updates every few seconds while bundles are being prepared.</ng-container>
|
||||
</p>
|
||||
</div>
|
||||
@if (bundles.length === 0) {
|
||||
<p class="mb-0 text-muted fst-italic" i18n>No bulk share links currently exist.</p>
|
||||
}
|
||||
@ -26,17 +31,42 @@
|
||||
<tr>
|
||||
<th scope="col" i18n>Created</th>
|
||||
<th scope="col" i18n>Status</th>
|
||||
<th scope="col" i18n>Size</th>
|
||||
<th scope="col" i18n>Expires</th>
|
||||
<th scope="col" i18n>Documents</th>
|
||||
<th scope="col" i18n>Actions</th>
|
||||
<th scope="col" i18n>File version</th>
|
||||
<th scope="col" class="text-end" i18n>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (bundle of bundles; track bundle.id) {
|
||||
<tr>
|
||||
<td>{{ bundle.created | date: 'short' }}</td>
|
||||
<td>
|
||||
<span class="badge text-bg-secondary text-uppercase">{{ bundle.status }}</span>
|
||||
<div>{{ bundle.created | date: 'short' }}</div>
|
||||
@if (bundle.built_at) {
|
||||
<div class="small text-muted">
|
||||
<ng-container i18n>Built:</ng-container> {{ bundle.built_at | date: 'short' }}
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
@if (bundle.status === statuses.Processing || bundle.status === statuses.Pending) {
|
||||
<span class="spinner-border spinner-border-sm" role="status"></span>
|
||||
}
|
||||
<span>{{ statusLabel(bundle.status) }}</span>
|
||||
</div>
|
||||
@if (bundle.last_error && bundle.status === statuses.Failed) {
|
||||
<div class="small text-danger mt-1">{{ bundle.last_error }}</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (bundle.size_bytes !== undefined && bundle.size_bytes !== null) {
|
||||
{{ bundle.size_bytes | fileSize }}
|
||||
}
|
||||
@if (bundle.size_bytes === undefined || bundle.size_bytes === null) {
|
||||
<span class="text-muted">—</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (bundle.expiration) {
|
||||
@ -47,11 +77,13 @@
|
||||
}
|
||||
</td>
|
||||
<td>{{ bundle.document_count }}</td>
|
||||
<td>
|
||||
<td>{{ fileVersionLabel(bundle.file_version) }}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary"
|
||||
[disabled]="bundle.status !== statuses.Ready"
|
||||
(click)="copy(bundle)"
|
||||
>
|
||||
@if (copiedSlug === bundle.slug) {
|
||||
@ -62,9 +94,21 @@
|
||||
}
|
||||
<span class="visually-hidden" i18n>Copy link</span>
|
||||
</button>
|
||||
@if (bundle.status === statuses.Failed) {
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-warning"
|
||||
[disabled]="loading"
|
||||
(click)="retry(bundle)"
|
||||
>
|
||||
<i-bs name="arrow-clockwise"></i-bs>
|
||||
<span class="visually-hidden" i18n>Retry</span>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-danger"
|
||||
[disabled]="loading"
|
||||
(click)="delete(bundle)"
|
||||
>
|
||||
<i-bs name="trash"></i-bs>
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
import { Clipboard } from '@angular/cdk/clipboard'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { Component, OnInit, inject } from '@angular/core'
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { first } from 'rxjs'
|
||||
import { ShareBundleSummary } from 'src/app/data/share-bundle'
|
||||
import { Subject, catchError, of, switchMap, takeUntil, timer } from 'rxjs'
|
||||
import {
|
||||
ShareBundleStatus,
|
||||
ShareBundleSummary,
|
||||
} from 'src/app/data/share-bundle'
|
||||
import { FileVersion } from 'src/app/data/share-link'
|
||||
import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
|
||||
import { ShareBundleService } from 'src/app/services/rest/share-bundle.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
@ -14,11 +19,11 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
|
||||
selector: 'pngx-share-bundle-manage-dialog',
|
||||
templateUrl: './share-bundle-manage-dialog.component.html',
|
||||
standalone: true,
|
||||
imports: [CommonModule, NgxBootstrapIconsModule],
|
||||
imports: [CommonModule, NgxBootstrapIconsModule, FileSizePipe],
|
||||
})
|
||||
export class ShareBundleManageDialogComponent
|
||||
extends LoadingComponentWithPermissions
|
||||
implements OnInit
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
private activeModal = inject(NgbActiveModal)
|
||||
private shareBundleService = inject(ShareBundleService)
|
||||
@ -28,33 +33,70 @@ export class ShareBundleManageDialogComponent
|
||||
title = $localize`Bulk Share Links`
|
||||
|
||||
bundles: ShareBundleSummary[] = []
|
||||
error: string
|
||||
copiedSlug: string
|
||||
error: string | null = null
|
||||
copiedSlug: string | null = null
|
||||
|
||||
readonly statuses = ShareBundleStatus
|
||||
readonly fileVersions = FileVersion
|
||||
|
||||
private readonly refresh$ = new Subject<boolean>()
|
||||
|
||||
private readonly statusLabels: Record<ShareBundleStatus, string> = {
|
||||
[ShareBundleStatus.Pending]: $localize`Pending`,
|
||||
[ShareBundleStatus.Processing]: $localize`Processing`,
|
||||
[ShareBundleStatus.Ready]: $localize`Ready`,
|
||||
[ShareBundleStatus.Failed]: $localize`Failed`,
|
||||
}
|
||||
|
||||
private readonly fileVersionLabels: Record<FileVersion, string> = {
|
||||
[FileVersion.Archive]: $localize`Archive`,
|
||||
[FileVersion.Original]: $localize`Original`,
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.fetchBundles()
|
||||
this.refresh$
|
||||
.pipe(
|
||||
switchMap((silent) => {
|
||||
if (!silent) {
|
||||
this.loading = true
|
||||
}
|
||||
this.error = null
|
||||
return this.shareBundleService.listAllBundles().pipe(
|
||||
catchError((error) => {
|
||||
if (!silent) {
|
||||
this.loading = false
|
||||
}
|
||||
this.error = $localize`Failed to load bulk share links.`
|
||||
this.toastService.showError(
|
||||
$localize`Error retrieving bulk share links.`,
|
||||
error
|
||||
)
|
||||
return of(null)
|
||||
})
|
||||
)
|
||||
}),
|
||||
takeUntil(this.unsubscribeNotifier)
|
||||
)
|
||||
.subscribe((results) => {
|
||||
if (results) {
|
||||
this.bundles = results
|
||||
this.copiedSlug = null
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
|
||||
this.triggerRefresh(false)
|
||||
timer(5000, 5000)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => this.triggerRefresh(true))
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
super.ngOnDestroy()
|
||||
}
|
||||
|
||||
fetchBundles(): void {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
this.shareBundleService
|
||||
.listAllBundles()
|
||||
.pipe(first())
|
||||
.subscribe({
|
||||
next: (results) => {
|
||||
this.bundles = results
|
||||
this.loading = false
|
||||
},
|
||||
error: (e) => {
|
||||
this.loading = false
|
||||
this.error = $localize`Failed to load bulk share links.`
|
||||
this.toastService.showError(
|
||||
$localize`Error retrieving bulk share links.`,
|
||||
e
|
||||
)
|
||||
},
|
||||
})
|
||||
this.triggerRefresh(false)
|
||||
}
|
||||
|
||||
getShareUrl(bundle: ShareBundleSummary): string {
|
||||
@ -65,22 +107,29 @@ export class ShareBundleManageDialogComponent
|
||||
}
|
||||
|
||||
copy(bundle: ShareBundleSummary): void {
|
||||
if (bundle.status !== ShareBundleStatus.Ready) {
|
||||
return
|
||||
}
|
||||
const success = this.clipboard.copy(this.getShareUrl(bundle))
|
||||
if (success) {
|
||||
this.copiedSlug = bundle.slug
|
||||
setTimeout(() => {
|
||||
this.copiedSlug = null
|
||||
}, 3000)
|
||||
this.toastService.showInfo($localize`Share link copied to clipboard.`)
|
||||
}
|
||||
}
|
||||
|
||||
delete(bundle: ShareBundleSummary): void {
|
||||
this.error = null
|
||||
this.loading = true
|
||||
this.shareBundleService.delete(bundle).subscribe({
|
||||
next: () => {
|
||||
this.toastService.showInfo($localize`Bulk share link deleted.`)
|
||||
this.fetchBundles()
|
||||
this.triggerRefresh(false)
|
||||
},
|
||||
error: (e) => {
|
||||
this.loading = false
|
||||
this.toastService.showError(
|
||||
$localize`Error deleting bulk share link.`,
|
||||
e
|
||||
@ -89,7 +138,47 @@ export class ShareBundleManageDialogComponent
|
||||
})
|
||||
}
|
||||
|
||||
retry(bundle: ShareBundleSummary): void {
|
||||
this.error = null
|
||||
this.shareBundleService.rebuildBundle(bundle.id).subscribe({
|
||||
next: (updated) => {
|
||||
this.toastService.showInfo(
|
||||
$localize`Bulk share link rebuild requested.`
|
||||
)
|
||||
this.replaceBundle(updated)
|
||||
},
|
||||
error: (e) => {
|
||||
this.toastService.showError($localize`Error requesting rebuild.`, e)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
statusLabel(status: ShareBundleStatus): string {
|
||||
return this.statusLabels[status] ?? status
|
||||
}
|
||||
|
||||
fileVersionLabel(version: FileVersion): string {
|
||||
return this.fileVersionLabels[version] ?? version
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.activeModal.close()
|
||||
}
|
||||
|
||||
private replaceBundle(updated: ShareBundleSummary): void {
|
||||
const index = this.bundles.findIndex((bundle) => bundle.id === updated.id)
|
||||
if (index >= 0) {
|
||||
this.bundles = [
|
||||
...this.bundles.slice(0, index),
|
||||
updated,
|
||||
...this.bundles.slice(index + 1),
|
||||
]
|
||||
} else {
|
||||
this.bundles = [updated, ...this.bundles]
|
||||
}
|
||||
}
|
||||
|
||||
private triggerRefresh(silent: boolean): void {
|
||||
this.refresh$.next(silent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,13 @@ export class ShareBundleService extends AbstractNameFilterService<ShareBundleSum
|
||||
this.clearCache()
|
||||
return this.http.post<ShareBundleSummary>(this.getResourceUrl(), payload)
|
||||
}
|
||||
rebuildBundle(bundleId: number): Observable<ShareBundleSummary> {
|
||||
this.clearCache()
|
||||
return this.http.post<ShareBundleSummary>(
|
||||
this.getResourceUrl(bundleId, 'rebuild'),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
listAllBundles(): Observable<ShareBundleSummary[]> {
|
||||
return this.list(1, 1000, 'created', true).pipe(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user