diff --git a/frontend/Exence/src/app/data-model/modules/admin/SystemSettingsPatchRequest.ts b/frontend/Exence/src/app/data-model/modules/admin/SystemSettingsPatchRequest.ts new file mode 100644 index 00000000..315411ff --- /dev/null +++ b/frontend/Exence/src/app/data-model/modules/admin/SystemSettingsPatchRequest.ts @@ -0,0 +1,8 @@ +export interface SystemSettingsPatchRequest { + domainWhitelistOnly?: boolean; + verificationRequiredPaths?: string[]; + rateLimitingEnabled?: boolean; + rateLimitingCooldownMinutes?: number; + logoutFromAllDevices?: boolean; + passwordHistoryCount?: number; +} diff --git a/frontend/Exence/src/app/data-model/modules/admin/SystemSettingsResponse.ts b/frontend/Exence/src/app/data-model/modules/admin/SystemSettingsResponse.ts new file mode 100644 index 00000000..0c75b21f --- /dev/null +++ b/frontend/Exence/src/app/data-model/modules/admin/SystemSettingsResponse.ts @@ -0,0 +1,8 @@ +export interface SystemSettingsResponse { + domainWhitelistOnly: boolean; + verificationRequiredPaths: string[]; + rateLimitingEnabled: boolean; + rateLimitingCooldownMinutes: number; + logoutFromAllDevices: boolean; + passwordHistoryCount: number; +} diff --git a/frontend/Exence/src/app/private/admin/admin-settings.service.ts b/frontend/Exence/src/app/private/admin/admin-settings.service.ts new file mode 100644 index 00000000..ece187f0 --- /dev/null +++ b/frontend/Exence/src/app/private/admin/admin-settings.service.ts @@ -0,0 +1,20 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpService } from '../../shared/http/http.service'; +import { SystemSettingsResponse } from '../../data-model/modules/admin/SystemSettingsResponse'; +import { SystemSettingsPatchRequest } from '../../data-model/modules/admin/SystemSettingsPatchRequest'; +import { lastValueFrom } from 'rxjs'; + +@Injectable() +export class AdminSettingsService { + private readonly http = inject(HttpService); + + private baseUrl = '/api/admin/settings'; + + public getSettings(): Promise { + return lastValueFrom(this.http.get(this.baseUrl)); + } + + public updateSettings(request: SystemSettingsPatchRequest): Promise { + return lastValueFrom(this.http.patch(this.baseUrl, request)); + } +} diff --git a/frontend/Exence/src/app/private/admin/admin.component.html b/frontend/Exence/src/app/private/admin/admin.component.html index 913ea812..81329628 100644 --- a/frontend/Exence/src/app/private/admin/admin.component.html +++ b/frontend/Exence/src/app/private/admin/admin.component.html @@ -69,6 +69,10 @@

+ + + + diff --git a/frontend/Exence/src/app/private/admin/admin.component.ts b/frontend/Exence/src/app/private/admin/admin.component.ts index 196ca7d4..45ded648 100644 --- a/frontend/Exence/src/app/private/admin/admin.component.ts +++ b/frontend/Exence/src/app/private/admin/admin.component.ts @@ -8,6 +8,7 @@ import { TranslatePipe } from '../../shared/pipes/translate.pipe'; import { AdminRegistrationComponent } from './admin-registration/admin-registration.component'; import { AdminStatisticsListComponent } from './admin-statistics-list/admin-statistics-list.component'; import { EmailBroadcastComponent } from './email-broadcast/email-broadcast.component'; +import { SystemSettingsComponent } from './system-settings/system-settings.component'; @Component({ selector: 'ex-admin', @@ -21,6 +22,7 @@ import { EmailBroadcastComponent } from './email-broadcast/email-broadcast.compo AdminStatisticsListComponent, AdminRegistrationComponent, EmailBroadcastComponent, + SystemSettingsComponent, TranslatePipe, ], }) diff --git a/frontend/Exence/src/app/private/admin/system-settings/add-path-dialog/add-path-dialog.component.html b/frontend/Exence/src/app/private/admin/system-settings/add-path-dialog/add-path-dialog.component.html new file mode 100644 index 00000000..276ec618 --- /dev/null +++ b/frontend/Exence/src/app/private/admin/system-settings/add-path-dialog/add-path-dialog.component.html @@ -0,0 +1,22 @@ + +
{{ 'admin.configurations.systemSettings.verificationRequiredPaths' | translate }}
+ +
+ + {{ 'admin.configurations.systemSettings.addPath' | translate }} + + +
+ +
+ + {{ 'literals.cancel' | translate }} + + + {{ 'literals.add' | translate }} + +
+
diff --git a/frontend/Exence/src/app/private/admin/system-settings/add-path-dialog/add-path-dialog.component.ts b/frontend/Exence/src/app/private/admin/system-settings/add-path-dialog/add-path-dialog.component.ts new file mode 100644 index 00000000..d598f2d3 --- /dev/null +++ b/frontend/Exence/src/app/private/admin/system-settings/add-path-dialog/add-path-dialog.component.ts @@ -0,0 +1,31 @@ +import { Component, inject } from '@angular/core'; +import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInput } from '@angular/material/input'; +import { ButtonComponent } from '../../../../shared/button/button.component'; +import { DialogCardComponent } from '../../../../shared/dialog-card/dialog-card.component'; +import { DialogComponent, DialogRef } from '../../../../shared/dialog/dialog.service'; +import { TranslatePipe } from '../../../../shared/pipes/translate.pipe'; + +@Component({ + selector: 'ex-add-path-dialog', + templateUrl: './add-path-dialog.component.html', + imports: [ReactiveFormsModule, MatFormFieldModule, MatInput, ButtonComponent, DialogCardComponent, TranslatePipe], +}) +export class AddPathDialogComponent extends DialogComponent { + private readonly fb = inject(NonNullableFormBuilder); + + readonly form = this.fb.group({ + path: this.fb.control('', [Validators.required]), + }); + + constructor() { + super(inject(DialogRef)); + } + + add(): void { + const trimmed = this.form.controls.path.value.trim(); + if (!trimmed) return; + this.dialogRef.submit(trimmed); + } +} diff --git a/frontend/Exence/src/app/private/admin/system-settings/system-settings.component.html b/frontend/Exence/src/app/private/admin/system-settings/system-settings.component.html new file mode 100644 index 00000000..c788ef41 --- /dev/null +++ b/frontend/Exence/src/app/private/admin/system-settings/system-settings.component.html @@ -0,0 +1,122 @@ +

{{ 'admin.configurations.systemSettings.title' | translate }}

+ +@if (settingsResource.isLoading()) { +
+
+ @for (_ of [1, 2, 3]; track $index) { +
+
+ + + +
+
+ + +
+
+ } +
+ +
+ @for (_ of [1, 2]; track $index) { + + } +
+ +
+ +
+ @for (_ of [1, 2, 3]; track $index) { + + } +
+
+ +
+ +
+
+} @else { +
+
+ + + + + +
+ +
+ + + +
+ +
+ {{ + 'admin.configurations.systemSettings.verificationRequiredPaths' | translate + }} + + + add + {{ 'literals.add' | translate }} + + @for (path of verificationPaths(); track path) { + + {{ path }} + + + } + +
+ +
+ + {{ 'literals.cancel' | translate }} + + + {{ 'literals.save' | translate }} + +
+
+} diff --git a/frontend/Exence/src/app/private/admin/system-settings/system-settings.component.scss b/frontend/Exence/src/app/private/admin/system-settings/system-settings.component.scss new file mode 100644 index 00000000..a66b59e6 --- /dev/null +++ b/frontend/Exence/src/app/private/admin/system-settings/system-settings.component.scss @@ -0,0 +1,6 @@ +.skeleton-toggle-card { + border-radius: 10px; + border: 1px solid var(--primary-color); + background-color: var(--app-card-color); + box-shadow: 0 0 7px var(--shadow-color); +} diff --git a/frontend/Exence/src/app/private/admin/system-settings/system-settings.component.ts b/frontend/Exence/src/app/private/admin/system-settings/system-settings.component.ts new file mode 100644 index 00000000..a7ef07a7 --- /dev/null +++ b/frontend/Exence/src/app/private/admin/system-settings/system-settings.component.ts @@ -0,0 +1,110 @@ +import { Component, inject, resource, signal } from '@angular/core'; +import { toRawValueSignal } from '../../../shared/util/utils'; +import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { TranslocoService } from '@jsverse/transloco'; +import { SystemSettingsPatchRequest } from '../../../data-model/modules/admin/SystemSettingsPatchRequest'; +import { AmountStepperComponent } from '../../../shared/amount-stepper/amount-stepper.component'; +import { AnimatedSkeletonLoaderComponent } from '../../../shared/animated-skeleton-loader/animated-skeleton-loader.component'; +import { ToggleCardComponent } from '../../../shared/toggle-card/toggle-card.component'; +import { ButtonComponent } from '../../../shared/button/button.component'; +import { DialogService } from '../../../shared/dialog/dialog.service'; +import { TranslatePipe } from '../../../shared/pipes/translate.pipe'; +import { SnackbarService } from '../../../shared/snackbar/snackbar.service'; +import { AdminSettingsService } from '../admin-settings.service'; +import { AddPathDialogComponent } from './add-path-dialog/add-path-dialog.component'; + +@Component({ + selector: 'ex-system-settings', + templateUrl: './system-settings.component.html', + styleUrl: './system-settings.component.scss', + imports: [ + ReactiveFormsModule, + MatFormFieldModule, + MatChipsModule, + MatIconModule, + MatTooltipModule, + AmountStepperComponent, + AnimatedSkeletonLoaderComponent, + ToggleCardComponent, + ButtonComponent, + TranslatePipe, + ], + providers: [AdminSettingsService], +}) +export class SystemSettingsComponent { + private readonly fb = inject(NonNullableFormBuilder); + private readonly adminSettingsService = inject(AdminSettingsService); + private readonly snackbarService = inject(SnackbarService); + private readonly translocoService = inject(TranslocoService); + private readonly dialogService = inject(DialogService); + + readonly saving = signal(false); + readonly verificationPaths = signal([]); + + readonly form = this.fb.group({ + domainWhitelistOnly: this.fb.control(false), + rateLimitingEnabled: this.fb.control(false), + rateLimitingCooldownMinutes: this.fb.control(1, [Validators.required, Validators.min(1)]), + logoutFromAllDevices: this.fb.control(false), + passwordHistoryCount: this.fb.control(0, [Validators.required, Validators.min(0)]), + }); + readonly formValue = toRawValueSignal(this.form); + + readonly settingsResource = resource({ + loader: async () => { + const settings = await this.adminSettingsService.getSettings(); + this.verificationPaths.set(settings.verificationRequiredPaths); + this.form.patchValue({ + domainWhitelistOnly: settings.domainWhitelistOnly, + rateLimitingEnabled: settings.rateLimitingEnabled, + rateLimitingCooldownMinutes: settings.rateLimitingCooldownMinutes, + logoutFromAllDevices: settings.logoutFromAllDevices, + passwordHistoryCount: settings.passwordHistoryCount, + }); + return settings; + }, + }); + + async openAddPathDialog(): Promise { + const path = await this.dialogService.openNonModal(AddPathDialogComponent, undefined); + if (path && !this.verificationPaths().includes(path)) { + this.verificationPaths.update(paths => [...paths, path]); + } + } + + removePath(path: string): void { + this.verificationPaths.update(paths => paths.filter(p => p !== path)); + } + + cancel(): void { + this.settingsResource.reload(); + } + + async save(): Promise { + if (this.saving() || this.form.invalid) return; + this.saving.set(true); + + try { + const formValue = this.form.getRawValue(); + const request: SystemSettingsPatchRequest = { + domainWhitelistOnly: formValue.domainWhitelistOnly, + verificationRequiredPaths: this.verificationPaths(), + rateLimitingEnabled: formValue.rateLimitingEnabled, + rateLimitingCooldownMinutes: formValue.rateLimitingCooldownMinutes, + logoutFromAllDevices: formValue.logoutFromAllDevices, + passwordHistoryCount: formValue.passwordHistoryCount, + }; + const updated = await this.adminSettingsService.updateSettings(request); + this.verificationPaths.set(updated.verificationRequiredPaths); + this.snackbarService.showSuccess( + this.translocoService.translate('admin.configurations.systemSettings.success'), + ); + } finally { + this.saving.set(false); + } + } +} diff --git a/frontend/Exence/src/app/shared/toggle-card/toggle-card.component.html b/frontend/Exence/src/app/shared/toggle-card/toggle-card.component.html new file mode 100644 index 00000000..879df958 --- /dev/null +++ b/frontend/Exence/src/app/shared/toggle-card/toggle-card.component.html @@ -0,0 +1,14 @@ +
+ @if (matIcon()) { + {{ matIcon() }} + } + @if (svgIcon()) { + + } + {{ label() | translate }} + +
+
+ {{ description() | translate }} + +
diff --git a/frontend/Exence/src/app/shared/toggle-card/toggle-card.component.scss b/frontend/Exence/src/app/shared/toggle-card/toggle-card.component.scss new file mode 100644 index 00000000..9cc07099 --- /dev/null +++ b/frontend/Exence/src/app/shared/toggle-card/toggle-card.component.scss @@ -0,0 +1,18 @@ +:host { + display: block; + padding: 1rem; + border-radius: 10px; + border: 1px solid var(--primary-color); + background-color: var(--app-card-color); + box-shadow: 0 0 7px var(--shadow-color); + transition: box-shadow 0.3s ease; + + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 1rem; + + &.active { + box-shadow: 0 0 14px 2px var(--primary-color); + } +} diff --git a/frontend/Exence/src/app/shared/toggle-card/toggle-card.component.ts b/frontend/Exence/src/app/shared/toggle-card/toggle-card.component.ts new file mode 100644 index 00000000..8dc12cc1 --- /dev/null +++ b/frontend/Exence/src/app/shared/toggle-card/toggle-card.component.ts @@ -0,0 +1,37 @@ +import { Component, effect, input, model } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { MatIconModule } from '@angular/material/icon'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { BaseComponent } from '../base-component/base.component'; +import { TranslationCode } from '../i18n/translation-types'; +import { InfoButtonComponent } from '../info-button/info-button.component'; +import { TranslatePipe } from '../pipes/translate.pipe'; +import { SvgIcons } from '../svg-icons/svg-icons'; + +@Component({ + selector: 'ex-toggle-card', + templateUrl: './toggle-card.component.html', + styleUrl: './toggle-card.component.scss', + imports: [ReactiveFormsModule, MatIconModule, MatSlideToggleModule, InfoButtonComponent, TranslatePipe], + host: { + '[class.active]': 'value()', + }, +}) +export class ToggleCardComponent extends BaseComponent { + matIcon = input(); + svgIcon = input(); + label = input.required(); + description = input.required(); + tooltip = input.required(); + value = model(false); + + control = new FormControl(false, { nonNullable: true }); + + constructor() { + super(); + + effect(() => this.control.setValue(this.value(), { emitEvent: false })); + + this.addSubscription(this.control.valueChanges.subscribe(v => this.value.set(v))); + } +} diff --git a/frontend/Exence/src/assets/i18n/de.json b/frontend/Exence/src/assets/i18n/de.json index 045f5df4..901876ff 100644 --- a/frontend/Exence/src/assets/i18n/de.json +++ b/frontend/Exence/src/assets/i18n/de.json @@ -26,7 +26,8 @@ "to": "An", "yes": "Ja", "no": "Nein", - "duplicate": "Duplicate" + "duplicate": "Duplicate", + "add": "Hinzufügen" }, "dashboard": { "title": "Willkommen zurück, {{username}}!", @@ -895,6 +896,24 @@ "confirmMessage": "Sind Sie sicher, dass Sie diese E-Mail an alle Benutzer senden möchten?", "success": "E-Mail an alle Benutzer gesendet!", "sendDisabled": "Sie können die E-Mail nicht versenden, ohne zuvor den Betreff und den Inhalt festzulegen." + }, + "systemSettings": { + "title": "Systemeinstellungen", + "domainWhitelistOnly": "Nur Whitelist-Domains", + "domainWhitelistOnlyDescription": "Registrierungen auf zugelassene E-Mail-Domains beschränken", + "domainWhitelistOnlyTooltip": "Wenn aktiviert, können sich nur Benutzer mit E-Mail-Adressen aus der konfigurierten Domain-Whitelist registrieren. Alle anderen Registrierungsversuche werden abgelehnt.", + "rateLimitingEnabled": "Rate-Limiting aktiviert", + "rateLimitingEnabledDescription": "Wiederholte Anfragen begrenzen, um Brute-Force-Angriffe zu verhindern", + "rateLimitingEnabledTooltip": "Wenn aktiviert, werden Benutzer, die das Anfragelimit überschreiten, vorübergehend für die konfigurierte Abkühlzeit gesperrt, um Missbrauch zu verhindern.", + "logoutFromAllDevices": "Abmelden bei Passwortänderung", + "logoutFromAllDevicesDescription": "Alle Sitzungen bei Passwortänderung abmelden", + "logoutFromAllDevicesTooltip": "Wenn aktiviert, werden bei einer Passwortänderung automatisch alle aktiven Sitzungen auf allen Geräten ungültig gemacht und erneutes Anmelden erforderlich.", + "rateLimitingCooldownMinutes": "Rate-Limiting Abklingzeit (Minuten)", + "rateLimitingCooldownDisabled": "Rate-Limiting aktivieren, um die Abklingzeit zu konfigurieren", + "passwordHistoryCount": "Passwort-Verlauf Anzahl", + "verificationRequiredPaths": "Verifizierungspflichtige Pfade", + "addPath": "Pfad hinzufügen (z.B. /api/goals)", + "success": "Systemeinstellungen erfolgreich aktualisiert!" } } }, diff --git a/frontend/Exence/src/assets/i18n/en.json b/frontend/Exence/src/assets/i18n/en.json index b3628ec7..150295e3 100644 --- a/frontend/Exence/src/assets/i18n/en.json +++ b/frontend/Exence/src/assets/i18n/en.json @@ -26,7 +26,8 @@ "to": "To", "yes": "Yes", "no": "No", - "duplicate": "Duplicate" + "duplicate": "Duplicate", + "add": "Add" }, "dashboard": { "title": "Welcome back, {{username}}!", @@ -895,6 +896,24 @@ "confirmMessage": "Are you sure you want to send this email to every user?", "success": "Email sent to every user!", "sendDisabled": "You cannot send the email without setting the subject and content before." + }, + "systemSettings": { + "title": "System Settings", + "domainWhitelistOnly": "Domain whitelist only", + "domainWhitelistOnlyDescription": "Restrict registrations to whitelisted email domains only", + "domainWhitelistOnlyTooltip": "When enabled, only users with email addresses from the configured domain whitelist can register. All other registration attempts will be rejected.", + "rateLimitingEnabled": "Rate limiting enabled", + "rateLimitingEnabledDescription": "Limit repeated requests to prevent brute-force attacks", + "rateLimitingEnabledTooltip": "When enabled, users who exceed the request limit will be temporarily blocked for the configured cooldown period to prevent abuse and brute-force attacks.", + "logoutFromAllDevices": "Logout on password change", + "logoutFromAllDevicesDescription": "Sign out all sessions when password is changed", + "logoutFromAllDevicesTooltip": "When enabled, changing a user's password will automatically invalidate all active sessions across all devices, requiring them to log in again.", + "rateLimitingCooldownMinutes": "Rate limiting cooldown (minutes)", + "rateLimitingCooldownDisabled": "Enable rate limiting to configure the cooldown", + "passwordHistoryCount": "Password history count", + "verificationRequiredPaths": "Verification required paths", + "addPath": "Add path (e.g. /api/goals)", + "success": "System settings updated successfully!" } } }, diff --git a/frontend/Exence/src/assets/i18n/es.json b/frontend/Exence/src/assets/i18n/es.json index 01989ca5..7651f266 100644 --- a/frontend/Exence/src/assets/i18n/es.json +++ b/frontend/Exence/src/assets/i18n/es.json @@ -26,7 +26,8 @@ "to": "Hasta", "yes": "Sí", "no": "No", - "duplicate": "Duplicate" + "duplicate": "Duplicate", + "add": "Agregar" }, "dashboard": { "title": "Bienvenido/a de nuevo, {{username}}!", @@ -895,6 +896,24 @@ "confirmMessage": "¿Está seguro de que desea enviar este correo a todos los usuarios?", "success": "¡Correo enviado a todos los usuarios!", "sendDisabled": "No puedes enviar el correo electrónico sin haber configurado antes el asunto y el contenido." + }, + "systemSettings": { + "title": "Configuración del sistema", + "domainWhitelistOnly": "Solo dominios en lista blanca", + "domainWhitelistOnlyDescription": "Restringir registros solo a dominios de correo permitidos", + "domainWhitelistOnlyTooltip": "Cuando está habilitado, solo los usuarios con direcciones de correo de la lista de dominios permitidos pueden registrarse. Todos los demás intentos serán rechazados.", + "rateLimitingEnabled": "Limitación de velocidad activada", + "rateLimitingEnabledDescription": "Limitar solicitudes repetidas para prevenir ataques de fuerza bruta", + "rateLimitingEnabledTooltip": "Cuando está habilitado, los usuarios que superen el límite de solicitudes serán bloqueados temporalmente durante el período de enfriamiento configurado.", + "logoutFromAllDevices": "Cerrar sesión al cambiar contraseña", + "logoutFromAllDevicesDescription": "Cerrar sesión en todos los dispositivos al cambiar contraseña", + "logoutFromAllDevicesTooltip": "Cuando está habilitado, cambiar la contraseña invalidará automáticamente todas las sesiones activas en todos los dispositivos.", + "rateLimitingCooldownMinutes": "Tiempo de enfriamiento de la limitación (minutos)", + "rateLimitingCooldownDisabled": "Activa la limitación de velocidad para configurar el tiempo de enfriamiento", + "passwordHistoryCount": "Número de contraseñas históricas", + "verificationRequiredPaths": "Rutas que requieren verificación", + "addPath": "Agregar ruta (p.ej. /api/goals)", + "success": "¡Configuración del sistema actualizada con éxito!" } } }, diff --git a/frontend/Exence/src/assets/i18n/fr.json b/frontend/Exence/src/assets/i18n/fr.json index a0843790..8b8f5c32 100644 --- a/frontend/Exence/src/assets/i18n/fr.json +++ b/frontend/Exence/src/assets/i18n/fr.json @@ -26,7 +26,8 @@ "to": "À", "yes": "Oui", "no": "Non", - "duplicate": "Duplicate" + "duplicate": "Duplicate", + "add": "Ajouter" }, "dashboard": { "title": "Bon retour, {{username}} !", @@ -895,6 +896,24 @@ "confirmMessage": "Êtes-vous sûr de vouloir envoyer cet e-mail à tous les utilisateurs ?", "success": "E-mail envoyé à tous les utilisateurs !", "sendDisabled": "Vous ne pouvez pas envoyer l'e-mail sans avoir préalablement défini l'objet et le contenu." + }, + "systemSettings": { + "title": "Paramètres système", + "domainWhitelistOnly": "Domaines en liste blanche uniquement", + "domainWhitelistOnlyDescription": "Limiter les inscriptions aux domaines de messagerie autorisés", + "domainWhitelistOnlyTooltip": "Lorsque cette option est activée, seuls les utilisateurs avec des adresses e-mail provenant de la liste blanche de domaines peuvent s'inscrire. Toutes les autres tentatives seront rejetées.", + "rateLimitingEnabled": "Limitation de débit activée", + "rateLimitingEnabledDescription": "Limiter les requêtes répétées pour prévenir les attaques par force brute", + "rateLimitingEnabledTooltip": "Lorsque cette option est activée, les utilisateurs dépassant la limite de requêtes seront temporairement bloqués pendant la période de refroidissement configurée.", + "logoutFromAllDevices": "Déconnexion au changement de mot de passe", + "logoutFromAllDevicesDescription": "Déconnecter toutes les sessions lors d'un changement de mot de passe", + "logoutFromAllDevicesTooltip": "Lorsque cette option est activée, le changement de mot de passe invalidera automatiquement toutes les sessions actives sur tous les appareils.", + "rateLimitingCooldownMinutes": "Délai de récupération de la limitation (minutes)", + "rateLimitingCooldownDisabled": "Activez la limitation de débit pour configurer le délai de récupération", + "passwordHistoryCount": "Nombre d'historiques de mots de passe", + "verificationRequiredPaths": "Chemins nécessitant une vérification", + "addPath": "Ajouter un chemin (ex: /api/goals)", + "success": "Paramètres système mis à jour avec succès !" } } }, diff --git a/frontend/Exence/src/assets/i18n/hu.json b/frontend/Exence/src/assets/i18n/hu.json index 544e6e7f..fda9fead 100644 --- a/frontend/Exence/src/assets/i18n/hu.json +++ b/frontend/Exence/src/assets/i18n/hu.json @@ -26,7 +26,8 @@ "to": "Ig", "yes": "Igen", "no": "Nem", - "duplicate": "Duplicate" + "duplicate": "Duplicate", + "add": "Hozzáadás" }, "dashboard": { "title": "Üdvözöllek újra, {{username}}!", @@ -895,6 +896,24 @@ "confirmMessage": "Biztosan el szeretnéd küldeni ezt az e-mailt minden felhasználónak?", "success": "E-mail elküldve minden felhasználónak!", "sendDisabled": "Tárgy és a szöveg megadása nélkül nem küldheti el az e-mailt." + }, + "systemSettings": { + "title": "Rendszerbeállítások", + "domainWhitelistOnly": "Csak engedélyezett domének", + "domainWhitelistOnlyDescription": "Regisztrációk korlátozása engedélyezett e-mail doménekre", + "domainWhitelistOnlyTooltip": "Ha engedélyezve van, csak a konfigurált domain fehérlistán szereplő e-mail-címmel rendelkező felhasználók regisztrálhatnak. Minden más regisztrációs kísérletet el kell utasítani.", + "rateLimitingEnabled": "Rate limiting engedélyezve", + "rateLimitingEnabledDescription": "Ismételt kérések korlátozása brute-force támadások megelőzéséhez", + "rateLimitingEnabledTooltip": "Ha engedélyezve van, az igénylési korlátot túllépő felhasználók ideiglenesen blokkolva lesznek a konfigurált lehűlési időszakra a visszaélések megelőzése érdekében.", + "logoutFromAllDevices": "Kijelentkezés jelszóváltoztatáskor", + "logoutFromAllDevicesDescription": "Minden munkamenet kijelentkeztetése jelszóváltoztatáskor", + "logoutFromAllDevicesTooltip": "Ha engedélyezve van, a jelszó megváltoztatása automatikusan érvényteleníti az összes aktív munkamenetet minden eszközön, és újbóli bejelentkezést igényel.", + "rateLimitingCooldownMinutes": "Rate limiting visszaszámlálás (perc)", + "rateLimitingCooldownDisabled": "Engedélyezze a rate limitinget a visszaszámlálás beállításához", + "passwordHistoryCount": "Jelszóelőzmények száma", + "verificationRequiredPaths": "Hitelesítést igénylő útvonalak", + "addPath": "Útvonal hozzáadása (pl. /api/goals)", + "success": "Rendszerbeállítások sikeresen frissítve!" } } }, diff --git a/frontend/Exence/src/assets/i18n/it.json b/frontend/Exence/src/assets/i18n/it.json index 1fbc23bd..a9de481f 100644 --- a/frontend/Exence/src/assets/i18n/it.json +++ b/frontend/Exence/src/assets/i18n/it.json @@ -26,7 +26,8 @@ "to": "A", "yes": "Sí", "no": "No", - "duplicate": "Duplicate" + "duplicate": "Duplicate", + "add": "Aggiungi" }, "dashboard": { "title": "Bentornato/a, {{username}}!", @@ -895,6 +896,24 @@ "confirmMessage": "Sei sicuro di voler inviare questa e-mail a tutti gli utenti?", "success": "E-mail inviata a tutti gli utenti!", "sendDisabled": "Non è possibile inviare l'e-mail senza aver prima impostato l'oggetto e il contenuto." + }, + "systemSettings": { + "title": "Impostazioni di sistema", + "domainWhitelistOnly": "Solo domini nella whitelist", + "domainWhitelistOnlyDescription": "Limita le registrazioni ai domini email nella whitelist", + "domainWhitelistOnlyTooltip": "Se abilitato, solo gli utenti con indirizzi email dai domini nella whitelist possono registrarsi. Tutti gli altri tentativi di registrazione verranno rifiutati.", + "rateLimitingEnabled": "Limitazione della velocità abilitata", + "rateLimitingEnabledDescription": "Limita le richieste ripetute per prevenire attacchi brute-force", + "rateLimitingEnabledTooltip": "Se abilitato, gli utenti che superano il limite di richieste verranno temporaneamente bloccati per il periodo di raffreddamento configurato.", + "logoutFromAllDevices": "Disconnessione al cambio password", + "logoutFromAllDevicesDescription": "Disconnetti tutte le sessioni al cambio della password", + "logoutFromAllDevicesTooltip": "Se abilitato, la modifica della password invaliderà automaticamente tutte le sessioni attive su tutti i dispositivi, richiedendo un nuovo accesso.", + "rateLimitingCooldownMinutes": "Tempo di attesa della limitazione (minuti)", + "rateLimitingCooldownDisabled": "Abilita la limitazione della velocità per configurare il tempo di attesa", + "passwordHistoryCount": "Numero di password nella cronologia", + "verificationRequiredPaths": "Percorsi che richiedono verifica", + "addPath": "Aggiungi percorso (es. /api/goals)", + "success": "Impostazioni di sistema aggiornate con successo!" } } }, diff --git a/frontend/Exence/src/assets/i18n/pl.json b/frontend/Exence/src/assets/i18n/pl.json index 836f0c3b..669a9314 100644 --- a/frontend/Exence/src/assets/i18n/pl.json +++ b/frontend/Exence/src/assets/i18n/pl.json @@ -26,7 +26,8 @@ "to": "Do", "yes": "Tak", "no": "Nie", - "duplicate": "Duplicate" + "duplicate": "Duplicate", + "add": "Dodaj" }, "dashboard": { "title": "Witamy ponownie, {{username}}!", @@ -895,6 +896,24 @@ "confirmMessage": "Czy na pewno chcesz wysłać tę wiadomość e-mail do każdego użytkownika?", "success": "Wiadomość e-mail wysłana do każdego użytkownika!", "sendDisabled": "Nie można wysłać wiadomości e-mail bez uprzedniego podania tematu i treści." + }, + "systemSettings": { + "title": "Ustawienia systemu", + "domainWhitelistOnly": "Tylko domeny z białej listy", + "domainWhitelistOnlyDescription": "Ogranicz rejestracje do dozwolonych domen e-mail", + "domainWhitelistOnlyTooltip": "Gdy włączone, tylko użytkownicy z adresami e-mail z dozwolonych domen mogą się rejestrować. Wszystkie inne próby rejestracji zostaną odrzucone.", + "rateLimitingEnabled": "Ograniczenie szybkości włączone", + "rateLimitingEnabledDescription": "Ogranicz powtarzające się żądania, aby zapobiec atakom brute-force", + "rateLimitingEnabledTooltip": "Gdy włączone, użytkownicy przekraczający limit żądań zostaną tymczasowo zablokowani na skonfigurowany okres, aby zapobiec nadużyciom.", + "logoutFromAllDevices": "Wylogowanie przy zmianie hasła", + "logoutFromAllDevicesDescription": "Wyloguj wszystkie sesje przy zmianie hasła", + "logoutFromAllDevicesTooltip": "Gdy włączone, zmiana hasła automatycznie unieważni wszystkie aktywne sesje na wszystkich urządzeniach, wymagając ponownego zalogowania.", + "rateLimitingCooldownMinutes": "Czas odnowienia ograniczenia (minuty)", + "rateLimitingCooldownDisabled": "Włącz ograniczenie szybkości, aby skonfigurować czas odnowienia", + "passwordHistoryCount": "Liczba haseł w historii", + "verificationRequiredPaths": "Ścieżki wymagające weryfikacji", + "addPath": "Dodaj ścieżkę (np. /api/goals)", + "success": "Ustawienia systemu zaktualizowane pomyślnie!" } } }, diff --git a/frontend/Exence/src/assets/i18n/sk.json b/frontend/Exence/src/assets/i18n/sk.json index bc52cc9b..cb47de30 100644 --- a/frontend/Exence/src/assets/i18n/sk.json +++ b/frontend/Exence/src/assets/i18n/sk.json @@ -26,7 +26,8 @@ "to": "Do", "yes": "Áno", "no": "Nie", - "duplicate": "Duplicate" + "duplicate": "Duplicate", + "add": "Pridať" }, "dashboard": { "title": "Vitajte späť, {{username}}!", @@ -895,6 +896,24 @@ "confirmMessage": "Ste si istí, že chcete poslať tento e-mail každému používateľovi?", "success": "E-mail odoslaný každému používateľovi!", "sendDisabled": "E-mail nemôžete odoslať, pokiaľ predtým nenastavíte predmet a obsah." + }, + "systemSettings": { + "title": "Systémové nastavenia", + "domainWhitelistOnly": "Iba domény na bielej listine", + "domainWhitelistOnlyDescription": "Obmedziť registrácie na povolené e-mailové domény", + "domainWhitelistOnlyTooltip": "Keď je povolené, iba používatelia s e-mailovými adresami z nakonfigurovaného zoznamu domén sa môžu registrovať. Všetky ostatné pokusy o registráciu budú zamietnuté.", + "rateLimitingEnabled": "Obmedzenie rýchlosti povolené", + "rateLimitingEnabledDescription": "Obmedziť opakované požiadavky na prevenciu brute-force útokov", + "rateLimitingEnabledTooltip": "Keď je povolené, používatelia, ktorí prekročia limit požiadaviek, budú dočasne zablokovaní na nakonfigurované obdobie, aby sa predišlo zneužívaniu.", + "logoutFromAllDevices": "Odhlásenie pri zmene hesla", + "logoutFromAllDevicesDescription": "Odhlásiť všetky relácie pri zmene hesla", + "logoutFromAllDevicesTooltip": "Keď je povolené, zmena hesla automaticky zneplatní všetky aktívne relácie na všetkých zariadeniach a vyžaduje opätovné prihlásenie.", + "rateLimitingCooldownMinutes": "Čas obnovenia obmedzenia (minúty)", + "rateLimitingCooldownDisabled": "Povoľte obmedzenie rýchlosti na konfiguráciu času obnovenia", + "passwordHistoryCount": "Počet hesiel v histórii", + "verificationRequiredPaths": "Cesty vyžadujúce overenie", + "addPath": "Pridať cestu (napr. /api/goals)", + "success": "Systémové nastavenia úspešne aktualizované!" } } },