From 87c2efef4d09a8030db5acbf97b87e90017534a5 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 10 Feb 2024 23:47:08 -0800 Subject: [PATCH] Frontend system status --- src-ui/messages.xlf | 235 ++++++++++++++---- src-ui/package-lock.json | 23 ++ src-ui/package.json | 1 + src-ui/src/app/app.module.ts | 10 + .../app-frame/app-frame.component.html | 4 + .../app-frame/app-frame.component.spec.ts | 7 + .../app-frame/app-frame.component.ts | 5 + .../system-status-dialog.component.html | 101 ++++++++ .../system-status-dialog.component.scss | 0 .../system-status-dialog.component.spec.ts | 99 ++++++++ .../system-status-dialog.component.ts | 38 +++ src-ui/src/app/data/system-status.ts | 34 +++ .../services/system-status.service.spec.ts | 35 +++ .../src/app/services/system-status.service.ts | 20 ++ 14 files changed, 564 insertions(+), 48 deletions(-) create mode 100644 src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.html create mode 100644 src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.scss create mode 100644 src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts create mode 100644 src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts create mode 100644 src-ui/src/app/data/system-status.ts create mode 100644 src-ui/src/app/services/system-status.service.spec.ts create mode 100644 src-ui/src/app/services/system-status.service.ts diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index ca23684f8..c59e120c0 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -563,11 +563,11 @@ src/app/components/app-frame/app-frame.component.html - 267 + 271 src/app/components/app-frame/app-frame.component.html - 270 + 274 @@ -622,6 +622,10 @@ src/app/components/common/permissions-dialog/permissions-dialog.component.html 23 + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 10 + src/app/components/dashboard/dashboard.component.html 15 @@ -675,11 +679,11 @@ src/app/components/app-frame/app-frame.component.html - 233 + 237 src/app/components/app-frame/app-frame.component.html - 235 + 239 @@ -1169,7 +1173,7 @@ src/app/components/app-frame/app-frame.component.html - 106 + 110 @@ -1579,7 +1583,7 @@ src/app/components/app-frame/app-frame.component.ts - 140 + 141 @@ -1597,11 +1601,11 @@ src/app/components/app-frame/app-frame.component.html - 256 + 260 src/app/components/app-frame/app-frame.component.html - 258 + 262 @@ -1795,11 +1799,11 @@ src/app/components/app-frame/app-frame.component.html - 247 + 251 src/app/components/app-frame/app-frame.component.html - 249 + 253 @@ -2151,37 +2155,48 @@ 55 + + System Status + + src/app/components/app-frame/app-frame.component.html + 63 + + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 2 + + Logout src/app/components/app-frame/app-frame.component.html - 62 + 66 Documentation src/app/components/app-frame/app-frame.component.html - 67 + 71 src/app/components/app-frame/app-frame.component.html - 275 + 279 src/app/components/app-frame/app-frame.component.html - 278 + 282 Dashboard src/app/components/app-frame/app-frame.component.html - 90 + 94 src/app/components/app-frame/app-frame.component.html - 92 + 96 src/app/components/dashboard/dashboard.component.html @@ -2192,11 +2207,11 @@ Documents src/app/components/app-frame/app-frame.component.html - 97 + 101 src/app/components/app-frame/app-frame.component.html - 99 + 103 src/app/components/document-list/document-list.component.ts @@ -2223,36 +2238,36 @@ Open documents src/app/components/app-frame/app-frame.component.html - 136 + 140 Close all src/app/components/app-frame/app-frame.component.html - 156 + 160 src/app/components/app-frame/app-frame.component.html - 158 + 162 Manage src/app/components/app-frame/app-frame.component.html - 166 + 170 Correspondents src/app/components/app-frame/app-frame.component.html - 172 + 176 src/app/components/app-frame/app-frame.component.html - 174 + 178 src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html @@ -2263,11 +2278,11 @@ Tags src/app/components/app-frame/app-frame.component.html - 179 + 183 src/app/components/app-frame/app-frame.component.html - 182 + 186 src/app/components/common/input/tags/tags.component.ts @@ -2294,11 +2309,11 @@ Document Types src/app/components/app-frame/app-frame.component.html - 188 + 192 src/app/components/app-frame/app-frame.component.html - 190 + 194 src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html @@ -2309,11 +2324,11 @@ Storage Paths src/app/components/app-frame/app-frame.component.html - 195 + 199 src/app/components/app-frame/app-frame.component.html - 197 + 201 src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html @@ -2324,11 +2339,11 @@ Custom Fields src/app/components/app-frame/app-frame.component.html - 202 + 206 src/app/components/app-frame/app-frame.component.html - 204 + 208 src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.html @@ -2343,11 +2358,11 @@ Workflows src/app/components/app-frame/app-frame.component.html - 211 + 215 src/app/components/app-frame/app-frame.component.html - 213 + 217 src/app/components/manage/workflows/workflows.component.html @@ -2358,92 +2373,92 @@ Mail src/app/components/app-frame/app-frame.component.html - 218 + 222 src/app/components/app-frame/app-frame.component.html - 221 + 225 Administration src/app/components/app-frame/app-frame.component.html - 227 + 231 Configuration src/app/components/app-frame/app-frame.component.html - 240 + 244 src/app/components/app-frame/app-frame.component.html - 242 + 246 GitHub src/app/components/app-frame/app-frame.component.html - 285 + 289 is available. src/app/components/app-frame/app-frame.component.html - 294,295 + 298,299 Click to view. src/app/components/app-frame/app-frame.component.html - 295 + 299 Paperless-ngx can automatically check for updates src/app/components/app-frame/app-frame.component.html - 299 + 303 How does this work? src/app/components/app-frame/app-frame.component.html - 306,308 + 310,312 Update available src/app/components/app-frame/app-frame.component.html - 319 + 323 Sidebar views updated src/app/components/app-frame/app-frame.component.ts - 282 + 283 Error updating sidebar views src/app/components/app-frame/app-frame.component.ts - 285 + 286 An error occurred while saving update checking settings. src/app/components/app-frame/app-frame.component.ts - 306 + 307 @@ -4139,6 +4154,10 @@ src/app/components/common/share-links-dropdown/share-links-dropdown.component.html 29 + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 96 + Regenerate auth token @@ -4370,6 +4389,126 @@ 151 + + General Info + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 14 + + + + Paperless-ngx Version: + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 16 + + + + Install Type: + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 18 + + + + Server OS: + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 20 + + + + Media Storage: + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 22 + + + + available + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 23 + + + + total + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 23 + + + + Database + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 26 + + + + Type: + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 28 + + + + URL: + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 30 + + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 74 + + + + Status: + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 32 + + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 76 + + + + Migration Status: + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 41 + + + + Latest Migration + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 49 + + + + Pending Migrations + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 51 + + + + Pending Migrations: + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 61 + + + + Redis + + src/app/components/common/system-status-dialog/system-status-dialog.component.html + 72 + + Status diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index 16fda59f0..0978ca81a 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -29,6 +29,7 @@ "ngx-color": "^9.0.0", "ngx-cookie-service": "^17.1.0", "ngx-file-drop": "^16.0.0", + "ngx-filesize": "^3.0.3", "ngx-ui-tour-ng-bootstrap": "^14.0.2", "pdfjs-dist": "^3.11.174", "rxjs": "^7.8.1", @@ -9844,6 +9845,15 @@ "node": ">=10" } }, + "node_modules/filesize": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-9.0.11.tgz", + "integrity": "sha512-gTAiTtI0STpKa5xesyTA9hA3LX4ga8sm2nWRcffEa1L/5vQwb4mj2MdzMkoHoGv4QzfDshQZuYscQSf8c4TKOA==", + "peer": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -14105,6 +14115,19 @@ "@angular/core": ">=14.0.0" } }, + "node_modules/ngx-filesize": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ngx-filesize/-/ngx-filesize-3.0.3.tgz", + "integrity": "sha512-qqP2p4WbbF7R+NXC9NqRQdAfWfMAYJ2Ijf4ezRCq7j3tPY6ybSP9AZ3FY1U7/95n1hmOJ2U5oY+oFb7LhHQRBw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">= 14.2.0 < 18.0.0", + "@angular/core": ">= 14.2.0 < 18.0.0", + "filesize": ">= 6.0.0 < 10.0.0" + } + }, "node_modules/ngx-ui-tour-core": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/ngx-ui-tour-core/-/ngx-ui-tour-core-12.0.1.tgz", diff --git a/src-ui/package.json b/src-ui/package.json index 1ee957e04..be12c3ad4 100644 --- a/src-ui/package.json +++ b/src-ui/package.json @@ -31,6 +31,7 @@ "ngx-color": "^9.0.0", "ngx-cookie-service": "^17.1.0", "ngx-file-drop": "^16.0.0", + "ngx-filesize": "^3.0.3", "ngx-ui-tour-ng-bootstrap": "^14.0.2", "pdfjs-dist": "^3.11.174", "rxjs": "^7.8.1", diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index 69213846f..1f9814a38 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -114,6 +114,8 @@ import { FileComponent } from './components/common/input/file/file.component' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { ConfirmButtonComponent } from './components/common/confirm-button/confirm-button.component' import { MonetaryComponent } from './components/common/input/monetary/monetary.component' +import { SystemStatusDialogComponent } from './components/common/system-status-dialog/system-status-dialog.component' +import { NgxFilesizeModule } from 'ngx-filesize' import { archive, arrowCounterclockwise, @@ -129,12 +131,14 @@ import { boxes, calendar, calendarEvent, + cardChecklist, caretDown, caretUp, chatLeftText, check, check2All, checkAll, + checkCircleFill, checkLg, chevronDoubleLeft, chevronDoubleRight, @@ -149,6 +153,7 @@ import { download, envelope, exclamationTriangle, + exclamationTriangleFill, eye, fileEarmark, fileEarmarkCheck, @@ -214,12 +219,14 @@ const icons = { boxes, calendar, calendarEvent, + cardChecklist, caretDown, caretUp, chatLeftText, check, check2All, checkAll, + checkCircleFill, checkLg, chevronDoubleLeft, chevronDoubleRight, @@ -234,6 +241,7 @@ const icons = { download, envelope, exclamationTriangle, + exclamationTriangleFill, eye, fileEarmark, fileEarmarkCheck, @@ -445,6 +453,7 @@ function initializeApp(settings: SettingsService) { FileComponent, ConfirmButtonComponent, MonetaryComponent, + SystemStatusDialogComponent, ], imports: [ BrowserModule, @@ -459,6 +468,7 @@ function initializeApp(settings: SettingsService) { TourNgBootstrapModule, DragDropModule, NgxBootstrapIconsModule.pick(icons), + NgxFilesizeModule, ], providers: [ { diff --git a/src-ui/src/app/components/app-frame/app-frame.component.html b/src-ui/src/app/components/app-frame/app-frame.component.html index 261c033e0..e7cafa1a4 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.html +++ b/src-ui/src/app/components/app-frame/app-frame.component.html @@ -58,6 +58,10 @@ *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }"> Settings + Logout diff --git a/src-ui/src/app/components/app-frame/app-frame.component.spec.ts b/src-ui/src/app/components/app-frame/app-frame.component.spec.ts index e1a553047..989c2780f 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.spec.ts +++ b/src-ui/src/app/components/app-frame/app-frame.component.spec.ts @@ -38,6 +38,7 @@ import { CdkDragDrop, DragDropModule } from '@angular/cdk/drag-drop' import { SavedView } from 'src/app/data/saved-view' import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' +import { SystemStatusDialogComponent } from '../common/system-status-dialog/system-status-dialog.component' const saved_views = [ { @@ -415,4 +416,10 @@ describe('AppFrameComponent', () => { expect(toastErrorSpy).toHaveBeenCalledTimes(2) expect(toastInfoSpy).toHaveBeenCalledTimes(3) }) + + it('should open system status dialog', () => { + const modalOpenSpy = jest.spyOn(modalService, 'open') + component.showSystemStatus() + expect(modalOpenSpy).toHaveBeenCalledWith(SystemStatusDialogComponent) + }) }) diff --git a/src-ui/src/app/components/app-frame/app-frame.component.ts b/src-ui/src/app/components/app-frame/app-frame.component.ts index ab9322380..159781827 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.ts +++ b/src-ui/src/app/components/app-frame/app-frame.component.ts @@ -46,6 +46,7 @@ import { } from '@angular/cdk/drag-drop' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component' +import { SystemStatusDialogComponent } from '../common/system-status-dialog/system-status-dialog.component' @Component({ selector: 'pngx-app-frame', @@ -316,4 +317,8 @@ export class AppFrameComponent onLogout() { this.openDocumentsService.closeAll() } + + showSystemStatus() { + this.modalService.open(SystemStatusDialogComponent) + } } diff --git a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.html b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.html new file mode 100644 index 000000000..acdb86b47 --- /dev/null +++ b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.html @@ -0,0 +1,101 @@ + + + diff --git a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.scss b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts new file mode 100644 index 000000000..c9d691983 --- /dev/null +++ b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.spec.ts @@ -0,0 +1,99 @@ +import { + ComponentFixture, + TestBed, + fakeAsync, + tick, +} from '@angular/core/testing' +import { + NgbActiveModal, + NgbModalModule, + NgbPopoverModule, +} from '@ng-bootstrap/ng-bootstrap' +import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard' +import { SystemStatusService } from 'src/app/services/system-status.service' +import { SystemStatusDialogComponent } from './system-status-dialog.component' +import { of } from 'rxjs' +import { + PaperlessConnectionStatus, + PaperlessInstallType, + PaperlessSystemStatus, +} from 'src/app/data/system-status' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' +import { NgxFilesizeModule } from 'ngx-filesize' + +const status: PaperlessSystemStatus = { + pngx_version: '2.4.3', + server_os: 'macOS-14.1.1-arm64-arm-64bit', + install_type: PaperlessInstallType.BareMetal, + storage: { total: 494384795648, available: 13573525504 }, + database: { + type: 'sqlite', + url: '/paperless-ngx/data/db.sqlite3', + status: PaperlessConnectionStatus.ERROR, + error: null, + migration_status: { + latest_migration: 'socialaccount.0006_alter_socialaccount_extra_data', + unapplied_migrations: [], + }, + }, + redis: { + url: 'redis://localhost:6379', + status: PaperlessConnectionStatus.ERROR, + error: 'Error 61 connecting to localhost:6379. Connection refused.', + }, +} + +describe('SystemStatusDialogComponent', () => { + let component: SystemStatusDialogComponent + let fixture: ComponentFixture + let systemStatusService: SystemStatusService + let clipboard: Clipboard + let getStatusSpy + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [SystemStatusDialogComponent], + providers: [NgbActiveModal], + imports: [ + NgbModalModule, + ClipboardModule, + HttpClientTestingModule, + NgxBootstrapIconsModule.pick(allIcons), + NgxFilesizeModule, + NgbPopoverModule, + ], + }).compileComponents() + + systemStatusService = TestBed.inject(SystemStatusService) + getStatusSpy = jest + .spyOn(systemStatusService, 'get') + .mockReturnValue(of(status)) + fixture = TestBed.createComponent(SystemStatusDialogComponent) + component = fixture.componentInstance + clipboard = TestBed.inject(Clipboard) + fixture.detectChanges() + }) + + it('should subscribe to system status service', () => { + expect(getStatusSpy).toHaveBeenCalled() + expect(component.status).toEqual(status) + }) + + it('should close the active modal', () => { + const closeSpy = jest.spyOn(component.activeModal, 'close') + component.close() + expect(closeSpy).toHaveBeenCalled() + }) + + it('should copy the system status to clipboard', fakeAsync(() => { + jest.spyOn(clipboard, 'copy') + component.copy() + expect(clipboard.copy).toHaveBeenCalledWith( + JSON.stringify(component.status) + ) + expect(component.copied).toBeTruthy() + tick(3000) + expect(component.copied).toBeFalsy() + })) +}) diff --git a/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts new file mode 100644 index 000000000..81e88200e --- /dev/null +++ b/src-ui/src/app/components/common/system-status-dialog/system-status-dialog.component.ts @@ -0,0 +1,38 @@ +import { Component } from '@angular/core' +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' +import { PaperlessSystemStatus } from 'src/app/data/system-status' +import { SystemStatusService } from 'src/app/services/system-status.service' +import { Clipboard } from '@angular/cdk/clipboard' + +@Component({ + selector: 'pngx-system-status-dialog', + templateUrl: './system-status-dialog.component.html', + styleUrl: './system-status-dialog.component.scss', +}) +export class SystemStatusDialogComponent { + public status: PaperlessSystemStatus + + public copied: boolean = false + + constructor( + public activeModal: NgbActiveModal, + private systemStatusService: SystemStatusService, + private clipboard: Clipboard + ) { + this.systemStatusService.get().subscribe((status) => { + this.status = status + }) + } + + public close() { + this.activeModal.close() + } + + public copy() { + this.clipboard.copy(JSON.stringify(this.status)) + this.copied = true + setTimeout(() => { + this.copied = false + }, 3000) + } +} diff --git a/src-ui/src/app/data/system-status.ts b/src-ui/src/app/data/system-status.ts new file mode 100644 index 000000000..c1aba913a --- /dev/null +++ b/src-ui/src/app/data/system-status.ts @@ -0,0 +1,34 @@ +export enum PaperlessInstallType { + Containerized = 'containerized', + BareMetal = 'bare-metal', +} + +export enum PaperlessConnectionStatus { + OK = 'OK', + ERROR = 'ERROR', +} + +export interface PaperlessSystemStatus { + pngx_version: string + server_os: string + install_type: PaperlessInstallType + storage: { + total: number + available: number + } + database: { + type: string + url: string + status: PaperlessConnectionStatus + error?: string + migration_status: { + latest_migration: string + unapplied_migrations: string[] + } + } + redis: { + url: string + status: PaperlessConnectionStatus + error: string + } +} diff --git a/src-ui/src/app/services/system-status.service.spec.ts b/src-ui/src/app/services/system-status.service.spec.ts new file mode 100644 index 000000000..236d21a8e --- /dev/null +++ b/src-ui/src/app/services/system-status.service.spec.ts @@ -0,0 +1,35 @@ +import { TestBed } from '@angular/core/testing' + +import { SystemStatusService } from './system-status.service' +import { + HttpClientTestingModule, + HttpTestingController, +} from '@angular/common/http/testing' +import { environment } from 'src/environments/environment' + +describe('SystemStatusService', () => { + let httpTestingController: HttpTestingController + let service: SystemStatusService + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [SystemStatusService], + imports: [HttpClientTestingModule], + }) + + httpTestingController = TestBed.inject(HttpTestingController) + service = TestBed.inject(SystemStatusService) + }) + + afterEach(() => { + httpTestingController.verify() + }) + + it('calls get statys endpoint', () => { + service.get().subscribe() + const req = httpTestingController.expectOne( + `${environment.apiBaseUrl}status/` + ) + expect(req.request.method).toEqual('GET') + }) +}) diff --git a/src-ui/src/app/services/system-status.service.ts b/src-ui/src/app/services/system-status.service.ts new file mode 100644 index 000000000..2a538658f --- /dev/null +++ b/src-ui/src/app/services/system-status.service.ts @@ -0,0 +1,20 @@ +import { HttpClient } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { Observable } from 'rxjs' +import { PaperlessSystemStatus } from '../data/system-status' +import { environment } from 'src/environments/environment' + +@Injectable({ + providedIn: 'root', +}) +export class SystemStatusService { + private endpoint = 'status' + + constructor(private http: HttpClient) {} + + get(): Observable { + return this.http.get( + `${environment.apiBaseUrl}${this.endpoint}/` + ) + } +}