diff --git a/src/Ombi/ClientApp/src/app/settings/emby/emby-server-dialog/emby-server-dialog.component.html b/src/Ombi/ClientApp/src/app/settings/emby/emby-server-dialog/emby-server-dialog.component.html new file mode 100644 index 000000000..298048234 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/emby/emby-server-dialog/emby-server-dialog.component.html @@ -0,0 +1,113 @@ +

Server Configuration

+ +
+

Connection

+ + + Server Name + + Auto populated during discovery of the server if left empty. + + +
+ + Hostname / IP + + Must be specified. + + + + Port + + Must be specified. + Must be a number. + + + + SSL + +
+ + + Base URL + + Optional url path to be used with reverse proxy. Example: 'emby' to get + 'https://ip:port/emby'. + + + + API Key + + Must be specified. + + + + Externally Facing Hostname + + + The external address that users will navigate to when they press the 'View On Emby' + button. +
+ Current URL: {{server.serverHostname ? server.serverHostname : + 'https://app.emby.media'}}/#!/item/item.html?id=1 +
+
+ +

Libraries

+ +
+ +
+
+
+ + {{lib.title}} + +
+
+
+
+ +
+
+ + + + + + + + + + \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/emby/emby-server-dialog/emby-server-dialog.component.scss b/src/Ombi/ClientApp/src/app/settings/emby/emby-server-dialog/emby-server-dialog.component.scss new file mode 100644 index 000000000..8499187cc --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/emby/emby-server-dialog/emby-server-dialog.component.scss @@ -0,0 +1,28 @@ +@media (max-width: 978px) { + ::ng-deep .mat-dialog-container { + overflow: unset; + display: flex; + flex-direction: column; + + .mat-dialog-content{ + max-height: unset; + } + + .mat-dialog-actions{ + min-height: unset; + } + + emby-server-dialog-component { + display: flex; + flex-direction: column; + min-height: 1px; + } + } +} + +::ng-deep mat-form-field .mat-form-field { + &-subscript-wrapper { + position: static; + } +} + diff --git a/src/Ombi/ClientApp/src/app/settings/emby/emby-server-dialog/emby-server-dialog.component.ts b/src/Ombi/ClientApp/src/app/settings/emby/emby-server-dialog/emby-server-dialog.component.ts new file mode 100644 index 000000000..dd28d0940 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/emby/emby-server-dialog/emby-server-dialog.component.ts @@ -0,0 +1,217 @@ +import { Component, Inject, ViewChild } from "@angular/core"; +import { NgForm } from "@angular/forms"; +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { + IEmbyLibrariesSettings, + IEmbyServer, + IEmbySettings, +} from "app/interfaces"; +import { + EmbyService, + NotificationService, + SettingsService, + TesterService, +} from "app/services"; +import { isEqual } from "lodash"; + +export interface EmbyServerDialogData { + server: IEmbyServer; + isNewServer: boolean; + savedSettings: IEmbySettings; +} + +@Component({ + selector: "emby-server-dialog-component", + templateUrl: "emby-server-dialog.component.html", + styleUrls: ["emby-server-dialog.component.scss"], +}) +export class EmbyServerDialog { + @ViewChild("embyServerForm") embyServerForm: NgForm; + public server: IEmbyServer; + public isServerNameMissing: boolean; + public isChangeDetected: boolean; + public serverDiscoveryRequired: boolean; + private validatedFields: { + ip: string; + port: number; + ssl: boolean; + apiKey: string; + subDir: string; + }; + + constructor( + private dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: EmbyServerDialogData, + private notificationService: NotificationService, + private settingsService: SettingsService, + private testerService: TesterService, + private embyService: EmbyService + ) { + this.server = structuredClone(data.server); + this.isChangeDetected = false; + this.serverDiscoveryRequired = data.isNewServer; + this.isServerNameMissing = !this.server.name; + this.validatedFields = { + ip: this.server.ip, + port: this.server.port, + ssl: this.server.ssl, + apiKey: this.server.apiKey, + subDir: this.server.subDir, + }; + } + + public processChangeEvent() { + if ( + this.validatedFields.ip !== this.server.ip || + this.validatedFields.port?.toString() !== this.server.port?.toString() || + this.validatedFields.ssl !== this.server.ssl || + this.validatedFields.apiKey !== this.server.apiKey || + this.validatedFields.subDir !== this.server.subDir || + !this.embyServerForm.valid + ) { + this.serverDiscoveryRequired = true; + } else { + this.serverDiscoveryRequired = false; + } + + this.isServerNameMissing = !this.server.name; + this.isChangeDetected = !isEqual(this.data.server, this.server); + } + + public cancel() { + this.dialogRef.close(); + } + + public delete() { + const settings: IEmbySettings = structuredClone(this.data.savedSettings); + const index = settings.servers.findIndex((i) => i.id === this.server.id); + if (index == -1) return; + + settings.servers.splice(index, 1); + const errorMessage = "There was an error removing the server."; + this.settingsService.saveEmby(settings).subscribe({ + next: (result) => { + if (result) { + this.data.savedSettings.servers.splice(index, 1); + this.dialogRef.close(); + } else { + this.notificationService.error(errorMessage); + } + }, + error: () => { + this.notificationService.error(errorMessage); + }, + }); + } + + public save() { + const settings: IEmbySettings = structuredClone(this.data.savedSettings); + if (this.data.isNewServer) { + settings.servers.push(this.server); + } else { + const index = settings.servers.findIndex((i) => i.id === this.server.id); + if (index !== -1) settings.servers[index] = this.server; + } + + const errorMessage = "There was an error saving the server."; + this.settingsService.saveEmby(settings).subscribe({ + next: (result) => { + if (result) { + if (this.data.isNewServer) { + this.data.savedSettings.servers.push(this.server); + } else { + const index = this.data.savedSettings.servers.findIndex( + (i) => i.id === this.server.id + ); + if (index !== -1) + this.data.savedSettings.servers[index] = this.server; + } + this.dialogRef.close(); + } else { + this.notificationService.error(errorMessage); + } + }, + error: () => { + this.notificationService.error(errorMessage); + }, + }); + } + + public discoverServer() { + this.embyServerForm.form.markAllAsTouched(); + if (!this.embyServerForm.valid) return; + + const errorMessage = `Failed to connect to the server. Make sure configuration is correct.`; + this.testerService.embyTest(this.server).subscribe({ + next: (result) => { + if (!result) { + this.notificationService.error(errorMessage); + return; + } + + this.retrieveServerNameAndId(this.server); + }, + error: () => { + this.notificationService.error(errorMessage); + }, + }); + } + + private retrieveServerNameAndId(server: IEmbyServer) { + const errorMessage = + "Failed to discover server. Make sure configuration is correct."; + this.embyService.getPublicInfo(server).subscribe({ + next: (result) => { + if (!result) { + this.notificationService.error(errorMessage); + return; + } + + if (!server.name) { + server.name = result.serverName; + this.isServerNameMissing = false; + } + server.serverId = result.id; + this.loadLibraries(server); + }, + error: () => { + this.notificationService.error(errorMessage); + }, + }); + } + + private loadLibraries(server: IEmbyServer) { + this.embyService.getLibraries(server).subscribe({ + next: (result) => { + server.embySelectedLibraries = result.items.map((item) => { + const index = server.embySelectedLibraries.findIndex( + (x) => x.key == item.id + ); + const enabled = + index === -1 ? false : server.embySelectedLibraries[index].enabled; + const lib: IEmbyLibrariesSettings = { + key: item.id, + title: item.name, + enabled: enabled, + collectionType: item.collectionType, + }; + return lib; + }); + + this.serverDiscoveryRequired = false; + this.validatedFields = { + ip: this.server.ip, + port: this.server.port, + ssl: this.server.ssl, + apiKey: this.server.apiKey, + subDir: this.server.subDir, + }; + this.notificationService.success("Successfully discovered the server."); + }, + error: () => { + const errorMessage = "There was an error retrieving libraries."; + this.notificationService.error(errorMessage); + }, + }); + } +} diff --git a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html index 11d19c5ca..70296f6fe 100644 --- a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html +++ b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html @@ -1,142 +1,56 @@ - - +
-
-
- - Emby Configuration - - -
-
-
- Enable +
+
+ Emby Configuration +
+
+ + Enable -
-
- - -
- -
-
-
-
-
- - Server Name - - -
- -
- - Hostname / IP - - - -
- - Server ID - - -
- - Port - - +
- - SSL - -
-
- - API Key - - -
-
- - Base URL - - -
-
- - Externally Facing Hostname - - - - Current URL: "{{server.serverHostname}}/#!/item/item.html?id=1" - Current URL: "https://app.emby.media/#!/item/item.html?id=1 - -
- -
- Note: if nothing is selected, we will monitor all libraries -
-
- -
-
-
-
-
-
-
- {{lib.title}} -
-
-
-
+
+

Servers

+
+ + + -
-
- -
-
-
-
- -
-
-
-
- -
-
-
- -
-
- -
-
- - - + + + +
+
- -
-
-
-
- +
+

Actions

+
+ + + +
-
- +
- -
- + \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.scss b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.scss index 3a50f011f..8aa7c68e7 100644 --- a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.scss +++ b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.scss @@ -1,41 +1,29 @@ @import "./styles/shared.scss"; .small-middle-container { - margin: auto; - width: 95%; - margin-top: 10px; + margin: auto; + width: 95%; + margin-top: 10px; } -.col-md-10 { - display: grid; -} - -.col-md-2 { - display: contents; -} - -.control-label { - font-weight: 400; -} +.emby-server-card { + margin: 0em 1em 1em 0; + width: 13em; + min-height: 8em; -.row { - display: block; -} + button { + text-align: center; + align-content: center; + white-space: normal; + overflow-wrap: anywhere; + height: 100%; + width: 100%; + } -.btn-danger-outline { - background-color: #E84C3D; -} + i { + margin-top: 0.25em; + } -.btn-success-outline { - background-color: #1b9d1b; + h3 { + margin: 0; + } } - -::ng-deep .dark .btn:hover { - box-shadow: 0 5px 11px 0 rgba(255, 255, 255, 0.18), 0 4px 15px 0 rgba(255, 255, 255, 0.15); - color: inherit; -} - -@media (min-width:1440px) { - .col-md-2 { - display: inline-table; - } -} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts index f9e22c976..f9d2697a8 100644 --- a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts @@ -1,136 +1,160 @@ import { Component, OnInit } from "@angular/core"; -import { EmbyService, JobService, NotificationService, SettingsService, TesterService } from "../../services"; -import { IEmbyLibrariesSettings, IEmbyServer, IEmbySettings } from "../../interfaces"; - -import {UntypedFormControl} from '@angular/forms'; -import { MatTabChangeEvent } from "@angular/material/tabs"; +import { + JobService, + NotificationService, + SettingsService, +} from "../../services"; +import { IEmbyServer, IEmbySettings } from "../../interfaces"; +import { MatSlideToggleChange } from "@angular/material/slide-toggle"; +import { MatDialog } from "@angular/material/dialog"; +import { + EmbyServerDialog, + EmbyServerDialogData, +} from "./emby-server-dialog/emby-server-dialog.component"; @Component({ - templateUrl: "./emby.component.html", - styleUrls: ["./emby.component.scss"] + templateUrl: "./emby.component.html", + styleUrls: ["./emby.component.scss"], }) export class EmbyComponent implements OnInit { + public savedSettings: IEmbySettings; - public settings: IEmbySettings; - public hasDiscoveredOrDirty: boolean; - selected = new UntypedFormControl(0); - - constructor(private settingsService: SettingsService, - private notificationService: NotificationService, - private testerService: TesterService, - private jobService: JobService, - private embyService: EmbyService) { } + constructor( + private settingsService: SettingsService, + private notificationService: NotificationService, + private jobService: JobService, + private dialog: MatDialog + ) {} - public ngOnInit() { - this.settingsService.getEmby().subscribe(x => this.settings = x); - } + public ngOnInit() { + this.settingsService.getEmby().subscribe({ + next: (result) => { + if (result.servers == null) result.servers = []; + this.savedSettings = result; + }, + error: () => { + this.notificationService.error("Failed to retrieve Emby settings."); + }, + }); + } - public async discoverServerInfo(server: IEmbyServer) { - const result = await this.embyService.getPublicInfo(server).toPromise(); - server.name = result.serverName; - server.serverId = result.id; - this.hasDiscoveredOrDirty = true; - } - - public addTab(event: MatTabChangeEvent) { - const tabName = event.tab.textLabel; - if (tabName == "Add Server"){ - if (this.settings.servers == null) { - this.settings.servers = []; - } - this.settings.servers.push({ - name: "New " + this.settings.servers.length + "*", - id: Math.floor(Math.random() * (99999 - 0 + 1) + 1), - apiKey: "", - administratorId: "", - enableEpisodeSearching: false, - ip: "", - port: 0, - ssl: false, - subDir: "", - } as IEmbyServer); - this.selected.setValue(this.settings.servers.length - 1); + public toggleEnableFlag(event: MatSlideToggleChange) { + const newSettings: IEmbySettings = { + ...structuredClone(this.savedSettings), + enable: event.checked, + }; + const errorMessage = event.checked + ? "There was an error enabling Emby settings. Check that all servers are configured correctly." + : "There was an error disabling Emby settings."; + this.settingsService.saveEmby(newSettings).subscribe({ + next: (result) => { + if (result) { + this.savedSettings.enable = event.checked; + this.notificationService.success( + `Successfully ${ + event.checked ? "enabled" : "disabled" + } Emby settings.` + ); + } else { + event.source.checked = !event.checked; + this.notificationService.error(errorMessage); } - } + }, + error: () => { + event.source.checked = !event.checked; + this.notificationService.error(errorMessage); + }, + }); + } - public toggle() { - this.hasDiscoveredOrDirty = true; - } + public newServer() { + const newServer: IEmbyServer = { + name: "", + id: Math.floor(Math.random() * 99999 + 1), + apiKey: "", + administratorId: "", + enableEpisodeSearching: false, + ip: "", + port: undefined, + ssl: false, + subDir: "", + serverId: "", + serverHostname: "", + embySelectedLibraries: [], + }; + const data: EmbyServerDialogData = { + server: newServer, + isNewServer: true, + savedSettings: this.savedSettings, + }; + this.dialog.open(EmbyServerDialog, { + width: "700px", + data: data, + panelClass: "modal-panel", + }); + } - public test(server: IEmbyServer) { - this.testerService.embyTest(server).subscribe(x => { - if (x === true) { - this.notificationService.success(`Successfully connected to the Emby server ${server.name}!`); - } else { - this.notificationService.error(`We could not connect to the Emby server ${server.name}!`); - } - }); - } + public editServer(server: IEmbyServer) { + const data: EmbyServerDialogData = { + server: server, + isNewServer: false, + savedSettings: this.savedSettings, + }; + this.dialog.open(EmbyServerDialog, { + width: "700px", + data: data, + panelClass: "modal-panel", + }); + } - public removeServer(server: IEmbyServer) { - const index = this.settings.servers.indexOf(server, 0); - if (index > -1) { - this.settings.servers.splice(index, 1); - this.selected.setValue(this.settings.servers.length - 1); - this.toggle(); + public runIncrementalSync(): void { + const errorMessage = "There was an error triggering the incremental sync."; + this.jobService.runEmbyRecentlyAddedCacher().subscribe({ + next: (result) => { + if (result) { + this.notificationService.success("Triggered the incremental sync."); + } else { + this.notificationService.error(errorMessage); } - } - - public save() { - this.settingsService.saveEmby(this.settings).subscribe(x => { - if (x) { - this.notificationService.success("Successfully saved Emby settings"); - } else { - this.notificationService.success("There was an error when saving the Emby settings"); - } - }); - } + }, + error: () => { + this.notificationService.error(errorMessage); + }, + }); + } - public runCacher(): void { - this.jobService.runEmbyCacher().subscribe(x => { - if(x) { - this.notificationService.success("Triggered the Emby Content Cacher"); - } - }); - } - - public runRecentlyAddedCacher(): void { - this.jobService.runEmbyRecentlyAddedCacher().subscribe(x => { - if (x) { - this.notificationService.success("Triggered the Emby Recently Added Sync"); - } - }); - } - - public clearDataAndResync(): void { - this.jobService.clearMediaserverData().subscribe(x => { - if (x) { - this.notificationService.success("Triggered the Clear MediaServer Resync"); - } - }); - } + public runFullSync(): void { + const errorMessage = "There was an error triggering the full sync."; + this.jobService.runEmbyCacher().subscribe({ + next: (result) => { + if (result) { + this.notificationService.success("Triggered the full sync."); + } else { + this.notificationService.error(errorMessage); + } + }, + error: () => { + this.notificationService.error(errorMessage); + }, + }); + } - public loadLibraries(server: IEmbyServer) { - if (server.ip == null) { - this.notificationService.error("Emby is not yet configured correctly"); - return; + public runCacheWipeWithFullSync(): void { + const errorMessage = + "There was an error triggering the cache wipe with a full sync."; + this.jobService.clearMediaserverData().subscribe({ + next: (result) => { + if (result) { + this.notificationService.success( + "Triggered the cache wipe with a full sync." + ); + } else { + this.notificationService.error(errorMessage); } - this.embyService.getLibraries(server).subscribe(x => { - server.embySelectedLibraries = []; - if (x.totalRecordCount > 0) { - x.items.forEach((item) => { - const lib: IEmbyLibrariesSettings = { - key: item.id, - title: item.name, - enabled: false, - collectionType: item.collectionType - }; - server.embySelectedLibraries.push(lib); - }); - } else { - this.notificationService.error("Couldn't find any libraries"); - } - }, - err => { this.notificationService.error(err); }); - } + }, + error: () => { + this.notificationService.error(errorMessage); + }, + }); + } } diff --git a/src/Ombi/ClientApp/src/app/settings/settings.module.ts b/src/Ombi/ClientApp/src/app/settings/settings.module.ts index 990e89837..aa2a2f391 100644 --- a/src/Ombi/ClientApp/src/app/settings/settings.module.ts +++ b/src/Ombi/ClientApp/src/app/settings/settings.module.ts @@ -37,6 +37,7 @@ import { DiscordComponent } from "./notifications/discord.component"; import { DogNzbComponent } from "./dognzb/dognzb.component"; import { EmailNotificationComponent } from "./notifications/emailnotification.component"; import { EmbyComponent } from "./emby/emby.component"; +import { EmbyServerDialog } from "./emby/emby-server-dialog/emby-server-dialog.component"; import { FailedRequestsComponent } from "./failedrequests/failedrequests.component"; import { FeaturesComponent } from "./features/features.component"; import { GotifyComponent } from "./notifications/gotify.component"; @@ -154,6 +155,7 @@ const routes: Routes = [ OmbiComponent, PlexComponent, EmbyComponent, + EmbyServerDialog, JellyfinComponent, JobsComponent, LandingPageComponent,