diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts
index d008b8e5c7..69791908b3 100644
--- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts
+++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts
@@ -129,10 +129,6 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
this.showError(req.method + ': ' + req.url + '
' +
errorResponse.status + ': ' + errorResponse.statusText);
}
- } else if (errorResponse.status === 504) {
- if (!ignoreErrors) {
- this.showError('Request timeout');
- }
} else {
unhandled = true;
}
diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts
index bf57dd4441..7f13f08ba6 100644
--- a/ui-ngx/src/app/core/utils.ts
+++ b/ui-ngx/src/app/core/utils.ts
@@ -23,7 +23,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
import { baseDetailsPageByEntityType, EntityType } from '@shared/models/entity-type.models';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';
-import { serverErrorCodesTranslations } from '@shared/models/constants';
+import { httpStatusMessageMap, serverErrorCodesTranslations } from '@shared/models/constants';
import { SubscriptionEntityInfo } from '@core/api/widget-api.models';
import {
CompiledTbFunction,
@@ -910,11 +910,13 @@ export function parseHttpErrorMessage(errorResponse: HttpErrorResponse,
} else {
error = errorResponse.error;
}
- if (error && !error.message) {
- errorMessage = prepareMessageFromData(error);
- } else if (error && error.message) {
+ if (error && error.message) {
errorMessage = error.message;
timeout = error.timeout ? error.timeout : 0;
+ } else if (isProxyError(errorResponse)) {
+ errorMessage = httpStatusMessageMap.get(errorResponse.status);
+ } else if (error) {
+ errorMessage = prepareMessageFromData(error);
} else {
errorMessage = `Unhandled error code ${error ? error.status : '\'Unknown\''}`;
}
@@ -951,6 +953,14 @@ function prepareMessageFromData(data): string {
}
}
+function isProxyError(errorResponse: HttpErrorResponse): boolean {
+ if (!httpStatusMessageMap.has(errorResponse.status)) {
+ return false;
+ }
+ const error = errorResponse.error;
+ return !error || typeof error === 'string' || (typeof error === 'object' && !error.message);
+}
+
export const genNextLabel = (name: string, datasources: Datasource[]): string => {
let label = name;
let i = 1;
diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.html b/ui-ngx/src/app/shared/components/string-items-list.component.html
index 106b79ab9a..7a0489345f 100644
--- a/ui-ngx/src/app/shared/components/string-items-list.component.html
+++ b/ui-ngx/src/app/shared/components/string-items-list.component.html
@@ -31,7 +31,6 @@
close
+ [matAutocompleteDisabled]="!predefinedValues?.length && !fetchOptionsFn">
@for (value of filteredValues | async; track value.value) {
-
+
} @empty {
@if (!allowUserValue) {
- {{ 'common.not-found' | translate }}
+ {{ 'common.not-found' | translate }}
}
}
diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.ts b/ui-ngx/src/app/shared/components/string-items-list.component.ts
index c7f905f68b..6d0c57ab0a 100644
--- a/ui-ngx/src/app/shared/components/string-items-list.component.ts
+++ b/ui-ngx/src/app/shared/components/string-items-list.component.ts
@@ -29,7 +29,7 @@ import { FloatLabelType, MatFormFieldAppearance, SubscriptSizing } from '@angula
import { coerceArray, coerceBoolean } from '@shared/decorators/coercion';
import { Observable, of } from 'rxjs';
import { filter, mergeMap, share, tap } from 'rxjs/operators';
-import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
+import { MatAutocompleteSelectedEvent, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { isDefined, isUndefined } from '@core/utils';
export interface StringItemsOption {
@@ -37,18 +37,18 @@ export interface StringItemsOption {
value: any;
}
@Component({
- selector: 'tb-string-items-list',
- templateUrl: './string-items-list.component.html',
- styleUrls: [],
- providers: [
- {
- provide: NG_VALUE_ACCESSOR,
- useExisting: forwardRef(() => StringItemsListComponent),
- multi: true
- }
- ],
- encapsulation: ViewEncapsulation.None,
- standalone: false
+ selector: 'tb-string-items-list',
+ templateUrl: './string-items-list.component.html',
+ styleUrls: [],
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => StringItemsListComponent),
+ multi: true
+ }
+ ],
+ encapsulation: ViewEncapsulation.None,
+ standalone: false
})
export class StringItemsListComponent implements ControlValueAccessor, OnInit {
@@ -149,9 +149,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
this.filteredValues = this.itemControl.valueChanges
.pipe(
tap((value) => {
- if (value && typeof value !== 'string') {
- this.add(value);
- } else if (value === null) {
+ if (value === null) {
this.clear();
}
}),
@@ -209,16 +207,17 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
this.dirty = true;
}
- addOnBlur(event: FocusEvent) {
- const target: HTMLElement = event.relatedTarget as HTMLElement;
- if ((target && target.tagName !== 'MAT-OPTION') || this.allowUserValue) {
- this.addItem(this.stringItemInput.nativeElement.value ?? '');
+ onOptionSelected(event: MatAutocompleteSelectedEvent): void {
+ if (event.option.value != null) {
+ this.add(event.option.value);
+ } else {
+ this.clear();
}
- this.onTouched();
}
addOnEnd(event: MatChipInputEvent): void {
- this.addItem(event.value ?? '')
+ this.addItem(event.value ?? '');
+ this.onTouched();
}
removeItems(item: StringItemsOption) {
@@ -295,6 +294,8 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
this.stringItemInput.nativeElement.value = value;
this.itemControl.patchValue(value, {emitEvent: true});
setTimeout(() => {
+ this.stringItemInput.nativeElement.value = value;
+ this.itemControl.patchValue(null, {emitEvent: false});
this.stringItemInput.nativeElement.blur();
this.stringItemInput.nativeElement.focus();
}, 0);
diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts
index 2890b4827c..afc68fdfcd 100644
--- a/ui-ngx/src/app/shared/models/constants.ts
+++ b/ui-ngx/src/app/shared/models/constants.ts
@@ -56,6 +56,12 @@ export const serverErrorCodesTranslations = new Map([
[Constants.serverErrorCode.entitiesLimitExceeded, 'server-error.entities-limit-exceeded'],
]);
+export const httpStatusMessageMap = new Map([
+ [502, 'Server is temporarily unavailable (Bad Gateway)'],
+ [503, 'Server is temporarily unavailable'],
+ [504, 'Server did not respond in time (Gateway Timeout)'],
+]);
+
export const MediaBreakpoints = {
xs: 'screen and (max-width: 599px)',
sm: 'screen and (min-width: 600px) and (max-width: 959px)',
diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock
index 721b8b8006..8aafb1a243 100644
--- a/ui-ngx/yarn.lock
+++ b/ui-ngx/yarn.lock
@@ -6033,9 +6033,9 @@ flat@^5.0.2:
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
flatted@^3.2.9:
- version "3.4.1"
- resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.1.tgz#84ccd9579e76e9cc0d246c11d8be0beb019143e6"
- integrity sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==
+ version "3.4.2"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726"
+ integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==
"flot.curvedlines@https://github.com/MichaelZinsmaier/CurvedLines.git#master":
version "1.1.1"