mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
Feature/progress widget (#760)
* Implemented a new widget to show when operations are occuring in the backend (tasks + progress events). Fixed an oversight on progress reporting where I sent 100F instead of 1F. * Hooked in more progress events for tasks on the backend. Cleaned up code and integrated some RBS into it. CSS needed. * Show a colored icon when events are active * Added some styling to the progress widget
This commit is contained in:
parent
a94fdbc9cb
commit
281352001d
@ -280,13 +280,13 @@ namespace API.Services
|
||||
"[MetadataService] Processed {SeriesStart} - {SeriesEnd} out of {TotalSeries} series in {ElapsedScanTime} milliseconds for {LibraryName}",
|
||||
chunk * chunkInfo.ChunkSize, (chunk * chunkInfo.ChunkSize) + nonLibrarySeries.Count, chunkInfo.TotalSize, stopwatch.ElapsedMilliseconds, library.Name);
|
||||
}
|
||||
var progress = Math.Max(0F, Math.Min(100F, i * 1F / chunkInfo.TotalChunks));
|
||||
var progress = Math.Max(0F, Math.Min(1F, i * 1F / chunkInfo.TotalChunks));
|
||||
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadataProgress,
|
||||
MessageFactory.RefreshMetadataProgressEvent(library.Id, progress));
|
||||
}
|
||||
|
||||
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadataProgress,
|
||||
MessageFactory.RefreshMetadataProgressEvent(library.Id, 100F));
|
||||
MessageFactory.RefreshMetadataProgressEvent(library.Id, 1F));
|
||||
|
||||
_logger.LogInformation("[MetadataService] Updated metadata for {SeriesNumber} series in library {LibraryName} in {ElapsedMilliseconds} milliseconds total", chunkInfo.TotalSize, library.Name, totalTime);
|
||||
}
|
||||
|
@ -8,7 +8,9 @@ using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.SignalR;
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@ -19,14 +21,17 @@ namespace API.Services.Tasks
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILogger<BackupService> _logger;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IHubContext<MessageHub> _messageHub;
|
||||
|
||||
private readonly IList<string> _backupFiles;
|
||||
|
||||
public BackupService(IUnitOfWork unitOfWork, ILogger<BackupService> logger, IDirectoryService directoryService, IConfiguration config)
|
||||
public BackupService(IUnitOfWork unitOfWork, ILogger<BackupService> logger,
|
||||
IDirectoryService directoryService, IConfiguration config, IHubContext<MessageHub> messageHub)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_logger = logger;
|
||||
_directoryService = directoryService;
|
||||
_messageHub = messageHub;
|
||||
|
||||
var maxRollingFiles = config.GetMaxRollingFiles();
|
||||
var loggingSection = config.GetLoggingFileName();
|
||||
@ -76,6 +81,8 @@ namespace API.Services.Tasks
|
||||
return;
|
||||
}
|
||||
|
||||
await SendProgress(0F);
|
||||
|
||||
var dateString = $"{DateTime.Now.ToShortDateString()}_{DateTime.Now.ToLongTimeString()}".Replace("/", "_").Replace(":", "_");
|
||||
var zipPath = Path.Join(backupDirectory, $"kavita_backup_{dateString}.zip");
|
||||
|
||||
@ -92,8 +99,12 @@ namespace API.Services.Tasks
|
||||
_directoryService.CopyFilesToDirectory(
|
||||
_backupFiles.Select(file => Path.Join(DirectoryService.ConfigDirectory, file)).ToList(), tempDirectory);
|
||||
|
||||
await SendProgress(0.25F);
|
||||
|
||||
await CopyCoverImagesToBackupDirectory(tempDirectory);
|
||||
|
||||
await SendProgress(0.75F);
|
||||
|
||||
try
|
||||
{
|
||||
ZipFile.CreateFromDirectory(tempDirectory, zipPath);
|
||||
@ -105,6 +116,7 @@ namespace API.Services.Tasks
|
||||
|
||||
DirectoryService.ClearAndDeleteDirectory(tempDirectory);
|
||||
_logger.LogInformation("Database backup completed");
|
||||
await SendProgress(1F);
|
||||
}
|
||||
|
||||
private async Task CopyCoverImagesToBackupDirectory(string tempDirectory)
|
||||
@ -137,6 +149,12 @@ namespace API.Services.Tasks
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendProgress(float progress)
|
||||
{
|
||||
await _messageHub.Clients.All.SendAsync(SignalREvents.BackupDatabaseProgress,
|
||||
MessageFactory.BackupDatabaseProgressEvent(progress));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes Database backups older than 30 days. If all backups are older than 30 days, the latest is kept.
|
||||
/// </summary>
|
||||
|
@ -2,7 +2,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.SignalR;
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services.Tasks
|
||||
@ -16,14 +18,16 @@ namespace API.Services.Tasks
|
||||
private readonly ILogger<CleanupService> _logger;
|
||||
private readonly IBackupService _backupService;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IHubContext<MessageHub> _messageHub;
|
||||
|
||||
public CleanupService(ICacheService cacheService, ILogger<CleanupService> logger,
|
||||
IBackupService backupService, IUnitOfWork unitOfWork)
|
||||
IBackupService backupService, IUnitOfWork unitOfWork, IHubContext<MessageHub> messageHub)
|
||||
{
|
||||
_cacheService = cacheService;
|
||||
_logger = logger;
|
||||
_backupService = backupService;
|
||||
_unitOfWork = unitOfWork;
|
||||
_messageHub = messageHub;
|
||||
}
|
||||
|
||||
public void CleanupCacheDirectory()
|
||||
@ -39,19 +43,31 @@ namespace API.Services.Tasks
|
||||
public async Task Cleanup()
|
||||
{
|
||||
_logger.LogInformation("Starting Cleanup");
|
||||
await SendProgress(0F);
|
||||
_logger.LogInformation("Cleaning temp directory");
|
||||
var tempDirectory = DirectoryService.TempDirectory;
|
||||
DirectoryService.ClearDirectory(tempDirectory);
|
||||
DirectoryService.ClearDirectory(DirectoryService.TempDirectory);
|
||||
await SendProgress(0.1F);
|
||||
CleanupCacheDirectory();
|
||||
await SendProgress(0.25F);
|
||||
_logger.LogInformation("Cleaning old database backups");
|
||||
_backupService.CleanupBackups();
|
||||
await SendProgress(0.50F);
|
||||
_logger.LogInformation("Cleaning deleted cover images");
|
||||
await DeleteSeriesCoverImages();
|
||||
await SendProgress(0.6F);
|
||||
await DeleteChapterCoverImages();
|
||||
await SendProgress(0.7F);
|
||||
await DeleteTagCoverImages();
|
||||
await SendProgress(1F);
|
||||
_logger.LogInformation("Cleanup finished");
|
||||
}
|
||||
|
||||
private async Task SendProgress(float progress)
|
||||
{
|
||||
await _messageHub.Clients.All.SendAsync(SignalREvents.CleanupProgress,
|
||||
MessageFactory.CleanupProgressEvent(progress));
|
||||
}
|
||||
|
||||
private async Task DeleteSeriesCoverImages()
|
||||
{
|
||||
var images = await _unitOfWork.SeriesRepository.GetAllCoverImagesAsync();
|
||||
|
@ -244,7 +244,7 @@ namespace API.Services.Tasks
|
||||
|
||||
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadata(libraryId, false));
|
||||
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,
|
||||
MessageFactory.ScanLibraryProgressEvent(libraryId, 100));
|
||||
MessageFactory.ScanLibraryProgressEvent(libraryId, 1F));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -342,7 +342,7 @@ namespace API.Services.Tasks
|
||||
await _messageHub.Clients.All.SendAsync(SignalREvents.SeriesRemoved, MessageFactory.SeriesRemovedEvent(missing.Id, missing.Name, library.Id));
|
||||
}
|
||||
|
||||
var progress = Math.Max(0, Math.Min(100, ((chunk + 1F) * chunkInfo.ChunkSize) / chunkInfo.TotalSize));
|
||||
var progress = Math.Max(0, Math.Min(1, ((chunk + 1F) * chunkInfo.ChunkSize) / chunkInfo.TotalSize));
|
||||
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,
|
||||
MessageFactory.ScanLibraryProgressEvent(library.Id, progress));
|
||||
}
|
||||
@ -405,7 +405,7 @@ namespace API.Services.Tasks
|
||||
series.Name, $"{series.Name}_{series.NormalizedName}_{series.LocalizedName}_{series.LibraryId}_{series.Format}");
|
||||
}
|
||||
|
||||
var progress = Math.Max(0F, Math.Min(100F, i * 1F / newSeries.Count));
|
||||
var progress = Math.Max(0F, Math.Min(1F, i * 1F / newSeries.Count));
|
||||
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,
|
||||
MessageFactory.ScanLibraryProgressEvent(library.Id, progress));
|
||||
i++;
|
||||
|
@ -89,6 +89,31 @@ namespace API.SignalR
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage BackupDatabaseProgressEvent(float progress)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
Name = SignalREvents.BackupDatabaseProgress,
|
||||
Body = new
|
||||
{
|
||||
Progress = progress
|
||||
}
|
||||
};
|
||||
}
|
||||
public static SignalRMessage CleanupProgressEvent(float progress)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
Name = SignalREvents.CleanupProgress,
|
||||
Body = new
|
||||
{
|
||||
Progress = progress
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static SignalRMessage UpdateVersionEvent(UpdateNotificationDto update)
|
||||
{
|
||||
return new SignalRMessage
|
||||
|
@ -19,5 +19,13 @@
|
||||
public const string OnlineUsers = "OnlineUsers";
|
||||
public const string SeriesAddedToCollection = "SeriesAddedToCollection";
|
||||
public const string ScanLibraryError = "ScanLibraryError";
|
||||
/// <summary>
|
||||
/// Event sent out during backing up the database
|
||||
/// </summary>
|
||||
public const string BackupDatabaseProgress = "BackupDatabaseProgress";
|
||||
/// <summary>
|
||||
/// Event sent out during cleaning up temp and cache folders
|
||||
/// </summary>
|
||||
public const string CleanupProgress = "CleanupProgress";
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export interface ScanLibraryProgressEvent {
|
||||
export interface ProgressEvent {
|
||||
libraryId: number;
|
||||
progress: number;
|
||||
eventTime: string;
|
||||
|
@ -7,7 +7,7 @@ import { BehaviorSubject, ReplaySubject } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { UpdateNotificationModalComponent } from '../shared/update-notification/update-notification-modal.component';
|
||||
import { RefreshMetadataEvent } from '../_models/events/refresh-metadata-event';
|
||||
import { ScanLibraryProgressEvent } from '../_models/events/scan-library-progress-event';
|
||||
import { ProgressEvent } from '../_models/events/scan-library-progress-event';
|
||||
import { ScanSeriesEvent } from '../_models/events/scan-series-event';
|
||||
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
||||
import { User } from '../_models/user';
|
||||
@ -22,7 +22,9 @@ export enum EVENTS {
|
||||
ScanLibraryProgress = 'ScanLibraryProgress',
|
||||
OnlineUsers = 'OnlineUsers',
|
||||
SeriesAddedToCollection = 'SeriesAddedToCollection',
|
||||
ScanLibraryError = 'ScanLibraryError'
|
||||
ScanLibraryError = 'ScanLibraryError',
|
||||
BackupDatabaseProgress = 'BackupDatabaseProgress',
|
||||
CleanupProgress = 'CleanupProgress'
|
||||
}
|
||||
|
||||
export interface Message<T> {
|
||||
@ -45,7 +47,7 @@ export class MessageHubService {
|
||||
onlineUsers$ = this.onlineUsersSource.asObservable();
|
||||
|
||||
public scanSeries: EventEmitter<ScanSeriesEvent> = new EventEmitter<ScanSeriesEvent>();
|
||||
public scanLibrary: EventEmitter<ScanLibraryProgressEvent> = new EventEmitter<ScanLibraryProgressEvent>();
|
||||
public scanLibrary: EventEmitter<ProgressEvent> = new EventEmitter<ProgressEvent>(); // TODO: Refactor this name to be generic
|
||||
public seriesAdded: EventEmitter<SeriesAddedEvent> = new EventEmitter<SeriesAddedEvent>();
|
||||
public refreshMetadata: EventEmitter<RefreshMetadataEvent> = new EventEmitter<RefreshMetadataEvent>();
|
||||
|
||||
@ -90,6 +92,20 @@ export class MessageHubService {
|
||||
this.scanLibrary.emit(resp.body);
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.BackupDatabaseProgress, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.BackupDatabaseProgress,
|
||||
payload: resp.body
|
||||
});
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.CleanupProgress, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.CleanupProgress,
|
||||
payload: resp.body
|
||||
});
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.RefreshMetadataProgress, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.RefreshMetadataProgress,
|
||||
|
@ -4,7 +4,7 @@ import { ToastrService } from 'ngx-toastr';
|
||||
import { Subject } from 'rxjs';
|
||||
import { take, takeUntil } from 'rxjs/operators';
|
||||
import { ConfirmService } from 'src/app/shared/confirm.service';
|
||||
import { ScanLibraryProgressEvent } from 'src/app/_models/events/scan-library-progress-event';
|
||||
import { ProgressEvent } from 'src/app/_models/events/scan-library-progress-event';
|
||||
import { Library, LibraryType } from 'src/app/_models/library';
|
||||
import { LibraryService } from 'src/app/_services/library.service';
|
||||
import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service';
|
||||
@ -40,7 +40,7 @@ export class ManageLibraryComponent implements OnInit, OnDestroy {
|
||||
this.hubService.messages$.pipe(takeUntil(this.onDestroy)).subscribe((event) => {
|
||||
if (event.event !== EVENTS.ScanLibraryProgress) return;
|
||||
|
||||
const scanEvent = event.payload as ScanLibraryProgressEvent;
|
||||
const scanEvent = event.payload as ProgressEvent;
|
||||
this.scanInProgress[scanEvent.libraryId] = {progress: scanEvent.progress !== 100};
|
||||
if (scanEvent.progress === 0) {
|
||||
this.scanInProgress[scanEvent.libraryId].timestamp = scanEvent.eventTime;
|
||||
|
@ -7,7 +7,7 @@ import { AppComponent } from './app.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { NgbCollapseModule, NgbDropdownModule, NgbNavModule, NgbPaginationModule, NgbRatingModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NgbCollapseModule, NgbDropdownModule, NgbNavModule, NgbPaginationModule, NgbPopoverModule, NgbRatingModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NavHeaderComponent } from './nav-header/nav-header.component';
|
||||
import { JwtInterceptor } from './_interceptors/jwt.interceptor';
|
||||
import { UserLoginComponent } from './user-login/user-login.component';
|
||||
@ -32,6 +32,7 @@ import { CollectionsModule } from './collections/collections.module';
|
||||
import { ReadingListModule } from './reading-list/reading-list.module';
|
||||
import { SAVER, getSaver } from './shared/_providers/saver.provider';
|
||||
import { ConfigData } from './_models/config-data';
|
||||
import { NavEventsToggleComponent } from './nav-events-toggle/nav-events-toggle.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
@ -48,6 +49,7 @@ import { ConfigData } from './_models/config-data';
|
||||
RecentlyAddedComponent,
|
||||
OnDeckComponent,
|
||||
DashboardComponent,
|
||||
NavEventsToggleComponent,
|
||||
],
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
@ -59,6 +61,7 @@ import { ConfigData } from './_models/config-data';
|
||||
|
||||
NgbDropdownModule, // Nav
|
||||
AutocompleteLibModule, // Nav
|
||||
NgbPopoverModule, // Nav Events toggle
|
||||
NgbRatingModule, // Series Detail
|
||||
NgbNavModule,
|
||||
NgbPaginationModule,
|
||||
|
@ -0,0 +1,22 @@
|
||||
<ng-container>
|
||||
|
||||
<button type="button" class="btn btn-icon {{progressEventsSource.getValue().length > 0 ? 'colored' : ''}}"
|
||||
[ngbPopover]="popContent" title="Activity" placement="bottom" [popoverClass]="'nav-events'">
|
||||
<i aria-hidden="true" class="fa fa-wave-square"></i>
|
||||
</button>
|
||||
|
||||
<ng-template #popContent>
|
||||
<ul class="list-group list-group-flush dark-menu">
|
||||
<li class="list-group-item dark-menu-item" *ngFor="let event of progressEvents$ | async">
|
||||
<div class="spinner-border text-primary small-spinner"
|
||||
role="status" title="Started at {{event.timestamp | date: 'short'}}"
|
||||
attr.aria-valuetext="{{prettyPrintProgress(event.progress)}}%" [attr.aria-valuenow]="prettyPrintProgress(event.progress)">
|
||||
<span class="sr-only">Scan for {{event.libraryName}} in progress</span>
|
||||
</div>
|
||||
{{prettyPrintProgress(event.progress)}}%
|
||||
{{prettyPrintEvent(event.eventType)}} {{event.libraryName}}
|
||||
</li>
|
||||
<li class="list-group-item dark-menu-item" *ngIf="progressEventsSource.getValue().length === 0">Not much going on here</li>
|
||||
</ul>
|
||||
</ng-template>
|
||||
</ng-container>
|
@ -0,0 +1,23 @@
|
||||
@import "../../theme/colors";
|
||||
|
||||
.small-spinner {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.nav-events {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.nav-events .popover-body {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.colored {
|
||||
background-color: $primary-color;
|
||||
border-radius: 60px;
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { ProgressEvent } from '../_models/events/scan-library-progress-event';
|
||||
import { User } from '../_models/user';
|
||||
import { LibraryService } from '../_services/library.service';
|
||||
import { EVENTS, Message, MessageHubService } from '../_services/message-hub.service';
|
||||
|
||||
interface ProcessedEvent {
|
||||
eventType: string;
|
||||
timestamp?: string;
|
||||
progress: number;
|
||||
libraryId: number;
|
||||
libraryName: string;
|
||||
}
|
||||
|
||||
type ProgressType = EVENTS.ScanLibraryProgress | EVENTS.RefreshMetadataProgress | EVENTS.BackupDatabaseProgress | EVENTS.CleanupProgress;
|
||||
|
||||
@Component({
|
||||
selector: 'app-nav-events-toggle',
|
||||
templateUrl: './nav-events-toggle.component.html',
|
||||
styleUrls: ['./nav-events-toggle.component.scss']
|
||||
})
|
||||
export class NavEventsToggleComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input() user!: User;
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
/**
|
||||
* Events that come through and are merged (ie progress event gets merged into a progress event)
|
||||
*/
|
||||
progressEventsSource = new BehaviorSubject<ProcessedEvent[]>([]);
|
||||
progressEvents$ = this.progressEventsSource.asObservable();
|
||||
|
||||
constructor(private messageHub: MessageHubService, private libraryService: LibraryService) { }
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.messageHub.messages$.pipe(takeUntil(this.onDestroy)).subscribe(event => {
|
||||
if (event.event === EVENTS.ScanLibraryProgress || event.event === EVENTS.RefreshMetadataProgress || event.event === EVENTS.BackupDatabaseProgress || event.event === EVENTS.CleanupProgress) {
|
||||
this.processProgressEvent(event, event.event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
processProgressEvent(event: Message<ProgressEvent>, eventType: string) {
|
||||
const scanEvent = event.payload as ProgressEvent;
|
||||
console.log(event.event, event.payload);
|
||||
|
||||
|
||||
this.libraryService.getLibraryNames().subscribe(names => {
|
||||
const data = this.progressEventsSource.getValue();
|
||||
const index = data.findIndex(item => item.eventType === eventType && item.libraryId === event.payload.libraryId);
|
||||
if (index >= 0) {
|
||||
data.splice(index, 1);
|
||||
}
|
||||
|
||||
if (scanEvent.progress !== 1) {
|
||||
const libraryName = names[scanEvent.libraryId] || '';
|
||||
const newEvent = {eventType: eventType, timestamp: scanEvent.eventTime, progress: scanEvent.progress, libraryId: scanEvent.libraryId, libraryName};
|
||||
data.push(newEvent);
|
||||
}
|
||||
|
||||
|
||||
this.progressEventsSource.next(data);
|
||||
});
|
||||
}
|
||||
|
||||
prettyPrintProgress(progress: number) {
|
||||
return Math.trunc(progress * 100);
|
||||
}
|
||||
|
||||
prettyPrintEvent(eventType: string) {
|
||||
switch(eventType) {
|
||||
case (EVENTS.ScanLibraryProgress): return 'Scanning ';
|
||||
case (EVENTS.RefreshMetadataProgress): return 'Refreshing ';
|
||||
case (EVENTS.CleanupProgress): return 'Clearing Cache';
|
||||
case (EVENTS.BackupDatabaseProgress): return 'Backing up Database';
|
||||
default: return eventType;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -62,6 +62,10 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="nav-item" *ngIf="(accountService.currentUser$ | async) as user">
|
||||
<app-nav-events-toggle [user]="user"></app-nav-events-toggle>
|
||||
</div>
|
||||
|
||||
<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 | sentenceCase}}
|
||||
|
@ -94,6 +94,17 @@ $dark-item-accent-bg: #292d32;
|
||||
border-color: $dark-form-border;
|
||||
}
|
||||
|
||||
.dark-menu {
|
||||
background-color: $dark-form-background-no-opacity;
|
||||
border-color: $dark-form-background;
|
||||
}
|
||||
|
||||
.dark-menu-item {
|
||||
color: $dark-text-color;
|
||||
background-color: $dark-form-background-no-opacity;
|
||||
border-color: $dark-form-background;
|
||||
}
|
||||
|
||||
.dropdown .list-group-item:hover {
|
||||
background-color: $dark-hover-color;
|
||||
}
|
||||
@ -177,6 +188,10 @@ $dark-item-accent-bg: #292d32;
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
.nav-events, .nav-events .popover-body {
|
||||
background-color: $dark-form-background-no-opacity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user