mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-08-28 21:47:34 +00:00
Chorehancement: update to Angular v22, 'zoneless' / 'reactive' (#13114)
This commit is contained in:
+70
-70
@@ -3,7 +3,7 @@
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@if (!status) {
|
||||
@if (!status()) {
|
||||
<div class="w-100 h-100 d-flex align-items-center justify-content-center">
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
@@ -21,25 +21,25 @@
|
||||
<dl class="card-text">
|
||||
<dt i18n>Paperless-ngx Version</dt>
|
||||
<dd>
|
||||
{{status.pngx_version}}
|
||||
@if (versionMismatch) {
|
||||
{{status().pngx_version}}
|
||||
@if (versionMismatch()) {
|
||||
<button class="btn btn-sm d-inline align-items-center btn-dark text-uppercase small" [ngbPopover]="versionPopover" triggers="click mouseenter:mouseleave">
|
||||
<i-bs name="exclamation-triangle-fill" class="text-danger lh-1"></i-bs>
|
||||
</button>
|
||||
}
|
||||
<ng-template #versionPopover>
|
||||
Frontend version: {{frontendVersion}}<br>
|
||||
Backend version: {{status.pngx_version}}
|
||||
Backend version: {{status().pngx_version}}
|
||||
</ng-template>
|
||||
</dd>
|
||||
<dt i18n>Install Type</dt>
|
||||
<dd>{{status.install_type}}</dd>
|
||||
<dd>{{status().install_type}}</dd>
|
||||
<dt i18n>Server OS</dt>
|
||||
<dd>{{status.server_os}}</dd>
|
||||
<dd>{{status().server_os}}</dd>
|
||||
<dt i18n>Media Storage</dt>
|
||||
<dd>
|
||||
<ngb-progressbar style="height: 4px;" class="mt-2 mb-1" type="primary" [max]="status.storage.total" [value]="status.storage.total - status.storage.available"></ngb-progressbar>
|
||||
<span class="small">{{status.storage.available | fileSize}} <ng-container i18n>available</ng-container> ({{status.storage.total | fileSize}} <ng-container i18n>total</ng-container>)</span>
|
||||
<ngb-progressbar style="height: 4px;" class="mt-2 mb-1" type="primary" [max]="status().storage.total" [value]="status().storage.total - status().storage.available"></ngb-progressbar>
|
||||
<span class="small">{{status().storage.available | fileSize}} <ng-container i18n>available</ng-container> ({{status().storage.total | fileSize}} <ng-container i18n>total</ng-container>)</span>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
@@ -54,39 +54,39 @@
|
||||
<div class="card-body">
|
||||
<dl class="card-text">
|
||||
<dt i18n>Type</dt>
|
||||
<dd>{{status.database.type}}</dd>
|
||||
<dd>{{status().database.type}}</dd>
|
||||
<dt i18n>Status</dt>
|
||||
<dd>
|
||||
<button class="btn btn-sm d-flex align-items-center btn-dark text-uppercase small" [ngbPopover]="databaseStatus" triggers="click mouseenter:mouseleave">
|
||||
{{status.database.status}}
|
||||
@if (status.database.status === 'OK') {
|
||||
{{status().database.status}}
|
||||
@if (status().database.status === 'OK') {
|
||||
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
|
||||
} @else {
|
||||
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1"></i-bs>
|
||||
}
|
||||
</button>
|
||||
<ng-template #databaseStatus>
|
||||
@if (status.database.status === 'OK') {
|
||||
{{status.database.url}}
|
||||
@if (status().database.status === 'OK') {
|
||||
{{status().database.url}}
|
||||
} @else {
|
||||
{{status.database.url}}: {{status.database.error}}
|
||||
{{status().database.url}}: {{status().database.error}}
|
||||
}
|
||||
</ng-template>
|
||||
</dd>
|
||||
<dt i18n>Migration Status</dt>
|
||||
<dd>
|
||||
<button class="btn btn-sm d-flex align-items-center btn-dark text-uppercase small" [ngbPopover]="migrationStatus" triggers="click mouseenter:mouseleave">
|
||||
@if (status.database.migration_status.unapplied_migrations.length === 0) {
|
||||
@if (status().database.migration_status.unapplied_migrations.length === 0) {
|
||||
<ng-container i18n>Up to date</ng-container><i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
|
||||
} @else {
|
||||
<ng-container>{{status.database.migration_status.unapplied_migrations.length}} Pending</ng-container><i-bs name="exclamation-triangle-fill" class="text-warning ms-2 lh-1"></i-bs>
|
||||
<ng-container>{{status().database.migration_status.unapplied_migrations.length}} Pending</ng-container><i-bs name="exclamation-triangle-fill" class="text-warning ms-2 lh-1"></i-bs>
|
||||
}
|
||||
<ng-template #migrationStatus>
|
||||
<h6><ng-container i18n>Latest Migration</ng-container>:</h6> <span class="font-monospace small">{{status.database.migration_status.latest_migration}}</span>
|
||||
@if (status.database.migration_status.unapplied_migrations.length > 0) {
|
||||
<h6><ng-container i18n>Latest Migration</ng-container>:</h6> <span class="font-monospace small">{{status().database.migration_status.latest_migration}}</span>
|
||||
@if (status().database.migration_status.unapplied_migrations.length > 0) {
|
||||
<h6 class="mt-3"><ng-container i18n>Pending Migrations</ng-container>:</h6>
|
||||
<ul>
|
||||
@for (migration of status.database.migration_status.unapplied_migrations; track migration) {
|
||||
@for (migration of status().database.migration_status.unapplied_migrations; track migration) {
|
||||
<li class="font-monospace small">{{migration}}</li>
|
||||
}
|
||||
</ul>
|
||||
@@ -109,60 +109,60 @@
|
||||
<dt i18n>Redis Status</dt>
|
||||
<dd>
|
||||
<button class="btn btn-sm d-flex align-items-center btn-dark text-uppercase small" [ngbPopover]="redisStatus" triggers="click mouseenter:mouseleave">
|
||||
{{status.tasks.redis_status}}
|
||||
@if (status.tasks.redis_status === 'OK') {
|
||||
{{status().tasks.redis_status}}
|
||||
@if (status().tasks.redis_status === 'OK') {
|
||||
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
|
||||
} @else {
|
||||
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1"></i-bs>
|
||||
}
|
||||
</button>
|
||||
<ng-template #redisStatus>
|
||||
@if (status.tasks.redis_status === 'OK') {
|
||||
{{status.tasks.redis_url}}
|
||||
@if (status().tasks.redis_status === 'OK') {
|
||||
{{status().tasks.redis_url}}
|
||||
} @else {
|
||||
{{status.tasks.redis_url}}: {{status.tasks.redis_error}}
|
||||
{{status().tasks.redis_url}}: {{status().tasks.redis_error}}
|
||||
}
|
||||
</ng-template>
|
||||
</dd>
|
||||
<dt i18n>Celery Status</dt>
|
||||
<dd>
|
||||
<button class="btn btn-sm d-flex align-items-center btn-dark text-uppercase small" [ngbPopover]="celeryStatus" triggers="click mouseenter:mouseleave">
|
||||
{{status.tasks.celery_status}}
|
||||
@if (status.tasks.celery_status === 'OK') {
|
||||
{{status().tasks.celery_status}}
|
||||
@if (status().tasks.celery_status === 'OK') {
|
||||
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
|
||||
} @else {
|
||||
<i-bs name="exclamation-triangle-fill" class="ms-2 lh-1"
|
||||
[class.text-danger]="status.tasks.celery_status === SystemStatusItemStatus.ERROR"
|
||||
[class.text-warning]="status.tasks.celery_status === SystemStatusItemStatus.WARNING"></i-bs>
|
||||
[class.text-danger]="status().tasks.celery_status === SystemStatusItemStatus.ERROR"
|
||||
[class.text-warning]="status().tasks.celery_status === SystemStatusItemStatus.WARNING"></i-bs>
|
||||
}
|
||||
</button>
|
||||
<ng-template #celeryStatus>
|
||||
@if (status.tasks.celery_status === 'OK') {
|
||||
{{status.tasks.celery_url}}
|
||||
@if (status().tasks.celery_status === 'OK') {
|
||||
{{status().tasks.celery_url}}
|
||||
} @else {
|
||||
{{status.tasks.celery_error}}
|
||||
{{status().tasks.celery_error}}
|
||||
}
|
||||
</ng-template>
|
||||
</dd>
|
||||
<dt i18n>Recent Task Activity <span class="small text-muted fw-light">({{status.tasks.summary.days}} days)</span></dt>
|
||||
<dt i18n>Recent Task Activity <span class="small text-muted fw-light">({{status().tasks.summary.days}} days)</span></dt>
|
||||
<dd class="mb-0">
|
||||
@if (status.tasks.summary.total_count > 0) {
|
||||
@if (status().tasks.summary.total_count > 0) {
|
||||
<ul class="list-group border-light mt-2">
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span class="small"><ng-container i18n>Total</ng-container>:</span>
|
||||
<span class="badge bg-light rounded-pill">{{status.tasks.summary.total_count}}</span>
|
||||
<span class="badge bg-light rounded-pill">{{status().tasks.summary.total_count}}</span>
|
||||
</li>
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span class="small"><ng-container i18n>Successful</ng-container>:</span>
|
||||
<span class="badge bg-primary rounded-pill">{{status.tasks.summary.success_count}}</span>
|
||||
<span class="badge bg-primary rounded-pill">{{status().tasks.summary.success_count}}</span>
|
||||
</li>
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span class="small"><ng-container i18n>Failed</ng-container>:</span>
|
||||
<span class="badge bg-danger rounded-pill">{{status.tasks.summary.failure_count}}</span>
|
||||
<span class="badge bg-danger rounded-pill">{{status().tasks.summary.failure_count}}</span>
|
||||
</li>
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span class="small"><ng-container i18n>Pending</ng-container>:</span>
|
||||
<span class="badge bg-warning rounded-pill">{{status.tasks.summary.pending_count}}</span>
|
||||
<span class="badge bg-warning rounded-pill">{{status().tasks.summary.pending_count}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
} @else {
|
||||
@@ -184,8 +184,8 @@
|
||||
<dt i18n>Search Index</dt>
|
||||
<dd class="d-flex align-items-center">
|
||||
<button class="btn btn-sm d-flex align-items-center btn-dark text-uppercase small" [ngbPopover]="indexStatus" triggers="click mouseenter:mouseleave">
|
||||
{{status.tasks.index_status}}
|
||||
@if (status.tasks.index_status === 'OK') {
|
||||
{{status().tasks.index_status}}
|
||||
@if (status().tasks.index_status === 'OK') {
|
||||
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
|
||||
} @else {
|
||||
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1"></i-bs>
|
||||
@@ -193,26 +193,26 @@
|
||||
</button>
|
||||
</dd>
|
||||
<ng-template #indexStatus>
|
||||
@if (status.tasks.index_status === 'OK') {
|
||||
<h6><ng-container i18n>Last Updated</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.index_last_modified | customDate:'medium'}}</span>
|
||||
@if (status().tasks.index_status === 'OK') {
|
||||
<h6><ng-container i18n>Last Updated</ng-container>:</h6> <span class="font-monospace small">{{status().tasks.index_last_modified | customDate:'medium'}}</span>
|
||||
} @else {
|
||||
<h6><ng-container i18n>Error</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.index_error}}</span>
|
||||
<h6><ng-container i18n>Error</ng-container>:</h6> <span class="font-monospace small">{{status().tasks.index_error}}</span>
|
||||
}
|
||||
</ng-template>
|
||||
<dt i18n>Classifier</dt>
|
||||
<dd class="d-flex align-items-center">
|
||||
<button class="btn btn-sm d-flex align-items-center btn-dark text-uppercase small" [ngbPopover]="classifierStatus" triggers="click mouseenter:mouseleave">
|
||||
{{status.tasks.classifier_status}}
|
||||
@if (status.tasks.classifier_status === 'OK') {
|
||||
@if (isStale(status.tasks.classifier_last_trained)) {
|
||||
{{status().tasks.classifier_status}}
|
||||
@if (status().tasks.classifier_status === 'OK') {
|
||||
@if (isStale(status().tasks.classifier_last_trained)) {
|
||||
<i-bs name="exclamation-triangle-fill" class="text-warning ms-2 lh-1"></i-bs>
|
||||
} @else {
|
||||
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
|
||||
}
|
||||
} @else {
|
||||
<i-bs name="exclamation-triangle-fill" class="ms-2 lh-1"
|
||||
[class.text-danger]="status.tasks.classifier_status === SystemStatusItemStatus.ERROR"
|
||||
[class.text-warning]="status.tasks.classifier_status === SystemStatusItemStatus.WARNING"></i-bs>
|
||||
[class.text-danger]="status().tasks.classifier_status === SystemStatusItemStatus.ERROR"
|
||||
[class.text-warning]="status().tasks.classifier_status === SystemStatusItemStatus.WARNING"></i-bs>
|
||||
}
|
||||
</button>
|
||||
@if (currentUserIsSuperUser) {
|
||||
@@ -227,26 +227,26 @@
|
||||
}
|
||||
</dd>
|
||||
<ng-template #classifierStatus>
|
||||
@if (status.tasks.classifier_status === 'OK') {
|
||||
<h6><ng-container i18n>Last Trained</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.classifier_last_trained | customDate:'medium'}}</span>
|
||||
@if (status().tasks.classifier_status === 'OK') {
|
||||
<h6><ng-container i18n>Last Trained</ng-container>:</h6> <span class="font-monospace small">{{status().tasks.classifier_last_trained | customDate:'medium'}}</span>
|
||||
} @else {
|
||||
<h6><ng-container i18n>Error</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.classifier_error}}</span>
|
||||
<h6><ng-container i18n>Error</ng-container>:</h6> <span class="font-monospace small">{{status().tasks.classifier_error}}</span>
|
||||
}
|
||||
</ng-template>
|
||||
<dt i18n>Sanity Checker</dt>
|
||||
<dd class="d-flex align-items-center">
|
||||
<button class="btn btn-sm d-flex align-items-center btn-dark text-uppercase small" [ngbPopover]="sanityCheckerStatus" triggers="click mouseenter:mouseleave">
|
||||
{{status.tasks.sanity_check_status}}
|
||||
@if (status.tasks.sanity_check_status === 'OK') {
|
||||
@if (isStale(status.tasks.sanity_check_last_run)) {
|
||||
{{status().tasks.sanity_check_status}}
|
||||
@if (status().tasks.sanity_check_status === 'OK') {
|
||||
@if (isStale(status().tasks.sanity_check_last_run)) {
|
||||
<i-bs name="exclamation-triangle-fill" class="text-warning ms-2 lh-1"></i-bs>
|
||||
} @else {
|
||||
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
|
||||
}
|
||||
} @else {
|
||||
<i-bs name="exclamation-triangle-fill" class="ms-2 lh-1"
|
||||
[class.text-danger]="status.tasks.sanity_check_status === SystemStatusItemStatus.ERROR"
|
||||
[class.text-warning]="status.tasks.sanity_check_status === SystemStatusItemStatus.WARNING"></i-bs>
|
||||
[class.text-danger]="status().tasks.sanity_check_status === SystemStatusItemStatus.ERROR"
|
||||
[class.text-warning]="status().tasks.sanity_check_status === SystemStatusItemStatus.WARNING"></i-bs>
|
||||
}
|
||||
</button>
|
||||
@if (currentUserIsSuperUser) {
|
||||
@@ -261,16 +261,16 @@
|
||||
}
|
||||
</dd>
|
||||
<ng-template #sanityCheckerStatus>
|
||||
@if (status.tasks.sanity_check_status === 'OK') {
|
||||
<h6><ng-container i18n>Last Run</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.sanity_check_last_run | customDate:'medium'}}</span>
|
||||
@if (status().tasks.sanity_check_status === 'OK') {
|
||||
<h6><ng-container i18n>Last Run</ng-container>:</h6> <span class="font-monospace small">{{status().tasks.sanity_check_last_run | customDate:'medium'}}</span>
|
||||
} @else {
|
||||
<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>
|
||||
<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') {
|
||||
@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 {
|
||||
@@ -283,18 +283,18 @@
|
||||
<dt i18n>AI Index</dt>
|
||||
<dd class="d-flex align-items-center">
|
||||
<button class="btn btn-sm d-flex align-items-center btn-dark text-uppercase small" [ngbPopover]="llmIndexStatus" triggers="click mouseenter:mouseleave">
|
||||
{{status.tasks.llmindex_status}}
|
||||
@if (status.tasks.llmindex_status === 'OK') {
|
||||
@if (isStale(status.tasks.llmindex_last_modified)) {
|
||||
{{status().tasks.llmindex_status}}
|
||||
@if (status().tasks.llmindex_status === 'OK') {
|
||||
@if (isStale(status().tasks.llmindex_last_modified)) {
|
||||
<i-bs name="exclamation-triangle-fill" class="text-warning ms-2 lh-1"></i-bs>
|
||||
} @else {
|
||||
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
|
||||
}
|
||||
} @else {
|
||||
<i-bs name="exclamation-triangle-fill" class="ms-2 lh-1"
|
||||
[class.text-danger]="status.tasks.llmindex_status === SystemStatusItemStatus.ERROR"
|
||||
[class.text-warning]="status.tasks.llmindex_status === SystemStatusItemStatus.WARNING"
|
||||
[class.text-muted]="status.tasks.llmindex_status === SystemStatusItemStatus.DISABLED"></i-bs>
|
||||
[class.text-danger]="status().tasks.llmindex_status === SystemStatusItemStatus.ERROR"
|
||||
[class.text-warning]="status().tasks.llmindex_status === SystemStatusItemStatus.WARNING"
|
||||
[class.text-muted]="status().tasks.llmindex_status === SystemStatusItemStatus.DISABLED"></i-bs>
|
||||
}
|
||||
</button>
|
||||
@if (currentUserIsSuperUser) {
|
||||
@@ -309,10 +309,10 @@
|
||||
}
|
||||
</dd>
|
||||
<ng-template #llmIndexStatus>
|
||||
@if (status.tasks.llmindex_status === 'OK') {
|
||||
<h6><ng-container i18n>Last Run</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.llmindex_last_modified | customDate:'medium'}}</span>
|
||||
@if (status().tasks.llmindex_status === 'OK') {
|
||||
<h6><ng-container i18n>Last Run</ng-container>:</h6> <span class="font-monospace small">{{status().tasks.llmindex_last_modified | customDate:'medium'}}</span>
|
||||
} @else {
|
||||
<h6><ng-container i18n>Error</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.llmindex_error}}</span>
|
||||
<h6><ng-container i18n>Error</ng-container>:</h6> <span class="font-monospace small">{{status().tasks.llmindex_error}}</span>
|
||||
}
|
||||
</ng-template>
|
||||
}
|
||||
@@ -325,10 +325,10 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-sm d-flex align-items-center btn-dark btn btn-sm d-flex align-items-center btn-dark btn-outline-secondary" (click)="copy()">
|
||||
@if (!copied) {
|
||||
@if (!copied()) {
|
||||
<i-bs name="clipboard-fill" class="me-1"></i-bs>
|
||||
}
|
||||
@if (copied) {
|
||||
@if (copied()) {
|
||||
<i-bs name="clipboard-check-fill" class="me-1"></i-bs>
|
||||
}
|
||||
<ng-container i18n>Copy</ng-container>
|
||||
|
||||
+20
-20
@@ -16,12 +16,7 @@ jest.mock('src/environments/environment', () => ({
|
||||
import { Clipboard } from '@angular/cdk/clipboard'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { Subject, of, throwError } from 'rxjs'
|
||||
@@ -106,7 +101,7 @@ describe('SystemStatusDialogComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(SystemStatusDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
component.status = status
|
||||
component.status.set({ ...status })
|
||||
clipboard = TestBed.inject(Clipboard)
|
||||
tasksService = TestBed.inject(TasksService)
|
||||
systemStatusService = TestBed.inject(SystemStatusService)
|
||||
@@ -126,16 +121,18 @@ describe('SystemStatusDialogComponent', () => {
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should copy the system status to clipboard', fakeAsync(() => {
|
||||
it('should copy the system status to clipboard', () => {
|
||||
jest.useFakeTimers()
|
||||
jest.spyOn(clipboard, 'copy')
|
||||
component.copy()
|
||||
expect(clipboard.copy).toHaveBeenCalledWith(
|
||||
JSON.stringify(component.status, null, 4)
|
||||
JSON.stringify(component.status(), null, 4)
|
||||
)
|
||||
expect(component.copied).toBeTruthy()
|
||||
tick(3000)
|
||||
expect(component.copied).toBeFalsy()
|
||||
}))
|
||||
expect(component.copied()).toBeTruthy()
|
||||
jest.advanceTimersByTime(3000)
|
||||
expect(component.copied()).toBeFalsy()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('should calculate if date is stale', () => {
|
||||
const date = new Date()
|
||||
@@ -180,25 +177,28 @@ describe('SystemStatusDialogComponent', () => {
|
||||
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)')
|
||||
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.status.update((status) => ({
|
||||
...status,
|
||||
pngx_version: '2.4.3',
|
||||
}))
|
||||
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(
|
||||
expect(component.status().websocket_connected).toEqual(
|
||||
SystemStatusItemStatus.OK
|
||||
)
|
||||
websocketSubject.next(false)
|
||||
expect(component.status.websocket_connected).toEqual(
|
||||
expect(component.status().websocket_connected).toEqual(
|
||||
SystemStatusItemStatus.ERROR
|
||||
)
|
||||
websocketSubject.next(true)
|
||||
expect(component.status.websocket_connected).toEqual(
|
||||
expect(component.status().websocket_connected).toEqual(
|
||||
SystemStatusItemStatus.OK
|
||||
)
|
||||
})
|
||||
|
||||
+56
-30
@@ -1,5 +1,12 @@
|
||||
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core'
|
||||
import {
|
||||
NgbActiveModal,
|
||||
NgbModalModule,
|
||||
@@ -50,13 +57,11 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
public SystemStatusItemStatus = SystemStatusItemStatus
|
||||
public PaperlessTaskType = PaperlessTaskType
|
||||
public status: SystemStatus
|
||||
@Input() status = signal<SystemStatus>(undefined)
|
||||
public frontendVersion: string = environment.version
|
||||
public versionMismatch: boolean = false
|
||||
|
||||
public copied: boolean = false
|
||||
|
||||
private runningTasks: Set<PaperlessTaskType> = new Set()
|
||||
readonly versionMismatch = signal(false)
|
||||
readonly copied = signal(false)
|
||||
readonly runningTasks = signal<Set<PaperlessTaskType>>(new Set())
|
||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
get currentUserIsSuperUser(): boolean {
|
||||
@@ -68,25 +73,24 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.versionMismatch =
|
||||
const status = this.status()
|
||||
this.versionMismatch.set(
|
||||
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})`
|
||||
status.pngx_version &&
|
||||
this.frontendVersion &&
|
||||
status.pngx_version !== this.frontendVersion
|
||||
)
|
||||
if (this.versionMismatch()) {
|
||||
this.status.set({
|
||||
...status,
|
||||
pngx_version: `${status.pngx_version} (frontend: ${this.frontendVersion})`,
|
||||
})
|
||||
}
|
||||
this.status.websocket_connected = this.websocketStatusService.isConnected()
|
||||
? SystemStatusItemStatus.OK
|
||||
: SystemStatusItemStatus.ERROR
|
||||
this.updateWebsocketStatus(this.websocketStatusService.isConnected())
|
||||
this.websocketStatusService
|
||||
.onConnectionStatus()
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe((connected) => {
|
||||
this.status.websocket_connected = connected
|
||||
? SystemStatusItemStatus.OK
|
||||
: SystemStatusItemStatus.ERROR
|
||||
})
|
||||
.subscribe((connected) => this.updateWebsocketStatus(connected))
|
||||
}
|
||||
|
||||
public close() {
|
||||
@@ -94,10 +98,10 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public copy() {
|
||||
this.clipboard.copy(JSON.stringify(this.status, null, 4))
|
||||
this.copied = true
|
||||
this.clipboard.copy(JSON.stringify(this.status(), null, 4))
|
||||
this.copied.set(true)
|
||||
setTimeout(() => {
|
||||
this.copied = false
|
||||
this.copied.set(false)
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
@@ -108,23 +112,26 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public isRunning(taskName: PaperlessTaskType): boolean {
|
||||
return this.runningTasks.has(taskName)
|
||||
return this.runningTasks().has(taskName)
|
||||
}
|
||||
|
||||
public runTask(taskName: PaperlessTaskType) {
|
||||
this.runningTasks.add(taskName)
|
||||
this.setTaskRunning(taskName, true)
|
||||
this.toastService.showInfo(`Task ${taskName} started`)
|
||||
this.tasksService.run(taskName).subscribe({
|
||||
next: () => {
|
||||
this.runningTasks.delete(taskName)
|
||||
this.setTaskRunning(taskName, false)
|
||||
this.systemStatusService.get().subscribe({
|
||||
next: (status) => {
|
||||
Object.assign(this.status, status)
|
||||
next: (statusUpdate) => {
|
||||
this.status.set({
|
||||
...this.status(),
|
||||
...statusUpdate,
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
error: (err) => {
|
||||
this.runningTasks.delete(taskName)
|
||||
this.setTaskRunning(taskName, false)
|
||||
this.toastService.showError(
|
||||
`Failed to start task ${taskName}, see the logs for more details`,
|
||||
err
|
||||
@@ -133,6 +140,25 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
}
|
||||
|
||||
private updateWebsocketStatus(connected: boolean): void {
|
||||
this.status.set({
|
||||
...this.status(),
|
||||
websocket_connected: connected
|
||||
? SystemStatusItemStatus.OK
|
||||
: SystemStatusItemStatus.ERROR,
|
||||
})
|
||||
}
|
||||
|
||||
private setTaskRunning(taskName: PaperlessTaskType, running: boolean): void {
|
||||
const runningTasks = new Set(this.runningTasks())
|
||||
if (running) {
|
||||
runningTasks.add(taskName)
|
||||
} else {
|
||||
runningTasks.delete(taskName)
|
||||
}
|
||||
this.runningTasks.set(runningTasks)
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.unsubscribeNotifier.next(this)
|
||||
this.unsubscribeNotifier.complete()
|
||||
|
||||
Reference in New Issue
Block a user