Update Notification Refactor (#511)
* Replaced profile links to anchors so we can open in new tab if we like * Refactored how update checking works. We now explicitly check and send back on the same API. We have a weekly job that will push an update to the user. * Implemented a changelog tab * Ported over a GA fix for using ' in PR bodies. * Don't check cert for Github
This commit is contained in:
parent
0e48aeebc5
commit
2a76092566
21 changed files with 246 additions and 56 deletions
|
@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
|||
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { User } from '@sentry/angular';
|
||||
import { BehaviorSubject, ReplaySubject } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { UpdateNotificationModalComponent } from '../shared/update-notification/update-notification-modal.component';
|
||||
|
||||
|
@ -9,6 +10,11 @@ export enum EVENTS {
|
|||
UpdateAvailable = 'UpdateAvailable'
|
||||
}
|
||||
|
||||
export interface Message<T> {
|
||||
event: EVENTS;
|
||||
payload: T;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
|
@ -17,6 +23,9 @@ export class MessageHubService {
|
|||
private hubConnection!: HubConnection;
|
||||
private updateNotificationModalRef: NgbModalRef | null = null;
|
||||
|
||||
private messagesSource = new ReplaySubject<Message<any>>(1);
|
||||
public messages$ = this.messagesSource.asObservable();
|
||||
|
||||
constructor(private modalService: NgbModal) { }
|
||||
|
||||
createHubConnection(user: User) {
|
||||
|
@ -36,6 +45,10 @@ export class MessageHubService {
|
|||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.UpdateAvailable, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.UpdateAvailable,
|
||||
payload: resp.body
|
||||
});
|
||||
// Ensure only 1 instance of UpdateNotificationModal can be open at once
|
||||
if (this.updateNotificationModalRef != null) { return; }
|
||||
this.updateNotificationModalRef = this.modalService.open(UpdateNotificationModalComponent, { scrollable: true, size: 'lg' });
|
||||
|
|
|
@ -2,6 +2,7 @@ import { HttpClient } from '@angular/common/http';
|
|||
import { Injectable } from '@angular/core';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { ServerInfo } from '../admin/_models/server-info';
|
||||
import { UpdateVersionEvent } from '../_models/events/update-version-event';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -29,6 +30,10 @@ export class ServerService {
|
|||
}
|
||||
|
||||
checkForUpdate() {
|
||||
return this.httpClient.post(this.baseUrl + 'server/check-update', {});
|
||||
return this.httpClient.get<UpdateVersionEvent>(this.baseUrl + 'server/check-update', {});
|
||||
}
|
||||
|
||||
getChangelog() {
|
||||
return this.httpClient.get<UpdateVersionEvent[]>(this.baseUrl + 'server/changelog', {});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import { ManageSettingsComponent } from './manage-settings/manage-settings.compo
|
|||
import { FilterPipe } from './filter.pipe';
|
||||
import { EditRbsModalComponent } from './_modals/edit-rbs-modal/edit-rbs-modal.component';
|
||||
import { ManageSystemComponent } from './manage-system/manage-system.component';
|
||||
import { ChangelogComponent } from './changelog/changelog.component';
|
||||
|
||||
|
||||
|
||||
|
@ -31,7 +32,8 @@ import { ManageSystemComponent } from './manage-system/manage-system.component';
|
|||
ManageSettingsComponent,
|
||||
FilterPipe,
|
||||
EditRbsModalComponent,
|
||||
ManageSystemComponent
|
||||
ManageSystemComponent,
|
||||
ChangelogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
|
14
UI/Web/src/app/admin/changelog/changelog.component.html
Normal file
14
UI/Web/src/app/admin/changelog/changelog.component.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<ng-container *ngFor="let update of updates; let indx = index;">
|
||||
<div class="card w-100 mb-2" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{update.updateTitle}} <span class="badge badge-secondary" *ngIf="update.updateVersion === update.currentVersion">Installed</span></h5>
|
||||
<pre class="card-text update-body" [innerHtml]="update.updateBody | safeHtml"></pre>
|
||||
<a *ngIf="!update.isDocker" href="{{update.updateUrl}}" class="btn btn-{{indx === 0 ? 'primary' : 'secondary'}} float-right" target="_blank">Download</a>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<div class="spinner-border text-secondary" *ngIf="isLoading" role="status">
|
||||
<span class="invisible">Loading...</span>
|
||||
</div>
|
5
UI/Web/src/app/admin/changelog/changelog.component.scss
Normal file
5
UI/Web/src/app/admin/changelog/changelog.component.scss
Normal file
|
@ -0,0 +1,5 @@
|
|||
.update-body {
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
23
UI/Web/src/app/admin/changelog/changelog.component.ts
Normal file
23
UI/Web/src/app/admin/changelog/changelog.component.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { UpdateVersionEvent } from 'src/app/_models/events/update-version-event';
|
||||
import { ServerService } from 'src/app/_services/server.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-changelog',
|
||||
templateUrl: './changelog.component.html',
|
||||
styleUrls: ['./changelog.component.scss']
|
||||
})
|
||||
export class ChangelogComponent implements OnInit {
|
||||
|
||||
updates: Array<UpdateVersionEvent> = [];
|
||||
isLoading: boolean = true;
|
||||
|
||||
constructor(private serverService: ServerService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.serverService.getChangelog().subscribe(updates => {
|
||||
this.updates = updates;
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -16,6 +16,9 @@
|
|||
</ng-container>
|
||||
<ng-container *ngIf="tab.fragment === 'system'">
|
||||
<app-manage-system></app-manage-system>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="tab.fragment === 'changelog'">
|
||||
<app-changelog></app-changelog>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
|
|
@ -17,7 +17,8 @@ export class DashboardComponent implements OnInit {
|
|||
{title: 'General', fragment: ''},
|
||||
{title: 'Users', fragment: 'users'},
|
||||
{title: 'Libraries', fragment: 'libraries'},
|
||||
{title: 'System', fragment: 'system'}
|
||||
{title: 'System', fragment: 'system'},
|
||||
{title: 'Changelog', fragment: 'changelog'},
|
||||
];
|
||||
counter = this.tabs.length + 1;
|
||||
active = this.tabs[0];
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="float-right">
|
||||
<div class="d-inline-block" ngbDropdown #myDrop="ngbDropdown">
|
||||
<button class="btn btn-outline-primary mr-2" id="dropdownManual" ngbDropdownAnchor (focus)="myDrop.open()">
|
||||
<ng-container *ngIf="backupDBInProgress || clearCacheInProgress">
|
||||
<ng-container *ngIf="backupDBInProgress || clearCacheInProgress || isCheckingForUpdate || downloadLogsInProgress">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</ng-container>
|
||||
|
@ -19,7 +19,7 @@
|
|||
<button ngbDropdownItem (click)="downloadLogs()" [disabled]="downloadLogsInProgress">
|
||||
Download Logs
|
||||
</button>
|
||||
<button ngbDropdownItem (click)="checkForUpdates()" [disabled]="hasCheckedForUpdate">
|
||||
<button ngbDropdownItem (click)="checkForUpdates()">
|
||||
Check for Updates
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { finalize, take, takeWhile } from 'rxjs/operators';
|
||||
import { UpdateNotificationModalComponent } from 'src/app/shared/update-notification/update-notification-modal.component';
|
||||
import { DownloadService } from 'src/app/shared/_services/download.service';
|
||||
import { ServerService } from 'src/app/_services/server.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
|
@ -21,11 +23,12 @@ export class ManageSystemComponent implements OnInit {
|
|||
|
||||
clearCacheInProgress: boolean = false;
|
||||
backupDBInProgress: boolean = false;
|
||||
hasCheckedForUpdate: boolean = false;
|
||||
isCheckingForUpdate: boolean = false;
|
||||
downloadLogsInProgress: boolean = false;
|
||||
|
||||
constructor(private settingsService: SettingsService, private toastr: ToastrService,
|
||||
private serverService: ServerService, public downloadService: DownloadService) { }
|
||||
private serverService: ServerService, public downloadService: DownloadService,
|
||||
private modalService: NgbModal) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
|
@ -82,9 +85,15 @@ export class ManageSystemComponent implements OnInit {
|
|||
}
|
||||
|
||||
checkForUpdates() {
|
||||
this.hasCheckedForUpdate = true;
|
||||
this.serverService.checkForUpdate().subscribe(() => {
|
||||
this.toastr.info('This might take a few minutes. If an update is available, the server will notify you.');
|
||||
this.isCheckingForUpdate = true;
|
||||
this.serverService.checkForUpdate().subscribe((update) => {
|
||||
this.isCheckingForUpdate = false;
|
||||
if (update === null) {
|
||||
this.toastr.info('No updates available');
|
||||
return;
|
||||
}
|
||||
const modalRef = this.modalService.open(UpdateNotificationModalComponent, { scrollable: true, size: 'lg' });
|
||||
modalRef.componentInstance.updateData = update;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -61,13 +61,14 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<!-- TODO: Put SignalR notification button dropdown here. -->
|
||||
<div ngbDropdown class="nav-item dropdown" display="dynamic" placement="bottom-right" *ngIf="(accountService.currentUser$ | async) as user" dropdown>
|
||||
<button class="btn btn-outline-secondary primary-text" ngbDropdownToggle>{{user.username | titlecase}}</button>
|
||||
<div ngbDropdownMenu >
|
||||
<button ngbDropdownItem routerLink="/preferences/">User Settings</button>
|
||||
<button ngbDropdownItem routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')">Server Settings</button>
|
||||
<button ngbDropdownItem (click)="logout()">Logout</button>
|
||||
<button class="btn btn-outline-secondary primary-text" ngbDropdownToggle>
|
||||
{{user.username | titlecase}}
|
||||
</button>
|
||||
<div ngbDropdownMenu>
|
||||
<a ngbDropdownItem routerLink="/preferences/">User Settings</a>
|
||||
<a ngbDropdownItem routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')">Server Settings</a>
|
||||
<a ngbDropdownItem (click)="logout()">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,6 @@ import { Router } from '@angular/router';
|
|||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { ScrollService } from '../scroll.service';
|
||||
import { UtilityService } from '../shared/_services/utility.service';
|
||||
import { SearchResult } from '../_models/search-result';
|
||||
import { AccountService } from '../_services/account.service';
|
||||
import { ImageService } from '../_services/image.service';
|
||||
|
@ -31,7 +30,8 @@ export class NavHeaderComponent implements OnInit, OnDestroy {
|
|||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
constructor(public accountService: AccountService, private router: Router, public navService: NavService,
|
||||
private libraryService: LibraryService, public imageService: ImageService, @Inject(DOCUMENT) private document: Document, private scrollService: ScrollService) { }
|
||||
private libraryService: LibraryService, public imageService: ImageService, @Inject(DOCUMENT) private document: Document,
|
||||
private scrollService: ScrollService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.navService.darkMode$.pipe(takeUntil(this.onDestroy)).subscribe(res => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue