Move to settings, add celery, updated styling
This commit is contained in:
parent
87c2efef4d
commit
c40b2adad7
@ -117,6 +117,7 @@ import { MonetaryComponent } from './components/common/input/monetary/monetary.c
|
||||
import { SystemStatusDialogComponent } from './components/common/system-status-dialog/system-status-dialog.component'
|
||||
import { NgxFilesizeModule } from 'ngx-filesize'
|
||||
import {
|
||||
airplane,
|
||||
archive,
|
||||
arrowCounterclockwise,
|
||||
arrowDown,
|
||||
@ -205,6 +206,7 @@ import {
|
||||
} from 'ngx-bootstrap-icons'
|
||||
|
||||
const icons = {
|
||||
airplane,
|
||||
archive,
|
||||
arrowCounterclockwise,
|
||||
arrowDown,
|
||||
|
@ -4,10 +4,16 @@
|
||||
info="Options to customize appearance, notifications, saved views and more. Settings apply to the <strong>current user only</strong>."
|
||||
i18n-info
|
||||
>
|
||||
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()"><ng-container i18n>Start tour</ng-container></button>
|
||||
<a *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }" class="btn btn-sm btn-primary ms-3" href="admin/" target="_blank">
|
||||
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()">
|
||||
<i-bs class="me-1" name="airplane"></i-bs> <ng-container i18n>Start tour</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary ms-5" (click)="showSystemStatus()"
|
||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }">
|
||||
<i-bs class="me-1" name="card-checklist"></i-bs> <ng-container i18n>System Status</ng-container>
|
||||
</button>
|
||||
<a *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }" class="btn btn-sm btn-primary ms-2" href="admin/" target="_blank">
|
||||
<ng-container i18n>Open Django Admin</ng-container>
|
||||
<i-bs name="arrow-up-right"></i-bs>
|
||||
<i-bs name="arrow-up-right"></i-bs>
|
||||
</a>
|
||||
</pngx-page-header>
|
||||
|
||||
|
@ -9,6 +9,8 @@ import {
|
||||
NgbModule,
|
||||
NgbAlertModule,
|
||||
NgbNavLink,
|
||||
NgbModal,
|
||||
NgbModalModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { of, throwError } from 'rxjs'
|
||||
@ -39,6 +41,7 @@ import { SettingsComponent } from './settings.component'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
|
||||
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
|
||||
|
||||
const savedViews = [
|
||||
{ id: 1, name: 'view1', show_in_sidebar: true, show_on_dashboard: true },
|
||||
@ -65,6 +68,7 @@ describe('SettingsComponent', () => {
|
||||
let userService: UserService
|
||||
let permissionsService: PermissionsService
|
||||
let groupService: GroupService
|
||||
let modalService: NgbModal
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -96,6 +100,7 @@ describe('SettingsComponent', () => {
|
||||
NgbAlertModule,
|
||||
NgSelectModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
NgbModalModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
@ -107,6 +112,7 @@ describe('SettingsComponent', () => {
|
||||
settingsService.currentUser = users[0]
|
||||
userService = TestBed.inject(UserService)
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
|
||||
@ -372,4 +378,13 @@ describe('SettingsComponent', () => {
|
||||
fixture.detectChanges()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
})
|
||||
|
||||
it('should open system status dialog', () => {
|
||||
const modalOpenSpy = jest.spyOn(modalService, 'open')
|
||||
completeSetup()
|
||||
component.showSystemStatus()
|
||||
expect(modalOpenSpy).toHaveBeenCalledWith(SystemStatusDialogComponent, {
|
||||
size: 'xl',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
} from '@angular/core'
|
||||
import { FormGroup, FormControl } from '@angular/forms'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgbModal, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms'
|
||||
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
||||
import {
|
||||
@ -40,6 +40,7 @@ import {
|
||||
} from 'src/app/services/settings.service'
|
||||
import { ToastService, Toast } from 'src/app/services/toast.service'
|
||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
|
||||
|
||||
enum SettingsNavIDs {
|
||||
General = 1,
|
||||
@ -131,7 +132,8 @@ export class SettingsComponent
|
||||
private usersService: UserService,
|
||||
private groupsService: GroupService,
|
||||
private router: Router,
|
||||
public permissionsService: PermissionsService
|
||||
public permissionsService: PermissionsService,
|
||||
private modalService: NgbModal
|
||||
) {
|
||||
super()
|
||||
this.settings.settingsSaved.subscribe(() => {
|
||||
@ -565,4 +567,10 @@ export class SettingsComponent
|
||||
clearThemeColor() {
|
||||
this.settingsForm.get('themeColor').patchValue('')
|
||||
}
|
||||
|
||||
showSystemStatus() {
|
||||
this.modalService.open(SystemStatusDialogComponent, {
|
||||
size: 'xl',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -58,10 +58,6 @@
|
||||
*pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }">
|
||||
<i-bs class="me-2" name="gear"></i-bs><ng-container i18n>Settings</ng-container>
|
||||
</a>
|
||||
<button ngbDropdownItem class="nav-link" (click)="showSystemStatus()"
|
||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }">
|
||||
<i-bs class="me-2" name="card-checklist"></i-bs> <ng-container i18n>System Status</ng-container>
|
||||
</button>
|
||||
<a ngbDropdownItem class="nav-link d-flex" href="accounts/logout/" (click)="onLogout()">
|
||||
<i-bs class="me-2" name="door-open"></i-bs><ng-container i18n>Logout</ng-container>
|
||||
</a>
|
||||
|
@ -38,7 +38,6 @@ 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 = [
|
||||
{
|
||||
@ -416,10 +415,4 @@ 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)
|
||||
})
|
||||
})
|
||||
|
@ -46,7 +46,6 @@ 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',
|
||||
@ -317,8 +316,4 @@ export class AppFrameComponent
|
||||
onLogout() {
|
||||
this.openDocumentsService.closeAll()
|
||||
}
|
||||
|
||||
showSystemStatus() {
|
||||
this.modalService.open(SystemStatusDialogComponent)
|
||||
}
|
||||
}
|
||||
|
@ -11,28 +11,42 @@
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<h5 i18n>General Info</h5>
|
||||
<dl>
|
||||
<dt i18n>Paperless-ngx Version:</dt>
|
||||
<div class="row row-cols-1 row-cols-md-3 g-3">
|
||||
<div class="col">
|
||||
<div class="card bg-light h-100">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0" i18n>Environment</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="card-text">
|
||||
<dt i18n>Paperless-ngx Version</dt>
|
||||
<dd>{{status.pngx_version}}</dd>
|
||||
<dt i18n>Install Type:</dt>
|
||||
<dt i18n>Install Type</dt>
|
||||
<dd>{{status.install_type}}</dd>
|
||||
<dt i18n>Server OS:</dt>
|
||||
<dt i18n>Server OS</dt>
|
||||
<dd>{{status.server_os}}</dd>
|
||||
<dt i18n>Media Storage:</dt>
|
||||
<dt i18n>Media Storage</dt>
|
||||
<dd>
|
||||
<ngb-progressbar style="height: 4px;" class="my-2" type="primary" [max]="status.storage.total" [value]="status.storage.total - status.storage.available"></ngb-progressbar>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 i18n>Database</h5>
|
||||
<dl>
|
||||
<dt i18n>Type:</dt>
|
||||
<div class="col">
|
||||
<div class="card bg-light h-100">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0" i18n>Database</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="card-text">
|
||||
<dt i18n>Type</dt>
|
||||
<dd>{{status.database.type}}</dd>
|
||||
<dt i18n>URL:</dt>
|
||||
<dt i18n>URL</dt>
|
||||
<dd>{{status.database.url}}</dd>
|
||||
<dt i18n>Status:</dt>
|
||||
<dt i18n>Status</dt>
|
||||
<dd>
|
||||
{{status.database.status}}
|
||||
@if (status.database.status === 'OK') {
|
||||
@ -41,7 +55,7 @@
|
||||
<i-bs name="exclamation-triangle-fill" class="text-danger ms-1" ngbPopover="{{status.database.error}}" triggers="mouseenter:mouseleave"></i-bs>
|
||||
}
|
||||
</dd>
|
||||
<dt i18n>Migration Status:</dt>
|
||||
<dt i18n>Migration Status</dt>
|
||||
<dd>
|
||||
@if (status.database.migration_status.unapplied_migrations.length === 0) {
|
||||
<ng-container>Up to date</ng-container><i-bs name="check-circle-fill" class="text-success ms-1" [ngbPopover]="migrationStatus" triggers="mouseenter:mouseleave"></i-bs>
|
||||
@ -71,21 +85,42 @@
|
||||
</dd>
|
||||
}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 i18n>Redis</h5>
|
||||
<dl>
|
||||
<dt i18n>URL:</dt>
|
||||
<dd>{{status.redis.url}}</dd>
|
||||
<dt i18n>Status:</dt>
|
||||
<div class="col">
|
||||
<div class="card bg-light h-100">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0" i18n>Tasks</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="card-text">
|
||||
<dt i18n>Redis URL</dt>
|
||||
<dd>{{status.tasks.redis_url}}</dd>
|
||||
<dt i18n>Redis Status</dt>
|
||||
<dd>
|
||||
{{status.redis.status}}
|
||||
@if (status.redis.status === 'OK') {
|
||||
{{status.tasks.redis_status}}
|
||||
@if (status.tasks.redis_status === 'OK') {
|
||||
<i-bs name="check-circle-fill" class="text-success ms-1"></i-bs>
|
||||
} @else {
|
||||
<i-bs name="exclamation-triangle-fill" class="text-danger ms-1" ngbPopover="{{status.redis.error}}" triggers="mouseenter:mouseleave"></i-bs>
|
||||
<i-bs name="exclamation-triangle-fill" class="text-danger ms-1" ngbPopover="{{status.tasks.redis_error}}" triggers="mouseenter:mouseleave"></i-bs>
|
||||
}
|
||||
</dd>
|
||||
<dt i18n>Celery Status</dt>
|
||||
<dd>
|
||||
{{status.tasks.celery_status}}
|
||||
@if (status.tasks.celery_status === 'OK') {
|
||||
<i-bs name="check-circle-fill" class="text-success ms-1"></i-bs>
|
||||
} @else {
|
||||
<i-bs name="exclamation-triangle-fill" class="text-danger ms-1"></i-bs>
|
||||
}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -37,10 +37,11 @@ const status: PaperlessSystemStatus = {
|
||||
unapplied_migrations: [],
|
||||
},
|
||||
},
|
||||
redis: {
|
||||
url: 'redis://localhost:6379',
|
||||
status: PaperlessConnectionStatus.ERROR,
|
||||
error: 'Error 61 connecting to localhost:6379. Connection refused.',
|
||||
tasks: {
|
||||
redis_url: 'redis://localhost:6379',
|
||||
redis_status: PaperlessConnectionStatus.ERROR,
|
||||
redis_error: 'Error 61 connecting to localhost:6379. Connection refused.',
|
||||
celery_status: PaperlessConnectionStatus.ERROR,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,10 @@ export interface PaperlessSystemStatus {
|
||||
unapplied_migrations: string[]
|
||||
}
|
||||
}
|
||||
redis: {
|
||||
url: string
|
||||
status: PaperlessConnectionStatus
|
||||
error: string
|
||||
tasks: {
|
||||
redis_url: string
|
||||
redis_status: PaperlessConnectionStatus
|
||||
redis_error: string
|
||||
celery_status: PaperlessConnectionStatus
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,6 @@ class TestSystemStatusView(APITestCase):
|
||||
self.assertEqual(response.data["database"]["status"], "OK")
|
||||
self.assertIsNone(response.data["database"]["error"])
|
||||
self.assertIsNotNone(response.data["database"]["migration_status"])
|
||||
self.assertEqual(response.data["redis"]["url"], "redis://localhost:6379")
|
||||
self.assertEqual(response.data["redis"]["status"], "ERROR")
|
||||
self.assertIsNotNone(response.data["redis"]["error"])
|
||||
self.assertEqual(response.data["tasks"]["redis_url"], "redis://localhost:6379")
|
||||
self.assertEqual(response.data["tasks"]["redis_status"], "ERROR")
|
||||
self.assertIsNotNone(response.data["tasks"]["redis_error"])
|
||||
|
@ -14,6 +14,7 @@ from unicodedata import normalize
|
||||
from urllib.parse import quote
|
||||
|
||||
import pathvalidate
|
||||
from celery import Celery
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import connections
|
||||
@ -1555,6 +1556,12 @@ class SystemStatusView(GenericAPIView, PassUserMixin):
|
||||
|
||||
current_version = version.__full_version_str__
|
||||
|
||||
install_type = "bare-metal"
|
||||
if os.environ.get("KUBERNETES_SERVICE_HOST") is not None:
|
||||
install_type = "kubernetes"
|
||||
elif os.environ.get("PNGX_CONTAINERIZED") == "1":
|
||||
install_type = "docker"
|
||||
|
||||
media_stats = os.statvfs(settings.MEDIA_ROOT)
|
||||
|
||||
db_conn = connections["default"]
|
||||
@ -1583,15 +1590,21 @@ class SystemStatusView(GenericAPIView, PassUserMixin):
|
||||
redis_status = "ERROR"
|
||||
redis_error = str(e)
|
||||
|
||||
try:
|
||||
app = Celery("paperless")
|
||||
app.config_from_object("django.conf:settings", namespace="CELERY")
|
||||
ping = app.control.inspect().ping()
|
||||
first_worker_ping = ping[next(iter(ping.keys()))]
|
||||
if first_worker_ping["ok"] == "pong":
|
||||
celery_active = "OK"
|
||||
except Exception:
|
||||
celery_active = "ERROR"
|
||||
|
||||
return Response(
|
||||
{
|
||||
"pngx_version": current_version,
|
||||
"server_os": platform.platform(),
|
||||
"install_type": (
|
||||
"containerized"
|
||||
if os.environ.get("PNGX_CONTAINERIZED") == "1"
|
||||
else "bare-metal"
|
||||
),
|
||||
"install_type": install_type,
|
||||
"storage": {
|
||||
"total": media_stats.f_frsize * media_stats.f_blocks,
|
||||
"available": media_stats.f_frsize * media_stats.f_bavail,
|
||||
@ -1608,10 +1621,11 @@ class SystemStatusView(GenericAPIView, PassUserMixin):
|
||||
],
|
||||
},
|
||||
},
|
||||
"redis": {
|
||||
"url": redis_url,
|
||||
"status": redis_status,
|
||||
"error": redis_error,
|
||||
"tasks": {
|
||||
"redis_url": redis_url,
|
||||
"redis_status": redis_status,
|
||||
"redis_error": redis_error,
|
||||
"celery_status": celery_active,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user