This commit is contained in:
tidusjar 2022-10-19 22:29:00 +01:00
commit 90f369ad6f
9 changed files with 380 additions and 32 deletions

View file

@ -0,0 +1,7 @@
import { IPlexServer } from "../../../../interfaces";
export interface PlexServerDialogData {
server: IPlexServer;
deleted?: boolean;
closed?: boolean;
}

View file

@ -1,2 +1,3 @@
export * from './PlexSyncType'; export * from './PlexSyncType';
export * from './PlexCreds'; export * from './PlexCreds';
export * from './PlexServerDialogData';

View file

@ -0,0 +1,111 @@
<h1 mat-dialog-title>Server Configuration</h1>
<mat-dialog-content>
<h2>Connection</h2>
<mat-form-field appearance="outline" floatLabel=auto>
<mat-label>Server Name</mat-label>
<input matInput placeholder="Server Name" name="name" [(ngModel)]="this.data.server.name" value="{{this.data.server.name}}">
<mat-hint>Auto populated during discovery of the server if left empty.</mat-hint>
</mat-form-field>
<div class="row">
<mat-form-field class="col-md-6 col-12" appearance="outline" floatLabel=auto>
<mat-label>Hostname / IP</mat-label>
<input matInput placeholder="Hostname or IP" name="ip" [(ngModel)]="this.data.server.ip" value="{{this.data.server.ip}}"
#serverHostnameIpControl="ngModel" required>
<mat-error *ngIf="serverHostnameIpControl.hasError('required')">Must be specified.</mat-error>
</mat-form-field>
<mat-form-field class="col-md-4 col-7" appearance="outline" floatLabel=auto>
<mat-label>Port</mat-label>
<input matInput placeholder="Port" name="port" [(ngModel)]="this.data.server.port" value="{{this.data.server.port}}"
#serverPortControl="ngModel" required pattern="^[0-9]*$">
<mat-error *ngIf="serverPortControl.hasError('required')">Must be specified.</mat-error>
<mat-error *ngIf="serverPortControl.hasError('pattern')">Must be a number.</mat-error>
</mat-form-field>
<mat-slide-toggle class="col-md-2 col-5 mt-3" id="ssl" name="ssl" [(ngModel)]="this.data.server.ssl" [checked]="this.data.server.ssl">
SSL
</mat-slide-toggle>
</div>
<mat-form-field appearance="outline" floatLabel=auto>
<mat-label>Plex Authorization Token</mat-label>
<input matInput placeholder="Plex Authorization Token" name="authToken" [(ngModel)]="this.data.server.plexAuthToken" value="{{this.data.server.plexAuthToken}}"
#serverApiKeyControl="ngModel" required>
<mat-error *ngIf="serverApiKeyControl.hasError('required')">Must be specified.</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" floatLabel=auto>
<mat-label>Machine Identifier</mat-label>
<input matInput placeholder="Machine Identifier" name="MachineIdentifier" [(ngModel)]="this.data.server.machineIdentifier" value="{{this.data.server.machineIdentifier}}"
#serverApiKeyControl="ngModel" required>
<mat-error *ngIf="serverApiKeyControl.hasError('required')">Must be specified.</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" floatLabel=auto>
<mat-label>Externally Facing Hostname</mat-label>
<input matInput placeholder="e.g. https://emby.this.data.server.com/" name="serverHostname" name="hostname"
[(ngModel)]="this.data.server.serverHostname" value="{{this.data.server.serverHostname}}" >
<mat-hint>
This will be the external address that users will navigate to when they press the 'View On Plex' button
<br>
<span *ngIf="this.data.server.serverHostname">Current URL: "{{this.data.server.serverHostname}}/web/app#!/server/{{this.data.server.machineIdentifier}}/details?key=%2flibrary%2Fmetadata%2F53334"</span>
<span *ngIf="!this.data.server.serverHostname">Current URL: "https://app.plex.tv/web/app#!/server/{{this.data.server.machineIdentifier}}/details?key=%2flibrary%2Fmetadata%2F53334"</span>
</mat-hint>
</mat-form-field>
<h2>Libraries</h2>
<div>
<button mat-raised-button (click)="loadLibraries()"
class="mat-focus-indicator mat-stroked-button mat-button-base">Load Libraries
<i class="fas fa-film"></i>
</button>
</div>
<div *ngIf="this.data.server.plexSelectedLibraries && this.data.server.plexSelectedLibraries.length > 0">
<label>Please select the libraries for Ombi to monitor. If nothing is selected, Ombi will monitor all
libraries.</label>
<div *ngFor="let lib of this.data.server.plexSelectedLibraries">
<div class="md-form-field">
<div class="checkbox">
<mat-slide-toggle [(ngModel)]="lib.enabled" [checked]="lib.enabled"
for="{{lib.title}}">{{lib.title}}</mat-slide-toggle>
</div>
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions align=end>
<button style="margin: .5em 0 0 .5em;" align-middle mat-stroked-button color="warn"
(click)="delete()">
<span style="display: flex; align-items: baseline; white-space: pre-wrap;">
<i class="fas fa-trash"></i>
<span> Delete</span>
</span>
</button>
<button style="margin: .5em 0 0 0.5em;" mat-stroked-button color="basic" (click)="cancel()">
<span style="display: flex; align-items: baseline; white-space: pre-wrap;">
<i class="fas fa-times"></i>
<span> Cancel</span>
</span>
</button>
<button style="margin: .5em 0 0 .5em;" mat-stroked-button color="accent"
(click)="save()">
<span style="display: flex; align-items: baseline; white-space: pre-wrap;">
<i style="vertical-align: text-top;" class="fas fa-plus"></i>
<span> Save</span>
</span>
</button>
</mat-dialog-actions>

View file

@ -0,0 +1,27 @@
@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;
}
}

View file

@ -0,0 +1,79 @@
import { Component, Inject } from "@angular/core";
import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog";
import {
PlexService,
NotificationService,
TesterService,
} from "../../../../services";
import { take } from "rxjs";
import { IPlexLibrariesSettings, IPlexServer } from "../../../../interfaces";
import { PlexServerDialogData } from "../models";
@Component({
selector: "plex-server-dialog-component",
templateUrl: "plex-server-dialog.component.html",
styleUrls: ["plex-server-dialog.component.scss"],
})
export class PlexServerDialogComponent {
public password: string;
public username: string;
constructor(
private dialogRef: MatDialogRef<PlexServerDialogData>,
@Inject(MAT_DIALOG_DATA) public data: PlexServerDialogData,
private notificationService: NotificationService,
private testerService: TesterService,
private plexService: PlexService
) {
}
public cancel() {
this.dialogRef.close({closed: true});
}
public testPlex() {
this.testerService.plexTest(this.data.server).pipe(take(1))
.subscribe(x => {
if (x === true) {
this.notificationService.success(`Successfully connected to the Plex server ${this.data.server.name}!`);
} else {
this.notificationService.error(`We could not connect to the Plex server ${this.data.server.name}!`);
}
});
}
public delete() {
this.dialogRef.close({deleted: true});
}
public save() {
this.dialogRef.close({server: this.data.server});
}
public loadLibraries() {
if (this.data.server.ip == null) {
this.notificationService.error("Plex is not yet configured correctly");
return;
}
this.plexService.getLibraries(this.data.server).subscribe(x => {
this.data.server.plexSelectedLibraries = [];
if (x.successful) {
x.data.mediaContainer.directory.forEach((item) => {
const lib: IPlexLibrariesSettings = {
key: item.key,
title: item.title,
enabled: false,
};
this.data.server.plexSelectedLibraries.push(lib);
});
} else {
this.notificationService.error(x.message);
}
});
}
}

View file

@ -15,35 +15,97 @@
<hr> <hr>
<div class="md-form-field">
<label for="username" class="control-label">
<h3>Plex Credentials</h3>
<small>These fields are optional to automatically fill in your Plex server settings. <br>
This will pass your username and password to the Plex.tv API to grab the servers associated with this user.
<br>
If you have 2FA enabled on your account, you need to append the 2FA code to the end of your password.</small>
</label>
</div>
<settings-plex-form-field [label]="'Username'" [id]="'username'" [(value)]="username"></settings-plex-form-field>
<settings-plex-form-field [label]="'Password'" [id]="'password'" [type]="'password'" [(value)]="password"></settings-plex-form-field>
<div class="md-form-field">
<div class="right">
<button mat-raised-button id="loadServers" (click)="requestServers()"
class="mat-stroked-button">Load Servers
<i class="fas fa-key"></i>
</button>
</div>
</div>
<div class="row"> <div class="row">
<mat-tab-group #tabGroup [selectedIndex]="selected.value" (selectedTabChange)="addTab($event)" <div class="col-2 align-self-center">
(selectedIndexChange)="selected.setValue($event)" animationDuration="0ms" style="width:100%;"> Please select the server:
<mat-tab *ngFor="let server of settings.servers" [label]="server.name"> </div>
<div class="md-form-field col-10">
<div *ngIf="!loadedServers">
<mat-form-field appearance="outline" floatLabel=auto>
<input disabled matInput placeholder="No Servers Loaded" id="selectServer-noservers">
</mat-form-field>
</div>
<div class="col-md-6 col-6 col-sm-6 align-right"> <div *ngIf="loadedServers">
<button type="button" (click)="removeServer(server)" <mat-form-field appearance="outline">
class="mat-focus-indicator mat-flat-button mat-button-base mat-warn">Remove Server</button> <mat-select placeholder="Servers Loaded! Please Select">
</div> <mat-option (click)="selectServer(s)"
<br /> *ngFor="let s of loadedServers.servers.server" [value]="s.server">
<br /> {{s.name}}</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
<settings-plex-form
[server]="server"
[advancedEnabled]="advanced"
[loadedServers]="loadedServers"
(loadLibraries)="loadLibraries(server)"
(loadServers)="requestServers($event)"
(test)="testPlex(server)"
(runSync)="runSync($event)"
(selectServer)="selectServer($event, server)"
>
</settings-plex-form>
</mat-tab> <h2 style="margin: 1em 0 0 0;">Servers</h2>
<mat-tab label="" disabled=true> </mat-tab> <mat-list style="display:flex; flex-flow: wrap;">
<mat-tab label="Add Server" position=100> </mat-tab> <mat-card class="server-card" *ngFor="let server of settings.servers">
<button mat-button (click)="edit(server)">
<h3>{{server.name}}</h3>
</button>
</mat-card>
<mat-card class="server-card new-server-card">
<button mat-button (click)="newServer()">
<i class="fas fa-plus fa-xl"></i>
<h3>Manually Add Server</h3>
</button>
</mat-card>
</mat-list>
<div class="row">
<br />
<div class="form-group col-1">
<button mat-raised-button (click)="runSync(PlexSyncType.Full)" type="button" id="fullSync"
class="mat-focus-indicator mat-stroked-button mat-button-base">Full
Sync</button><br />
</div>
<div class="form-group col-1">
<button mat-raised-button (click)="runSync(PlexSyncType.RecentlyAdded)" type="button" id="recentlyAddedSync"
class="mat-focus-indicator mat-stroked-button mat-button-base">Partial Sync</button>
</div>
<div class="form-group col-1">
<button mat-raised-button (click)="runSync(PlexSyncType.ClearAndReSync)" type="button" id="clearData"
class="mat-focus-indicator mat-stroked-button mat-button-base">
Clear Data And Resync
</button>
</div>
<div class="form-group col-12">
<button mat-raised-button (click)="runSync(PlexSyncType.WatchlistImport)" type="button" id="watchlistImport"
class="mat-focus-indicator mat-stroked-button mat-button-base">
Run Watchlist Import
</button>
</div>
</div>
<div class="row">
</mat-tab-group>
<div class="col-md-2"> <div class="col-md-2">
<div class="form-group"> <div class="form-group">
<div> <div>

View file

@ -22,3 +22,25 @@
::ng-deep div .mat-tab-body-content { ::ng-deep div .mat-tab-body-content {
overflow: hidden; overflow: hidden;
} }
.server-card {
margin: 0em 1em 1em 0;
width: 13em;
min-height: 8em;
button {
text-align: center;
align-content: center;
white-space: normal;
overflow-wrap: anywhere;
height: 100%;
width: 100%;
}
i {
margin-top: 0.25em;
}
h3 {
margin: 0;
}
}

View file

@ -8,7 +8,8 @@ import { MatTabChangeEvent, MatTabGroup } from "@angular/material/tabs";
import {UntypedFormControl} from '@angular/forms'; import {UntypedFormControl} from '@angular/forms';
import { MatDialog } from "@angular/material/dialog"; import { MatDialog } from "@angular/material/dialog";
import { PlexWatchlistComponent } from "./components/watchlist/plex-watchlist.component"; import { PlexWatchlistComponent } from "./components/watchlist/plex-watchlist.component";
import { PlexCreds, PlexSyncType } from "./components/models"; import { PlexServerDialogComponent } from "./components/plex-server-dialog/plex-server-dialog.component";
import { PlexCreds, PlexServerDialogData, PlexSyncType } from "./components/models";
@Component({ @Component({
templateUrl: "./plex.component.html", templateUrl: "./plex.component.html",
@ -21,9 +22,14 @@ export class PlexComponent implements OnInit, OnDestroy {
selected = new UntypedFormControl(0); selected = new UntypedFormControl(0);
@ViewChild("tabGroup", {static: false}) public tagGroup: MatTabGroup; @ViewChild("tabGroup", {static: false}) public tagGroup: MatTabGroup;
public username: string;
public password: string;
public advanced = false; public advanced = false;
private subscriptions = new Subject<void>(); private subscriptions = new Subject<void>();
public PlexSyncType = PlexSyncType;
constructor( constructor(
private settingsService: SettingsService, private settingsService: SettingsService,
@ -39,8 +45,8 @@ export class PlexComponent implements OnInit, OnDestroy {
}); });
} }
public requestServers({ username, password }: PlexCreds) { public requestServers() {
this.plexService.getServers(username, password).pipe( this.plexService.getServers(this.username, this.password).pipe(
takeUntil(this.subscriptions), takeUntil(this.subscriptions),
).subscribe(x => { ).subscribe(x => {
if (x.success) { if (x.success) {
@ -53,7 +59,9 @@ export class PlexComponent implements OnInit, OnDestroy {
}); });
} }
public selectServer(selectedServer: IPlexServerResponse, server: IPlexServer) { public selectServer(selectedServer: IPlexServerResponse) {
const server = <IPlexServer> { name: "New" + this.settings.servers.length + "*", id: Math.floor(Math.random() * (99999 - 0 + 1) + 1) };
var splitServers = selectedServer.localAddresses.split(","); var splitServers = selectedServer.localAddresses.split(",");
if (splitServers.length > 1) { if (splitServers.length > 1) {
server.ip = splitServers[splitServers.length - 1]; server.ip = splitServers[splitServers.length - 1];
@ -68,6 +76,7 @@ export class PlexComponent implements OnInit, OnDestroy {
server.serverHostname = ""; server.serverHostname = "";
this.notificationService.success(`Selected ${server.name}!`); this.notificationService.success(`Selected ${server.name}!`);
this.newServer(server);
} }
public testPlex(server: IPlexServer) { public testPlex(server: IPlexServer) {
@ -167,6 +176,34 @@ export class PlexComponent implements OnInit, OnDestroy {
} }
} }
public edit(server: IPlexServer) {
const data: PlexServerDialogData = {
server: server,
};
const dialog = this.dialog.open(PlexServerDialogComponent, {
width: "700px",
data: data,
panelClass: "modal-panel",
});
dialog.afterClosed().subscribe((x) => {
console.log(x);
});
}
public newServer(server: IPlexServer) {
if(!server) {
server = <IPlexServer> { name: "New" + this.settings.servers.length + "*", id: Math.floor(Math.random() * (99999 - 0 + 1) + 1) };
}
const dialog = this.dialog.open(PlexServerDialogComponent, {
width: "700px",
data: {server: server},
panelClass: "modal-panel",
});
dialog.afterClosed().subscribe((x) => {
console.log(x);
});
}
private runCacher(): void { private runCacher(): void {
this.jobService.runPlexCacher().subscribe(x => { this.jobService.runPlexCacher().subscribe(x => {
if (x) { if (x) {

View file

@ -50,7 +50,7 @@ import { LandingPageComponent } from "./landingpage/landingpage.component";
import { LidarrComponent } from "./lidarr/lidarr.component"; import { LidarrComponent } from "./lidarr/lidarr.component";
import { LogsComponent } from "./logs/logs.component"; import { LogsComponent } from "./logs/logs.component";
import { MassEmailComponent } from "./massemail/massemail.component"; import { MassEmailComponent } from "./massemail/massemail.component";
import { MatDialogModule } from "@angular/material/dialog"; import { MatDialogActions, MatDialogModule } from "@angular/material/dialog";
import { MatMenuModule } from "@angular/material/menu"; import { MatMenuModule } from "@angular/material/menu";
import { MattermostComponent } from "./notifications/mattermost.component"; import { MattermostComponent } from "./notifications/mattermost.component";
import {MenuModule} from "primeng/menu"; import {MenuModule} from "primeng/menu";
@ -86,6 +86,7 @@ import { WikiComponent } from "./wiki.component";
import { PlexWatchlistComponent } from "./plex/components/watchlist/plex-watchlist.component"; import { PlexWatchlistComponent } from "./plex/components/watchlist/plex-watchlist.component";
import { PlexFormComponent } from "./plex/components/plex-form/plex-form.component"; import { PlexFormComponent } from "./plex/components/plex-form/plex-form.component";
import { PlexFormFieldComponent } from "./plex/components/form-field/plex-form-field.component"; import { PlexFormFieldComponent } from "./plex/components/form-field/plex-form-field.component";
import { PlexServerDialogComponent } from "./plex/components/plex-server-dialog/plex-server-dialog.component";
const routes: Routes = [ const routes: Routes = [
{ path: "Ombi", component: OmbiComponent, canActivate: [AuthGuard] }, { path: "Ombi", component: OmbiComponent, canActivate: [AuthGuard] },
@ -146,7 +147,7 @@ const routes: Routes = [
DialogModule, DialogModule,
SharedModule, SharedModule,
MatMenuModule, MatMenuModule,
MatDialogModule MatDialogModule,
], ],
declarations: [ declarations: [
SettingsMenuComponent, SettingsMenuComponent,
@ -195,6 +196,7 @@ const routes: Routes = [
PlexWatchlistComponent, PlexWatchlistComponent,
PlexFormComponent, PlexFormComponent,
PlexFormFieldComponent, PlexFormFieldComponent,
PlexServerDialogComponent,
], ],
exports: [ exports: [
RouterModule, RouterModule,