Add default values for when Sync is off

This commit is contained in:
Amelia 2025-07-01 20:02:37 +02:00
parent 9fb29dec20
commit a122ae07a9
No known key found for this signature in database
GPG key ID: D6D0ECE365407EAA
12 changed files with 203 additions and 42 deletions

View file

@ -12,13 +12,17 @@ export class AgeRatingPipe implements PipeTransform {
private readonly translocoService = inject(TranslocoService);
transform(value: AgeRating | AgeRatingDto | undefined): string {
transform(value: AgeRating | AgeRatingDto | undefined | string): string {
if (value === undefined || value === null) return this.translocoService.translate('age-rating-pipe.unknown');
if (value.hasOwnProperty('title')) {
return (value as AgeRatingDto).title;
}
if (typeof value === 'string') {
value = parseInt(value, 10) as AgeRating;
}
switch (value) {
case AgeRating.Unknown:
return this.translocoService.translate('age-rating-pipe.unknown');

View file

@ -3,7 +3,7 @@ import {UserOwner} from "../_models/user";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'creationSourcePipe'
name: 'userOwnerPipe'
})
export class UserOwnerPipe implements PipeTransform {

View file

@ -1,3 +1,4 @@
import {AgeRating} from "../../_models/metadata/age-rating";
export interface OidcConfig {
authority: string;
@ -8,6 +9,10 @@ export interface OidcConfig {
autoLogin: boolean;
disablePasswordAuthentication: boolean;
providerName: string;
defaultRoles: string[];
defaultLibraries: number[];
defaultAgeRating: AgeRating;
defaultIncludeUnknowns: boolean;
}
export interface OidcPublicConfig {

View file

@ -78,12 +78,12 @@
@if (userForm.get('owner'); as formControl) {
<app-setting-item [title]="t('owner')" [subtitle]="t('owner-tooltip')">
<ng-template #view>
<div>{{member().owner | UserOwnerPipe}}</div>
<div>{{member().owner | userOwnerPipe}}</div>
</ng-template>
<ng-template #edit>
<select class="form-select" id="creationSource" formControlName="creationSource">
@for (source of UserOwners; track source) {
<option [value]="source">{{source | UserOwnerPipe}}</option>
<option [value]="source">{{source | userOwnerPipe}}</option>
}
</select>
</ng-template>

View file

@ -77,7 +77,7 @@ export class EditUserComponent implements OnInit {
ngOnInit(): void {
this.userForm.addControl('email', new FormControl(this.member().email, [Validators.required]));
this.userForm.addControl('username', new FormControl(this.member().username, [Validators.required, Validators.pattern(AllowedUsernameCharacters)]));
this.userForm.addControl('creationSource', new FormControl(this.member().owner, [Validators.required]));
this.userForm.addControl('owner', new FormControl(this.member().owner, [Validators.required]));
// TODO: Rework, bad hack
// Work around isLocked so we're able to downgrade users

View file

@ -3,7 +3,7 @@ import {
ChangeDetectorRef,
Component,
EventEmitter,
inject,
inject, input,
Input,
OnInit,
Output
@ -29,6 +29,7 @@ export class LibrarySelectorComponent implements OnInit {
private readonly cdRef = inject(ChangeDetectorRef);
@Input() member: Member | undefined;
preselectedLibraries = input<number[]>([]);
@Output() selected: EventEmitter<Array<Library>> = new EventEmitter<Array<Library>>();
allLibraries: Library[] = [];
@ -61,6 +62,14 @@ export class LibrarySelectorComponent implements OnInit {
});
this.selectAll = this.selections.selected().length === this.allLibraries.length;
this.selected.emit(this.selections.selected());
} else if (this.preselectedLibraries().length > 0) {
this.preselectedLibraries().forEach((id) => {
const foundLib = this.allLibraries.find(lib => lib.id === id);
if (foundLib) {
this.selections.toggle(foundLib, true, (a, b) => a.name === b.name);
}
});
this.selectAll = this.selections.selected().length === this.allLibraries.length;
}
this.cdRef.markForCheck();
}

View file

@ -141,6 +141,55 @@
</ng-container>
<div class="setting-section-break"></div>
<h4>{{t('defaults')}}</h4>
<div class="text-muted">{{t('defaults-requirement')}}</div>
<ng-container>
<div class="row g-0 mt-4 mb-4">
@if(settingsForm.get('defaultAgeRating'); as formControl) {
<app-setting-item [title]="t('defaultAgeRating')" [subtitle]="t('defaultAgeRating-tooltip')">
<ng-template #view>
<div>{{formControl.value | ageRating}}</div>
</ng-template>
<ng-template #edit>
<select class="form-select" formControlName="defaultAgeRating">
<option value="-1">{{t('no-restriction')}}</option>
@for (ageRating of ageRatings(); track ageRating.value) {
<option [value]="ageRating.value">{{ageRating.title}}</option>
}
</select>
</ng-template>
</app-setting-item>
}
</div>
<div class="row g-0 mt-4 mb-4">
@if(settingsForm.get('defaultIncludeUnknowns'); as formControl) {
<app-setting-switch [title]="t('defaultIncludeUnknowns')" [subtitle]="t('defaultIncludeUnknowns-tooltip')">
<ng-template #switch>
<div class="form-check form-switch float-end">
<input id="defaultIncludeUnknowns" type="checkbox" class="form-check-input" formControlName="defaultIncludeUnknowns">
</div>
</ng-template>
</app-setting-switch>
}
</div>
@if (this.oidcSettings()) {
<div class="row g-0 mb-3">
<div class="col-md-6 pe-4">
<app-role-selector (selected)="updateRoles($event)" [allowAdmin]="true" [preSelectedRoles]="selectedRoles()"></app-role-selector>
</div>
<div class="col-md-6">
<app-library-selector (selected)="updateLibraries($event)" [preselectedLibraries]="selectedLibraries()"></app-library-selector>
</div>
</div>
}
</ng-container>
</form>
</ng-container>

View file

@ -1,4 +1,4 @@
import {ChangeDetectorRef, Component, DestroyRef, OnInit} from '@angular/core';
import {ChangeDetectorRef, Component, DestroyRef, effect, OnInit, signal} from '@angular/core';
import {TranslocoDirective} from "@jsverse/transloco";
import {ServerSettings} from "../_models/server-settings";
import {
@ -16,6 +16,16 @@ import {SettingItemComponent} from "../../settings/_components/setting-item/sett
import {SettingSwitchComponent} from "../../settings/_components/setting-switch/setting-switch.component";
import {debounceTime, distinctUntilChanged, filter, map, of, switchMap, tap} from "rxjs";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {RestrictionSelectorComponent} from "../../user-settings/restriction-selector/restriction-selector.component";
import {AgeRatingPipe} from "../../_pipes/age-rating.pipe";
import {MetadataService} from "../../_services/metadata.service";
import {AgeRating} from "../../_models/metadata/age-rating";
import {AgeRatingDto} from "../../_models/metadata/age-rating-dto";
import {allRoles, Role} from "../../_services/account.service";
import {Library} from "../../_models/library/library";
import {LibraryService} from "../../_services/library.service";
import {LibrarySelectorComponent} from "../library-selector/library-selector.component";
import {RoleSelectorComponent} from "../role-selector/role-selector.component";
@Component({
selector: 'app-manage-open-idconnect',
@ -23,7 +33,10 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
TranslocoDirective,
ReactiveFormsModule,
SettingItemComponent,
SettingSwitchComponent
SettingSwitchComponent,
AgeRatingPipe,
LibrarySelectorComponent,
RoleSelectorComponent
],
templateUrl: './manage-open-idconnect.component.html',
styleUrl: './manage-open-idconnect.component.scss'
@ -31,30 +44,43 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
export class ManageOpenIDConnectComponent implements OnInit {
serverSettings!: ServerSettings;
oidcSettings!: OidcConfig;
oidcSettings = signal<OidcConfig | undefined>(undefined);
settingsForm: FormGroup = new FormGroup({});
ageRatings = signal<AgeRatingDto[]>([]);
selectedLibraries = signal<number[]>([]);
selectedRoles = signal<string[]>([]);
constructor(
private settingsService: SettingsService,
private cdRef: ChangeDetectorRef,
private destroyRef: DestroyRef,
private metadataService: MetadataService,
) {
}
ngOnInit(): void {
this.metadataService.getAllAgeRatings().subscribe(ratings => {
this.ageRatings.set(ratings);
});
this.settingsService.getServerSettings().subscribe({
next: data => {
this.serverSettings = data;
this.oidcSettings = this.serverSettings.oidcConfig;
this.oidcSettings.set(this.serverSettings.oidcConfig);
this.selectedRoles.set(this.serverSettings.oidcConfig.defaultRoles);
this.selectedLibraries.set(this.serverSettings.oidcConfig.defaultLibraries);
this.settingsForm.addControl('authority', new FormControl(this.oidcSettings.authority, [], [this.authorityValidator()]));
this.settingsForm.addControl('clientId', new FormControl(this.oidcSettings.clientId, [this.requiredIf('authority')]));
this.settingsForm.addControl('provisionAccounts', new FormControl(this.oidcSettings.provisionAccounts, []));
this.settingsForm.addControl('requireVerifiedEmail', new FormControl(this.oidcSettings.requireVerifiedEmail, []));
this.settingsForm.addControl('syncUserSettings', new FormControl(this.oidcSettings.syncUserSettings, []));
this.settingsForm.addControl('autoLogin', new FormControl(this.oidcSettings.autoLogin, []));
this.settingsForm.addControl('disablePasswordAuthentication', new FormControl(this.oidcSettings.disablePasswordAuthentication, []));
this.settingsForm.addControl('providerName', new FormControl(this.oidcSettings.providerName, []));
this.settingsForm.addControl('authority', new FormControl(this.serverSettings.oidcConfig.authority, [], [this.authorityValidator()]));
this.settingsForm.addControl('clientId', new FormControl(this.serverSettings.oidcConfig.clientId, [this.requiredIf('authority')]));
this.settingsForm.addControl('provisionAccounts', new FormControl(this.serverSettings.oidcConfig.provisionAccounts, []));
this.settingsForm.addControl('requireVerifiedEmail', new FormControl(this.serverSettings.oidcConfig.requireVerifiedEmail, []));
this.settingsForm.addControl('syncUserSettings', new FormControl(this.serverSettings.oidcConfig.syncUserSettings, []));
this.settingsForm.addControl('autoLogin', new FormControl(this.serverSettings.oidcConfig.autoLogin, []));
this.settingsForm.addControl('disablePasswordAuthentication', new FormControl(this.serverSettings.oidcConfig.disablePasswordAuthentication, []));
this.settingsForm.addControl('providerName', new FormControl(this.serverSettings.oidcConfig.providerName, []));
this.settingsForm.addControl("defaultAgeRating", new FormControl(this.serverSettings.oidcConfig.defaultAgeRating, []));
this.settingsForm.addControl('defaultIncludeUnknowns', new FormControl(this.serverSettings.oidcConfig.defaultIncludeUnknowns, []));
this.cdRef.markForCheck();
this.settingsForm.valueChanges.pipe(
@ -64,7 +90,7 @@ export class ManageOpenIDConnectComponent implements OnInit {
filter(() => {
// Do not auto save when provider settings have changed
const settings: OidcConfig = this.settingsForm.getRawValue();
return settings.authority == this.oidcSettings.authority && settings.clientId == this.oidcSettings.clientId;
return settings.authority == this.oidcSettings()?.authority && settings.clientId == this.oidcSettings()?.clientId;
}),
tap(() => this.save())
).subscribe();
@ -72,17 +98,30 @@ export class ManageOpenIDConnectComponent implements OnInit {
});
}
updateRoles(roles: string[]) {
this.selectedRoles.set(roles);
this.save();
}
updateLibraries(libraries: Library[]) {
this.selectedLibraries.set(libraries.map(l => l.id));
this.save();
}
save() {
if (!this.settingsForm.valid) return;
if (!this.settingsForm.valid || !this.serverSettings || !this.oidcSettings) return;
const data = this.settingsForm.getRawValue();
const newSettings = Object.assign({}, this.serverSettings);
newSettings.oidcConfig = data as OidcConfig;
newSettings.oidcConfig.defaultAgeRating = parseInt(newSettings.oidcConfig.defaultAgeRating as unknown as string, 10) as AgeRating;
newSettings.oidcConfig.defaultRoles = this.selectedRoles();
newSettings.oidcConfig.defaultLibraries = this.selectedLibraries();
this.settingsService.updateServerSettings(newSettings).subscribe({
next: data => {
this.serverSettings = data;
this.oidcSettings = data.oidcConfig;
this.oidcSettings.set(data.oidcConfig);
this.cdRef.markForCheck();
},
error: error => {

View file

@ -3,7 +3,7 @@ import {
ChangeDetectorRef,
Component,
EventEmitter,
inject,
inject, input,
Input,
OnInit,
Output
@ -33,6 +33,7 @@ export class RoleSelectorComponent implements OnInit {
* This must have roles
*/
@Input() member: Member | undefined | User;
preSelectedRoles = input<string[]>([]);
/**
* Allows the selection of Admin role
*/
@ -77,6 +78,13 @@ export class RoleSelectorComponent implements OnInit {
foundRole[0].selected = true;
}
});
} else if (this.preSelectedRoles().length > 0) {
this.preSelectedRoles().forEach((role) => {
const foundRole = this.selectedRoles.filter(item => item.data === role);
if (foundRole.length > 0) {
foundRole[0].selected = true;
}
});
} else {
// For new users, preselect LoginRole
this.selectedRoles.forEach(role => {

View file

@ -38,7 +38,15 @@
"disablePasswordAuthentication": "Disable password authentication",
"disablePasswordAuthentication-tooltip": "Users with the admin role can bypass this restriction",
"providerName": "Provider name",
"providerName-tooltip": "Name show on the login screen"
"providerName-tooltip": "Name show on the login screen",
"defaults": "Defaults",
"defaults-requirement": "The following settings are used when a user is registered via OIDC while SyncUserSettings is turned off",
"defaultIncludeUnknowns": "Include unknowns",
"defaultIncludeUnknowns-tooltip": "Include unknown age ratings",
"defaultAgeRating": "Age rating",
"defaultAgeRating-tooltip": "Maximum age rating shown to new users",
"no-restriction": "{{restriction-selector.no-restriction}}"
}
},