mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-22 06:13:22 -07:00
refactor: reworked plex settings page
This commit is contained in:
parent
94da025d28
commit
f4534d72a4
11 changed files with 181 additions and 190 deletions
|
@ -0,0 +1,43 @@
|
|||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "settings-plex-form-field",
|
||||
template: `
|
||||
<div class="row">
|
||||
<div class="col-2 align-self-center">
|
||||
{{label}}
|
||||
|
||||
<br>
|
||||
<!-- Content Below the label -->
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
<div class="md-form-field col-10">
|
||||
<mat-form-field appearance="outline" floatLabel=auto *ngIf="type === 'input' || type === 'password'">
|
||||
<input matInput placeholder={{placeholder}} [attr.type]="type" id="{{id}}" name="{{id}}" [ngModel]="value" (ngModelChange)="change($event)" value="{{value}}">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-slide-toggle *ngIf="type === 'checkbox'" id="{{id}}" [ngModel]="value" (ngModelChange)="change($event)" [checked]="value"></mat-slide-toggle>
|
||||
|
||||
<ng-content select="[below]"></ng-content>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<ng-content select="[bottom]"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class PlexFormFieldComponent {
|
||||
|
||||
@Input() public label: string;
|
||||
@Input() public value: any;
|
||||
@Output() public valueChange = new EventEmitter();
|
||||
@Input() public id: string;
|
||||
@Input() public placeholder: string;
|
||||
@Input() public type: "input" | "checkbox" | "password" = "input"
|
||||
|
||||
public change(newValue: string) {
|
||||
this.value = newValue;
|
||||
this.valueChange.emit(newValue);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface PlexCreds {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
export * from './PlexSyncType';
|
||||
export * from './PlexSyncType';
|
||||
export * from './PlexCreds';
|
|
@ -1,75 +1,41 @@
|
|||
<div class="col-md-7 col-8 col-sm-12">
|
||||
|
||||
<label for="username" class="control-label">
|
||||
<h3>Plex Server Configuration</h3>
|
||||
</label>
|
||||
<div class="md-form-field">
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>Server Name</mat-label>
|
||||
<input matInput id="name" name="name"
|
||||
[(ngModel)]="server.name" value="{{server.name}}">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<div class="md-form-field">
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>Hostname or IP</mat-label>
|
||||
<input matInput id="Ip" name="Ip" [(ngModel)]="server.ip"
|
||||
value="{{server.ip}}">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>Port</mat-label>
|
||||
<input matInput id="portNumber" name="Port"
|
||||
[(ngModel)]="server.port" value="{{server.port}}">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-slide-toggle id="ssl" [(ngModel)]="server.ssl" [checked]="server.ssl">
|
||||
SSL
|
||||
</mat-slide-toggle>
|
||||
<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>
|
||||
<div class="md-form-field">
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>Plex Authorization Token</mat-label>
|
||||
<input matInput id="authToken"
|
||||
[(ngModel)]="server.plexAuthToken" value="{{server.plexAuthToken}}">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>Machine Identifier</mat-label>
|
||||
<input matInput id="MachineIdentifier" name="MachineIdentifier"
|
||||
[(ngModel)]="server.machineIdentifier" value="{{server.machineIdentifier}}">
|
||||
</mat-form-field>
|
||||
</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">
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>Externally Facing Hostname</mat-label>
|
||||
<input matInput placeholder="e.g. https://app.plex.tv" [(ngModel)]="server.serverHostname" value="{{server.serverHostname}}" matTooltip="This will be the external address that users will navigate to when they press the 'View On Plex' button">
|
||||
</mat-form-field>
|
||||
<small>
|
||||
<span *ngIf="server.serverHostname">Current URL: "{{server.serverHostname}}/web/app#!/server/{{server.machineIdentifier}}/details?key=%2flibrary%2Fmetadata%2F53334"</span>
|
||||
<span *ngIf="!server.serverHostname">Current URL: "https://app.plex.tv/web/app#!/server/{{server.machineIdentifier}}/details?key=%2flibrary%2Fmetadata%2F53334"</span>
|
||||
</small>
|
||||
</div>
|
||||
<div class="md-form-field">
|
||||
<div class="md-form-field" *ngIf="advancedEnabled">
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label for="episodeBatchSize" class="control-label">Episode Batch Size</mat-label>
|
||||
<input matInput type="number" id="episodeBatchSize" name="episodeBatchSize"
|
||||
[(ngModel)]="server.episodeBatchSize" value="{{server.episodeBatchSize}}"
|
||||
matTooltip="This is used when we cache the episodes, we cache in batches of 150 by default, you can configure how many we do at a time here"
|
||||
matTooltipPosition="right">
|
||||
</mat-form-field>
|
||||
<div class="right">
|
||||
<button mat-raised-button id="loadServers" (click)="loadServers.emit({username, password})"
|
||||
class="mat-stroked-button">Load Servers
|
||||
<i class="fas fa-key"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-form-field" *ngIf="loadedServers">
|
||||
<label for="username" class="control-label">Please select the server</label>
|
||||
<br />
|
||||
<div class="btn-group">
|
||||
<div class="btn-group">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-2 align-self-center">
|
||||
Please select the server:
|
||||
</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 *ngIf="loadedServers">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Server</mat-label>
|
||||
<mat-select>
|
||||
<mat-select placeholder="Servers Loaded! Please Select">
|
||||
<mat-option (click)="selectServer.emit(s)"
|
||||
*ngFor="let s of loadedServers.servers.server" [value]="s.server">
|
||||
{{s.name}}</mat-option>
|
||||
|
@ -78,28 +44,72 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label>Please select the libraries you want Ombi to look in for content</label>
|
||||
<br />
|
||||
<small>Note: if nothing is selected, we will monitor all libraries</small>
|
||||
<div class="md-form-field">
|
||||
<div>
|
||||
<button mat-raised-button (click)="loadLibraries.emit()"
|
||||
class="mat-focus-indicator mat-stroked-button mat-button-base">Load Libraries
|
||||
<i class="fas fa-film"></i>
|
||||
</button>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 md-form-field">
|
||||
<div>
|
||||
<button mat-raised-button (click)="loadLibraries.emit()"
|
||||
class="mat-focus-indicator mat-stroked-button mat-button-base">Load Libraries
|
||||
<i class="fas fa-film"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div *ngIf="server.plexSelectedLibraries">
|
||||
<div *ngFor="let lib of 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 class="col-2 align-self-center">
|
||||
Please select the libraries you want Ombi to monitor
|
||||
<br>
|
||||
<small>Note: if nothing is selected, we will monitor all libraries</small>
|
||||
</div>
|
||||
<div class="md-form-field col-10">
|
||||
<div *ngIf="server.plexSelectedLibraries">
|
||||
<div *ngFor="let lib of 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<hr class="hr-margin">
|
||||
|
||||
<settings-plex-form-field [label]="'Server Name'" [id]="'name'" [(value)]="server.name"></settings-plex-form-field>
|
||||
<settings-plex-form-field [label]="'Hostname or IP'" [id]="'ip'" [(value)]="server.ip"></settings-plex-form-field>
|
||||
<settings-plex-form-field [label]="'Port'" [id]="'port'" [(value)]="server.port"></settings-plex-form-field>
|
||||
|
||||
<settings-plex-form-field [label]="'SSL'" [type]="'checkbox'" [id]="'ssl'" [(value)]="server.ssl"></settings-plex-form-field>
|
||||
|
||||
<settings-plex-form-field [label]="'Plex Authorization Token'" [id]="'authToken'" [(value)]="server.plexAuthToken"></settings-plex-form-field>
|
||||
<settings-plex-form-field [label]="'Machine Identifier'" [id]="'MachineIdentifier'" [(value)]="server.machineIdentifier"></settings-plex-form-field>
|
||||
<settings-plex-form-field
|
||||
[label]="'Externally Facing Hostname'"
|
||||
[placeholder]="'e.g. https://app.plex.tv'"
|
||||
[id]="'hostname'"
|
||||
[(value)]="server.serverHostname">
|
||||
|
||||
<small>This will be the external address that users will navigate to when they press the 'View On Plex' button</small>
|
||||
<small below>
|
||||
<span *ngIf="server.serverHostname">Current URL: "{{server.serverHostname}}/web/app#!/server/{{server.machineIdentifier}}/details?key=%2flibrary%2Fmetadata%2F53334"</span>
|
||||
<span *ngIf="!server.serverHostname">Current URL: "https://app.plex.tv/web/app#!/server/{{server.machineIdentifier}}/details?key=%2flibrary%2Fmetadata%2F53334"</span>
|
||||
</small>
|
||||
</settings-plex-form-field>
|
||||
|
||||
<settings-plex-form-field
|
||||
*ngIf="advancedEnabled"
|
||||
[label]="'Episode Batch Size'"
|
||||
[id]="'episodeBatchSize'"
|
||||
[(value)]="server.episodeBatchSize">
|
||||
<small>This is used when we cache the episodes, we cache in batches of 150 by default, you can configure how many we do at a time here</small>
|
||||
</settings-plex-form-field>
|
||||
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -109,66 +119,36 @@
|
|||
|
||||
<!-- Second section -->
|
||||
|
||||
<div class="col-md-5 col-4 col-sm-12">
|
||||
<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</small>
|
||||
</label>
|
||||
<div>
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>Username</mat-label>
|
||||
<input matInput id="username" [(ngModel)]="username"
|
||||
value="{{username}}">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>Password</mat-label>
|
||||
<input matInput type="password" id="password"
|
||||
[(ngModel)]="password" value="{{password}}">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-form-field">
|
||||
<div>
|
||||
<button mat-raised-button id="requestToken" (click)="loadServers.emit()"
|
||||
class="mat-stroked-button">Load Servers
|
||||
<i class="fas fa-key"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<br />
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<div class="form-group col-12">
|
||||
<button mat-raised-button id="testPlex" type="button" (click)="test.emit()"
|
||||
class="mat-focus-indicator mat-stroked-button mat-button-base">
|
||||
Test Connectivity
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group col-1">
|
||||
<button mat-raised-button (click)="runSync.emit(PlexSyncType.Full)" type="button" id="fullSync"
|
||||
class="mat-focus-indicator mat-stroked-button mat-button-base">Manually Run Full
|
||||
class="mat-focus-indicator mat-stroked-button mat-button-base">Full
|
||||
Sync</button><br />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group col-1">
|
||||
<button mat-raised-button (click)="runSync.emit(PlexSyncType.RecentlyAdded)" type="button" id="recentlyAddedSync"
|
||||
class="mat-focus-indicator mat-stroked-button mat-button-base">Manually Run Recently
|
||||
Added Sync</button>
|
||||
class="mat-focus-indicator mat-stroked-button mat-button-base">Partial Sync</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group col-1">
|
||||
<button mat-raised-button (click)="runSync.emit(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">
|
||||
<div class="form-group col-12">
|
||||
<button mat-raised-button (click)="runSync.emit(PlexSyncType.WatchlistImport)" type="button" id="watchlistImport"
|
||||
class="mat-focus-indicator mat-stroked-button mat-button-base">
|
||||
Run Watchlist Import
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
.hr-margin {
|
||||
margin-bottom: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// also exported from '@storybook/angular' if you can deal with breaking changes in 6.1
|
||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||
import { Story, Meta, moduleMetadata } from '@storybook/angular';
|
||||
import { SharedModule } from '../../../../shared/shared.module';
|
||||
import { PlexFormComponent } from './plex-form.component';
|
||||
|
||||
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'Plex Form Component',
|
||||
component: PlexFormComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
providers: [
|
||||
{
|
||||
provide: APP_BASE_HREF,
|
||||
useValue: ""
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule
|
||||
]
|
||||
})
|
||||
]
|
||||
} as Meta;
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args
|
||||
const Template: Story<PlexFormComponent> = (args: PlexFormComponent) => ({
|
||||
props: args,
|
||||
});
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { IPlexServer, IPlexServerResponse, IPlexServerViewModel } from "app/interfaces";
|
||||
import { PlexSyncType } from "../models";
|
||||
import { PlexCreds, PlexSyncType } from "../models";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./plex-form.component.html",
|
||||
|
@ -14,7 +14,7 @@ export class PlexFormComponent {
|
|||
@Input() public loadedServers: IPlexServerViewModel;
|
||||
|
||||
@Output() public loadLibraries = new EventEmitter();
|
||||
@Output() public loadServers = new EventEmitter();
|
||||
@Output() public loadServers = new EventEmitter<PlexCreds>();
|
||||
@Output() public selectServer = new EventEmitter<IPlexServerResponse>();
|
||||
@Output() public test = new EventEmitter();
|
||||
@Output() public runSync = new EventEmitter<PlexSyncType>();
|
||||
|
|
|
@ -2,30 +2,16 @@
|
|||
<div class="small-middle-container" *ngIf="settings">
|
||||
<fieldset style="width:100%;">
|
||||
<legend>Plex Configuration</legend>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<div class="md-form-field">
|
||||
<mat-slide-toggle [(ngModel)]="settings.enable" [checked]="settings.enable">Enable
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<div class="md-form-field">
|
||||
<mat-slide-toggle [(ngModel)]="settings.enableWatchlistImport" [checked]="settings.enableWatchlistImport">Enable User Watchlist Requests
|
||||
</mat-slide-toggle>
|
||||
<p>When a Plex User adds something to their watchlist in Plex, it will turn up in Ombi as a Request if enabled. This <b>only</b> applies to users that are logging in with their Plex Account</p>
|
||||
<p>Request limits if set are all still applied etc.</p>
|
||||
</div>
|
||||
<div class="md-form-field">
|
||||
<mat-slide-toggle [(ngModel)]="advanced">Advanced</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<div class="md-form-field align-right">
|
||||
<button (click)="openWatchlistUserLog()" type="button" class="mat-focus-indicator mat-flat-button mat-button-base mat-accent">Watchlist User Errors</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<settings-plex-form-field [label]="'Enable'" [type]="'checkbox'" [id]="'enable'" [(value)]="settings.enable"></settings-plex-form-field>
|
||||
|
||||
<settings-plex-form-field [label]="'Enable User Watchlist Requests'" [type]="'checkbox'" [id]="'enable'" [(value)]="settings.enableWatchlistImport">
|
||||
<small bottom>When a Plex User adds something to their watchlist in Plex, it will turn up in Ombi as a Request if enabled. This <b>only</b> applies to users that are logging in with their Plex Account
|
||||
<br>Request limits if set are all still applied
|
||||
</small>
|
||||
</settings-plex-form-field>
|
||||
|
||||
<settings-plex-form-field [label]="'Advanced Options'" [type]="'checkbox'" [id]="'advanced'" [(value)]="advanced"></settings-plex-form-field>
|
||||
|
||||
<hr>
|
||||
|
||||
|
@ -43,8 +29,10 @@
|
|||
|
||||
<settings-plex-form
|
||||
[server]="server"
|
||||
[advancedEnabled]="advanced"
|
||||
[loadedServers]="loadedServers"
|
||||
(loadLibraries)="loadLibraries(server)"
|
||||
(loadServers)="requestServers()"
|
||||
(loadServers)="requestServers($event)"
|
||||
(test)="testPlex(server)"
|
||||
(runSync)="runSync($event)"
|
||||
(selectServer)="selectServer($event, server)"
|
||||
|
|
|
@ -17,4 +17,8 @@
|
|||
float: right;
|
||||
width:100%;
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
::ng-deep div .mat-tab-body-content {
|
||||
overflow: hidden;
|
||||
}
|
|
@ -8,7 +8,7 @@ import { MatTabChangeEvent, MatTabGroup } from "@angular/material/tabs";
|
|||
import {UntypedFormControl} from '@angular/forms';
|
||||
import { MatDialog } from "@angular/material/dialog";
|
||||
import { PlexWatchlistComponent } from "./components/watchlist/plex-watchlist.component";
|
||||
import { PlexSyncType } from "./components/models";
|
||||
import { PlexCreds, PlexSyncType } from "./components/models";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./plex.component.html",
|
||||
|
@ -17,8 +17,6 @@ import { PlexSyncType } from "./components/models";
|
|||
export class PlexComponent implements OnInit, OnDestroy {
|
||||
public settings: IPlexSettings;
|
||||
public loadedServers: IPlexServerViewModel; // This comes from the api call for the user to select a server
|
||||
public username: string;
|
||||
public password: string;
|
||||
public serversButton = false;
|
||||
selected = new UntypedFormControl(0);
|
||||
@ViewChild("tabGroup", {static: false}) public tagGroup: MatTabGroup;
|
||||
|
@ -41,8 +39,8 @@ export class PlexComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
public requestServers() {
|
||||
this.plexService.getServers(this.username, this.password).pipe(
|
||||
public requestServers({ username, password }: PlexCreds) {
|
||||
this.plexService.getServers(username, password).pipe(
|
||||
takeUntil(this.subscriptions),
|
||||
).subscribe(x => {
|
||||
if (x.success) {
|
||||
|
|
|
@ -85,6 +85,7 @@ import { WhatsAppComponent } from "./notifications/twilio/whatsapp.component";
|
|||
import { WikiComponent } from "./wiki.component";
|
||||
import { PlexWatchlistComponent } from "./plex/components/watchlist/plex-watchlist.component";
|
||||
import { PlexFormComponent } from "./plex/components/plex-form/plex-form.component";
|
||||
import { PlexFormFieldComponent } from "./plex/components/form-field/plex-form-field.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "Ombi", component: OmbiComponent, canActivate: [AuthGuard] },
|
||||
|
@ -193,6 +194,7 @@ const routes: Routes = [
|
|||
UpdateDialogComponent,
|
||||
PlexWatchlistComponent,
|
||||
PlexFormComponent,
|
||||
PlexFormFieldComponent,
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue