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 @@
+
+
+ @if (!status) {
+
+ } @else {
+
General Info
+
+ - Paperless-ngx Version:
+ - {{status.pngx_version}}
+ - Install Type:
+ - {{status.install_type}}
+ - Server OS:
+ - {{status.server_os}}
+ - Media Storage:
+ -
+
+ {{status.storage.available | filesize}} available ({{status.storage.total | filesize}} total)
+
+
+
+
Database
+
+ - Type:
+ - {{status.database.type}}
+ - URL:
+ - {{status.database.url}}
+ - Status:
+ -
+ {{status.database.status}}
+ @if (status.database.status === 'OK') {
+
+ } @else {
+
+ }
+
+ - Migration Status:
+ -
+ @if (status.database.migration_status.unapplied_migrations.length === 0) {
+ Up to date
+ } @else {
+ {{status.database.migration_status.unapplied_migrations.length}} Pending
+ }
+
+ Latest Migration: {{status.database.migration_status.latest_migration}}
+ @if (status.database.migration_status.unapplied_migrations.length > 0) {
+ Pending Migrations:
+
+ @for (migration of status.database.migration_status.unapplied_migrations; track migration) {
+ - {{migration}}
+ }
+
+ }
+
+
+ @if (status.database.migration_status.unapplied_migrations.length > 0) {
+ - Pending Migrations:
+ -
+
+ @for (migration of status.database.migration_status.unapplied_migrations; track migration) {
+ - {{migration}}
+ }
+
+
+ }
+
+
+
Redis
+
+ - URL:
+ - {{status.redis.url}}
+ - Status:
+ -
+ {{status.redis.status}}
+ @if (status.redis.status === 'OK') {
+
+ } @else {
+
+ }
+
+
+ }
+
+
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}/`
+ )
+ }
+}