Move to settings, add celery, updated styling
This commit is contained in:
@@ -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,81 +11,116 @@
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<h5 i18n>General Info</h5>
|
||||
<dl>
|
||||
<dt i18n>Paperless-ngx Version:</dt>
|
||||
<dd>{{status.pngx_version}}</dd>
|
||||
<dt i18n>Install Type:</dt>
|
||||
<dd>{{status.install_type}}</dd>
|
||||
<dt i18n>Server OS:</dt>
|
||||
<dd>{{status.server_os}}</dd>
|
||||
<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>
|
||||
<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 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>
|
||||
<dd>{{status.install_type}}</dd>
|
||||
<dt i18n>Server OS</dt>
|
||||
<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>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 i18n>Database</h5>
|
||||
<dl>
|
||||
<dt i18n>Type:</dt>
|
||||
<dd>{{status.database.type}}</dd>
|
||||
<dt i18n>URL:</dt>
|
||||
<dd>{{status.database.url}}</dd>
|
||||
<dt i18n>Status:</dt>
|
||||
<dd>
|
||||
{{status.database.status}}
|
||||
@if (status.database.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.database.error}}" triggers="mouseenter:mouseleave"></i-bs>
|
||||
}
|
||||
</dd>
|
||||
<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>
|
||||
} @else {
|
||||
<ng-container>{{status.database.migration_status.unapplied_migrations.length}} Pending</ng-container><i-bs name="exclamation-triangle-fill" class="text-warning ms-1" [ngbPopover]="migrationStatus" triggers="mouseenter:mouseleave"></i-bs>
|
||||
}
|
||||
<ng-template #migrationStatus>
|
||||
<ng-container i18n>Latest Migration</ng-container>: {{status.database.migration_status.latest_migration}}<br/>
|
||||
@if (status.database.migration_status.unapplied_migrations.length > 0) {
|
||||
<ng-container i18n>Pending Migrations</ng-container>:
|
||||
<ul>
|
||||
@for (migration of status.database.migration_status.unapplied_migrations; track migration) {
|
||||
<li>{{migration}}</li>
|
||||
<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>
|
||||
<dd>{{status.database.url}}</dd>
|
||||
<dt i18n>Status</dt>
|
||||
<dd>
|
||||
{{status.database.status}}
|
||||
@if (status.database.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.database.error}}" triggers="mouseenter:mouseleave"></i-bs>
|
||||
}
|
||||
</dd>
|
||||
<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>
|
||||
} @else {
|
||||
<ng-container>{{status.database.migration_status.unapplied_migrations.length}} Pending</ng-container><i-bs name="exclamation-triangle-fill" class="text-warning ms-1" [ngbPopover]="migrationStatus" triggers="mouseenter:mouseleave"></i-bs>
|
||||
}
|
||||
<ng-template #migrationStatus>
|
||||
<ng-container i18n>Latest Migration</ng-container>: {{status.database.migration_status.latest_migration}}<br/>
|
||||
@if (status.database.migration_status.unapplied_migrations.length > 0) {
|
||||
<ng-container i18n>Pending Migrations</ng-container>:
|
||||
<ul>
|
||||
@for (migration of status.database.migration_status.unapplied_migrations; track migration) {
|
||||
<li>{{migration}}</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</ng-template>
|
||||
</dd>
|
||||
@if (status.database.migration_status.unapplied_migrations.length > 0) {
|
||||
<dt i18n>Pending Migrations:</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
@for (migration of status.database.migration_status.unapplied_migrations; track migration) {
|
||||
<li>{{migration}}</li>
|
||||
}
|
||||
</ul>
|
||||
</dd>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</ng-template>
|
||||
</dd>
|
||||
@if (status.database.migration_status.unapplied_migrations.length > 0) {
|
||||
<dt i18n>Pending Migrations:</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
@for (migration of status.database.migration_status.unapplied_migrations; track migration) {
|
||||
<li>{{migration}}</li>
|
||||
}
|
||||
</ul>
|
||||
</dd>
|
||||
}
|
||||
</dl>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 i18n>Redis</h5>
|
||||
<dl>
|
||||
<dt i18n>URL:</dt>
|
||||
<dd>{{status.redis.url}}</dd>
|
||||
<dt i18n>Status:</dt>
|
||||
<dd>
|
||||
{{status.redis.status}}
|
||||
@if (status.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>
|
||||
}
|
||||
</dd>
|
||||
</dl>
|
||||
<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.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.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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user