diff --git a/backend/src/managers/image/image-converter.service.ts b/backend/src/managers/image/image-converter.service.ts index 9372856..1475d58 100644 --- a/backend/src/managers/image/image-converter.service.ts +++ b/backend/src/managers/image/image-converter.service.ts @@ -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 = { diff --git a/backend/src/routes/api/usage/usage.controller.ts b/backend/src/routes/api/usage/usage.controller.ts index d819e2f..45bc9e6 100644 --- a/backend/src/routes/api/usage/usage.controller.ts +++ b/backend/src/routes/api/usage/usage.controller.ts @@ -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; - }, + } }); } } diff --git a/frontend/src/app/components/pref-option/pref-option.component.html b/frontend/src/app/components/pref-option/pref-option.component.html index 4d13079..5b6611a 100644 --- a/frontend/src/app/components/pref-option/pref-option.component.html +++ b/frontend/src/app/components/pref-option/pref-option.component.html @@ -1,51 +1,96 @@ -
-
-

{{ name }}

-
-
- - - -
+
+ + {{ name }} + + + +
-
-
-

{{ name }}

-
-
- - - -
+
+ + {{ name }} + + +
-
-
-

{{ name }}

-
-
- -
+
+ + {{ name }} + + No + Yes + + +
diff --git a/frontend/src/app/components/pref-option/pref-option.component.ts b/frontend/src/app/components/pref-option/pref-option.component.ts index 22dfcdd..6190576 100644 --- a/frontend/src/app/components/pref-option/pref-option.component.ts +++ b/frontend/src/app/components/pref-option/pref-option.component.ts @@ -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; - @Input() @Required translator: { - [key in string]: string; - }; + @Input() @Required name: string = ''; + + @Input() helpText: string = ''; private updateSubject = new Subject(); @@ -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)) { diff --git a/frontend/src/app/components/pref-option/pref-option.module.ts b/frontend/src/app/components/pref-option/pref-option.module.ts index 27535c6..7953791 100644 --- a/frontend/src/app/components/pref-option/pref-option.module.ts +++ b/frontend/src/app/components/pref-option/pref-option.module.ts @@ -1,8 +1,11 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +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'; @@ -11,9 +14,12 @@ import { PrefOptionComponent } from './pref-option.component'; CommonModule, ErrorManagerModule, + MatIconModule, + MatTooltipModule, + MatButtonModule, MatFormFieldModule, MatInputModule, - MatSlideToggleModule, + MatSelectModule, ], declarations: [PrefOptionComponent], exports: [PrefOptionComponent], diff --git a/frontend/src/app/i18n/sys-pref.i18n.ts b/frontend/src/app/i18n/sys-pref.i18n.ts index ff3870a..f5e28c6 100644 --- a/frontend/src/app/i18n/sys-pref.i18n.ts +++ b/frontend/src/app/i18n/sys-pref.i18n.ts @@ -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', + }, }; diff --git a/frontend/src/app/i18n/usr-pref.i18n.ts b/frontend/src/app/i18n/usr-pref.i18n.ts index d2e97d7..cd0ce15 100644 --- a/frontend/src/app/i18n/usr-pref.i18n.ts +++ b/frontend/src/app/i18n/usr-pref.i18n.ts @@ -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.', +}; diff --git a/frontend/src/app/routes/settings/general/settings-general.component.html b/frontend/src/app/routes/settings/general/settings-general.component.html index d9ad3b5..39a3f04 100644 --- a/frontend/src/app/routes/settings/general/settings-general.component.html +++ b/frontend/src/app/routes/settings/general/settings-general.component.html @@ -1,9 +1,13 @@

Settings

- - - +
+ + + +
diff --git a/frontend/src/app/routes/settings/general/settings-general.component.ts b/frontend/src/app/routes/settings/general/settings-general.component.ts index 7fb7159..5b400b4 100644 --- a/frontend/src/app/routes/settings/general/settings-general.component.ts +++ b/frontend/src/app/routes/settings/general/settings-general.component.ts @@ -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; diff --git a/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.component.html b/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.component.html index ce4bf15..414808e 100644 --- a/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.component.html +++ b/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.component.html @@ -1,9 +1,16 @@

System Settings

- - + +

{{ category.category }}

+
+ + + +
diff --git a/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.component.scss b/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.component.ts b/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.component.ts index d2e336e..a22f745 100644 --- a/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.component.ts +++ b/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.component.ts @@ -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; + 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>; 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), + })); + }), + ) } } diff --git a/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.module.ts b/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.module.ts index 0008e83..835570d 100644 --- a/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.module.ts +++ b/frontend/src/app/routes/settings/sys-pref/settings-sys-pref.module.ts @@ -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 {} diff --git a/frontend/src/app/services/usage/usage.service.ts b/frontend/src/app/services/usage/usage.service.ts index dd47259..525c7cc 100644 --- a/frontend/src/app/services/usage/usage.service.ts +++ b/frontend/src/app/services/usage/usage.service.ts @@ -1,11 +1,10 @@ -import { DOCUMENT } from '@angular/common'; 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', @@ -25,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(); } @@ -38,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, ); }