]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Enhancement: auto-refresh logs & tasks (#4680)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Fri, 1 Dec 2023 03:08:03 +0000 (19:08 -0800)
committerGitHub <noreply@github.com>
Fri, 1 Dec 2023 03:08:03 +0000 (03:08 +0000)
src-ui/messages.xlf
src-ui/src/app/components/admin/logs/logs.component.html
src-ui/src/app/components/admin/logs/logs.component.spec.ts
src-ui/src/app/components/admin/logs/logs.component.ts
src-ui/src/app/components/admin/tasks/tasks.component.html
src-ui/src/app/components/admin/tasks/tasks.component.spec.ts
src-ui/src/app/components/admin/tasks/tasks.component.ts

index 870f7c6964584e8ad207a967a8247db01f7d7336..f80457c428523b76d51ad57d0d951823818489bc 100644 (file)
           <context context-type="linenumber">228</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="8838884664569764142" datatype="html">
+        <source>Auto refresh</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
+          <context context-type="linenumber">4</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="3894950702316166331" datatype="html">
         <source>Loading...</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">16</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
-          <context context-type="linenumber">20</context>
+          <context context-type="linenumber">25</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">19</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">27</context>
+          <context context-type="linenumber">22</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.html</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">40</context>
+          <context context-type="linenumber">35</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.html</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.html</context>
           <context context-type="linenumber">22</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1102717806459547726" datatype="html">
-        <source>Refresh</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">20</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="4207916966377787111" datatype="html">
         <source>Created</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">36</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
         <source>Results</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">42</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="314315645942131479" datatype="html">
         <source>Info</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">43</context>
+          <context context-type="linenumber">38</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8958063833276423847" datatype="html">
         <source>click for full output</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">66</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1536087519743707362" datatype="html">
         <source>Dismiss</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">81</context>
+          <context context-type="linenumber">76</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
-          <context context-type="linenumber">63</context>
+          <context context-type="linenumber">66</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2134950584701094962" datatype="html">
         <source>Open Document</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">87</context>
+          <context context-type="linenumber">82</context>
         </context-group>
       </trans-unit>
       <trans-unit id="428536141871853903" datatype="html">
         <source>{VAR_PLURAL, plural, =1 {One <x id="INTERPOLATION"/> task} other {<x id="INTERPOLATION_1"/> total <x id="INTERPOLATION"/> tasks}}</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">103</context>
+          <context context-type="linenumber">98</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1830925490604698731" datatype="html">
         <source>Failed<x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span *ngIf=&quot;tasksService.failedFileTasks.length &gt; 0&quot; class=&quot;badge bg-danger ms-2&quot;&gt;"/><x id="INTERPOLATION" equiv-text="{{tasksService.failedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">110</context>
+          <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6149567896789735123" datatype="html">
         <source>Complete<x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span *ngIf=&quot;tasksService.completedFileTasks.length &gt; 0&quot; class=&quot;badge bg-secondary ms-2&quot;&gt;"/><x id="INTERPOLATION" equiv-text="{{tasksService.completedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">116</context>
+          <context context-type="linenumber">111</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7531670556122409927" datatype="html">
         <source>Started<x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span *ngIf=&quot;tasksService.startedFileTasks.length &gt; 0&quot; class=&quot;badge bg-secondary ms-2&quot;&gt;"/><x id="INTERPOLATION" equiv-text="{{tasksService.startedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">122</context>
+          <context context-type="linenumber">117</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7252570681759700719" datatype="html">
         <source>Queued<x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span *ngIf=&quot;tasksService.queuedFileTasks.length &gt; 0&quot; class=&quot;badge bg-secondary ms-2&quot;&gt;"/><x id="INTERPOLATION" equiv-text="{{tasksService.queuedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
-          <context context-type="linenumber">128</context>
+          <context context-type="linenumber">123</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5404910960991552159" datatype="html">
         <source>Dismiss selected</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
-          <context context-type="linenumber">28</context>
+          <context context-type="linenumber">30</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8829078752502782653" datatype="html">
         <source>Dismiss all</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
-          <context context-type="linenumber">29</context>
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1323591410517879795" datatype="html">
         <source>Confirm Dismiss All</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">63</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4157200209636243740" datatype="html">
         <source>Dismiss all <x id="PH" equiv-text="tasks.size"/> tasks?</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">64</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9011556615675272238" datatype="html">
         <source>queued</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
-          <context context-type="linenumber">129</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6415892379431855826" datatype="html">
         <source>started</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">134</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7510279840486540181" datatype="html">
         <source>completed</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
-          <context context-type="linenumber">133</context>
+          <context context-type="linenumber">136</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4083337005045748464" datatype="html">
         <source>failed</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
-          <context context-type="linenumber">135</context>
+          <context context-type="linenumber">138</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8119815638230251386" datatype="html">
index 91f7f3bd6f545cb3747b0a21831bba458b14ac43..628886c62d156bad07e0c963186d83662792021c 100644 (file)
@@ -1,14 +1,19 @@
 <pngx-page-header title="Logs" i18n-title>
-
+  <div class="form-check form-switch" (click)="toggleAutoRefresh()">
+    <input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" [attr.checked]="autoRefreshInterval">
+    <label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
+  </div>
 </pngx-page-header>
 
 <ul ngbNav #nav="ngbNav" [(activeId)]="activeLog" (activeIdChange)="reloadLogs()" class="nav-tabs">
   <li *ngFor="let logFile of logFiles" [ngbNavItem]="logFile">
-    <a ngbNavLink>{{logFile}}.log</a>
+    <a ngbNavLink>
+      {{logFile}}.log
+    </a>
   </li>
-  <div *ngIf="isLoading && !logFiles.length" class="pb-2">
+  <div *ngIf="isLoading || !logFiles.length" class="ps-2 d-flex align-items-center">
     <div class="spinner-border spinner-border-sm me-2" role="status"></div>
-    <ng-container i18n>Loading...</ng-container>
+    <ng-container *ngIf="!logFiles.length" i18n>Loading...</ng-container>
   </div>
 </ul>
 
index a9c4b4613f6965a9429891a29ba5bc2898988046..690bbcdde0f53a20d302c46d32e068127895f2dd 100644 (file)
@@ -1,4 +1,9 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing'
+import {
+  ComponentFixture,
+  TestBed,
+  fakeAsync,
+  tick,
+} from '@angular/core/testing'
 import { LogService } from 'src/app/services/rest/log.service'
 import { PageHeaderComponent } from '../../common/page-header/page-header.component'
 import { LogsComponent } from './logs.component'
@@ -26,6 +31,7 @@ describe('LogsComponent', () => {
   let fixture: ComponentFixture<LogsComponent>
   let logService: LogService
   let logSpy
+  let reloadSpy
 
   beforeEach(async () => {
     TestBed.configureTestingModule({
@@ -42,7 +48,9 @@ describe('LogsComponent', () => {
     })
     fixture = TestBed.createComponent(LogsComponent)
     component = fixture.componentInstance
+    reloadSpy = jest.spyOn(component, 'reloadLogs')
     window.HTMLElement.prototype.scroll = function () {} // mock scroll
+    jest.useFakeTimers()
     fixture.detectChanges()
   })
 
@@ -68,4 +76,14 @@ describe('LogsComponent', () => {
     component.reloadLogs()
     expect(component.logs).toHaveLength(0)
   })
+
+  it('should auto refresh, allow toggle', () => {
+    jest.advanceTimersByTime(6000)
+    expect(reloadSpy).toHaveBeenCalledTimes(2)
+
+    component.toggleAutoRefresh()
+    expect(component.autoRefreshInterval).toBeNull()
+    jest.advanceTimersByTime(6000)
+    expect(reloadSpy).toHaveBeenCalledTimes(2)
+  })
 })
index cf88077e70adfa12ef4eef2295759e0688827d71..2ca2592183da363c0e8030022a2635e69e67c5e8 100644 (file)
@@ -27,6 +27,8 @@ export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
 
   public isLoading: boolean = false
 
+  public autoRefreshInterval: any
+
   @ViewChild('logContainer') logContainer: ElementRef
 
   ngOnInit(): void {
@@ -41,6 +43,7 @@ export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
           this.activeLog = this.logFiles[0]
           this.reloadLogs()
         }
+        this.toggleAutoRefresh()
       })
   }
 
@@ -91,4 +94,15 @@ export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
       behavior: 'auto',
     })
   }
+
+  toggleAutoRefresh(): void {
+    if (this.autoRefreshInterval) {
+      clearInterval(this.autoRefreshInterval)
+      this.autoRefreshInterval = null
+    } else {
+      this.autoRefreshInterval = setInterval(() => {
+        this.reloadLogs()
+      }, 5000)
+    }
+  }
 }
index 62799c9f6749afb20a6e7829c19d4dd0f68cbe43..7e0ac4caef69741c9a72523d1f49dfdb8997757b 100644 (file)
@@ -1,5 +1,5 @@
 <pngx-page-header title="File Tasks" i18n-title>
-  <div class="btn-toolbar col col-md-auto">
+  <div class="btn-toolbar col col-md-auto align-items-center">
     <button class="btn btn-sm btn-outline-secondary me-2" (click)="clearSelection()" [hidden]="selectedTasks.size === 0">
       <svg class="sidebaricon" fill="currentColor">
         <use xlink:href="assets/bootstrap-icons.svg#x"/>
         <use xlink:href="assets/bootstrap-icons.svg#check2-all"/>
       </svg>&nbsp;<ng-container i18n>{{dismissButtonText}}</ng-container>
     </button>
-    <button class="btn btn-sm btn-outline-primary" (click)="tasksService.reload()">
-      <svg *ngIf="!tasksService.loading" class="sidebaricon" fill="currentColor">
-        <use xlink:href="assets/bootstrap-icons.svg#arrow-clockwise"/>
-      </svg>
-      <ng-container *ngIf="tasksService.loading">
-        <div class="spinner-border spinner-border-sm fw-normal" role="status"></div>
-        <div class="visually-hidden" i18n>Loading...</div>
-      </ng-container>&nbsp;<ng-container i18n>Refresh</ng-container>
-    </button>
+    <div class="form-check form-switch mb-0" (click)="toggleAutoRefresh()">
+      <input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" [attr.checked]="autoRefreshInterval">
+      <label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
+    </div>
   </div>
 </pngx-page-header>
 
index c981dac6fab7fc0a37aeb9f5ba25ed49510fde8d..b86a170b743d7a4055e725756a8ac806b33e69cb 100644 (file)
@@ -112,6 +112,7 @@ describe('TasksComponent', () => {
   let modalService: NgbModal
   let router: Router
   let httpTestingController: HttpTestingController
+  let reloadSpy
 
   beforeEach(async () => {
     TestBed.configureTestingModule({
@@ -141,11 +142,13 @@ describe('TasksComponent', () => {
     }).compileComponents()
 
     tasksService = TestBed.inject(TasksService)
+    reloadSpy = jest.spyOn(tasksService, 'reload')
     httpTestingController = TestBed.inject(HttpTestingController)
     modalService = TestBed.inject(NgbModal)
     router = TestBed.inject(Router)
     fixture = TestBed.createComponent(TasksComponent)
     component = fixture.componentInstance
+    jest.useFakeTimers()
     fixture.detectChanges()
     httpTestingController
       .expectOne(`${environment.apiBaseUrl}tasks/`)
@@ -164,7 +167,7 @@ describe('TasksComponent', () => {
       `Failed${currentTasksLength}`
     )
     expect(
-      fixture.debugElement.queryAll(By.css('input[type="checkbox"]'))
+      fixture.debugElement.queryAll(By.css('table input[type="checkbox"]'))
     ).toHaveLength(currentTasksLength + 1)
 
     currentTasksLength = tasks.filter(
@@ -245,7 +248,7 @@ describe('TasksComponent', () => {
 
   it('should support toggle all tasks', () => {
     const toggleCheck = fixture.debugElement.query(
-      By.css('input[type=checkbox]')
+      By.css('table input[type=checkbox]')
     )
     toggleCheck.nativeElement.dispatchEvent(new MouseEvent('click'))
     fixture.detectChanges()
@@ -269,4 +272,15 @@ describe('TasksComponent', () => {
       tasks[3].related_document,
     ])
   })
+
+  it('should auto refresh, allow toggle', () => {
+    expect(reloadSpy).toHaveBeenCalledTimes(1)
+    jest.advanceTimersByTime(5000)
+    expect(reloadSpy).toHaveBeenCalledTimes(2)
+
+    component.toggleAutoRefresh()
+    expect(component.autoRefreshInterval).toBeNull()
+    jest.advanceTimersByTime(6000)
+    expect(reloadSpy).toHaveBeenCalledTimes(2)
+  })
 })
index 32cf6800f112306114aa981c359ae17691f710cb..5a1949e77eaad85eb863de38a1a16112a959fd2e 100644 (file)
@@ -23,6 +23,8 @@ export class TasksComponent
   public pageSize: number = 25
   public page: number = 1
 
+  public autoRefreshInterval: any
+
   get dismissButtonText(): string {
     return this.selectedTasks.size > 0
       ? $localize`Dismiss selected`
@@ -39,6 +41,7 @@ export class TasksComponent
 
   ngOnInit() {
     this.tasksService.reload()
+    this.toggleAutoRefresh()
   }
 
   ngOnDestroy() {
@@ -135,4 +138,15 @@ export class TasksComponent
         return $localize`failed`
     }
   }
+
+  toggleAutoRefresh(): void {
+    if (this.autoRefreshInterval) {
+      clearInterval(this.autoRefreshInterval)
+      this.autoRefreshInterval = null
+    } else {
+      this.autoRefreshInterval = setInterval(() => {
+        this.tasksService.reload()
+      }, 5000)
+    }
+  }
 }