mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Frontend update checking settings
This commit is contained in:
		| @@ -200,14 +200,25 @@ | ||||
|           <li class="nav-item mt-2"> | ||||
|             <div class="px-3 py-2 text-muted small d-flex align-items-center flex-wrap"> | ||||
|               <div class="me-3">{{ versionString }}</div> | ||||
|               <div *ngIf="appRemoteVersion" class="version-check"> | ||||
|               <div *ngIf="!settingsService.updateCheckingIsSet || appRemoteVersion" class="version-check"> | ||||
|                 <ng-template #updateAvailablePopContent> | ||||
|                   <span class="small">Paperless-ngx {{ appRemoteVersion.version }} <ng-container i18n>is available.</ng-container><br/><ng-container i18n>Click to view.</ng-container></span> | ||||
|                 </ng-template> | ||||
|                 <ng-template #updateCheckingNotEnabledPopContent> | ||||
|                   <span class="small"><ng-container i18n>Checking for updates is disabled.</ng-container><br/><ng-container i18n>Click for more information.</ng-container></span> | ||||
|                   <p class="small mb-2"> | ||||
|                     <ng-container i18n>Paperless-ngx can automatically check for updates</ng-container> | ||||
|                   </p> | ||||
|                   <div class="btn-group btn-group-xs flex-fill w-100"> | ||||
|                     <button class="btn btn-outline-primary" (click)="setUpdateChecking(true)">Enable</button> | ||||
|                     <button class="btn btn-outline-secondary" (click)="setUpdateChecking(false)">Disable</button> | ||||
|                   </div> | ||||
|                   <p class="small mb-0 mt-2"> | ||||
|                     <a class="small text-decoration-none fst-italic" routerLink="/settings" fragment="update-checking" i18n> | ||||
|                       How does this work? | ||||
|                     </a> | ||||
|                   </p> | ||||
|                 </ng-template> | ||||
|                 <ng-container *ngIf="appRemoteVersion.feature_is_set; else updateCheckNotSet"> | ||||
|                 <ng-container *ngIf="settingsService.updateCheckingIsSet; else updateCheckNotSet"> | ||||
|                   <a *ngIf="appRemoteVersion.update_available" class="small text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx/releases" | ||||
|                   [ngbPopover]="updateAvailablePopContent" popoverClass="shadow" triggers="mouseenter:mouseleave" container="body"> | ||||
|                     <svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16"> | ||||
| @@ -217,8 +228,8 @@ | ||||
|                   </a> | ||||
|                 </ng-container> | ||||
|                 <ng-template #updateCheckNotSet> | ||||
|                   <a class="small text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://paperless-ngx.readthedocs.io/en/latest/configuration.html#update-checking" | ||||
|                   [ngbPopover]="updateCheckingNotEnabledPopContent" popoverClass="shadow" triggers="mouseenter:mouseleave" container="body"> | ||||
|                   <a class="small text-decoration-none" routerLink="/settings" fragment="update-checking" | ||||
|                   [ngbPopover]="updateCheckingNotEnabledPopContent" popoverClass="shadow" triggers="mouseenter" container="body"> | ||||
|                     <svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16"> | ||||
|                       <use xlink:href="assets/bootstrap-icons.svg#info-circle" /> | ||||
|                     </svg> | ||||
|   | ||||
| @@ -24,6 +24,8 @@ import { | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { TasksService } from 'src/app/services/tasks.service' | ||||
| import { ComponentCanDeactivate } from 'src/app/guards/dirty-doc.guard' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-app-frame', | ||||
| @@ -40,13 +42,12 @@ export class AppFrameComponent implements ComponentCanDeactivate { | ||||
|     private remoteVersionService: RemoteVersionService, | ||||
|     private list: DocumentListViewService, | ||||
|     public settingsService: SettingsService, | ||||
|     public tasksService: TasksService | ||||
|     public tasksService: TasksService, | ||||
|     private readonly toastService: ToastService | ||||
|   ) { | ||||
|     this.remoteVersionService | ||||
|       .checkForUpdates() | ||||
|       .subscribe((appRemoteVersion: AppRemoteVersion) => { | ||||
|         this.appRemoteVersion = appRemoteVersion | ||||
|       }) | ||||
|     if (settingsService.updateCheckingEnabled) { | ||||
|       this.checkForUpdates() | ||||
|     } | ||||
|     tasksService.reload() | ||||
|   } | ||||
|  | ||||
| @@ -150,4 +151,30 @@ export class AppFrameComponent implements ComponentCanDeactivate { | ||||
|         } | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   private checkForUpdates() { | ||||
|     this.remoteVersionService | ||||
|       .checkForUpdates() | ||||
|       .subscribe((appRemoteVersion: AppRemoteVersion) => { | ||||
|         this.appRemoteVersion = appRemoteVersion | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   setUpdateChecking(enable: boolean) { | ||||
|     this.settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, enable) | ||||
|     this.settingsService | ||||
|       .storeSettings() | ||||
|       .pipe(first()) | ||||
|       .subscribe({ | ||||
|         error: (error) => { | ||||
|           this.toastService.showError( | ||||
|             $localize`An error occurred while saving update checking settings.` | ||||
|           ) | ||||
|           console.log(error) | ||||
|         }, | ||||
|       }) | ||||
|     if (enable) { | ||||
|       this.checkForUpdates() | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -17,25 +17,6 @@ | ||||
|   } | ||||
| } | ||||
|  | ||||
| .btn-group-xs { | ||||
|   > .btn { | ||||
|     padding: 0.2rem 0.25rem; | ||||
|     font-size: 0.675rem; | ||||
|     line-height: 1.2; | ||||
|     border-radius: 0.15rem; | ||||
|   } | ||||
|  | ||||
|   > .btn:not(:first-child) { | ||||
|     border-top-left-radius: 0; | ||||
|     border-bottom-left-radius: 0; | ||||
|   } | ||||
|  | ||||
|   > .btn:not(:last-child) { | ||||
|     border-top-right-radius: 0; | ||||
|     border-bottom-right-radius: 0; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .btn-group > label.disabled { | ||||
|   filter: brightness(0.5); | ||||
|  | ||||
|   | ||||
| @@ -116,6 +116,21 @@ | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <h4 class="mt-4" id="update-checking" i18n>Update checking</h4> | ||||
|  | ||||
|         <div class="row mb-3"> | ||||
|           <div class="offset-md-3 col"> | ||||
|             <p i18n> | ||||
|               Update checking works by pinging the the public <a href="https://api.github.com/repos/paperless-ngx/paperless-ngx/releases/latest" target="_blank" rel="noopener noreferrer">Github API</a> for the latest release to determine whether a new version is available.<br/> | ||||
|               Actual updating of the app must still be performed manually. | ||||
|             </p> | ||||
|             <p i18n> | ||||
|               <em>No tracking data is collected by the app in any way.</em> | ||||
|             </p> | ||||
|             <app-input-check i18n-title title="Enable update checking" formControlName="updateCheckingEnabled" i18n-hint hint="Note that for users of thirdy-party containers e.g. linuxserver.io this notification may be 'ahead' of the current third-party release."></app-input-check> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <h4 class="mt-4" i18n>Bulk editing</h4> | ||||
|  | ||||
|         <div class="row mb-3"> | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import { | ||||
|   LOCALE_ID, | ||||
|   OnInit, | ||||
|   OnDestroy, | ||||
|   Renderer2, | ||||
|   AfterViewInit, | ||||
| } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view' | ||||
| @@ -18,13 +18,17 @@ import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
| import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms' | ||||
| import { Observable, Subscription, BehaviorSubject, first } from 'rxjs' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' | ||||
| import { ActivatedRoute } from '@angular/router' | ||||
| import { ViewportScroller } from '@angular/common' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-settings', | ||||
|   templateUrl: './settings.component.html', | ||||
|   styleUrls: ['./settings.component.scss'], | ||||
| }) | ||||
| export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
| export class SettingsComponent | ||||
|   implements OnInit, AfterViewInit, OnDestroy, DirtyComponent | ||||
| { | ||||
|   savedViewGroup = new FormGroup({}) | ||||
|  | ||||
|   settingsForm = new FormGroup({ | ||||
| @@ -45,6 +49,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|     notificationsConsumerFailed: new FormControl(null), | ||||
|     notificationsConsumerSuppressOnDashboard: new FormControl(null), | ||||
|     commentsEnabled: new FormControl(null), | ||||
|     updateCheckingEnabled: new FormControl(null), | ||||
|   }) | ||||
|  | ||||
|   savedViews: PaperlessSavedView[] | ||||
| @@ -74,9 +79,19 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private toastService: ToastService, | ||||
|     private settings: SettingsService, | ||||
|     @Inject(LOCALE_ID) public currentLocale: string | ||||
|     @Inject(LOCALE_ID) public currentLocale: string, | ||||
|     private viewportScroller: ViewportScroller, | ||||
|     private activatedRoute: ActivatedRoute | ||||
|   ) {} | ||||
|  | ||||
|   ngAfterViewInit(): void { | ||||
|     if (this.activatedRoute.snapshot.fragment) { | ||||
|       this.viewportScroller.scrollToAnchor( | ||||
|         this.activatedRoute.snapshot.fragment | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ngOnInit() { | ||||
|     this.savedViewService.listAll().subscribe((r) => { | ||||
|       this.savedViews = r.results | ||||
| @@ -118,6 +133,9 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|           SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD | ||||
|         ), | ||||
|         commentsEnabled: this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED), | ||||
|         updateCheckingEnabled: this.settings.get( | ||||
|           SETTINGS_KEYS.UPDATE_CHECKING_ENABLED | ||||
|         ), | ||||
|       } | ||||
|  | ||||
|       for (let view of this.savedViews) { | ||||
| @@ -240,6 +258,10 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|       SETTINGS_KEYS.COMMENTS_ENABLED, | ||||
|       this.settingsForm.value.commentsEnabled | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, | ||||
|       this.settingsForm.value.updateCheckingEnabled | ||||
|     ) | ||||
|     this.settings.setLanguage(this.settingsForm.value.displayLanguage) | ||||
|     this.settings | ||||
|       .storeSettings() | ||||
|   | ||||
| @@ -37,6 +37,9 @@ export const SETTINGS_KEYS = { | ||||
|   NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD: | ||||
|     'general-settings:notifications:consumer-suppress-on-dashboard', | ||||
|   COMMENTS_ENABLED: 'general-settings:comments-enabled', | ||||
|   UPDATE_CHECKING_ENABLED: 'general-settings:update-checking:enabled', | ||||
|   UPDATE_CHECKING_BACKEND_SETTING: | ||||
|     'general-settings:update-checking:backend-setting', | ||||
| } | ||||
|  | ||||
| export const SETTINGS: PaperlessUiSetting[] = [ | ||||
| @@ -120,4 +123,14 @@ export const SETTINGS: PaperlessUiSetting[] = [ | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, | ||||
|     type: 'boolean', | ||||
|     default: false, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.UPDATE_CHECKING_BACKEND_SETTING, | ||||
|     type: 'string', | ||||
|     default: '', | ||||
|   }, | ||||
| ] | ||||
|   | ||||
| @@ -6,7 +6,6 @@ import { environment } from 'src/environments/environment' | ||||
| export interface AppRemoteVersion { | ||||
|   version: string | ||||
|   update_available: boolean | ||||
|   feature_is_set: boolean | ||||
| } | ||||
|  | ||||
| @Injectable({ | ||||
|   | ||||
| @@ -313,13 +313,7 @@ export class SettingsService { | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   get(key: string): any { | ||||
|     let setting = SETTINGS.find((s) => s.key == key) | ||||
|  | ||||
|     if (!setting) { | ||||
|       return null | ||||
|     } | ||||
|  | ||||
|   private getSettingRawValue(key: string): any { | ||||
|     let value = null | ||||
|     // parse key:key:key into nested object | ||||
|     const keys = key.replace('general-settings:', '').split(':') | ||||
| @@ -330,6 +324,17 @@ export class SettingsService { | ||||
|       if (index == keys.length - 1) value = settingObj[keyPart] | ||||
|       else settingObj = settingObj[keyPart] | ||||
|     }) | ||||
|     return value | ||||
|   } | ||||
|  | ||||
|   get(key: string): any { | ||||
|     let setting = SETTINGS.find((s) => s.key == key) | ||||
|  | ||||
|     if (!setting) { | ||||
|       return null | ||||
|     } | ||||
|  | ||||
|     let value = this.getSettingRawValue(key) | ||||
|  | ||||
|     if (value != null) { | ||||
|       switch (setting.type) { | ||||
| @@ -359,6 +364,11 @@ export class SettingsService { | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   private settingIsSet(key: string): boolean { | ||||
|     let value = this.getSettingRawValue(key) | ||||
|     return value != null | ||||
|   } | ||||
|  | ||||
|   storeSettings(): Observable<any> { | ||||
|     return this.http.post(this.baseUrl, { settings: this.settings }) | ||||
|   } | ||||
| @@ -401,4 +411,29 @@ export class SettingsService { | ||||
|         }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   get updateCheckingEnabled(): boolean { | ||||
|     const backendSetting = this.get( | ||||
|       SETTINGS_KEYS.UPDATE_CHECKING_BACKEND_SETTING | ||||
|     ) | ||||
|  | ||||
|     if ( | ||||
|       !this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED) && | ||||
|       backendSetting != 'default' | ||||
|     ) { | ||||
|       this.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, backendSetting === 'true') | ||||
|     } | ||||
|     return ( | ||||
|       this.get(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED) || | ||||
|       (!this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED) && | ||||
|         backendSetting == 'true') | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   get updateCheckingIsSet(): boolean { | ||||
|     return ( | ||||
|       this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED) || | ||||
|       this.get(SETTINGS_KEYS.UPDATE_CHECKING_BACKEND_SETTING) != 'default' | ||||
|     ) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -526,6 +526,25 @@ a.badge { | ||||
|     border-color: var(--bs-primary); | ||||
| } | ||||
|  | ||||
| .btn-group-xs { | ||||
|   > .btn { | ||||
|     padding: 0.2rem 0.25rem; | ||||
|     font-size: 0.675rem; | ||||
|     line-height: 1.2; | ||||
|     border-radius: 0.15rem; | ||||
|   } | ||||
|  | ||||
|   > .btn:not(:first-child) { | ||||
|     border-top-left-radius: 0; | ||||
|     border-bottom-left-radius: 0; | ||||
|   } | ||||
|  | ||||
|   > .btn:not(:last-child) { | ||||
|     border-top-right-radius: 0; | ||||
|     border-bottom-right-radius: 0; | ||||
|   } | ||||
| } | ||||
|  | ||||
| code { | ||||
|   color: var(--pngx-body-color-accent) | ||||
| } | ||||
|   | ||||
| @@ -780,42 +780,38 @@ class RemoteVersionView(GenericAPIView): | ||||
|         remote_version = "0.0.0" | ||||
|         is_greater_than_current = False | ||||
|         current_version = packaging_version.parse(version.__full_version_str__) | ||||
|         # TODO: this can likely be removed when frontend settings are saved to DB | ||||
|         feature_is_set = settings.ENABLE_UPDATE_CHECK != "default" | ||||
|         if feature_is_set and settings.ENABLE_UPDATE_CHECK: | ||||
|             try: | ||||
|                 req = urllib.request.Request( | ||||
|                     "https://api.github.com/repos/paperless-ngx/" | ||||
|                     "paperless-ngx/releases/latest", | ||||
|                 ) | ||||
|                 # Ensure a JSON response | ||||
|                 req.add_header("Accept", "application/json") | ||||
|  | ||||
|                 with urllib.request.urlopen(req) as response: | ||||
|                     remote = response.read().decode("utf-8") | ||||
|                 try: | ||||
|                     remote_json = json.loads(remote) | ||||
|                     remote_version = remote_json["tag_name"] | ||||
|                     # Basically PEP 616 but that only went in 3.9 | ||||
|                     if remote_version.startswith("ngx-"): | ||||
|                         remote_version = remote_version[len("ngx-") :] | ||||
|                 except ValueError: | ||||
|                     logger.debug("An error occurred parsing remote version json") | ||||
|             except urllib.error.URLError: | ||||
|                 logger.debug("An error occurred checking for available updates") | ||||
|  | ||||
|             is_greater_than_current = ( | ||||
|                 packaging_version.parse( | ||||
|                     remote_version, | ||||
|                 ) | ||||
|                 > current_version | ||||
|         try: | ||||
|             req = urllib.request.Request( | ||||
|                 "https://api.github.com/repos/paperless-ngx/" | ||||
|                 "paperless-ngx/releases/latest", | ||||
|             ) | ||||
|             # Ensure a JSON response | ||||
|             req.add_header("Accept", "application/json") | ||||
|  | ||||
|             with urllib.request.urlopen(req) as response: | ||||
|                 remote = response.read().decode("utf-8") | ||||
|             try: | ||||
|                 remote_json = json.loads(remote) | ||||
|                 remote_version = remote_json["tag_name"] | ||||
|                 # Basically PEP 616 but that only went in 3.9 | ||||
|                 if remote_version.startswith("ngx-"): | ||||
|                     remote_version = remote_version[len("ngx-") :] | ||||
|             except ValueError: | ||||
|                 logger.debug("An error occurred parsing remote version json") | ||||
|         except urllib.error.URLError: | ||||
|             logger.debug("An error occurred checking for available updates") | ||||
|  | ||||
|         is_greater_than_current = ( | ||||
|             packaging_version.parse( | ||||
|                 remote_version, | ||||
|             ) | ||||
|             > current_version | ||||
|         ) | ||||
|  | ||||
|         return Response( | ||||
|             { | ||||
|                 "version": remote_version, | ||||
|                 "update_available": is_greater_than_current, | ||||
|                 "feature_is_set": feature_is_set, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
| @@ -848,15 +844,23 @@ class UiSettingsView(GenericAPIView): | ||||
|         displayname = user.username | ||||
|         if user.first_name or user.last_name: | ||||
|             displayname = " ".join([user.first_name, user.last_name]) | ||||
|         settings = {} | ||||
|         ui_settings = {} | ||||
|         if hasattr(user, "ui_settings"): | ||||
|             settings = user.ui_settings.settings | ||||
|             ui_settings = user.ui_settings.settings | ||||
|         if ui_settings["update_checking"]: | ||||
|             ui_settings["update_checking"][ | ||||
|                 "backend_setting" | ||||
|             ] = settings.ENABLE_UPDATE_CHECK | ||||
|         else: | ||||
|             ui_settings["update_checking"] = { | ||||
|                 "backend_setting": settings.ENABLE_UPDATE_CHECK, | ||||
|             } | ||||
|         return Response( | ||||
|             { | ||||
|                 "user_id": user.id, | ||||
|                 "username": user.username, | ||||
|                 "display_name": displayname, | ||||
|                 "settings": settings, | ||||
|                 "settings": ui_settings, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon