perf: Use ngxs store for the whole customization section of the app

pull/4347/head
tidusjar 3 years ago
parent 83dd42c9f1
commit 97b493d869

@ -31,6 +31,7 @@
"@ngu/carousel": "^1.4.9-beta-2", "@ngu/carousel": "^1.4.9-beta-2",
"@ngx-translate/core": "^11.0.1", "@ngx-translate/core": "^11.0.1",
"@ngx-translate/http-loader": "^4.0.0", "@ngx-translate/http-loader": "^4.0.0",
"@ngxs/devtools-plugin": "^3.7.2",
"@ngxs/store": "^3.7.2", "@ngxs/store": "^3.7.2",
"@types/jquery": "^3.3.29", "@types/jquery": "^3.3.29",
"@yellowspot/ng-truncate": "^1.4.0", "@yellowspot/ng-truncate": "^1.4.0",
@ -42,6 +43,7 @@
"core-js": "^2.5.4", "core-js": "^2.5.4",
"eventemitter2": "^5.0.1", "eventemitter2": "^5.0.1",
"fullcalendar": "^4.0.0-alpha.4", "fullcalendar": "^4.0.0-alpha.4",
"immer": "^9.0.6",
"jquery": "3.3.1", "jquery": "3.3.1",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"moment": "^2.23.0", "moment": "^2.23.0",

@ -85,8 +85,6 @@ export class AppComponent implements OnInit {
} }
public ngOnInit() { public ngOnInit() {
// window["loading_screen"].finish();
this.customizationFacade.loadCustomziationSettings();
this.customizationFacade.settings$().subscribe(x => { this.customizationFacade.settings$().subscribe(x => {
this.customizationSettings = x; this.customizationSettings = x;
if (this.customizationSettings && this.customizationSettings.applicationName) { if (this.customizationSettings && this.customizationSettings.applicationName) {

@ -13,6 +13,7 @@ import { AuthService } from "./auth/auth.service";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { BrowserModule } from "@angular/platform-browser"; import { BrowserModule } from "@angular/platform-browser";
import { ButtonModule } from "primeng/button"; import { ButtonModule } from "primeng/button";
import { CUSTOMIZATION_INITIALIZER } from "./state/customization/customization-initializer";
import { ConfirmDialogModule } from "primeng/confirmdialog"; import { ConfirmDialogModule } from "primeng/confirmdialog";
import { CookieComponent } from "./auth/cookie.component"; import { CookieComponent } from "./auth/cookie.component";
import { CookieService } from "ng2-cookies"; import { CookieService } from "ng2-cookies";
@ -48,6 +49,7 @@ import { MyNavComponent } from './my-nav/my-nav.component';
import { NavSearchComponent } from "./my-nav/nav-search.component"; import { NavSearchComponent } from "./my-nav/nav-search.component";
import { NgModule } from "@angular/core"; import { NgModule } from "@angular/core";
import { NgxsModule } from '@ngxs/store'; import { NgxsModule } from '@ngxs/store';
import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';
import { NotificationService } from "./services"; import { NotificationService } from "./services";
import { OverlayModule } from "@angular/cdk/overlay"; import { OverlayModule } from "@angular/cdk/overlay";
import { OverlayPanelModule } from "primeng/overlaypanel"; import { OverlayPanelModule } from "primeng/overlaypanel";
@ -64,38 +66,6 @@ import { TranslateHttpLoader } from "@ngx-translate/http-loader";
import { UnauthorizedInterceptor } from "./auth/unauthorized.interceptor"; import { UnauthorizedInterceptor } from "./auth/unauthorized.interceptor";
import { environment } from "../environments/environment"; import { environment } from "../environments/environment";
// Components
// Services
const routes: Routes = [ const routes: Routes = [
{ path: "*", component: PageNotFoundComponent }, { path: "*", component: PageNotFoundComponent },
{ path: "", redirectTo: "/discover", pathMatch: "full" }, { path: "", redirectTo: "/discover", pathMatch: "full" },
@ -192,6 +162,10 @@ export function JwtTokenGetter() {
NgxsModule.forRoot([CustomizationState], { NgxsModule.forRoot([CustomizationState], {
developmentMode: !environment.production, developmentMode: !environment.production,
}), }),
...environment.production ? [] :
[
NgxsReduxDevtoolsPluginModule.forRoot(),
]
], ],
declarations: [ declarations: [
AppComponent, AppComponent,
@ -236,7 +210,8 @@ export function JwtTokenGetter() {
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,
useClass: UnauthorizedInterceptor, useClass: UnauthorizedInterceptor,
multi: true multi: true
} },
CUSTOMIZATION_INITIALIZER
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })

@ -10,6 +10,8 @@ import { DomSanitizer } from "@angular/platform-browser";
import { ImageService } from "../services"; import { ImageService } from "../services";
import { fadeInOutAnimation } from "../animations/fadeinout"; import { fadeInOutAnimation } from "../animations/fadeinout";
import { CustomizationFacade } from "../state/customization";
import { ThousandShortPipe } from "../pipes/ThousandShortPipe";
@Component({ @Component({
templateUrl: "./landingpage.component.html", templateUrl: "./landingpage.component.html",
@ -29,10 +31,11 @@ export class LandingPageComponent implements OnDestroy, OnInit {
constructor(private settingsService: SettingsService, constructor(private settingsService: SettingsService,
private images: ImageService, private sanitizer: DomSanitizer, private landingPageService: LandingPageService, private images: ImageService, private sanitizer: DomSanitizer, private landingPageService: LandingPageService,
private customizationFacade: CustomizationFacade,
@Inject(APP_BASE_HREF) href :string) { this.href = href } @Inject(APP_BASE_HREF) href :string) { this.href = href }
public ngOnInit() { public ngOnInit() {
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.customizationFacade.settings$().subscribe(x => this.customizationSettings = x);
this.settingsService.getLandingPage().subscribe(x => this.landingPageSettings = x); this.settingsService.getLandingPage().subscribe(x => this.landingPageSettings = x);
this.images.getRandomBackground().subscribe(x => { this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 19%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 79%, transparent 80%), url(" + x.url + ")"); this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 19%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 79%, transparent 80%), url(" + x.url + ")");

@ -16,6 +16,7 @@ import { ImageService } from "../services";
import { fadeInOutAnimation } from "../animations/fadeinout"; import { fadeInOutAnimation } from "../animations/fadeinout";
import { StorageService } from "../shared/storage/storage-service"; import { StorageService } from "../shared/storage/storage-service";
import { MatSnackBar } from "@angular/material/snack-bar"; import { MatSnackBar } from "@angular/material/snack-bar";
import { CustomizationFacade } from "../state/customization";
@Component({ @Component({
templateUrl: "./login.component.html", templateUrl: "./login.component.html",
@ -60,6 +61,7 @@ export class LoginComponent implements OnDestroy, OnInit {
private status: StatusService, private status: StatusService,
private fb: FormBuilder, private fb: FormBuilder,
private settingsService: SettingsService, private settingsService: SettingsService,
private customziationFacade: CustomizationFacade,
private images: ImageService, private images: ImageService,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -99,13 +101,13 @@ export class LoginComponent implements OnDestroy, OnInit {
} }
public ngOnInit() { public ngOnInit() {
this.customziationFacade.settings$().subscribe(x => this.customizationSettings = x);
this.settingsService this.settingsService
.getAuthentication() .getAuthentication()
.subscribe((x) => (this.authenticationSettings = x)); .subscribe((x) => (this.authenticationSettings = x));
this.settingsService.getClientId().subscribe((x) => (this.clientId = x)); this.settingsService.getClientId().subscribe((x) => (this.clientId = x));
this.settingsService
.getCustomization()
.subscribe((x) => (this.customizationSettings = x));
this.images.getRandomBackground().subscribe((x) => { this.images.getRandomBackground().subscribe((x) => {
this.background = this.sanitizer.bypassSecurityTrustStyle( this.background = this.sanitizer.bypassSecurityTrustStyle(
"url(" + x.url + ")" "url(" + x.url + ")"

@ -6,6 +6,7 @@ import { fadeInOutAnimation } from "../animations/fadeinout";
import { ICustomizationSettings } from "../interfaces"; import { ICustomizationSettings } from "../interfaces";
import { IdentityService, ImageService, NotificationService, SettingsService } from "../services"; import { IdentityService, ImageService, NotificationService, SettingsService } from "../services";
import { CustomizationFacade } from "../state/customization";
@Component({ @Component({
templateUrl: "./resetpassword.component.html", templateUrl: "./resetpassword.component.html",
@ -23,7 +24,7 @@ export class ResetPasswordComponent implements OnInit {
constructor(private identityService: IdentityService, private notify: NotificationService, constructor(private identityService: IdentityService, private notify: NotificationService,
private fb: FormBuilder, private settingsService: SettingsService, @Inject(APP_BASE_HREF) href:string, private fb: FormBuilder, private settingsService: SettingsService, @Inject(APP_BASE_HREF) href:string,
private images: ImageService, private sanitizer: DomSanitizer) { private images: ImageService, private sanitizer: DomSanitizer, private customizationFacade: CustomizationFacade) {
this.href = href; this.href = href;
this.form = this.fb.group({ this.form = this.fb.group({
email: ["", [Validators.required]], email: ["", [Validators.required]],
@ -38,7 +39,7 @@ export class ResetPasswordComponent implements OnInit {
if (base.length > 1) { if (base.length > 1) {
this.baseUrl = base; this.baseUrl = base;
} }
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.customizationFacade.settings$().subscribe(x => this.customizationSettings = x);
this.settingsService.getEmailSettingsEnabled().subscribe(x => this.emailSettingsEnabled = x); this.settingsService.getEmailSettingsEnabled().subscribe(x => this.emailSettingsEnabled = x);
} }

@ -1,15 +1,15 @@
import { PlatformLocation } from "@angular/common"; import { ActivatedRoute, Params } from "@angular/router";
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { DomSanitizer } from "@angular/platform-browser"; import { IdentityService, ImageService } from "../services";
import { Router } from "@angular/router";
import { ActivatedRoute, Params } from "@angular/router";
import { CustomizationFacade } from "../state/customization";
import { DomSanitizer } from "@angular/platform-browser";
import { ICustomizationSettings } from "../interfaces"; import { ICustomizationSettings } from "../interfaces";
import { IResetPasswordToken } from "../interfaces"; import { IResetPasswordToken } from "../interfaces";
import { IdentityService, ImageService } from "../services";
import { NotificationService } from "../services"; import { NotificationService } from "../services";
import { SettingsService } from "../services"; import { PlatformLocation } from "@angular/common";
import { Router } from "@angular/router";
@Component({ @Component({
templateUrl: "./tokenresetpassword.component.html", templateUrl: "./tokenresetpassword.component.html",
@ -23,8 +23,9 @@ export class TokenResetPasswordComponent implements OnInit {
public baseUrl: string; public baseUrl: string;
constructor(private identityService: IdentityService, private router: Router, private route: ActivatedRoute, private notify: NotificationService, constructor(private identityService: IdentityService, private router: Router, private route: ActivatedRoute, private notify: NotificationService,
private fb: FormBuilder, private settingsService: SettingsService, private location: PlatformLocation, private fb: FormBuilder, private location: PlatformLocation, private images: ImageService,
private images: ImageService, private sanitizer: DomSanitizer) { private sanitizer: DomSanitizer, private customizationFacade: CustomizationFacade,
) {
this.route.queryParams this.route.queryParams
.subscribe((params: Params) => { .subscribe((params: Params) => {
@ -45,7 +46,7 @@ export class TokenResetPasswordComponent implements OnInit {
if (base.length > 1) { if (base.length > 1) {
this.baseUrl = base; this.baseUrl = base;
} }
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.customizationFacade.settings$().subscribe(x => this.customizationSettings = x);
} }
public onSubmit(form: FormGroup) { public onSubmit(form: FormGroup) {

@ -1,4 +1,4 @@
import { PlatformLocation, APP_BASE_HREF } from "@angular/common"; import { APP_BASE_HREF } from "@angular/common";
import { HttpClient } from "@angular/common/http"; import { HttpClient } from "@angular/common/http";
import { Injectable, Inject } from "@angular/core"; import { Injectable, Inject } from "@angular/core";
import { Observable } from "rxjs"; import { Observable } from "rxjs";

@ -1,5 +1,6 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { CustomizationFacade } from "../../state/customization";
import { ICustomizationSettings } from "../../interfaces"; import { ICustomizationSettings } from "../../interfaces";
import { NotificationService } from "../../services"; import { NotificationService } from "../../services";
import { SettingsService } from "../../services"; import { SettingsService } from "../../services";
@ -13,13 +14,14 @@ export class CustomizationComponent implements OnInit {
public settings: ICustomizationSettings; public settings: ICustomizationSettings;
public advanced: boolean; public advanced: boolean;
constructor(private settingsService: SettingsService, private notificationService: NotificationService) { } constructor(private settingsService: SettingsService,
private notificationService: NotificationService,
private customizationFacade: CustomizationFacade) { }
public ngOnInit() { public ngOnInit() {
this.settingsService.getCustomization().subscribe(x => { this.customizationFacade.settings$().subscribe(x => {
this.settings = x; this.settings = { ...x };
}); });
} }
public save() { public save() {
@ -32,7 +34,7 @@ export class CustomizationComponent implements OnInit {
} }
} }
this.settingsService.saveCustomization(this.settings).subscribe(x => { this.customizationFacade.saveSettings(this.settings).subscribe(x => {
if (x) { if (x) {
this.notificationService.success("Successfully saved Ombi settings"); this.notificationService.success("Successfully saved Ombi settings");
} else { } else {

@ -0,0 +1,10 @@
import { APP_INITIALIZER } from "@angular/core";
import { CustomizationFacade } from ".";
import { Observable } from "rxjs";
export const CUSTOMIZATION_INITIALIZER = {
provide: APP_INITIALIZER,
useFactory: (customizationFacade: CustomizationFacade) => (): Observable<unknown> => customizationFacade.loadCustomziationSettings(),
multi: true,
deps: [CustomizationFacade],
};

@ -1,5 +1,11 @@
import { ICustomizationSettings } from "../../interfaces";
export class LoadSettings { export class LoadSettings {
public static readonly type = '[Customization] LoadSettings'; public static readonly type = '[Customization] LoadSettings';
} }
export class UpdateSettings {
public static readonly type = '[Customization] UpdateSettings';
constructor(public settings: ICustomizationSettings) { }
}

@ -1,7 +1,8 @@
import { LoadSettings, UpdateSettings } from "./customization.actions";
import { CustomizationSelectors } from "./customization.selectors"; import { CustomizationSelectors } from "./customization.selectors";
import { ICustomizationSettings } from "../../interfaces"; import { ICustomizationSettings } from "../../interfaces";
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { LoadSettings } from "./customization.actions";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { Store } from "@ngxs/store"; import { Store } from "@ngxs/store";
@ -15,4 +16,12 @@ export class CustomizationFacade {
public settings$ = (): Observable<ICustomizationSettings> => this.store.select(CustomizationSelectors.customizationSettings); public settings$ = (): Observable<ICustomizationSettings> => this.store.select(CustomizationSelectors.customizationSettings);
public loadCustomziationSettings = (): Observable<unknown> => this.store.dispatch(new LoadSettings()); public loadCustomziationSettings = (): Observable<unknown> => this.store.dispatch(new LoadSettings());
public logo = (): string => this.store.selectSnapshot(CustomizationSelectors.logo);
public appName = (): string => this.store.selectSnapshot(CustomizationSelectors.applicationName);
public appUrl = (): string => this.store.selectSnapshot(CustomizationSelectors.applicationUrl);
public saveSettings = (settings: ICustomizationSettings): Observable<unknown> => this.store.dispatch(new UpdateSettings(settings));
} }

@ -8,4 +8,19 @@ export class CustomizationSelectors {
public static customizationSettings(settings: ICustomizationSettings): ICustomizationSettings { public static customizationSettings(settings: ICustomizationSettings): ICustomizationSettings {
return settings; return settings;
} }
@Selector([CustomizationSelectors.customizationSettings])
public static logo({logo}: ICustomizationSettings): string {
return logo;
}
@Selector([CustomizationSelectors.customizationSettings])
public static applicationName({applicationName}: ICustomizationSettings): string {
return applicationName;
}
@Selector([CustomizationSelectors.customizationSettings])
public static applicationUrl({applicationUrl}: ICustomizationSettings): string {
return applicationUrl;
}
} }

@ -1,11 +1,12 @@
import { Action, State, StateContext } from "@ngxs/store"; import { Action, State, StateContext } from "@ngxs/store";
import { LoadSettings, UpdateSettings } from "./customization.actions";
import { CUSTOMIZATION_STATE_TOKEN } from "./types"; import { CUSTOMIZATION_STATE_TOKEN } from "./types";
import { ICustomizationSettings } from "../../interfaces"; import { ICustomizationSettings } from "../../interfaces";
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { LoadSettings } from "./customization.actions";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { SettingsService } from "../../services"; import { SettingsService } from "../../services";
import { produce } from 'immer';
import { tap } from "rxjs/operators"; import { tap } from "rxjs/operators";
@State({ @State({
@ -16,9 +17,18 @@ export class CustomizationState {
constructor(private settingsService: SettingsService) { } constructor(private settingsService: SettingsService) { }
@Action(LoadSettings) @Action(LoadSettings)
public load({setState}: StateContext<ICustomizationSettings>): Observable<ICustomizationSettings> { public load({ setState }: StateContext<ICustomizationSettings>): Observable<ICustomizationSettings> {
return this.settingsService.getCustomization().pipe( return this.settingsService.getCustomization().pipe(
tap(settings => setState(settings)) tap(settings =>
setState(settings)
)
);
}
@Action(UpdateSettings)
public update({ setState }: StateContext<ICustomizationSettings>, { settings }: UpdateSettings): Observable<boolean> {
return this.settingsService.saveCustomization(settings).pipe(
tap(() => setState(settings))
); );
} }
} }

@ -1,4 +1,4 @@
import { ICustomizationSettings } from "../../interfaces"; import { ICustomizationSettings } from "../../interfaces";
import { StateToken } from "@ngxs/store"; import { StateToken } from "@ngxs/store";
export const CUSTOMIZATION_STATE_TOKEN = new StateToken<ICustomizationSettings>('customization'); export const CUSTOMIZATION_STATE_TOKEN = new StateToken<ICustomizationSettings>('customizationSettings');

@ -1,12 +1,13 @@
import { Component, Inject, OnInit } from "@angular/core"; import { Component, Inject, OnInit } from "@angular/core";
import { AuthService } from "../../../auth/auth.service"; import { AuthService } from "../../../auth/auth.service";
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
import { AvailableLanguages, ILanguage } from "./user-preference.constants"; import { AvailableLanguages } from "./user-preference.constants";
import { IdentityService, NotificationService, SettingsService, ValidationService } from "../../../services"; import { IdentityService, NotificationService, ValidationService } from "../../../services";
import { ICustomizationSettings, IUser, UserType } from "../../../interfaces"; import { IUser, UserType } from "../../../interfaces";
import { Md5 } from "ts-md5"; import { Md5 } from "ts-md5";
import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { APP_BASE_HREF } from "@angular/common"; import { APP_BASE_HREF } from "@angular/common";
import { CustomizationFacade } from "../../../state/customization";
@Component({ @Component({
templateUrl: "./user-preference.component.html", templateUrl: "./user-preference.component.html",
@ -22,21 +23,22 @@ export class UserPreferenceComponent implements OnInit {
public qrCodeEnabled: boolean; public qrCodeEnabled: boolean;
public countries: string[]; public countries: string[];
public selectedCountry: string; public selectedCountry: string;
public customizationSettings: ICustomizationSettings;
public UserType = UserType; public UserType = UserType;
public baseUrl: string; public baseUrl: string;
public passwordForm: FormGroup; public passwordForm: FormGroup;
private user: IUser; private user: IUser;
private applicationUrl: string = this.customizationFacade.appUrl();
private logo: string = this.customizationFacade.logo();
constructor(private authService: AuthService, constructor(private authService: AuthService,
private readonly translate: TranslateService, private readonly translate: TranslateService,
private readonly notification: NotificationService, private readonly notification: NotificationService,
private readonly identityService: IdentityService, private readonly identityService: IdentityService,
private readonly settingsService: SettingsService,
private readonly fb: FormBuilder, private readonly fb: FormBuilder,
private readonly validationService: ValidationService, private readonly validationService: ValidationService,
private readonly customizationFacade: CustomizationFacade,
@Inject(APP_BASE_HREF) public internalBaseUrl: string) { } @Inject(APP_BASE_HREF) public internalBaseUrl: string) { }
public async ngOnInit() { public async ngOnInit() {
@ -47,14 +49,13 @@ export class UserPreferenceComponent implements OnInit {
if (user.name) { if (user.name) {
this.username = user.name; this.username = user.name;
} }
this.customizationSettings = await this.settingsService.getCustomization().toPromise();
this.selectedLang = this.translate.currentLang; this.selectedLang = this.translate.currentLang;
const accessToken = await this.identityService.getAccessToken().toPromise(); const accessToken = await this.identityService.getAccessToken().toPromise();
this.qrCode = `${this.customizationSettings.applicationUrl}|${accessToken}`; this.qrCode = `${this.applicationUrl}|${accessToken}`;
if(!this.customizationSettings.applicationUrl) { if(!this.applicationUrl) {
this.qrCodeEnabled = false; this.qrCodeEnabled = false;
} else { } else {
this.qrCodeEnabled = true; this.qrCodeEnabled = true;
@ -64,7 +65,6 @@ export class UserPreferenceComponent implements OnInit {
this.selectedCountry = this.user.streamingCountry; this.selectedCountry = this.user.streamingCountry;
this.setProfileImageUrl(this.user); this.setProfileImageUrl(this.user);
this.identityService.getSupportedStreamingCountries().subscribe(x => this.countries = x); this.identityService.getSupportedStreamingCountries().subscribe(x => this.countries = x);
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
this.passwordForm = this.fb.group({ this.passwordForm = this.fb.group({
password: [null], password: [null],
@ -112,8 +112,8 @@ export class UserPreferenceComponent implements OnInit {
} }
private getFallbackProfileImageUrl() { private getFallbackProfileImageUrl() {
return this.customizationSettings?.logo return this.logo
? this.customizationSettings.logo ? this.logo
: "https://raw.githubusercontent.com/Ombi-app/Ombi/gh-pages/img/android-chrome-512x512.png"; : "https://raw.githubusercontent.com/Ombi-app/Ombi/gh-pages/img/android-chrome-512x512.png";
} }

@ -182,7 +182,7 @@
<button type="button" data-test="deletebtn" mat-raised-button color="warn" class="btn btn-danger-outline" (click)="delete()">Delete</button> <button type="button" data-test="deletebtn" mat-raised-button color="warn" class="btn btn-danger-outline" (click)="delete()">Delete</button>
<button type="button" style="float:right;" mat-raised-button color="primary" class="btn btn-info-outline" (click)="resetPassword()" matTooltip="You need your SMTP settings setup">Send <button type="button" style="float:right;" mat-raised-button color="primary" class="btn btn-info-outline" (click)="resetPassword()" matTooltip="You need your SMTP settings setup">Send
Reset Password Link</button> Reset Password Link</button>
<button *ngIf="customization?.applicationUrl" type="button" mat-raised-button color="accent" class="btn btn-info-outline" (click)="appLink()" matTooltip="Send this link to the user and they can then open the app and directly login">Copy users App Link</button> <button *ngIf="appUrl" type="button" mat-raised-button color="accent" class="btn btn-info-outline" (click)="appLink()" matTooltip="Send this link to the user and they can then open the app and directly login">Copy users App Link</button>
</div> </div>

@ -4,6 +4,7 @@ import { ICheckbox, ICustomizationSettings, INotificationAgent, INotificationPre
import { IdentityService, MessageService, RadarrService, SettingsService, SonarrService } from "../services"; import { IdentityService, MessageService, RadarrService, SettingsService, SonarrService } from "../services";
import { Clipboard } from '@angular/cdk/clipboard'; import { Clipboard } from '@angular/cdk/clipboard';
import { CustomizationFacade } from "../state/customization";
import { Location } from "@angular/common"; import { Location } from "@angular/common";
@Component({ @Component({
@ -30,7 +31,7 @@ export class UserManagementUserComponent implements OnInit {
public requestLimitTypes: RequestLimitType[]; public requestLimitTypes: RequestLimitType[];
public RequestLimitType = RequestLimitType; public RequestLimitType = RequestLimitType;
private customization: ICustomizationSettings; private appUrl: string = this.customizationFacade.appUrl();
private accessToken: string; private accessToken: string;
constructor(private identityService: IdentityService, constructor(private identityService: IdentityService,
@ -41,7 +42,9 @@ export class UserManagementUserComponent implements OnInit {
private sonarrService: SonarrService, private sonarrService: SonarrService,
private radarrService: RadarrService, private radarrService: RadarrService,
private clipboard: Clipboard, private clipboard: Clipboard,
private location: Location) { private location: Location,
private customizationFacade: CustomizationFacade,
) {
this.route.params.subscribe((params: any) => { this.route.params.subscribe((params: any) => {
if(params.id) { if(params.id) {
@ -68,7 +71,6 @@ export class UserManagementUserComponent implements OnInit {
this.radarrService.getQualityProfilesFromSettings().subscribe(x => this.radarrQualities = x); this.radarrService.getQualityProfilesFromSettings().subscribe(x => this.radarrQualities = x);
this.radarrService.getRootFoldersFromSettings().subscribe(x => this.radarrRootFolders = x); this.radarrService.getRootFoldersFromSettings().subscribe(x => this.radarrRootFolders = x);
this.settingsService.getCustomization().subscribe(x => this.customization = x);
this.identityService.getUserAccessToken(this.userId).subscribe(x => this.accessToken = x); this.identityService.getUserAccessToken(this.userId).subscribe(x => this.accessToken = x);
if(!this.edit) { if(!this.edit) {
@ -191,7 +193,7 @@ export class UserManagementUserComponent implements OnInit {
} }
public async appLink() { public async appLink() {
this.clipboard.copy(`ombi://${this.customization.applicationUrl}|${this.accessToken}`); this.clipboard.copy(`ombi://${this.appUrl}|${this.accessToken}`);
this.notificationService.send("Copied!"); this.notificationService.send("Copied!");
} }

@ -105,8 +105,8 @@
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> </th> <th mat-header-cell *matHeaderCellDef> </th>
<td mat-cell *matCellDef="let u"> <td mat-cell *matCellDef="let u">
<button id="edit{{u.userName}}" mat-raised-button color="accent" [routerLink]="['/usermanagement/user/' + u.id]">Edit</button> <button id="edit{{u.userName}}" mat-raised-button color="accent" [routerLink]="['/usermanagement/user/' + u.id]">Edit</button>
<button *ngIf="!u.hasLoggedIn" mat-raised-button color="accent" (click)="welcomeEmail(u)" [disabled]="!customizationSettings?.applicationUrl"><i class="far fa-paper-plane"></i> Welcome</button> <button *ngIf="!u.hasLoggedIn" mat-raised-button color="accent" (click)="welcomeEmail(u)" [disabled]="!applicationUrl"><i class="far fa-paper-plane"></i> Welcome</button>
</td> </td>
</ng-container> </ng-container>

@ -2,6 +2,7 @@
import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces"; import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces";
import { IdentityService, NotificationService, SettingsService } from "../services"; import { IdentityService, NotificationService, SettingsService } from "../services";
import { CustomizationFacade } from "../state/customization";
import { MatSort } from "@angular/material/sort"; import { MatSort } from "@angular/material/sort";
import { MatTableDataSource } from "@angular/material/table"; import { MatTableDataSource } from "@angular/material/table";
import { SelectionModel } from "@angular/cdk/collections"; import { SelectionModel } from "@angular/cdk/collections";
@ -21,7 +22,7 @@ export class UserManagementComponent implements OnInit {
public users: IUser[]; public users: IUser[];
public checkAll = false; public checkAll = false;
public emailSettings: IEmailNotificationSettings; public emailSettings: IEmailNotificationSettings;
public customizationSettings: ICustomizationSettings; public applicationUrl: string;
public showBulkEdit = false; public showBulkEdit = false;
public availableClaims: ICheckbox[]; public availableClaims: ICheckbox[];
public bulkMovieLimit?: number; public bulkMovieLimit?: number;
@ -35,7 +36,8 @@ export class UserManagementComponent implements OnInit {
constructor(private identityService: IdentityService, constructor(private identityService: IdentityService,
private settingsService: SettingsService, private settingsService: SettingsService,
private notificationService: NotificationService, private notificationService: NotificationService,
private plexSettings: SettingsService) { private plexSettings: SettingsService,
private customizationFacade: CustomizationFacade) {
this.dataSource = new MatTableDataSource(); this.dataSource = new MatTableDataSource();
} }
@ -49,7 +51,7 @@ export class UserManagementComponent implements OnInit {
this.plexSettings.getPlex().subscribe(x => this.plexEnabled = x.enable); this.plexSettings.getPlex().subscribe(x => this.plexEnabled = x.enable);
this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x); this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x);
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.applicationUrl = this.customizationFacade.appUrl();
this.settingsService.getEmailNotificationSettings().subscribe(x => this.emailSettings = x); this.settingsService.getEmailNotificationSettings().subscribe(x => this.emailSettings = x);
} }

@ -1445,6 +1445,13 @@
dependencies: dependencies:
tslib "^1.9.0" tslib "^1.9.0"
"@ngxs/devtools-plugin@^3.7.2":
version "3.7.2"
resolved "https://registry.yarnpkg.com/@ngxs/devtools-plugin/-/devtools-plugin-3.7.2.tgz#995424e5faf48df55a1b54b9e1b36ce9c47c1d52"
integrity sha512-kRuOx1GPXHHZZAeQMm1J1msTZxjgiAUY4NR7bzaQPn+UwSY2OgGEsd8driMM5YSTF1hOjcFHinaLCM1vmu8FmQ==
dependencies:
tslib "^1.9.0"
"@ngxs/store@^3.7.2": "@ngxs/store@^3.7.2":
version "3.7.2" version "3.7.2"
resolved "https://registry.yarnpkg.com/@ngxs/store/-/store-3.7.2.tgz#1088b0669adc382d36ca7ae8438c603e55879b42" resolved "https://registry.yarnpkg.com/@ngxs/store/-/store-3.7.2.tgz#1088b0669adc382d36ca7ae8438c603e55879b42"
@ -4981,6 +4988,11 @@ immediate@~3.0.5:
version "3.0.6" version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
immer@^9.0.6:
version "9.0.6"
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.6.tgz#7a96bf2674d06c8143e327cbf73539388ddf1a73"
integrity sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==
import-fresh@^2.0.0: import-fresh@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"

Loading…
Cancel
Save