From 30a74514fb5a8d49551247bc80d0b049af02d987 Mon Sep 17 00:00:00 2001
From: cv5ch <176032962+cv5ch@users.noreply.github.com>
Date: Mon, 11 Aug 2025 15:32:29 +0200
Subject: [PATCH 1/3] Added URL validation to URL fields and fixed also no
error text shown when forms are built with the dynamic FormComponent
---
src/app/core/_services/metadata.service.ts | 24 ++++++++-----------
.../dynamicform.component.html | 14 +++++++++++
src/app/shared/input/text/text.component.html | 1 +
src/app/shared/input/text/text.component.ts | 2 +-
4 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/src/app/core/_services/metadata.service.ts b/src/app/core/_services/metadata.service.ts
index d095d1f7f..b129b7825 100644
--- a/src/app/core/_services/metadata.service.ts
+++ b/src/app/core/_services/metadata.service.ts
@@ -2,11 +2,9 @@ import { Injectable } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { SERV } from '@services/main.config';
-import { GlobalService } from '@services/main.service';
import { TooltipService } from '@services/shared/tooltip.service';
import { fileFormat } from '@src/app/core/_constants/files.config';
-import { ACTIONARRAY, NOTIFARRAY } from '@src/app/core/_constants/notifications.config';
import { ACCESS_GROUP_FIELD_MAPPING } from '@src/app/core/_constants/select.config';
import { dateFormats, proxytype, serverlog } from '@src/app/core/_constants/settings.config';
import { environment } from '@src/environments/environment';
@@ -15,12 +13,11 @@ import { environment } from '@src/environments/environment';
providedIn: 'root'
})
export class MetadataService {
- tooltip: any;
+ tooltip: unknown;
- constructor(
- private tooltipService: TooltipService,
- private gs: GlobalService
- ) {
+ URL_PATTERN = '[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)';
+
+ constructor(private tooltipService: TooltipService) {
this.tooltip = this.tooltipService.getConfigTooltips();
}
@@ -388,10 +385,10 @@ export class MetadataService {
{
name: 'downloadUrl',
label: 'Download URL',
- type: 'text',
+ type: 'url',
requiredasterisk: true,
tooltip: 'Link where the client can download a 7zip with the binary',
- validators: [Validators.required]
+ validators: [Validators.required, Validators.pattern(this.URL_PATTERN)]
},
{
name: 'crackerBinaryTypeId',
@@ -425,10 +422,10 @@ export class MetadataService {
{
name: 'downloadUrl',
label: 'Download URL',
- type: 'text',
+ type: 'url',
requiredasterisk: true,
tooltip: 'Link where the client can download a 7zip with the binary',
- validators: [Validators.required]
+ validators: [Validators.required, Validators.pattern(this.URL_PATTERN)]
}
];
@@ -483,10 +480,10 @@ export class MetadataService {
{
name: 'url',
label: 'Download URL',
- type: 'text',
+ type: 'url',
requiredasterisk: true,
tooltip: false,
- validators: [Validators.required]
+ validators: [Validators.required, Validators.pattern(this.URL_PATTERN)]
},
{ label: 'Commands (set to empty if not available)', isTitle: true },
{
@@ -1078,7 +1075,6 @@ export class MetadataService {
}
];
-
//This variable holds information about the fields required when creating a new user.
newuser = [
{
diff --git a/src/app/shared/dynamic-form-builder/dynamicform.component.html b/src/app/shared/dynamic-form-builder/dynamicform.component.html
index 18e6f6499..d4429eaf2 100644
--- a/src/app/shared/dynamic-form-builder/dynamicform.component.html
+++ b/src/app/shared/dynamic-form-builder/dynamicform.component.html
@@ -59,6 +59,13 @@
{{ field.label }}
[formControlName]="field.name"
/>
+
+
+
{{ field.label }}
+
+ {{ field.label }} is required
+
+
+ Invalid URL format
+
+
diff --git a/src/app/shared/input/text/text.component.html b/src/app/shared/input/text/text.component.html
index 3345405a4..57324bee1 100644
--- a/src/app/shared/input/text/text.component.html
+++ b/src/app/shared/input/text/text.component.html
@@ -51,6 +51,7 @@
{{ title }} is required
Please enter a valid {{ title?.toLowerCase() }}
+ Please enter a valid {{ title?.toLowerCase() }}
Minimum length is {{ inputField.errors.minlength.requiredLength }}
diff --git a/src/app/shared/input/text/text.component.ts b/src/app/shared/input/text/text.component.ts
index 18b71c8cc..04749391d 100644
--- a/src/app/shared/input/text/text.component.ts
+++ b/src/app/shared/input/text/text.component.ts
@@ -31,7 +31,7 @@ import { AbstractInputComponent } from '@src/app/shared/input/abstract-input';
})
export class InputTextComponent extends AbstractInputComponent {
@Input() pattern: string | RegExp;
- @Input() inputType: 'text' | 'password' | 'email' = 'text';
+ @Input() inputType: 'text' | 'password' | 'email' | 'url' = 'text';
@Input() icon: string;
@Input() width: string = '';
@Input() minLength?: number;
From 34d470fc9fedf0ece91e9b546835841e43243619 Mon Sep 17 00:00:00 2001
From: cv5ch <176032962+cv5ch@users.noreply.github.com>
Date: Mon, 11 Aug 2025 15:45:20 +0200
Subject: [PATCH 2/3] Once again try to fix unit test which sometimes runs,
sometimes not...
---
.../settings/acc-settings/acc-settings.component.spec.ts | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/app/account/settings/acc-settings/acc-settings.component.spec.ts b/src/app/account/settings/acc-settings/acc-settings.component.spec.ts
index 6a11d7352..b64ed9387 100644
--- a/src/app/account/settings/acc-settings/acc-settings.component.spec.ts
+++ b/src/app/account/settings/acc-settings/acc-settings.component.spec.ts
@@ -121,17 +121,14 @@ describe('AccountSettingsComponent', () => {
});
describe('Main form tests', () => {
- it('initializes the form with default values', async () => {
- await fixture.whenStable();
- fixture.detectChanges();
-
+ it('initializes the form with default values', () => {
const formValue = component.form.getRawValue();
expect(formValue.name).toBe(userResponse.attributes.name);
expect(formValue.email).toBe(userResponse.attributes.email);
expect(formValue.registeredSince).toBe('08/04/2025 6:25:56');
});
- it('validates email as required', async () => {
+ it('validates email as required', () => {
const emailControl = component.form.get('email');
emailControl?.patchValue(null);
component.form.updateValueAndValidity();
From 3bc1c273b1df13ca0d5a5ee40d4b638508d0406a Mon Sep 17 00:00:00 2001
From: cv5ch <176032962+cv5ch@users.noreply.github.com>
Date: Tue, 12 Aug 2025 14:27:20 +0200
Subject: [PATCH 3/3] Get rid of any typings...
---
.../forms/simple-forms/form.component.ts | 26 ++--
.../simple-forms/formconfig.component.ts | 37 +++--
src/app/core/_constants/settings.config.ts | 8 +
src/app/core/_services/metadata.service.ts | 143 ++++++++++--------
.../core/_services/shared/tooltip.service.ts | 33 ++--
src/app/core/_validators/url.validator.ts | 22 +++
.../dynamicform.component.html | 2 +-
7 files changed, 163 insertions(+), 108 deletions(-)
create mode 100644 src/app/core/_validators/url.validator.ts
diff --git a/src/app/core/_components/forms/simple-forms/form.component.ts b/src/app/core/_components/forms/simple-forms/form.component.ts
index cfd19b12b..f6f024877 100644
--- a/src/app/core/_components/forms/simple-forms/form.component.ts
+++ b/src/app/core/_components/forms/simple-forms/form.component.ts
@@ -1,19 +1,20 @@
import { Subscription } from 'rxjs';
-import { GlobalService } from 'src/app/core/_services/main.service';
-import { MetadataService } from 'src/app/core/_services/metadata.service';
-import { AlertService } from 'src/app/core/_services/shared/alert.service';
-import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service';
-import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { ActivatedRoute, Params, Router } from '@angular/router';
+import { BaseModel } from '@models/base.model';
import { ResponseWrapper } from '@models/response.model';
import { JsonAPISerializer } from '@services/api/serializer-service';
import { ConfirmDialogService } from '@services/confirm/confirm-dialog.service';
import { ServiceConfig } from '@services/main.config';
+import { GlobalService } from '@services/main.service';
+import { MetadataService } from '@services/metadata.service';
+import { AlertService } from '@services/shared/alert.service';
+import { AutoTitleService } from '@services/shared/autotitle.service';
+import { UnsubscribeService } from '@services/unsubscribe.service';
@Component({
selector: 'app-form',
@@ -25,7 +26,8 @@ import { ServiceConfig } from '@services/main.config';
*/
export class FormComponent implements OnInit, OnDestroy {
// Metadata Text, titles, subtitles, forms, and API path
- globalMetadata: any[] = [];
+ globalMetadata: ReturnType[0];
+
serviceConfig: ServiceConfig;
/**
@@ -78,16 +80,14 @@ export class FormComponent implements OnInit, OnDestroy {
/**
* An array of form field metadata that describes the form structure.
* Each item in the array represents a form field, including its type, label, and other properties.
- * @type {any[]}
*/
- formMetadata: any[] = [];
+ formMetadata: ReturnType = [];
/**
* Initial values for form fields (optional).
* If provided, these values are used to initialize form controls in the dynamic form.
- * @type {any[]}
*/
- formValues: any[] = [];
+ formValues: (BaseModel & Record)[] = [];
// Subscription for managing asynchronous data retrieval
private subscriptionService: Subscription;
@@ -128,7 +128,7 @@ export class FormComponent implements OnInit, OnDestroy {
this.formMetadata = this.metadataService.getFormMetadata(formKind);
this.title = this.globalMetadata['title'];
this.customform = this.globalMetadata['customform'];
- titleService.set([this.title]);
+ this.titleService.set([this.title]);
// Load metadata and form information
if (this.type === 'edit') {
this.getIndex();
@@ -199,7 +199,7 @@ export class FormComponent implements OnInit, OnDestroy {
* Handles the submission of the form.
* @param formValues - The values submitted from the form.
*/
- onFormSubmit(formValues: any) {
+ onFormSubmit(formValues: (BaseModel & Record)[]) {
if (this.customform) {
this.modifyFormValues(formValues);
}
@@ -226,7 +226,7 @@ export class FormComponent implements OnInit, OnDestroy {
* @param formValues - The form values to be modified.
* @returns The modified form values.
*/
- modifyFormValues(formValues: any) {
+ modifyFormValues(formValues: (BaseModel | Record)[]) {
// Check the formMetadata for fields with 'replacevalue' property
this.getIndex();
for (const field of this.formMetadata) {
diff --git a/src/app/core/_components/forms/simple-forms/formconfig.component.ts b/src/app/core/_components/forms/simple-forms/formconfig.component.ts
index 94880ff54..b8a1e9a09 100644
--- a/src/app/core/_components/forms/simple-forms/formconfig.component.ts
+++ b/src/app/core/_components/forms/simple-forms/formconfig.component.ts
@@ -4,18 +4,22 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
+import { JConfig } from '@models/configs.model';
import { HorizontalNav } from '@models/horizontalnav.model';
import { ResponseWrapper } from '@models/response.model';
import { JsonAPISerializer } from '@services/api/serializer-service';
import { SERV, ServiceConfig } from '@services/main.config';
import { GlobalService } from '@services/main.service';
-import { MetadataService } from '@services/metadata.service';
+import { InfoMetadataForm, MetadataFormField, MetadataService } from '@services/metadata.service';
import { AlertService } from '@services/shared/alert.service';
import { AutoTitleService } from '@services/shared/autotitle.service';
import { UIConfigService } from '@services/shared/storage.service';
import { UnsubscribeService } from '@services/unsubscribe.service';
+type ConfigValues = Record;
+type ConfigIds = Record;
+
@Component({
selector: 'app-form',
templateUrl: 'formconfig.component.html',
@@ -26,7 +30,7 @@ import { UnsubscribeService } from '@services/unsubscribe.service';
*/
export class FormConfigComponent implements OnInit, OnDestroy {
// Metadata Text, titles, subtitles, forms, and API path
- globalMetadata: any[] = [];
+ globalMetadata: InfoMetadataForm;
serviceConfig: ServiceConfig;
/**
@@ -51,23 +55,21 @@ export class FormConfigComponent implements OnInit, OnDestroy {
/**
* An array of form field metadata that describes the form structure.
* Each item in the array represents a form field, including its type, label, and other properties.
- * @type {any[]}
*/
- formMetadata: any[] = [];
+ formMetadata: MetadataFormField[] = [];
/**
* Initial values for form fields (optional).
* If provided, these values are used to initialize form controls in the dynamic form.
- * @type {any[]}
+
*/
- formValues: any[] = [];
+ formValues: object;
/**
* An array of objects containing IDs and corresponding item names.
* This information is used to map form field names to their associated IDs.
- * @type {any[]}
*/
- formIds: any[] = [];
+ formIds: ConfigIds = {};
// Subscription for managing asynchronous data retrieval
private mySubscription: Subscription;
@@ -135,19 +137,22 @@ export class FormConfigComponent implements OnInit, OnDestroy {
.getAll(this.serviceConfig, { page: { size: 500 } })
.subscribe((response: ResponseWrapper) => {
const responseBody = { data: response.data, included: response.included };
- const configValues = this.serializer.deserialize(responseBody) as any[];
+ const config = this.serializer.deserialize(responseBody);
+
+ this.formValues = config.reduce((configValues, item) => {
+ let value: string | boolean = item.value;
- this.formValues = configValues.reduce((configValues, item) => {
if (item.value === '1') {
- item.value = true;
+ value = true;
} else if (item.value === '0') {
- item.value = false;
+ value = false;
}
- configValues[item.item] = item.value;
+
+ configValues[item.item] = value;
return configValues;
}, {});
// Maps the item with the id, so can be used for update
- this.formIds = configValues.reduce((result, item) => {
+ this.formIds = config.reduce((result, item) => {
result[item.item] = item.id;
return result;
}, {});
@@ -168,10 +173,10 @@ export class FormConfigComponent implements OnInit, OnDestroy {
* Handles the submission of the form.
* @param form - The form object containing the updated values.
*/
- onFormSubmit(form: any) {
+ onFormSubmit(form: FormGroup) {
const currentFormValues = form;
const initialFormValues = this.formValues;
- const changedFields: Record = {};
+ const changedFields: Record = {};
for (const key in currentFormValues) {
if (Object.prototype.hasOwnProperty.call(currentFormValues, key)) {
diff --git a/src/app/core/_constants/settings.config.ts b/src/app/core/_constants/settings.config.ts
index d35bee83e..3adab70f2 100644
--- a/src/app/core/_constants/settings.config.ts
+++ b/src/app/core/_constants/settings.config.ts
@@ -6,6 +6,14 @@ export interface Setting {
description: string;
}
+/**
+ * Options for select inputs, used in various settings
+ */
+export interface Option {
+ label: string;
+ value: number | string | boolean;
+}
+
export const dateFormats: Setting[] = [
{ value: 'd/M/yy', description: 'd/M/yy (ie. 6/7/23 )' },
{
diff --git a/src/app/core/_services/metadata.service.ts b/src/app/core/_services/metadata.service.ts
index b129b7825..41a8b2361 100644
--- a/src/app/core/_services/metadata.service.ts
+++ b/src/app/core/_services/metadata.service.ts
@@ -1,69 +1,85 @@
+import { Observable, of } from 'rxjs';
+
import { Injectable } from '@angular/core';
-import { FormControl, Validators } from '@angular/forms';
+import { FormControl, ValidatorFn, Validators } from '@angular/forms';
import { SERV } from '@services/main.config';
-import { TooltipService } from '@services/shared/tooltip.service';
+import { ConfigTooltipsLevel, TooltipService } from '@services/shared/tooltip.service';
import { fileFormat } from '@src/app/core/_constants/files.config';
-import { ACCESS_GROUP_FIELD_MAPPING } from '@src/app/core/_constants/select.config';
-import { dateFormats, proxytype, serverlog } from '@src/app/core/_constants/settings.config';
-import { environment } from '@src/environments/environment';
+import { ACCESS_GROUP_FIELD_MAPPING, FieldMapping } from '@src/app/core/_constants/select.config';
+import { Option, Setting, dateFormats, proxytype, serverlog } from '@src/app/core/_constants/settings.config';
+import { urlValidator } from '@src/app/core/_validators/url.validator';
+
+/**
+ * Metadata information for the form page.
+ *
+ * Properties:
+ * - title: Title for the form page
+ * - customform: Whether the form is custom or standard
+ * - subtitle: Whether the form has a subtitle
+ * - submitok: Message displayed upon successful submission
+ * - submitokredirect: Redirect URL upon successful submission
+ * - deltitle: Title for deletion confirmation dialog
+ * - delsubmitok: Message displayed upon successful deletion
+ * - delsubmitokredirect: Redirect URL upon successful deletion
+ * - delsubmitcancel: Message displayed when deletion is canceled
+ */
+export interface InfoMetadataForm {
+ title: string;
+ customform?: boolean;
+ subtitle?: boolean;
+ submitok?: string;
+ submitokredirect?: string;
+ deltitle?: string;
+ delsubmitok?: string;
+ delsubmitokredirect?: string;
+ delsubmitcancel?: string;
+}
+
+/**
+ * Metadata for each field in the form.
+ *
+ * Properties:
+ * - name: API name to be mapped with the formControl
+ * - label: Label name to be displayed
+ * - type: Type of the form field; e.g., select, text, checkbox
+ * - placeholder: Placeholder text for the input
+ * - selectOptions: Select options if the type is 'select'
+ * - selectOptions$: Select options observable if type is 'select' and used with selectEndpoint$
+ * - selectEndpoint$: API endpoint route, usually a constant like SERV
+ * - fieldMapping: Object with the dropdown options mapping, e.g., { id: '_id', name: 'groupName' }
+ * - requiredasterisk: Indicates if the field is required (shows asterisk)
+ * - tooltip: Tooltip information as string or more complex type
+ * - validators: Validation rules
+ * - isTitle: If true, will use only the label field as a title
+ */
+export interface MetadataFormField {
+ name?: string;
+ label?: string;
+ type?: string;
+ placeholder?: string;
+ selectOptions?: (Setting | Option)[];
+ selectOptions$?: Observable<{ label: string; value: number }[]>;
+ selectEndpoint$?: SERV;
+ fieldMapping?: Record | FieldMapping;
+ requiredasterisk?: boolean;
+ tooltip?: string | boolean;
+ validators?: ValidatorFn[] | boolean;
+ isTitle?: boolean;
+ replacevalue?: string;
+}
@Injectable({
providedIn: 'root'
})
export class MetadataService {
- tooltip: unknown;
-
- URL_PATTERN = '[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)';
+ private tooltip: ConfigTooltipsLevel;
constructor(private tooltipService: TooltipService) {
this.tooltip = this.tooltipService.getConfigTooltips();
}
- private maxResults = environment.config.prodApiMaxResults;
-
- // ToDo in validators, go to the database and add max lenght
- //checboxes issues when value is false,
-
- // //
- // Metadata Structure
- // //
-
- // Info Metadata, it contains information about the page such as title, subtitles, and notifications configuration.
- infoMetadataForm = {
- title: 'Title for the form page',
- customform: false,
- subtitle: false,
- submitok: 'Message displayed upon successful submission',
- submitokredirect: 'Redirect URL upon successful submission',
- deltitle: 'Title for deletion confirmation',
- delsubmitok: 'Message displayed upon successful deletion',
- delsubmitokredirect: 'Redirect URL upon successful deletion',
- delsubmitcancel: 'Message displayed when deletion is canceled'
- };
-
- // Metadata form, it contains information about each field.
- metadataFormField = [
- {
- name: 'API name to be map with the formControl',
- label: 'Label name to be displayed',
- type: 'Type of the form field; (e.g., select, text, checkbox)',
- placeholder: 'Type option text, then add placeholder',
- selectOptions: "Select options if the type is 'select'",
- selectOptions$: "Select options if the type is 'selectd', used with selectEndpoint",
- selectEndpoint$: 'API endpoint route, use SERV',
- fieldMapping: 'Object with the dropdown options to be mapped, that is id and name. ie. id: _id, name:groupName',
- requiredasterisk: 'Indicates if the field is required',
- tooltip: 'Tooltip information as string or using ',
- validators: 'Validation rules',
- isTitle: 'boolean, if its true will use only the label field'
- }
- ];
-
- // Examples
- // Create title between fields. use { label: 'More settings', isTitle: true }
-
// // // // // // // //
// AUTH SECTION //
// // // // // // // //
@@ -203,7 +219,7 @@ export class MetadataService {
type: 'selectd',
requiredasterisk: true,
selectEndpoint$: SERV.ACCESS_GROUPS,
- selectOptions$: [],
+ selectOptions$: of([]),
fieldMapping: ACCESS_GROUP_FIELD_MAPPING
},
{ name: 'isSecret', label: 'Secret', type: 'checkbox' }
@@ -387,8 +403,8 @@ export class MetadataService {
label: 'Download URL',
type: 'url',
requiredasterisk: true,
- tooltip: 'Link where the client can download a 7zip with the binary',
- validators: [Validators.required, Validators.pattern(this.URL_PATTERN)]
+ tooltip: 'Link where the client can download a 7zip with the binary, e.g. https://example.com/cracker-1.0.0.7z',
+ validators: [Validators.required, urlValidator()]
},
{
name: 'crackerBinaryTypeId',
@@ -424,8 +440,8 @@ export class MetadataService {
label: 'Download URL',
type: 'url',
requiredasterisk: true,
- tooltip: 'Link where the client can download a 7zip with the binary',
- validators: [Validators.required, Validators.pattern(this.URL_PATTERN)]
+ tooltip: 'Link where the client can download a 7zip with the binary, e.g. https://example.com/cracker-1.0.0.7z',
+ validators: [Validators.required, urlValidator()]
}
];
@@ -482,8 +498,9 @@ export class MetadataService {
label: 'Download URL',
type: 'url',
requiredasterisk: true,
- tooltip: false,
- validators: [Validators.required, Validators.pattern(this.URL_PATTERN)]
+ tooltip:
+ 'Link where the client can download a 7zip with the preprocessor, e.g. https://example.com/preprocessor-1.0.0.7z',
+ validators: [Validators.required, urlValidator()]
},
{ label: 'Commands (set to empty if not available)', isTitle: true },
{
@@ -1040,7 +1057,7 @@ export class MetadataService {
type: 'selectd',
requiredasterisk: true,
selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS,
- selectOptions$: [],
+ selectOptions$: of([]),
fieldMapping: { id: 'crackerBinaryTypeId', name: 'typeName' },
validators: [Validators.required]
},
@@ -1050,7 +1067,7 @@ export class MetadataService {
type: 'selectd',
requiredasterisk: true,
selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS,
- selectOptions$: [],
+ selectOptions$: of([]),
fieldMapping: { id: 'crackerBinaryId', name: 'version' },
validators: [Validators.required]
}
@@ -1097,7 +1114,7 @@ export class MetadataService {
type: 'selectd',
requiredasterisk: true,
selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS,
- selectOptions$: [],
+ selectOptions$: of([]),
fieldMapping: { id: 'id', name: 'name' },
validators: [Validators.required]
}
@@ -1206,7 +1223,7 @@ export class MetadataService {
* @param formName - The name of the form for which metadata is requested.
* @returns An array of form metadata.editnotifInfo
*/
- getFormMetadata(formName: string): any[] {
+ getFormMetadata(formName: string): MetadataFormField[] {
if (formName === 'authforgot') {
return this.authforgot;
} else if (formName === 'editwordlist' || formName === 'editrule' || formName === 'editother') {
@@ -1253,7 +1270,7 @@ export class MetadataService {
* @param formName - The name of the info metadata for which information is requested.
* @returns An array of info metadata.
*/
- getInfoMetadata(formName: string): any[] {
+ getInfoMetadata(formName: string): InfoMetadataForm[] {
if (formName === 'authforgotInfo') {
return this.authforgotInfo;
} else if (formName === 'editwordlistInfo') {
diff --git a/src/app/core/_services/shared/tooltip.service.ts b/src/app/core/_services/shared/tooltip.service.ts
index b606fa3ae..7cae823ff 100644
--- a/src/app/core/_services/shared/tooltip.service.ts
+++ b/src/app/core/_services/shared/tooltip.service.ts
@@ -1,28 +1,31 @@
-import { CookieService } from '../../_services/shared/cookies.service';
-import { environment } from './../../../../environments/environment';
-import { Injectable } from "@angular/core";
+import { Injectable } from '@angular/core';
+
+import { CookieService } from '@services/shared/cookies.service';
+
+import { DEFAULT_CONFIG_TOOLTIP } from '@src/config/default/app/tooltip';
+import { environment } from '@src/environments/environment';
+
+// This gets the union of all task tooltip levels: tasks['0'] | tasks['1'] | tasks['2']
+export type TaskTooltipsLevel = (typeof DEFAULT_CONFIG_TOOLTIP.tasks)[keyof typeof DEFAULT_CONFIG_TOOLTIP.tasks];
+
+// Same for config, union of all config levels
+export type ConfigTooltipsLevel = (typeof DEFAULT_CONFIG_TOOLTIP.config)[keyof typeof DEFAULT_CONFIG_TOOLTIP.config];
@Injectable({
providedIn: 'root'
})
export class TooltipService {
+ constructor(private cookieService: CookieService) {}
- constructor(
- private cookieService: CookieService,
- ) {}
-
- public getTaskTooltips(){
- return environment.tooltip.tasks[this.getTooltipLevel()]
+ public getTaskTooltips(): TaskTooltipsLevel {
+ return environment.tooltip.tasks[this.getTooltipLevel()];
}
- public getConfigTooltips(){
- return environment.tooltip.config[this.getTooltipLevel()]
+ public getConfigTooltips(): ConfigTooltipsLevel {
+ return environment.tooltip.config[this.getTooltipLevel()];
}
- public getTooltipLevel(){
+ public getTooltipLevel() {
return this.cookieService.getCookie('tooltip');
}
-
}
-
-
diff --git a/src/app/core/_validators/url.validator.ts b/src/app/core/_validators/url.validator.ts
new file mode 100644
index 000000000..def19b5be
--- /dev/null
+++ b/src/app/core/_validators/url.validator.ts
@@ -0,0 +1,22 @@
+// url.validator.ts
+import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
+
+/**
+ * Creates an Angular ValidatorFn that validates whether a control's value is a valid URL.
+ * This validator attempts to parse the value using the browser's native URL API.
+ * @returns A ValidatorFn function to use in Angular Reactive Forms.
+ */
+export function urlValidator(): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const rawValue = control.value?.trim();
+ if (!rawValue) {
+ // Don't validate empty values; leave that to Validators.required
+ return null;
+ }
+ if (URL.canParse(rawValue)) {
+ const { hostname } = new URL(rawValue);
+ return hostname ? null : { invalidUrl: { value: rawValue } };
+ }
+ return { invalidUrl: { value: rawValue } };
+ };
+}
diff --git a/src/app/shared/dynamic-form-builder/dynamicform.component.html b/src/app/shared/dynamic-form-builder/dynamicform.component.html
index d4429eaf2..f53453b4a 100644
--- a/src/app/shared/dynamic-form-builder/dynamicform.component.html
+++ b/src/app/shared/dynamic-form-builder/dynamicform.component.html
@@ -89,7 +89,7 @@ {{ field.label }}
{{ field.label }} is required
-
+
Invalid URL format