Add alert badge to button if errors
This commit is contained in:
parent
c40b2adad7
commit
4f4a7aee14
@ -7,11 +7,20 @@
|
|||||||
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()">
|
<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>
|
<i-bs class="me-1" name="airplane"></i-bs> <ng-container i18n>Start tour</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-primary ms-5" (click)="showSystemStatus()"
|
<button class="btn btn-sm btn-outline-primary position-relative ms-5" (click)="showSystemStatus()"
|
||||||
|
[disabled]="!systemStatus"
|
||||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }">
|
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }">
|
||||||
<i-bs class="me-1" name="card-checklist"></i-bs> <ng-container i18n>System Status</ng-container>
|
@if (!systemStatus) {
|
||||||
|
<div class="spinner-border spinner-border-sm me-1" role="status"></div>
|
||||||
|
} @else {
|
||||||
|
<i-bs class="me-1" name="card-checklist"></i-bs>
|
||||||
|
@if (systemStatusHasErrors) {
|
||||||
|
<span class="badge bg-danger position-absolute top-0 start-100 translate-middle rounded-pill py-1 px-2">!</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<ng-container i18n>System Status</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<a *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }" class="btn btn-sm btn-primary ms-2" href="admin/" target="_blank">
|
<a *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }" class="btn btn-sm btn-primary ms-3" href="admin/" target="_blank">
|
||||||
<ng-container i18n>Open Django Admin</ng-container>
|
<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>
|
</a>
|
||||||
|
@ -42,6 +42,12 @@ import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
|||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
|
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
|
||||||
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
|
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
|
||||||
|
import { SystemStatusService } from 'src/app/services/system-status.service'
|
||||||
|
import {
|
||||||
|
PaperlessSystemStatus,
|
||||||
|
PaperlessInstallType,
|
||||||
|
PaperlessConnectionStatus,
|
||||||
|
} from 'src/app/data/system-status'
|
||||||
|
|
||||||
const savedViews = [
|
const savedViews = [
|
||||||
{ id: 1, name: 'view1', show_in_sidebar: true, show_on_dashboard: true },
|
{ id: 1, name: 'view1', show_in_sidebar: true, show_on_dashboard: true },
|
||||||
@ -69,6 +75,7 @@ describe('SettingsComponent', () => {
|
|||||||
let permissionsService: PermissionsService
|
let permissionsService: PermissionsService
|
||||||
let groupService: GroupService
|
let groupService: GroupService
|
||||||
let modalService: NgbModal
|
let modalService: NgbModal
|
||||||
|
let systemStatusService: SystemStatusService
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -113,6 +120,7 @@ describe('SettingsComponent', () => {
|
|||||||
userService = TestBed.inject(UserService)
|
userService = TestBed.inject(UserService)
|
||||||
permissionsService = TestBed.inject(PermissionsService)
|
permissionsService = TestBed.inject(PermissionsService)
|
||||||
modalService = TestBed.inject(NgbModal)
|
modalService = TestBed.inject(NgbModal)
|
||||||
|
systemStatusService = TestBed.inject(SystemStatusService)
|
||||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||||
jest
|
jest
|
||||||
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
|
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
|
||||||
@ -379,6 +387,36 @@ describe('SettingsComponent', () => {
|
|||||||
expect(toastErrorSpy).toBeCalled()
|
expect(toastErrorSpy).toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should load system status on initialize, show errors if needed', () => {
|
||||||
|
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: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tasks: {
|
||||||
|
redis_url: 'redis://localhost:6379',
|
||||||
|
redis_status: PaperlessConnectionStatus.ERROR,
|
||||||
|
redis_error:
|
||||||
|
'Error 61 connecting to localhost:6379. Connection refused.',
|
||||||
|
celery_status: PaperlessConnectionStatus.ERROR,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
jest.spyOn(systemStatusService, 'get').mockReturnValue(of(status))
|
||||||
|
completeSetup()
|
||||||
|
expect(component['systemStatus']).toEqual(status) // private
|
||||||
|
expect(component.systemStatusHasErrors).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
it('should open system status dialog', () => {
|
it('should open system status dialog', () => {
|
||||||
const modalOpenSpy = jest.spyOn(modalService, 'open')
|
const modalOpenSpy = jest.spyOn(modalService, 'open')
|
||||||
completeSetup()
|
completeSetup()
|
||||||
|
@ -9,7 +9,11 @@ import {
|
|||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { FormGroup, FormControl } from '@angular/forms'
|
import { FormGroup, FormControl } from '@angular/forms'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { NgbModal, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
|
import {
|
||||||
|
NgbModal,
|
||||||
|
NgbModalRef,
|
||||||
|
NgbNavChangeEvent,
|
||||||
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms'
|
import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms'
|
||||||
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
||||||
import {
|
import {
|
||||||
@ -41,6 +45,11 @@ import {
|
|||||||
import { ToastService, Toast } from 'src/app/services/toast.service'
|
import { ToastService, Toast } from 'src/app/services/toast.service'
|
||||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
|
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
|
||||||
|
import { SystemStatusService } from 'src/app/services/system-status.service'
|
||||||
|
import {
|
||||||
|
PaperlessConnectionStatus,
|
||||||
|
PaperlessSystemStatus,
|
||||||
|
} from 'src/app/data/system-status'
|
||||||
|
|
||||||
enum SettingsNavIDs {
|
enum SettingsNavIDs {
|
||||||
General = 1,
|
General = 1,
|
||||||
@ -112,6 +121,17 @@ export class SettingsComponent
|
|||||||
users: User[]
|
users: User[]
|
||||||
groups: Group[]
|
groups: Group[]
|
||||||
|
|
||||||
|
private systemStatus: PaperlessSystemStatus
|
||||||
|
|
||||||
|
get systemStatusHasErrors(): boolean {
|
||||||
|
return (
|
||||||
|
this.systemStatus.database.status === PaperlessConnectionStatus.ERROR ||
|
||||||
|
this.systemStatus.tasks.redis_status ===
|
||||||
|
PaperlessConnectionStatus.ERROR ||
|
||||||
|
this.systemStatus.tasks.celery_status === PaperlessConnectionStatus.ERROR
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
get computedDateLocale(): string {
|
get computedDateLocale(): string {
|
||||||
return (
|
return (
|
||||||
this.settingsForm.value.dateLocale ||
|
this.settingsForm.value.dateLocale ||
|
||||||
@ -133,7 +153,8 @@ export class SettingsComponent
|
|||||||
private groupsService: GroupService,
|
private groupsService: GroupService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
public permissionsService: PermissionsService,
|
public permissionsService: PermissionsService,
|
||||||
private modalService: NgbModal
|
private modalService: NgbModal,
|
||||||
|
private systemStatusService: SystemStatusService
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.settings.settingsSaved.subscribe(() => {
|
this.settings.settingsSaved.subscribe(() => {
|
||||||
@ -362,6 +383,17 @@ export class SettingsComponent
|
|||||||
// prevents loss of unsaved changes
|
// prevents loss of unsaved changes
|
||||||
this.settingsForm.patchValue(currentFormValue)
|
this.settingsForm.patchValue(currentFormValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.permissionsService.currentUserCan(
|
||||||
|
PermissionAction.View,
|
||||||
|
PermissionType.Admin
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.systemStatusService.get().subscribe((status) => {
|
||||||
|
this.systemStatus = status
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private emptyGroup(group: FormGroup) {
|
private emptyGroup(group: FormGroup) {
|
||||||
@ -569,8 +601,12 @@ export class SettingsComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
showSystemStatus() {
|
showSystemStatus() {
|
||||||
this.modalService.open(SystemStatusDialogComponent, {
|
const modal: NgbModalRef = this.modalService.open(
|
||||||
size: 'xl',
|
SystemStatusDialogComponent,
|
||||||
})
|
{
|
||||||
|
size: 'xl',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
modal.componentInstance.status = this.systemStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,9 +48,7 @@ const status: PaperlessSystemStatus = {
|
|||||||
describe('SystemStatusDialogComponent', () => {
|
describe('SystemStatusDialogComponent', () => {
|
||||||
let component: SystemStatusDialogComponent
|
let component: SystemStatusDialogComponent
|
||||||
let fixture: ComponentFixture<SystemStatusDialogComponent>
|
let fixture: ComponentFixture<SystemStatusDialogComponent>
|
||||||
let systemStatusService: SystemStatusService
|
|
||||||
let clipboard: Clipboard
|
let clipboard: Clipboard
|
||||||
let getStatusSpy
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
@ -66,21 +64,13 @@ describe('SystemStatusDialogComponent', () => {
|
|||||||
],
|
],
|
||||||
}).compileComponents()
|
}).compileComponents()
|
||||||
|
|
||||||
systemStatusService = TestBed.inject(SystemStatusService)
|
|
||||||
getStatusSpy = jest
|
|
||||||
.spyOn(systemStatusService, 'get')
|
|
||||||
.mockReturnValue(of(status))
|
|
||||||
fixture = TestBed.createComponent(SystemStatusDialogComponent)
|
fixture = TestBed.createComponent(SystemStatusDialogComponent)
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
||||||
|
component.status = status
|
||||||
clipboard = TestBed.inject(Clipboard)
|
clipboard = TestBed.inject(Clipboard)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should subscribe to system status service', () => {
|
|
||||||
expect(getStatusSpy).toHaveBeenCalled()
|
|
||||||
expect(component.status).toEqual(status)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should close the active modal', () => {
|
it('should close the active modal', () => {
|
||||||
const closeSpy = jest.spyOn(component.activeModal, 'close')
|
const closeSpy = jest.spyOn(component.activeModal, 'close')
|
||||||
component.close()
|
component.close()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { PaperlessSystemStatus } from 'src/app/data/system-status'
|
import { PaperlessSystemStatus } from 'src/app/data/system-status'
|
||||||
import { SystemStatusService } from 'src/app/services/system-status.service'
|
import { SystemStatusService } from 'src/app/services/system-status.service'
|
||||||
@ -16,13 +16,8 @@ export class SystemStatusDialogComponent {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public activeModal: NgbActiveModal,
|
public activeModal: NgbActiveModal,
|
||||||
private systemStatusService: SystemStatusService,
|
|
||||||
private clipboard: Clipboard
|
private clipboard: Clipboard
|
||||||
) {
|
) {}
|
||||||
this.systemStatusService.get().subscribe((status) => {
|
|
||||||
this.status = status
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public close() {
|
public close() {
|
||||||
this.activeModal.close()
|
this.activeModal.close()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user