Read Only Accounts (#2658)
This commit is contained in:
parent
4f5bb57085
commit
9c84e19960
17 changed files with 155 additions and 65 deletions
|
@ -20,7 +20,8 @@ export enum Role {
|
|||
ChangePassword = 'Change Password',
|
||||
Bookmark = 'Bookmark',
|
||||
Download = 'Download',
|
||||
ChangeRestriction = 'Change Restriction'
|
||||
ChangeRestriction = 'Change Restriction',
|
||||
ReadOnly = 'Read Only'
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
|
@ -80,6 +81,10 @@ export class AccountService {
|
|||
return user && user.roles.includes(Role.Bookmark);
|
||||
}
|
||||
|
||||
hasReadOnlyRole(user: User) {
|
||||
return user && user.roles.includes(Role.ReadOnly);
|
||||
}
|
||||
|
||||
getRoles() {
|
||||
return this.httpClient.get<string[]>(this.baseUrl + 'account/roles');
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {ChangeDetectorRef, Component, DestroyRef, HostListener, inject, Inject, OnInit} from '@angular/core';
|
||||
import {NavigationStart, Router, RouterOutlet} from '@angular/router';
|
||||
import {map, shareReplay, take} from 'rxjs/operators';
|
||||
import {map, shareReplay, take, tap} from 'rxjs/operators';
|
||||
import { AccountService } from './_services/account.service';
|
||||
import { LibraryService } from './_services/library.service';
|
||||
import { NavService } from './_services/nav.service';
|
||||
|
@ -67,8 +67,21 @@ export class AppComponent implements OnInit {
|
|||
|
||||
});
|
||||
|
||||
// Every hour, have the UI check for an update. People seriously stay out of date
|
||||
// interval(60 * 60 * 1000) // 60 minutes in milliseconds
|
||||
// .pipe(
|
||||
// switchMap(() => this.accountService.currentUser$),
|
||||
// filter(u => u !== undefined && this.accountService.hasAdminRole(u)),
|
||||
// switchMap(_ => this.serverService.checkForUpdates())
|
||||
// )
|
||||
// .subscribe();
|
||||
|
||||
this.transitionState$ = this.accountService.currentUser$.pipe(map((user) => {
|
||||
|
||||
this.transitionState$ = this.accountService.currentUser$.pipe(
|
||||
tap(user => {
|
||||
|
||||
}),
|
||||
map((user) => {
|
||||
if (!user) return false;
|
||||
return user.preferences.noTransitions;
|
||||
}), takeUntilDestroyed(this.destroyRef));
|
||||
|
|
|
@ -26,23 +26,27 @@ import {translate, TranslocoDirective} from "@ngneat/transloco";
|
|||
})
|
||||
export class ApiKeyComponent implements OnInit {
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly confirmService = inject(ConfirmService);
|
||||
private readonly accountService = inject(AccountService);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly clipboard = inject(Clipboard);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
|
||||
@Input() title: string = 'API Key';
|
||||
@Input() showRefresh: boolean = true;
|
||||
@Input() transform: (val: string) => string = (val: string) => val;
|
||||
@Input() tooltipText: string = '';
|
||||
@Input() hideData = true;
|
||||
@ViewChild('apiKey') inputElem!: ElementRef;
|
||||
key: string = '';
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
key: string = '';
|
||||
isDataHidden: boolean = this.hideData;
|
||||
|
||||
get InputType() {
|
||||
return (this.hideData && this.isDataHidden) ? 'password' : 'text';
|
||||
}
|
||||
|
||||
constructor(private confirmService: ConfirmService, private accountService: AccountService, private toastr: ToastrService, private clipboard: Clipboard,
|
||||
private readonly cdRef: ChangeDetectorRef) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
|
||||
|
@ -53,6 +57,8 @@ export class ApiKeyComponent implements OnInit {
|
|||
key = translate('api-key.no-key');
|
||||
}
|
||||
|
||||
this.showRefresh = !this.accountService.hasReadOnlyRole(user!);
|
||||
|
||||
if (this.transform != undefined) {
|
||||
this.key = this.transform(key);
|
||||
this.cdRef.markForCheck();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div class="container-fluid row mb-2">
|
||||
<div class="col-10 col-sm-11"><h4 id="age-restriction">{{t('age-restriction-label')}}</h4></div>
|
||||
<div class="col-1 text-end">
|
||||
<button class="btn btn-primary btn-sm" (click)="toggleViewMode()" *ngIf="(hasChangeAgeRestrictionAbility | async)">{{isViewMode ? t('edit') : t('cancel')}}</button>
|
||||
<button class="btn btn-primary btn-sm" (click)="toggleViewMode()" [disabled]="!(hasChangeAgeRestrictionAbility | async)">{{isViewMode ? t('edit') : t('cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -50,7 +50,7 @@ export class ChangeAgeRestrictionComponent implements OnInit {
|
|||
});
|
||||
|
||||
this.hasChangeAgeRestrictionAbility = this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay(), map(user => {
|
||||
return user !== undefined && (!this.accountService.hasAdminRole(user) && this.accountService.hasChangeAgeRestrictionRole(user));
|
||||
return user !== undefined && !this.accountService.hasReadOnlyRole(user) && (!this.accountService.hasAdminRole(user) && this.accountService.hasChangeAgeRestrictionRole(user));
|
||||
}));
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</h4>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button class="btn btn-primary btn-sm" (click)="toggleViewMode()">{{isViewMode ? 'Edit' : 'Cancel'}}</button>
|
||||
<button class="btn btn-primary btn-sm" (click)="toggleViewMode()" [disabled]="!canEdit">{{isViewMode ? t('edit') : t('cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,6 +29,7 @@ export class ChangeEmailComponent implements OnInit {
|
|||
emailLink: string = '';
|
||||
emailConfirmed: boolean = true;
|
||||
hasValidEmail: boolean = true;
|
||||
canEdit: boolean = false;
|
||||
|
||||
|
||||
public get email() { return this.form.get('email'); }
|
||||
|
@ -38,8 +39,9 @@ export class ChangeEmailComponent implements OnInit {
|
|||
constructor(public accountService: AccountService, private toastr: ToastrService, private readonly cdRef: ChangeDetectorRef) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay(), take(1)).subscribe(user => {
|
||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay()).subscribe(user => {
|
||||
this.user = user;
|
||||
this.canEdit = !this.accountService.hasReadOnlyRole(user!);
|
||||
this.form.addControl('email', new FormControl(user?.email, [Validators.required, Validators.email]));
|
||||
this.form.addControl('password', new FormControl('', [Validators.required]));
|
||||
this.cdRef.markForCheck();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div class="container-fluid row mb-2">
|
||||
<div class="col-10 col-sm-11"><h4>{{t('password-label')}}</h4></div>
|
||||
<div class="col-1 text-end">
|
||||
<button class="btn btn-primary btn-sm" (click)="toggleViewMode()" *ngIf="(hasChangePasswordAbility | async)">{{isViewMode ? t('edit') : t('cancel')}}</button>
|
||||
<button class="btn btn-primary btn-sm" (click)="toggleViewMode()" [disabled]="!(hasChangePasswordAbility | async)">{{isViewMode ? t('edit') : t('cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -44,13 +44,13 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
|
|||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay(), take(1)).subscribe(user => {
|
||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay()).subscribe(user => {
|
||||
this.user = user;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.hasChangePasswordAbility = this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay(), map(user => {
|
||||
return user !== undefined && (this.accountService.hasAdminRole(user) || this.accountService.hasChangePasswordRole(user));
|
||||
return user !== undefined && !this.accountService.hasReadOnlyRole(user) && (this.accountService.hasAdminRole(user) || this.accountService.hasChangePasswordRole(user));
|
||||
}));
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from '@angular/core';
|
||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { take } from 'rxjs/operators';
|
||||
import {take, tap} from 'rxjs/operators';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import {
|
||||
readingDirections,
|
||||
|
@ -129,7 +129,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
opdsUrl: string = '';
|
||||
makeUrl: (val: string) => string = (val: string) => { return this.opdsUrl; };
|
||||
hasActiveLicense = false;
|
||||
|
||||
canEdit = true;
|
||||
|
||||
|
||||
|
||||
|
@ -142,6 +142,11 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.settingsService.getOpdsEnabled().subscribe(res => {
|
||||
this.opdsEnabled = res;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.localizationService.getLocales().subscribe(res => {
|
||||
this.locales = res;
|
||||
this.cdRef.markForCheck();
|
||||
|
@ -149,7 +154,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
|
||||
|
||||
|
||||
this.accountService.hasValidLicense$.pipe(take(1), takeUntilDestroyed(this.destroyRef)).subscribe(res => {
|
||||
this.accountService.hasValidLicense$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(res => {
|
||||
if (res) {
|
||||
this.tabs.push({title: 'scrobbling-tab', fragment: FragmentID.Scrobbling});
|
||||
this.hasActiveLicense = true;
|
||||
|
@ -169,10 +174,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
|
||||
|
||||
this.settingsService.getOpdsEnabled().subscribe(res => {
|
||||
this.opdsEnabled = res;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue