Change settings layout

This commit is contained in:
rubikscraft 2022-12-25 23:19:49 +01:00
parent 3b50cdeb1e
commit 145ff6973f
No known key found for this signature in database
GPG Key ID: 1463EBE9200A5CD4
14 changed files with 276 additions and 118 deletions

View File

@ -58,7 +58,7 @@ export class ImageConverterService {
return Fail(FT.Internal, 'Failed to get conversion limits');
}
let timeLimitMS = ms(timeLimit as any);
if (isNaN(timeLimitMS)) timeLimitMS = 15 * 1000; // 15 seconds
if (isNaN(timeLimitMS) || timeLimitMS === 0) timeLimitMS = 15 * 1000; // 15 seconds
const sharpWrapper = new SharpWrapper(timeLimitMS, memLimit);
const sharpOptions: SharpOptions = {

View File

@ -1,21 +1,8 @@
import {
Body,
Controller,
Logger,
Param,
Post,
Req,
Res,
} from '@nestjs/common';
import { Controller, Logger, Post, Req, Res } from '@nestjs/common';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { Fail, FT, ThrowIfFailed } from 'picsur-shared/dist/types';
import { NoPermissions } from '../../../decorators/permissions.decorator';
import { ReturnsAnything } from '../../../decorators/returns.decorator';
import type { FastifyReply, FastifyRequest } from 'fastify';
import type AuthFastifyRequest from '../../../models/interfaces/authrequest.dto';
import { SysPrefController } from '../pref/sys-pref.controller';
import { SysPreferenceDbService } from '../../../collections/preference-db/sys-preference-db.service';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { Fail, FT, ThrowIfFailed } from 'picsur-shared/dist/types';
import { URLRegex } from 'picsur-shared/dist/util/common-regex';
import { UsageService } from '../../../managers/usage/usage.service';
@Controller('api/usage')
@ -28,6 +15,7 @@ export class UsageController {
@Post(['report', 'report/*'])
@ReturnsAnything()
async deleteRole(
@Req() req: FastifyRequest,
@Res({
passthrough: true,
})
@ -40,11 +28,18 @@ export class UsageController {
}
await res.from(`${trackingUrl}/api`, {
rewriteRequestHeaders(req, headers) {
rewriteRequestHeaders(request, headers) {
const req = request as any as FastifyRequest;
// remove cookies
delete headers.cookie;
// Add real ip, this should not work, but ackee uses a bad ip resolver
// So we might aswell use it
headers['X-Forwarded-For'] = req.ip;
return headers;
},
}
});
}
}

View File

@ -1,60 +1,96 @@
<ng-container *ngIf="pref.type === 'string'">
<div class="row">
<div class="y-center col-md-6 col-12">
<h3 mat-line>{{ name }}</h3>
</div>
<div class="y-center col-md-6 col-12">
<mat-form-field
appearance="outline"
color="primary"
subscriptSizing="dynamic"
<div class="y-center">
<mat-form-field appearance="outline" color="accent">
<mat-label>{{ name }}</mat-label>
<input
matInput
[value]="pref.value"
(change)="stringUpdateWrapper($event)"
autocorrect="off"
autocapitalize="none"
placeholder="Empty"
/>
<!-- show tooltip on press -->
<button
mat-icon-button
matSuffix
(click)="tooltip.show()"
*ngIf="helpText !== ''"
>
<input
matInput
[value]="pref.value"
(change)="stringUpdateWrapper($event)"
autocorrect="off"
autocapitalize="none"
/>
</mat-form-field>
</div>
<mat-icon
matSuffix
fontSet="material-icons-outlined"
#tooltip="matTooltip"
[matTooltip]="helpText"
matTooltipPosition="left"
matTooltipHideDelay="0"
>
help_outline
</mat-icon>
</button>
</mat-form-field>
</div>
</ng-container>
<ng-container *ngIf="pref.type === 'number'">
<div class="row">
<div class="y-center col-md-6 col-12">
<h3 mat-line>{{ name }}</h3>
</div>
<div class="y-center col-md-6 col-12">
<mat-form-field
appearance="outline"
color="primary"
subscriptSizing="dynamic"
<div class="y-center">
<mat-form-field appearance="outline" color="accent">
<mat-label>{{ name }}</mat-label>
<input
matInput
type="number"
[value]="pref.value"
(change)="numberUpdateWrapper($event)"
placeholder="Empty"
/>
<button
mat-icon-button
matSuffix
(click)="tooltip.show()"
*ngIf="helpText !== ''"
>
<input
matInput
type="number"
[value]="pref.value"
(change)="numberUpdateWrapper($event)"
/>
</mat-form-field>
</div>
<mat-icon
matSuffix
fontSet="material-icons-outlined"
#tooltip="matTooltip"
[matTooltip]="helpText"
matTooltipPosition="left"
matTooltipHideDelay="0"
>
help_outline
</mat-icon>
</button>
</mat-form-field>
</div>
</ng-container>
<ng-container *ngIf="pref.type === 'boolean'">
<div class="row">
<div class="y-center col-md-6 col-12">
<h3 mat-line>{{ name }}</h3>
</div>
<div class="y-center col-md-6 col-12">
<mat-slide-toggle
color="primary"
class="col-md-6 col-12"
[checked]="valBool"
(change)="update($event.checked)"
></mat-slide-toggle>
</div>
<div class="y-center">
<mat-form-field appearance="outline" color="accent">
<mat-label>{{ name }}</mat-label>
<mat-select
[value]="pref.value"
(valueChange)="booleanUpdateWrapper($event)"
>
<mat-option [value]="false">No</mat-option>
<mat-option [value]="true">Yes</mat-option>
</mat-select>
<button
mat-icon-button
matSuffix
(click)="tooltip.show()"
*ngIf="helpText !== ''"
>
<mat-icon
fontSet="material-icons-outlined"
#tooltip="matTooltip"
[matTooltip]="helpText"
matTooltipPosition="left"
matTooltipHideDelay="0"
>
help_outline
</mat-icon>
</button>
</mat-form-field>
</div>
</ng-container>

View File

@ -2,7 +2,7 @@ import { Component, Input, OnInit } from '@angular/core';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import {
DecodedPref,
PrefValueType,
PrefValueType
} from 'picsur-shared/dist/dto/preferences.dto';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { Subject } from 'rxjs';
@ -24,9 +24,9 @@ export class PrefOptionComponent implements OnInit {
key: string,
pref: PrefValueType,
) => AsyncFailable<any>;
@Input() @Required translator: {
[key in string]: string;
};
@Input() @Required name: string = '';
@Input() helpText: string = '';
private updateSubject = new Subject<PrefValueType>();
@ -36,10 +36,6 @@ export class PrefOptionComponent implements OnInit {
this.subscribeUpdate();
}
get name(): string {
return this.translator[this.pref.key] ?? this.pref.key;
}
get valString(): string {
if (this.pref.type !== 'string') {
throw new Error('Not a string preference');
@ -76,6 +72,10 @@ export class PrefOptionComponent implements OnInit {
this.update(value);
}
booleanUpdateWrapper(e: boolean) {
this.update(e);
}
private async updatePreference(value: PrefValueType) {
const result = await this.updateFunction(this.pref.key, value);
if (!HasFailed(result)) {

View File

@ -1,9 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MatDividerModule } from '@angular/material/divider';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module';
import { PrefOptionComponent } from './pref-option.component';
@ -12,10 +14,12 @@ import { PrefOptionComponent } from './pref-option.component';
CommonModule,
ErrorManagerModule,
MatIconModule,
MatTooltipModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatDividerModule,
MatSlideToggleModule,
MatSelectModule,
],
declarations: [PrefOptionComponent],
exports: [PrefOptionComponent],

View File

@ -1,22 +1,83 @@
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
export const SysPreferenceFriendlyNames: {
[key in SysPreference]: string;
export const SysPreferenceUI: {
[key in SysPreference]: {
name: string;
helpText: string;
category: string;
};
} = {
[SysPreference.JwtSecret]: 'JWT Secret',
[SysPreference.JwtExpiresIn]: 'JWT Expiry Time',
[SysPreference.BCryptStrength]: 'BCrypt Strength',
[SysPreference.JwtSecret]: {
name: 'JWT Secret',
helpText: 'Secret used to sign JWT authentication tokens.',
category: 'Authentication',
},
[SysPreference.JwtExpiresIn]: {
name: 'JWT Expiry Time',
helpText: 'Time before JWT authentication tokens expire.',
category: 'Authentication',
},
[SysPreference.BCryptStrength]: {
name: 'BCrypt Strength',
helpText:
'Strength of BCrypt hashing algorithm, 10 is recommended. Reduce this if running on a low powered device.',
category: 'Authentication',
},
[SysPreference.RemoveDerivativesAfter]: 'Cached Images Expiry Time',
[SysPreference.SaveDerivatives]: 'Cache Trancoded Images',
[SysPreference.AllowEditing]: 'Allow images to be edited (e.g. resize)',
[SysPreference.RemoveDerivativesAfter]: {
name: 'Cached Images Expiry Time',
helpText:
'Time before cached images are deleted. This does not affect the original image. Set to 0 to disable.',
category: 'Image Processing',
},
[SysPreference.SaveDerivatives]: {
name: 'Cache Converted Images',
helpText:
'Cache converted images, this will reduce the time it takes to load images. It does however use more disk space.',
category: 'Image Processing',
},
[SysPreference.AllowEditing]: {
name: 'Allow images to be edited',
helpText:
'Allow images to be edited (e.g. resize, flip). Using these features will use more CPU power.',
[SysPreference.ConversionTimeLimit]: 'Transcode/Edit Time Limit',
[SysPreference.ConversionMemoryLimit]: 'Transcode/Edit Memory Limit MB',
category: 'Image Processing',
},
[SysPreference.EnableTracking]: 'Enable Ackee Web Tracking',
[SysPreference.TrackingUrl]: 'Ackee tracking URL',
[SysPreference.TrackingId]: 'Ackee trackign website ID',
[SysPreference.ConversionTimeLimit]: {
name: 'Convert/Edit Time Limit',
helpText:
'Time limit for converting/editing images. You may need to increase this on low powered devices.',
category: 'Image Processing',
},
[SysPreference.ConversionMemoryLimit]: {
name: 'Convert/Edit Memory Limit MB',
helpText:
'Memory limit for converting/editing images. You only need to increase this if you are storing massive images.',
category: 'Image Processing',
},
[SysPreference.EnableTelemetry]: 'Enable System Telemetry',
[SysPreference.EnableTracking]: {
name: 'Enable Ackee Web Tracking',
helpText:
'Enable tracking of the website usage using Ackee. You will need to set the tracking URL and ID.',
category: 'Usage',
},
[SysPreference.TrackingUrl]: {
name: 'Ackee tracking URL',
helpText: 'URL of the Ackee tracking server.',
category: 'Usage',
},
[SysPreference.TrackingId]: {
name: 'Ackee trackign website ID',
helpText: 'ID of the website to track.',
category: 'Usage',
},
[SysPreference.EnableTelemetry]: {
name: 'Enable System Telemetry',
helpText:
'Enable system telemetry, this will send anonymous usage data to the developers.',
category: 'Usage',
},
};

View File

@ -5,3 +5,10 @@ export const UsrPreferenceFriendlyNames: {
} = {
[UsrPreference.KeepOriginal]: 'Keep original file',
};
export const UsrPreferenceHelpText: {
[key in UsrPreference]: string;
} = {
[UsrPreference.KeepOriginal]:
'Store the original files you upload to the service, this way no data will be lost. This will also store exif data.',
};

View File

@ -1,9 +1,13 @@
<h1>Settings</h1>
<ng-container *ngFor="let pref of preferences | async">
<pref-option
[pref]="pref"
[update]="usrPrefService.setPreference.bind(usrPrefService)"
[translator]="translator"
></pref-option>
</ng-container>
<div class="row">
<ng-container *ngFor="let pref of preferences | async">
<pref-option
class="col-md-6 col-12"
[pref]="pref"
[update]="usrPrefService.setPreference.bind(usrPrefService)"
[name]="getName(pref.key)"
[helpText]="getHelpText(pref.key)"
></pref-option>
</ng-container>
</div>

View File

@ -1,14 +1,26 @@
import { Component } from '@angular/core';
import { DecodedPref } from 'picsur-shared/dist/dto/preferences.dto';
import { Observable } from 'rxjs';
import { UsrPreferenceFriendlyNames } from 'src/app/i18n/usr-pref.i18n';
import {
UsrPreferenceFriendlyNames,
UsrPreferenceHelpText
} from 'src/app/i18n/usr-pref.i18n';
import { UsrPrefService } from 'src/app/services/api/usr-pref.service';
@Component({
templateUrl: './settings-general.component.html',
})
export class SettingsGeneralComponent {
public translator = UsrPreferenceFriendlyNames;
private readonly translator = UsrPreferenceFriendlyNames;
private readonly helpTranslator = UsrPreferenceHelpText;
public getName(key: string) {
return (this.translator as any)[key] ?? key;
}
public getHelpText(key: string) {
return (this.helpTranslator as any)[key] ?? '';
}
preferences: Observable<DecodedPref[]>;

View File

@ -1,9 +1,16 @@
<h1>System Settings</h1>
<ng-container *ngFor="let pref of preferences | async">
<pref-option
[pref]="pref"
[update]="sysPrefService.setPreference.bind(sysPrefService)"
[translator]="translator"
></pref-option>
<ng-container *ngFor="let category of preferences | async">
<h2 *ngIf="category.category !== null">{{ category.category }}</h2>
<div class="row">
<ng-container *ngFor="let pref of category.prefs">
<pref-option
class="col-md-6 col-12"
[pref]="pref"
[update]="sysPrefService.setPreference.bind(sysPrefService)"
[name]="getName(pref.key)"
[helpText]="getHelpText(pref.key)"
></pref-option>
</ng-container>
</div>
</ng-container>

View File

@ -1,18 +1,42 @@
import { Component } from '@angular/core';
import { DecodedPref } from 'picsur-shared/dist/dto/preferences.dto';
import { Observable } from 'rxjs';
import { SysPreferenceFriendlyNames } from 'src/app/i18n/sys-pref.i18n';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { map, Observable } from 'rxjs';
import { SysPreferenceUI } from 'src/app/i18n/sys-pref.i18n';
import { makeUnique } from 'picsur-shared/dist/util/unique';
import { SysPrefService } from 'src/app/services/api/sys-pref.service';
@Component({
templateUrl: './settings-sys-pref.component.html',
styleUrls: ['./settings-sys-pref.component.scss'],
})
export class SettingsSysprefComponent {
public readonly translator = SysPreferenceFriendlyNames;
private readonly syspreferenceUI = SysPreferenceUI;
preferences: Observable<DecodedPref[]>;
public getName(key: string) {
return this.syspreferenceUI[key as SysPreference]?.name ?? key;
}
public getHelpText(key: string) {
return this.syspreferenceUI[key as SysPreference]?.helpText ?? '';
}
public getCategory(key: string): null | string {
return this.syspreferenceUI[key as SysPreference]?.category ?? null;
}
preferences: Observable<Array<{ category: string | null; prefs: DecodedPref[] }>>;
constructor(public readonly sysPrefService: SysPrefService) {
this.preferences = sysPrefService.live;
this.preferences = sysPrefService.live.pipe(
map((prefs) => {
const categories = makeUnique(prefs.map((pref) => this.getCategory(pref.key)));
return categories.map((category) => ({
category,
prefs: prefs.filter((pref) => this.getCategory(pref.key) === category),
}));
}),
)
}
}

View File

@ -6,6 +6,10 @@ import { SettingsSysprefRoutingModule } from './settings-sys-pref.routing.module
@NgModule({
declarations: [SettingsSysprefComponent],
imports: [CommonModule, SettingsSysprefRoutingModule, PrefOptionModule],
imports: [
CommonModule,
SettingsSysprefRoutingModule,
PrefOptionModule,
],
})
export default class SettingsSysprefRouteModule {}

View File

@ -1,10 +1,10 @@
import { Inject, Injectable } from '@angular/core';
import { LOCATION, NAVIGATOR, WINDOW } from '@ng-web-apis/common';
import { Logger } from '../logger/logger.service';
import { InfoService } from '../api/info.service';
import { NAVIGATOR } from '@ng-web-apis/common';
import type { AckeeInstance, AckeeTrackingReturn } from 'ackee-tracker';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { TrackingState } from 'picsur-shared/dist/dto/tracking-state.enum';
import { InfoService } from '../api/info.service';
import { Logger } from '../logger/logger.service';
@Injectable({
providedIn: 'root',
@ -24,6 +24,11 @@ export class UsageService {
this.doNotTrack =
this.navigator.doNotTrack === '1' || this.navigator.doNotTrack === 'yes';
if (this.doNotTrack) {
this.logger.verbose('Usage reporting disabled by DNT');
return;
}
this.subscribeInfo();
}
@ -37,8 +42,7 @@ export class UsageService {
this.stop();
} else {
this.setup(
info.tracking.state === TrackingState.Detailed &&
this.doNotTrack === false,
info.tracking.state === TrackingState.Detailed,
info.tracking.id,
);
}