Browse Source

Merge remote-tracking branch 'upstream/master' into oauth2

pull/11231/head
rusikv 2 years ago
parent
commit
32c8025067
  1. 3
      ui-ngx/src/app/core/http/widget.service.ts
  2. 15
      ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts
  3. 23
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  4. 10
      ui-ngx/src/app/core/services/item-buffer.service.ts
  5. 903
      ui-ngx/src/app/core/services/menu.models.ts
  6. 983
      ui-ngx/src/app/core/services/menu.service.ts
  7. 33
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  8. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/add-new-breakpoint-dialog.component.html
  9. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.html
  10. 10
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.scss
  11. 14
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts
  12. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/select-dashboard-breakpoint.component.ts
  13. 6
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  14. 16
      ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts
  15. 2
      ui-ngx/src/app/modules/home/components/router-tabs.component.ts
  16. 21
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.html
  17. 8
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts
  18. 78
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html
  19. 6
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts
  20. 1
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts
  21. 11
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-link.component.ts
  22. 2
      ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html
  23. 10
      ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.ts
  24. 2
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  25. 2
      ui-ngx/src/app/modules/home/menu/menu-link.component.html
  26. 2
      ui-ngx/src/app/modules/home/menu/menu-toggle.component.html
  27. 7
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  28. 49
      ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts
  29. 4
      ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts
  30. 4
      ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts
  31. 4
      ui-ngx/src/app/modules/home/pages/asset-profile/asset-profile-routing.module.ts
  32. 4
      ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts
  33. 4
      ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts
  34. 4
      ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts
  35. 4
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts
  36. 4
      ui-ngx/src/app/modules/home/pages/device-profile/device-profile-routing.module.ts
  37. 4
      ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts
  38. 10
      ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts
  39. 4
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts
  40. 4
      ui-ngx/src/app/modules/home/pages/features/features-routing.module.ts
  41. 4
      ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts
  42. 2
      ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html
  43. 19
      ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts
  44. 1
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html
  45. 4
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts
  46. 4
      ui-ngx/src/app/modules/home/pages/profiles/profiles-routing.module.ts
  47. 5
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts
  48. 4
      ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-routing.module.ts
  49. 4
      ui-ngx/src/app/modules/home/pages/tenant/tenant-routing.module.ts
  50. 4
      ui-ngx/src/app/modules/home/pages/vc/vc-routing.module.ts
  51. 20
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts
  52. 10
      ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts
  53. 8
      ui-ngx/src/app/shared/components/breadcrumb.component.html
  54. 44
      ui-ngx/src/app/shared/components/breadcrumb.component.ts
  55. 8
      ui-ngx/src/app/shared/components/breadcrumb.ts
  56. 10
      ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html
  57. 2
      ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.scss
  58. 4
      ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts
  59. 9
      ui-ngx/src/app/shared/components/entity/entity-list.component.ts
  60. 8
      ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.html
  61. 4
      ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.ts
  62. 67
      ui-ngx/src/app/shared/import-export/import-export.service.ts
  63. 2
      ui-ngx/src/app/shared/models/dashboard.models.ts
  64. 4
      ui-ngx/src/app/shared/models/widget.models.ts
  65. 20
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  66. 3
      ui-ngx/src/assets/metadata/connector-default-configs/opcua.json
  67. 5
      ui-ngx/src/assets/metadata/connector-default-configs/opcua_asyncio.json

3
ui-ngx/src/app/core/http/widget.service.ts

@ -185,8 +185,9 @@ export class WidgetService {
public saveWidgetTypeDetails(widgetInfo: WidgetInfo, public saveWidgetTypeDetails(widgetInfo: WidgetInfo,
id: WidgetTypeId, id: WidgetTypeId,
createdTime: number, createdTime: number,
version: number,
config?: RequestConfig): Observable<WidgetTypeDetails> { config?: RequestConfig): Observable<WidgetTypeDetails> {
const widgetTypeDetails = toWidgetTypeDetails(widgetInfo, id, undefined, createdTime); const widgetTypeDetails = toWidgetTypeDetails(widgetInfo, id, undefined, createdTime, version);
return this.http.post<WidgetTypeDetails>('/api/widgetType', widgetTypeDetails, return this.http.post<WidgetTypeDetails>('/api/widgetType', widgetTypeDetails,
defaultHttpOptionsFromConfig(config)).pipe( defaultHttpOptionsFromConfig(config)).pipe(
tap((savedWidgetType) => { tap((savedWidgetType) => {

15
ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts

@ -20,6 +20,7 @@ import {
HttpEvent, HttpEvent,
HttpHandler, HttpHandler,
HttpInterceptor, HttpInterceptor,
HttpParams,
HttpRequest, HttpRequest,
HttpStatusCode HttpStatusCode
} from '@angular/common/http'; } from '@angular/common/http';
@ -32,6 +33,8 @@ import {
import { HasId } from '@shared/models/base-data'; import { HasId } from '@shared/models/base-data';
import { HasVersion } from '@shared/models/entity.models'; import { HasVersion } from '@shared/models/entity.models';
import { getInterceptorConfig } from './interceptor.util'; import { getInterceptorConfig } from './interceptor.util';
import { isDefined } from '@core/utils';
import { InterceptorConfig } from '@core/interceptors/interceptor-config';
@Injectable() @Injectable()
export class EntityConflictInterceptor implements HttpInterceptor { export class EntityConflictInterceptor implements HttpInterceptor {
@ -67,8 +70,12 @@ export class EntityConflictInterceptor implements HttpInterceptor {
return this.openConflictDialog(request.body, error.error.message).pipe( return this.openConflictDialog(request.body, error.error.message).pipe(
switchMap(result => { switchMap(result => {
if (result) { if (isDefined(result)) {
return next.handle(this.updateRequestVersion(request)); if (result) {
return next.handle(this.updateRequestVersion(request));
}
(request.params as HttpParams & { interceptorConfig: InterceptorConfig }).interceptorConfig.ignoreErrors = true;
return throwError(() => error);
} }
return of(null); return of(null);
}) })
@ -82,7 +89,9 @@ export class EntityConflictInterceptor implements HttpInterceptor {
private openConflictDialog(entity: unknown & HasId & HasVersion, message: string): Observable<boolean> { private openConflictDialog(entity: unknown & HasId & HasVersion, message: string): Observable<boolean> {
const dialogRef = this.dialog.open(EntityConflictDialogComponent, { const dialogRef = this.dialog.open(EntityConflictDialogComponent, {
data: { message, entity } disableClose: true,
data: { message, entity },
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
}); });
return dialogRef.afterClosed(); return dialogRef.afterClosed();

23
ui-ngx/src/app/core/services/dashboard-utils.service.ts

@ -698,16 +698,9 @@ export class DashboardUtilsService {
targetState: string, targetState: string,
targetLayout: DashboardLayoutId, targetLayout: DashboardLayoutId,
widgetId: string, widgetId: string,
breakpoint: string) { breakpoint: BreakpointId) {
const dashboardConfiguration = dashboard.configuration; const layout = this.getDashboardLayoutConfig(dashboard.configuration.states[targetState].layouts[targetLayout], breakpoint);
const states = dashboardConfiguration.states; delete layout.widgets[widgetId];
const state = states[targetState];
const layout = state.layouts[targetLayout];
if (layout.breakpoints[breakpoint]) {
delete layout.breakpoints[breakpoint].widgets[widgetId];
} else {
delete layout.widgets[widgetId];
}
this.removeUnusedWidgets(dashboard); this.removeUnusedWidgets(dashboard);
} }
@ -948,7 +941,7 @@ export class DashboardUtilsService {
dashboard: Dashboard, dashboard: Dashboard,
targetState: string, targetState: string,
targetLayout: DashboardLayoutId, targetLayout: DashboardLayoutId,
breakpointId: string, breakpointId: BreakpointId,
isRemoveWidget: boolean): Widget { isRemoveWidget: boolean): Widget {
const newWidget = deepClone(widget); const newWidget = deepClone(widget);
@ -972,14 +965,14 @@ export class DashboardUtilsService {
return newWidget; return newWidget;
} }
getDashboardLayoutConfig(layout: DashboardLayout, breakpointId: string): DashboardLayout { getDashboardLayoutConfig(layout: DashboardLayout, breakpointId: BreakpointId): DashboardLayout {
if (breakpointId !== 'default') { if (breakpointId !== 'default' && layout.breakpoints) {
return layout.breakpoints[breakpointId]; return layout.breakpoints[breakpointId];
} }
return layout; return layout;
} }
getOriginalColumns(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, breakpointId: string): number { getOriginalColumns(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, breakpointId: BreakpointId): number {
let originalColumns = 24; let originalColumns = 24;
let gridSettings = null; let gridSettings = null;
const state = dashboard.configuration.states[sourceState]; const state = dashboard.configuration.states[sourceState];
@ -998,7 +991,7 @@ export class DashboardUtilsService {
} }
getOriginalSize(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, getOriginalSize(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId,
widget: Widget, breakpointId: string): WidgetSize { widget: Widget, breakpointId: BreakpointId): WidgetSize {
const layout = this.getDashboardLayoutConfig(dashboard.configuration.states[sourceState].layouts[sourceLayout], breakpointId); const layout = this.getDashboardLayoutConfig(dashboard.configuration.states[sourceState].layouts[sourceLayout], breakpointId);
const widgetLayout = layout.widgets[widget.id]; const widgetLayout = layout.widgets[widget.id];
return { return {

10
ui-ngx/src/app/core/services/item-buffer.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Dashboard, DashboardLayoutId } from '@app/shared/models/dashboard.models'; import { BreakpointId, Dashboard, DashboardLayoutId } from '@app/shared/models/dashboard.models';
import { AliasesInfo, EntityAlias, EntityAliases, EntityAliasInfo } from '@shared/models/alias.models'; import { AliasesInfo, EntityAlias, EntityAliases, EntityAliasInfo } from '@shared/models/alias.models';
import { import {
Datasource, Datasource,
@ -87,7 +87,7 @@ export class ItemBufferService {
private utils: UtilsService) {} private utils: UtilsService) {}
public prepareWidgetItem(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, public prepareWidgetItem(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId,
widget: Widget, breakpoint: string): WidgetItem { widget: Widget, breakpoint: BreakpointId): WidgetItem {
const aliasesInfo: AliasesInfo = { const aliasesInfo: AliasesInfo = {
datasourceAliases: {}, datasourceAliases: {},
targetDeviceAlias: null targetDeviceAlias: null
@ -148,13 +148,13 @@ export class ItemBufferService {
}; };
} }
public copyWidget(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget, breakpoint: string): void { public copyWidget(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget, breakpoint: BreakpointId) {
const widgetItem = this.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget, breakpoint); const widgetItem = this.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget, breakpoint);
this.storeSet(WIDGET_ITEM, widgetItem); this.storeSet(WIDGET_ITEM, widgetItem);
} }
public copyWidgetReference(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, public copyWidgetReference(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId,
widget: Widget, breakpoint: string): void { widget: Widget, breakpoint: BreakpointId): void {
const widgetReference = this.prepareWidgetReference(dashboard, sourceState, sourceLayout, widget, breakpoint); const widgetReference = this.prepareWidgetReference(dashboard, sourceState, sourceLayout, widget, breakpoint);
this.storeSet(WIDGET_REFERENCE, widgetReference); this.storeSet(WIDGET_REFERENCE, widgetReference);
} }
@ -412,7 +412,7 @@ export class ItemBufferService {
} }
private prepareWidgetReference(dashboard: Dashboard, sourceState: string, private prepareWidgetReference(dashboard: Dashboard, sourceState: string,
sourceLayout: DashboardLayoutId, widget: Widget, breakpoint: string): WidgetReference { sourceLayout: DashboardLayoutId, widget: Widget, breakpoint: BreakpointId): WidgetReference {
const originalColumns = this.dashboardUtils.getOriginalColumns(dashboard, sourceState, sourceLayout, breakpoint); const originalColumns = this.dashboardUtils.getOriginalColumns(dashboard, sourceState, sourceLayout, breakpoint);
const originalSize = this.dashboardUtils.getOriginalSize(dashboard, sourceState, sourceLayout, widget, breakpoint); const originalSize = this.dashboardUtils.getOriginalSize(dashboard, sourceState, sourceLayout, widget, breakpoint);
return { return {

903
ui-ngx/src/app/core/services/menu.models.ts

@ -14,11 +14,14 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { HasUUID } from '@shared/models/id/has-uuid'; import { AuthState } from '@core/auth/auth.models';
import { Authority } from '@shared/models/authority.enum';
import { deepClone } from '@core/utils';
export declare type MenuSectionType = 'link' | 'toggle'; export declare type MenuSectionType = 'link' | 'toggle';
export interface MenuSection extends HasUUID{ export interface MenuSection {
id: MenuId | string;
name: string; name: string;
fullName?: string; fullName?: string;
type: MenuSectionType; type: MenuSectionType;
@ -26,17 +29,901 @@ export interface MenuSection extends HasUUID{
icon: string; icon: string;
pages?: Array<MenuSection>; pages?: Array<MenuSection>;
opened?: boolean; opened?: boolean;
disabled?: boolean;
rootOnly?: boolean; rootOnly?: boolean;
customTranslate?: boolean;
} }
export interface HomeSection { export interface MenuReference {
id: MenuId;
pages?: Array<MenuReference>;
}
export interface HomeSectionReference {
name: string; name: string;
places: Array<HomeSectionPlace>; places: Array<MenuId>;
} }
export interface HomeSectionPlace { export interface HomeSection {
name: string; name: string;
icon: string; places: Array<MenuSection>;
path: string; }
export enum MenuId {
home = 'home',
tenants = 'tenants',
tenant_profiles = 'tenant_profiles',
resources = 'resources',
widget_library = 'widget_library',
widget_types = 'widget_types',
widgets_bundles = 'widgets_bundles',
images = 'images',
scada_symbols = 'scada_symbols',
resources_library = 'resources_library',
notifications_center = 'notifications_center',
notification_inbox = 'notification_inbox',
notification_sent = 'notification_sent',
notification_recipients = 'notification_recipients',
notification_templates = 'notification_templates',
notification_rules = 'notification_rules',
settings = 'settings',
general = 'general',
mail_server = 'mail_server',
home_settings = 'home_settings',
notification_settings = 'notification_settings',
repository_settings = 'repository_settings',
auto_commit_settings = 'auto_commit_settings',
queues = 'queues',
mobile_app_settings = 'mobile_app_settings',
security_settings = 'security_settings',
security_settings_general = 'security_settings_general',
two_fa = '2fa',
oauth2 = 'oauth2',
audit_log = 'audit_log',
alarms = 'alarms',
dashboards = 'dashboards',
entities = 'entities',
devices = 'devices',
assets = 'assets',
entity_views = 'entity_views',
profiles = 'profiles',
device_profiles = 'device_profiles',
asset_profiles = 'asset_profiles',
customers = 'customers',
rule_chains = 'rule_chains',
edge_management = 'edge_management',
edges = 'edges',
edge_instances = 'edge_instances',
rulechain_templates = 'rulechain_templates',
features = 'features',
otaUpdates = 'otaUpdates',
version_control = 'version_control',
api_usage = 'api_usage'
} }
declare type MenuFilter = (authState: AuthState) => boolean;
export const menuSectionMap = new Map<MenuId, MenuSection>([
[
MenuId.home,
{
id: MenuId.home,
name: 'home.home',
type: 'link',
path: '/home',
icon: 'home'
}
],
[
MenuId.tenants,
{
id: MenuId.tenants,
name: 'tenant.tenants',
type: 'link',
path: '/tenants',
icon: 'supervisor_account'
}
],
[
MenuId.tenant_profiles,
{
id: MenuId.tenant_profiles,
name: 'tenant-profile.tenant-profiles',
type: 'link',
path: '/tenantProfiles',
icon: 'mdi:alpha-t-box'
}
],
[
MenuId.resources,
{
id: MenuId.resources,
name: 'admin.resources',
type: 'toggle',
path: '/resources',
icon: 'folder'
}
],
[
MenuId.widget_library,
{
id: MenuId.widget_library,
name: 'widget.widget-library',
type: 'link',
path: '/resources/widgets-library',
icon: 'now_widgets'
}
],
[
MenuId.widget_types,
{
id: MenuId.widget_types,
name: 'widget.widgets',
type: 'link',
path: '/resources/widgets-library/widget-types',
icon: 'now_widgets'
}
],
[
MenuId.widgets_bundles,
{
id: MenuId.widgets_bundles,
name: 'widgets-bundle.widgets-bundles',
type: 'link',
path: '/resources/widgets-library/widgets-bundles',
icon: 'now_widgets'
}
],
[
MenuId.images,
{
id: MenuId.images,
name: 'image.gallery',
type: 'link',
path: '/resources/images',
icon: 'filter'
}
],
[
MenuId.scada_symbols,
{
id: MenuId.scada_symbols,
name: 'scada.symbols',
type: 'link',
path: '/resources/scada-symbols',
icon: 'view_in_ar'
}
],
[
MenuId.resources_library,
{
id: MenuId.resources_library,
name: 'resource.resources-library',
type: 'link',
path: '/resources/resources-library',
icon: 'mdi:rhombus-split'
}
],
[
MenuId.notifications_center,
{
id: MenuId.notifications_center,
name: 'notification.notification-center',
type: 'link',
path: '/notification',
icon: 'mdi:message-badge'
}
],
[
MenuId.notification_inbox,
{
id: MenuId.notification_inbox,
name: 'notification.inbox',
fullName: 'notification.notification-inbox',
type: 'link',
path: '/notification/inbox',
icon: 'inbox'
}
],
[
MenuId.notification_sent,
{
id: MenuId.notification_sent,
name: 'notification.sent',
fullName: 'notification.notification-sent',
type: 'link',
path: '/notification/sent',
icon: 'outbox'
}
],
[
MenuId.notification_recipients,
{
id: MenuId.notification_recipients,
name: 'notification.recipients',
fullName: 'notification.notification-recipients',
type: 'link',
path: '/notification/recipients',
icon: 'contacts'
}
],
[
MenuId.notification_templates,
{
id: MenuId.notification_templates,
name: 'notification.templates',
fullName: 'notification.notification-templates',
type: 'link',
path: '/notification/templates',
icon: 'mdi:message-draw'
}
],
[
MenuId.notification_rules,
{
id: MenuId.notification_rules,
name: 'notification.rules',
fullName: 'notification.notification-rules',
type: 'link',
path: '/notification/rules',
icon: 'mdi:message-cog'
}
],
[
MenuId.settings,
{
id: MenuId.settings,
name: 'admin.settings',
type: 'link',
path: '/settings',
icon: 'settings'
}
],
[
MenuId.general,
{
id: MenuId.general,
name: 'admin.general',
fullName: 'admin.general-settings',
type: 'link',
path: '/settings/general',
icon: 'settings_applications'
}
],
[
MenuId.mail_server,
{
id: MenuId.mail_server,
name: 'admin.outgoing-mail',
type: 'link',
path: '/settings/outgoing-mail',
icon: 'mail'
}
],
[
MenuId.home_settings,
{
id: MenuId.home_settings,
name: 'admin.home',
fullName: 'admin.home-settings',
type: 'link',
path: '/settings/home',
icon: 'settings_applications'
}
],
[
MenuId.notification_settings,
{
id: MenuId.notification_settings,
name: 'admin.notifications',
fullName: 'admin.notifications-settings',
type: 'link',
path: '/settings/notifications',
icon: 'mdi:message-badge'
}
],
[
MenuId.repository_settings,
{
id: MenuId.repository_settings,
name: 'admin.repository',
fullName: 'admin.repository-settings',
type: 'link',
path: '/settings/repository',
icon: 'manage_history'
}
],
[
MenuId.auto_commit_settings,
{
id: MenuId.auto_commit_settings,
name: 'admin.auto-commit',
fullName: 'admin.auto-commit-settings',
type: 'link',
path: '/settings/auto-commit',
icon: 'settings_backup_restore'
}
],
[
MenuId.queues,
{
id: MenuId.queues,
name: 'admin.queues',
type: 'link',
path: '/settings/queues',
icon: 'swap_calls'
}
],
[
MenuId.mobile_app_settings,
{
id: MenuId.mobile_app_settings,
name: 'admin.mobile-app.mobile-app',
fullName: 'admin.mobile-app.mobile-app',
type: 'link',
path: '/settings/mobile-app',
icon: 'smartphone'
}
],
[
MenuId.security_settings,
{
id: MenuId.security_settings,
name: 'security.security',
type: 'toggle',
path: '/security-settings',
icon: 'security'
}
],
[
MenuId.security_settings_general,
{
id: MenuId.security_settings_general,
name: 'admin.general',
fullName: 'security.general-settings',
type: 'link',
path: '/security-settings/general',
icon: 'settings_applications'
}
],
[
MenuId.two_fa,
{
id: MenuId.two_fa,
name: 'admin.2fa.2fa',
type: 'link',
path: '/security-settings/2fa',
icon: 'mdi:two-factor-authentication'
}
],
[
MenuId.oauth2,
{
id: MenuId.oauth2,
name: 'admin.oauth2.oauth2',
type: 'link',
path: '/security-settings/oauth2',
icon: 'mdi:shield-account'
}
],
[
MenuId.audit_log,
{
id: MenuId.audit_log,
name: 'audit-log.audit-logs',
type: 'link',
path: '/security-settings/auditLogs',
icon: 'track_changes'
}
],
[
MenuId.alarms,
{
id: MenuId.alarms,
name: 'alarm.alarms',
type: 'link',
path: '/alarms',
icon: 'mdi:alert-outline'
}
],
[
MenuId.dashboards,
{
id: MenuId.dashboards,
name: 'dashboard.dashboards',
type: 'link',
path: '/dashboards',
icon: 'dashboards'
}
],
[
MenuId.entities,
{
id: MenuId.entities,
name: 'entity.entities',
type: 'toggle',
path: '/entities',
icon: 'category'
}
],
[
MenuId.devices,
{
id: MenuId.devices,
name: 'device.devices',
type: 'link',
path: '/entities/devices',
icon: 'devices_other'
}
],
[
MenuId.assets,
{
id: MenuId.assets,
name: 'asset.assets',
type: 'link',
path: '/entities/assets',
icon: 'domain'
}
],
[
MenuId.entity_views,
{
id: MenuId.entity_views,
name: 'entity-view.entity-views',
type: 'link',
path: '/entities/entityViews',
icon: 'view_quilt'
}
],
[
MenuId.profiles,
{
id: MenuId.profiles,
name: 'profiles.profiles',
type: 'toggle',
path: '/profiles',
icon: 'badge'
}
],
[
MenuId.device_profiles,
{
id: MenuId.device_profiles,
name: 'device-profile.device-profiles',
type: 'link',
path: '/profiles/deviceProfiles',
icon: 'mdi:alpha-d-box'
}
],
[
MenuId.asset_profiles,
{
id: MenuId.asset_profiles,
name: 'asset-profile.asset-profiles',
type: 'link',
path: '/profiles/assetProfiles',
icon: 'mdi:alpha-a-box'
}
],
[
MenuId.customers,
{
id: MenuId.customers,
name: 'customer.customers',
type: 'link',
path: '/customers',
icon: 'supervisor_account'
}
],
[
MenuId.rule_chains,
{
id: MenuId.rule_chains,
name: 'rulechain.rulechains',
type: 'link',
path: '/ruleChains',
icon: 'settings_ethernet'
}
],
[
MenuId.edge_management,
{
id: MenuId.edge_management,
name: 'edge.management',
type: 'toggle',
path: '/edgeManagement',
icon: 'settings_input_antenna'
}
],
[
MenuId.edges,
{
id: MenuId.edges,
name: 'edge.instances',
fullName: 'edge.edge-instances',
type: 'link',
path: '/edgeManagement/instances',
icon: 'router'
}
],
[
MenuId.edge_instances,
{
id: MenuId.edge_instances,
name: 'edge.edge-instances',
fullName: 'edge.edge-instances',
type: 'link',
path: '/edgeManagement/instances',
icon: 'router'
}
],
[
MenuId.rulechain_templates,
{
id: MenuId.rulechain_templates,
name: 'edge.rulechain-templates',
fullName: 'edge.edge-rulechain-templates',
type: 'link',
path: '/edgeManagement/ruleChains',
icon: 'settings_ethernet'
}
],
[
MenuId.features,
{
id: MenuId.features,
name: 'feature.advanced-features',
type: 'toggle',
path: '/features',
icon: 'construction'
}
],
[
MenuId.otaUpdates,
{
id: MenuId.otaUpdates,
name: 'ota-update.ota-updates',
type: 'link',
path: '/features/otaUpdates',
icon: 'memory'
}
],
[
MenuId.version_control,
{
id: MenuId.version_control,
name: 'version-control.version-control',
type: 'link',
path: '/features/vc',
icon: 'history'
}
],
[
MenuId.api_usage,
{
id: MenuId.api_usage,
name: 'api-usage.api-usage',
type: 'link',
path: '/usage',
icon: 'insert_chart'
}
]
]);
const menuFilters = new Map<MenuId, MenuFilter>([
[
MenuId.edges, (authState) => authState.edgesSupportEnabled
],
[
MenuId.edge_management, (authState) => authState.edgesSupportEnabled
],
[
MenuId.rulechain_templates, (authState) => authState.edgesSupportEnabled
]
]);
const defaultUserMenuMap = new Map<Authority, MenuReference[]>([
[
Authority.SYS_ADMIN,
[
{id: MenuId.home},
{id: MenuId.tenants},
{id: MenuId.tenant_profiles},
{
id: MenuId.resources,
pages: [
{
id: MenuId.widget_library,
pages: [
{id: MenuId.widget_types},
{id: MenuId.widgets_bundles}
]
},
{id: MenuId.images},
{id: MenuId.scada_symbols},
{id: MenuId.resources_library}
]
},
{
id: MenuId.notifications_center,
pages: [
{id: MenuId.notification_inbox},
{id: MenuId.notification_sent},
{id: MenuId.notification_recipients},
{id: MenuId.notification_templates},
{id: MenuId.notification_rules}
]
},
{
id: MenuId.settings,
pages: [
{id: MenuId.general},
{id: MenuId.mail_server},
{id: MenuId.notification_settings},
{id: MenuId.queues},
{id: MenuId.mobile_app_settings}
]
},
{
id: MenuId.security_settings,
pages: [
{id: MenuId.security_settings_general},
{id: MenuId.two_fa},
{id: MenuId.oauth2}
]
}
]
],
[
Authority.TENANT_ADMIN,
[
{id: MenuId.home},
{id: MenuId.alarms},
{id: MenuId.dashboards},
{
id: MenuId.entities,
pages: [
{id: MenuId.devices},
{id: MenuId.assets},
{id: MenuId.entity_views}
]
},
{
id: MenuId.profiles,
pages: [
{id: MenuId.device_profiles},
{id: MenuId.asset_profiles}
]
},
{id: MenuId.customers},
{id: MenuId.rule_chains},
{
id: MenuId.edge_management,
pages: [
{id: MenuId.edges},
{id: MenuId.rulechain_templates}
]
},
{
id: MenuId.features,
pages: [
{id: MenuId.otaUpdates},
{id: MenuId.version_control}
]
},
{
id: MenuId.resources,
pages: [
{
id: MenuId.widget_library,
pages: [
{id: MenuId.widget_types},
{id: MenuId.widgets_bundles}
]
},
{id: MenuId.images},
{id: MenuId.scada_symbols},
{id: MenuId.resources_library}
]
},
{
id: MenuId.notifications_center,
pages: [
{id: MenuId.notification_inbox},
{id: MenuId.notification_sent},
{id: MenuId.notification_recipients},
{id: MenuId.notification_templates},
{id: MenuId.notification_rules}
]
},
{id: MenuId.api_usage},
{
id: MenuId.settings,
pages: [
{id: MenuId.home_settings},
{id: MenuId.notification_settings},
{id: MenuId.repository_settings},
{id: MenuId.auto_commit_settings}
]
},
{
id: MenuId.security_settings,
pages: [
{id: MenuId.audit_log}
]
}
]
],
[
Authority.CUSTOMER_USER,
[
{id: MenuId.home},
{id: MenuId.alarms},
{id: MenuId.dashboards},
{
id: MenuId.entities,
pages: [
{id: MenuId.devices},
{id: MenuId.assets},
{id: MenuId.entity_views}
]
},
{id: MenuId.edge_instances},
{
id: MenuId.notifications_center,
pages: [
{id: MenuId.notification_inbox}
]
}
]
]
]);
const defaultHomeSectionMap = new Map<Authority, HomeSectionReference[]>([
[
Authority.SYS_ADMIN,
[
{
name: 'tenant.management',
places: [MenuId.tenants, MenuId.tenant_profiles]
},
{
name: 'widget.management',
places: [MenuId.widget_library]
},
{
name: 'admin.system-settings',
places: [MenuId.general, MenuId.mail_server,
MenuId.notification_settings, MenuId.security_settings, MenuId.oauth2, MenuId.two_fa, MenuId.resources_library, MenuId.queues]
}
]
],
[
Authority.TENANT_ADMIN,
[
{
name: 'rulechain.management',
places: [MenuId.rule_chains]
},
{
name: 'customer.management',
places: [MenuId.customers]
},
{
name: 'asset.management',
places: [MenuId.assets, MenuId.asset_profiles]
},
{
name: 'device.management',
places: [MenuId.devices, MenuId.device_profiles, MenuId.otaUpdates]
},
{
name: 'entity-view.management',
places: [MenuId.entity_views]
},
{
name: 'edge.management',
places: [MenuId.edges, MenuId.rulechain_templates]
},
{
name: 'dashboard.management',
places: [MenuId.widget_library, MenuId.dashboards]
},
{
name: 'version-control.management',
places: [MenuId.version_control]
},
{
name: 'audit-log.audit',
places: [MenuId.audit_log, MenuId.api_usage]
},
{
name: 'admin.system-settings',
places: [MenuId.home_settings, MenuId.resources_library, MenuId.repository_settings, MenuId.auto_commit_settings]
}
]
],
[
Authority.CUSTOMER_USER,
[
{
name: 'asset.view-assets',
places: [MenuId.assets]
},
{
name: 'device.view-devices',
places: [MenuId.devices]
},
{
name: 'entity-view.management',
places: [MenuId.entity_views]
},
{
name: 'edge.management',
places: [MenuId.edge_instances]
},
{
name: 'dashboard.view-dashboards',
places: [MenuId.dashboards]
}
]
]
]);
export const buildUserMenu = (authState: AuthState): Array<MenuSection> => {
const references = defaultUserMenuMap.get(authState.authUser.authority);
return (references || []).map(ref => referenceToMenuSection(authState, ref)).filter(section => !!section);
};
export const buildUserHome = (authState: AuthState, availableMenuSections: MenuSection[]): Array<HomeSection> => {
const references = defaultHomeSectionMap.get(authState.authUser.authority);
return (references || []).map(ref =>
homeReferenceToHomeSection(availableMenuSections, ref)).filter(section => !!section);
};
const referenceToMenuSection = (authState: AuthState, reference: MenuReference): MenuSection | undefined => {
if (filterMenuReference(authState, reference)) {
const section = menuSectionMap.get(reference.id);
if (section) {
const result = deepClone(section);
if (reference.pages?.length) {
result.pages = reference.pages.map(page =>
referenceToMenuSection(authState, page)).filter(page => !!page);
}
return result;
} else {
return undefined;
}
} else {
return undefined;
}
};
const filterMenuReference = (authState: AuthState, reference: MenuReference): boolean => {
const filter = menuFilters.get(reference.id);
if (filter) {
if (filter(authState)) {
if (reference.pages?.length) {
if (reference.pages.every(page => !filterMenuReference(authState, page))) {
return false;
}
}
return true;
}
return false;
} else {
return true;
}
};
const homeReferenceToHomeSection = (availableMenuSections: MenuSection[], reference: HomeSectionReference): HomeSection | undefined => {
const places = reference.places.map(id => availableMenuSections.find(m => m.id === id)).filter(p => !!p);
if (places.length) {
return {
name: reference.name,
places
};
} else {
return undefined;
}
};

983
ui-ngx/src/app/core/services/menu.service.ts

File diff suppressed because it is too large

33
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -86,7 +86,7 @@ import { Authority } from '@shared/models/authority.enum';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { AliasController } from '@core/api/alias-controller'; import { AliasController } from '@core/api/alias-controller';
import { BehaviorSubject, Observable, of, Subject, Subscription } from 'rxjs'; import { BehaviorSubject, Observable, of, Subject, Subscription, throwError } from 'rxjs';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { DashboardService } from '@core/http/dashboard.service'; import { DashboardService } from '@core/http/dashboard.service';
import { import {
@ -147,7 +147,7 @@ import { IAliasController } from '@core/api/widget-api.models';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { VersionControlComponent } from '@home/components/vc/version-control.component'; import { VersionControlComponent } from '@home/components/vc/version-control.component';
import { TbPopoverService } from '@shared/components/popover.service'; import { TbPopoverService } from '@shared/components/popover.service';
import { distinctUntilChanged, map, skip, tap } from 'rxjs/operators'; import { catchError, distinctUntilChanged, map, skip, tap } from 'rxjs/operators';
import { LayoutFixedSize, LayoutWidthType } from '@home/components/dashboard-page/layout/layout.models'; import { LayoutFixedSize, LayoutWidthType } from '@home/components/dashboard-page/layout/layout.models';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
@ -156,6 +156,7 @@ import {
MoveWidgetsDialogComponent, MoveWidgetsDialogComponent,
MoveWidgetsDialogResult MoveWidgetsDialogResult
} from '@home/components/dashboard-page/layout/move-widgets-dialog.component'; } from '@home/components/dashboard-page/layout/move-widgets-dialog.component';
import { HttpStatusCode } from '@angular/common/http';
// @dynamic // @dynamic
@Component({ @Component({
@ -1092,7 +1093,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
public saveDashboard() { public saveDashboard() {
this.translatedDashboardTitle = this.getTranslatedDashboardTitle(); this.translatedDashboardTitle = this.getTranslatedDashboardTitle();
this.setEditMode(false, false);
this.notifyDashboardUpdated(); this.notifyDashboardUpdated();
} }
@ -1204,8 +1204,33 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
data: widget data: widget
}; };
this.window.parent.postMessage(JSON.stringify(message), '*'); this.window.parent.postMessage(JSON.stringify(message), '*');
this.setEditMode(false, false);
} else { } else {
this.dashboardService.saveDashboard(this.dashboard).subscribe(); let reInitDashboard = false;
this.dashboardService.saveDashboard(this.dashboard).pipe(
catchError((err) => {
if (err.status === HttpStatusCode.Conflict) {
reInitDashboard = true;
return this.dashboardService.getDashboard(this.dashboard.id.id).pipe(
map(dashboard => this.dashboardUtils.validateAndUpdateDashboard(dashboard))
);
}
return throwError(() => err);
})
).subscribe((dashboard) => {
if (reInitDashboard) {
const dashboardPageInitData: DashboardPageInitData = {
dashboard,
currentDashboardId: dashboard.id ? dashboard.id.id : null,
widgetEditMode: this.widgetEditMode,
singlePageMode: this.singlePageMode
};
this.init(dashboardPageInitData);
} else {
this.dashboard = dashboard;
this.setEditMode(false, false);
}
});
} }
} }

2
ui-ngx/src/app/modules/home/components/dashboard-page/layout/add-new-breakpoint-dialog.component.html

@ -24,7 +24,7 @@
<mat-icon class="material-icons">close</mat-icon> <mat-icon class="material-icons">close</mat-icon>
</button> </button>
</mat-toolbar> </mat-toolbar>
<div mat-dialog-content style="width: 400px"> <div mat-dialog-content style="width: 400px; max-width: 100%;">
<form [formGroup]="addBreakpointFormGroup"> <form [formGroup]="addBreakpointFormGroup">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label translate>layout.breakpoint</mat-label> <mat-label translate>layout.breakpoint</mat-label>

2
ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.html

@ -25,7 +25,7 @@
<mat-icon class="material-icons">close</mat-icon> <mat-icon class="material-icons">close</mat-icon>
</button> </button>
</mat-toolbar> </mat-toolbar>
<div mat-dialog-content style="min-width: 300px; overflow: hidden;"> <div mat-dialog-content>
<section class="tb-form-panel no-padding no-border"> <section class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div class="fixed-title-width" translate>dashboard.layout</div> <div class="fixed-title-width" translate>dashboard.layout</div>

10
ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.scss

@ -34,7 +34,7 @@ $tb-warn: mat.get-color-from-palette(map-get($tb-theme, warn), text);
} }
.tb-layout-preview { .tb-layout-preview {
width: 120%; width: 100%;
background-color: rgba(mat.get-color-from-palette($tb-primary, 50), 0.6); background-color: rgba(mat.get-color-from-palette($tb-primary, 50), 0.6);
padding: 35px; padding: 35px;
@ -185,6 +185,14 @@ $tb-warn: mat.get-color-from-palette(map-get($tb-theme, warn), text);
} }
} }
:host-context(.tb-fullscreen-dialog .mat-mdc-dialog-container) {
.mat-mdc-dialog-content {
width: 550px;
max-width: 100%;
padding: 16px;
}
}
::ng-deep { ::ng-deep {
/* Alarm tooltip with side-to-side movement */ /* Alarm tooltip with side-to-side movement */
.tb-layout-error-tooltip-right { .tb-layout-error-tooltip-right {

14
ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts

@ -71,7 +71,7 @@ export interface DashboardLayoutSettings {
name: string; name: string;
descriptionSize?: string; descriptionSize?: string;
layout: DashboardLayout; layout: DashboardLayout;
breakpoint: string; breakpoint: BreakpointId;
} }
@Component({ @Component({
@ -103,8 +103,8 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
private submitted = false; private submitted = false;
allowBreakpointIds = []; allowBreakpointIds: BreakpointId[] = [];
selectedBreakpointIds = ['default']; selectedBreakpointIds: BreakpointId[] = ['default'];
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router, protected router: Router,
@ -293,8 +293,10 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
this.layouts.main.gridSettings.layoutType = layoutType; this.layouts.main.gridSettings.layoutType = layoutType;
if (!this.isDividerLayout) { if (!this.isDividerLayout) {
delete this.layouts.right; delete this.layouts.right;
for (const breakpoint of Object.values(this.layouts.main.breakpoints)) { if (this.layouts.main.breakpoints) {
breakpoint.gridSettings.layoutType = layoutType; for (const breakpoint of Object.values(this.layouts.main.breakpoints)) {
breakpoint.gridSettings.layoutType = layoutType;
}
} }
} else { } else {
delete this.layouts.main.breakpoints; delete this.layouts.main.breakpoints;
@ -442,7 +444,7 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
}); });
} }
deleteBreakpoint($event: Event, breakpointId: string): void { deleteBreakpoint($event: Event, breakpointId: BreakpointId): void {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }

4
ui-ngx/src/app/modules/home/components/dashboard-page/layout/select-dashboard-breakpoint.component.ts

@ -32,7 +32,7 @@ export class SelectDashboardBreakpointComponent implements OnInit, OnDestroy {
selectedBreakpoint: BreakpointId = 'default'; selectedBreakpoint: BreakpointId = 'default';
breakpointIds: Array<string> = ['default']; breakpointIds: Array<BreakpointId> = ['default'];
private layoutDataChanged$: Subscription; private layoutDataChanged$: Subscription;
@ -42,7 +42,7 @@ export class SelectDashboardBreakpointComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.layoutDataChanged$ = this.dashboardCtrl.layouts.main.layoutCtx.layoutDataChanged.subscribe(() => { this.layoutDataChanged$ = this.dashboardCtrl.layouts.main.layoutCtx.layoutDataChanged.subscribe(() => {
if (this.dashboardCtrl.layouts.main.layoutCtx.layoutData) { if (this.dashboardCtrl.layouts.main.layoutCtx.layoutData) {
this.breakpointIds = Object.keys(this.dashboardCtrl.layouts.main.layoutCtx?.layoutData); this.breakpointIds = Object.keys(this.dashboardCtrl.layouts.main.layoutCtx?.layoutData) as BreakpointId[];
this.breakpointIds.sort((a, b) => { this.breakpointIds.sort((a, b) => {
const aMaxWidth = this.dashboardUtils.getBreakpointInfoById(a)?.maxWidth || Infinity; const aMaxWidth = this.dashboardUtils.getBreakpointInfoById(a)?.maxWidth || Infinity;
const bMaxWidth = this.dashboardUtils.getBreakpointInfoById(b)?.maxWidth || Infinity; const bMaxWidth = this.dashboardUtils.getBreakpointInfoById(b)?.maxWidth || Infinity;

6
ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts

@ -403,7 +403,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
this.cd.detectChanges(); this.cd.detectChanges();
} }
updateData(closeDetails: boolean = true) { updateData(closeDetails: boolean = true, reloadEntity: boolean = true) {
if (closeDetails) { if (closeDetails) {
this.isDetailsOpen = false; this.isDetailsOpen = false;
} }
@ -428,7 +428,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
timePageLink.endTime = interval.endTime; timePageLink.endTime = interval.endTime;
} }
this.dataSource.loadEntities(this.pageLink); this.dataSource.loadEntities(this.pageLink);
if (this.isDetailsOpen && this.entityDetailsPanel) { if (reloadEntity && this.isDetailsOpen && this.entityDetailsPanel) {
this.entityDetailsPanel.reloadEntity(); this.entityDetailsPanel.reloadEntity();
} }
} }
@ -512,7 +512,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
} }
onEntityUpdated(entity: BaseData<HasId>) { onEntityUpdated(entity: BaseData<HasId>) {
this.updateData(false); this.updateData(false, false);
this.entitiesTableConfig.entityUpdated(entity); this.entitiesTableConfig.entityUpdated(entity);
} }

16
ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts

@ -40,11 +40,12 @@ import { UntypedFormGroup } from '@angular/forms';
import { EntityComponent } from './entity.component'; import { EntityComponent } from './entity.component';
import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
import { EntityAction } from '@home/models/entity/entity-component.models'; import { EntityAction } from '@home/models/entity/entity-component.models';
import { Observable, ReplaySubject, Subscription } from 'rxjs'; import { Observable, ReplaySubject, Subscription, throwError } from 'rxjs';
import { MatTab, MatTabGroup } from '@angular/material/tabs'; import { MatTab, MatTabGroup } from '@angular/material/tabs';
import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component'; import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component';
import { deepClone, mergeDeep } from '@core/utils'; import { deepClone, mergeDeep } from '@core/utils';
import { entityIdEquals } from '@shared/models/id/entity-id'; import { catchError } from 'rxjs/operators';
import { HttpStatusCode } from '@angular/common/http';
@Component({ @Component({
selector: 'tb-entity-details-panel', selector: 'tb-entity-details-panel',
@ -288,7 +289,16 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
editingEntity.additionalInfo = editingEntity.additionalInfo =
mergeDeep((this.editingEntity as any).additionalInfo, this.entityComponent.entityFormValue()?.additionalInfo); mergeDeep((this.editingEntity as any).additionalInfo, this.entityComponent.entityFormValue()?.additionalInfo);
} }
this.entitiesTableConfig.saveEntity(editingEntity, this.editingEntity).subscribe( this.entitiesTableConfig.saveEntity(editingEntity, this.editingEntity)
.pipe(
catchError((err) => {
if (err.status === HttpStatusCode.Conflict) {
return this.entitiesTableConfig.loadEntity(this.currentEntityId);
}
return throwError(() => err);
})
)
.subscribe(
(entity) => { (entity) => {
this.entity = entity; this.entity = entity;
this.entityComponent.entity = entity; this.entityComponent.entity = entity;

2
ui-ngx/src/app/modules/home/components/router-tabs.component.ts

@ -107,7 +107,7 @@ export class RouterTabsComponent extends PageComponent implements OnInit {
if (found) { if (found) {
const rootPath = sectionPath.substring(0, sectionPath.length - found.path.length); const rootPath = sectionPath.substring(0, sectionPath.length - found.path.length);
const isRoot = rootPath === ''; const isRoot = rootPath === '';
const tabs: Array<MenuSection> = found ? found.pages.filter(page => !page.disabled && (!page.rootOnly || isRoot)) : []; const tabs: Array<MenuSection> = found ? found.pages.filter(page => !page.rootOnly || isRoot) : [];
return tabs.map((tab) => ({...tab, path: rootPath + tab.path})); return tabs.map((tab) => ({...tab, path: rootPath + tab.path}));
} }
return []; return [];

21
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.html

@ -84,6 +84,27 @@
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{ 'gateway.hints.poll-period' | translate }}">
<div tbTruncateWithTooltip>{{ 'gateway.poll-period' | translate }}</div>
</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="50" name="value"
formControlName="pollPeriodInMillis" placeholder="{{ 'gateway.set' | translate }}"/>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'gateway.poll-period-error' | translate: {min: 50}"
*ngIf="(serverConfigFormGroup.get('pollPeriodInMillis').hasError('required') ||
serverConfigFormGroup.get('pollPeriodInMillis').hasError('min')) &&
serverConfigFormGroup.get('pollPeriodInMillis').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
</div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center"> <div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{ 'gateway.hints.sub-check-period' | translate }}"> <div class="fixed-title-width" tb-hint-tooltip-icon="{{ 'gateway.hints.sub-check-period' | translate }}">
<div tbTruncateWithTooltip>{{ 'gateway.sub-check-period' | translate }}</div> <div tbTruncateWithTooltip>{{ 'gateway.sub-check-period' | translate }}</div>

8
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts

@ -38,6 +38,7 @@ import { takeUntil } from 'rxjs/operators';
import { import {
SecurityConfigComponent SecurityConfigComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; } from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component';
import { HOUR } from '@shared/models/time/time.models';
@Component({ @Component({
selector: 'tb-opc-server-config', selector: 'tb-opc-server-config',
@ -78,7 +79,8 @@ export class OpcServerConfigComponent implements ControlValueAccessor, Validator
name: ['', []], name: ['', []],
url: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], url: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
timeoutInMillis: [1000, [Validators.required, Validators.min(1000)]], timeoutInMillis: [1000, [Validators.required, Validators.min(1000)]],
scanPeriodInMillis: [1000, [Validators.required, Validators.min(1000)]], scanPeriodInMillis: [HOUR, [Validators.required, Validators.min(1000)]],
pollPeriodInMillis: [5000, [Validators.required, Validators.min(50)]],
enableSubscriptions: [true, []], enableSubscriptions: [true, []],
subCheckPeriodInMillis: [100, [Validators.required, Validators.min(100)]], subCheckPeriodInMillis: [100, [Validators.required, Validators.min(100)]],
showMap: [false, []], showMap: [false, []],
@ -116,7 +118,8 @@ export class OpcServerConfigComponent implements ControlValueAccessor, Validator
writeValue(serverConfig: ServerConfig): void { writeValue(serverConfig: ServerConfig): void {
const { const {
timeoutInMillis = 1000, timeoutInMillis = 1000,
scanPeriodInMillis = 1000, scanPeriodInMillis = HOUR,
pollPeriodInMillis = 5000,
enableSubscriptions = true, enableSubscriptions = true,
subCheckPeriodInMillis = 100, subCheckPeriodInMillis = 100,
showMap = false, showMap = false,
@ -128,6 +131,7 @@ export class OpcServerConfigComponent implements ControlValueAccessor, Validator
...serverConfig, ...serverConfig,
timeoutInMillis, timeoutInMillis,
scanPeriodInMillis, scanPeriodInMillis,
pollPeriodInMillis,
enableSubscriptions, enableSubscriptions,
subCheckPeriodInMillis, subCheckPeriodInMillis,
showMap, showMap,

78
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html

@ -726,6 +726,10 @@
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.minPackSendDelayMS').hasError('min')"> <mat-error *ngIf="gatewayConfigGroup.get('thingsboard.minPackSendDelayMS').hasError('min')">
{{ 'gateway.min-pack-send-delay-min' | translate }} {{ 'gateway.min-pack-send-delay-min' | translate }}
</mat-error> </mat-error>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.minPackSendDelayMS').hasError('pattern')">
{{ 'gateway.min-pack-send-delay-min-pattern' | translate }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.minimal-pack-delay' | translate }}">info_outlined matTooltip="{{ 'gateway.hints.minimal-pack-delay' | translate }}">info_outlined
</mat-icon> </mat-icon>
@ -747,22 +751,64 @@
</mat-icon> </mat-icon>
</mat-form-field> </mat-form-field>
</section> </section>
<mat-form-field appearance="outline" class="mat-block"> <section class="tb-form-row no-border no-padding tb-standard-fields column-xs">
<mat-label translate>gateway.statistics.check-connectors-configuration</mat-label> <mat-form-field appearance="outline" class="flex">
<input matInput formControlName="checkConnectorsConfigurationInSeconds" type="number" min="0"/> <mat-label translate>gateway.statistics.check-connectors-configuration</mat-label>
<mat-error <input matInput formControlName="checkConnectorsConfigurationInSeconds" type="number" min="0"/>
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')"> <mat-error
{{ 'gateway.statistics.check-connectors-configuration-required' | translate }} *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')">
</mat-error> {{ 'gateway.statistics.check-connectors-configuration-required' | translate }}
<mat-error </mat-error>
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('min')"> <mat-error
{{ 'gateway.statistics.check-connectors-configuration-min' | translate }} *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('min')">
</mat-error> {{ 'gateway.statistics.check-connectors-configuration-min' | translate }}
<mat-error </mat-error>
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('pattern')"> <mat-error
{{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }} *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('pattern')">
</mat-error> {{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }}
</mat-form-field> </mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.statistics.max-payload-size-bytes</mat-label>
<input matInput formControlName="maxPayloadSizeBytes" type="number" min="0"/>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.maxPayloadSizeBytes').hasError('required')">
{{ 'gateway.statistics.max-payload-size-bytes-required' | translate }}
</mat-error>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.maxPayloadSizeBytes').hasError('min')">
{{ 'gateway.statistics.max-payload-size-bytes-min' | translate }}
</mat-error>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.maxPayloadSizeBytes').hasError('pattern')">
{{ 'gateway.statistics.max-payload-size-bytes-pattern' | translate }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.max-payload-size-bytes' | translate }}">info_outlined
</mat-icon>
</mat-form-field>
</section>
<section class="tb-form-row no-border no-padding tb-standard-fields column-xs">
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.statistics.min-pack-size-to-send</mat-label>
<input matInput formControlName="minPackSizeToSend" type="number" min="0"/>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.minPackSizeToSend').hasError('required')">
{{ 'gateway.statistics.min-pack-size-to-send-required' | translate }}
</mat-error>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.minPackSizeToSend').hasError('min')">
{{ 'gateway.statistics.min-pack-size-to-send-min' | translate }}
</mat-error>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.minPackSizeToSend').hasError('pattern')">
{{ 'gateway.statistics.min-pack-size-to-send-pattern' | translate }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.min-pack-size-to-send' | translate }}">info_outlined
</mat-icon>
</mat-form-field>
</section>
</div> </div>
</div> </div>
</ng-template> </ng-template>

6
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts

@ -102,9 +102,9 @@ export class GatewayConfigurationComponent implements OnInit {
statsSendPeriodInSeconds: [3600, [Validators.required, Validators.min(60), Validators.pattern(/^-?[0-9]+$/)]], statsSendPeriodInSeconds: [3600, [Validators.required, Validators.min(60), Validators.pattern(/^-?[0-9]+$/)]],
commands: this.fb.array([], []) commands: this.fb.array([], [])
}), }),
maxPayloadSizeBytes: [1024, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], maxPayloadSizeBytes: [8196, [Validators.required, Validators.min(100), Validators.pattern(/^-?[0-9]+$/)]],
minPackSendDelayMS: [200, [Validators.required, Validators.min(0), Validators.pattern(/^-?[0-9]+$/)]], minPackSendDelayMS: [50, [Validators.required, Validators.min(10), Validators.pattern(/^-?[0-9]+$/)]],
minPackSizeToSend: [500, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], minPackSizeToSend: [500, [Validators.required, Validators.min(100), Validators.pattern(/^-?[0-9]+$/)]],
handleDeviceRenaming: [true, []], handleDeviceRenaming: [true, []],
checkingDeviceActivity: this.fb.group({ checkingDeviceActivity: this.fb.group({
checkDeviceInactivity: [false, []], checkDeviceInactivity: [false, []],

1
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts

@ -151,6 +151,7 @@ export interface ServerConfig {
url: string; url: string;
timeoutInMillis: number; timeoutInMillis: number;
scanPeriodInMillis: number; scanPeriodInMillis: number;
pollPeriodInMillis: number;
enableSubscriptions: boolean; enableSubscriptions: boolean;
subCheckPeriodInMillis: number; subCheckPeriodInMillis: number;
showMap: boolean; showMap: boolean;

11
ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-link.component.ts

@ -49,6 +49,7 @@ import { PageLink } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order'; import { Direction } from '@shared/models/page/sort-order';
import { emptyPageData, PageData } from '@shared/models/page/page-data'; import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { deepClone } from '@core/utils'; import { deepClone } from '@core/utils';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
@Component({ @Component({
selector: 'tb-quick-link', selector: 'tb-quick-link',
@ -114,6 +115,7 @@ export class QuickLinkComponent extends PageComponent implements OnInit, Control
private fb: UntypedFormBuilder, private fb: UntypedFormBuilder,
private menuService: MenuService, private menuService: MenuService,
public translate: TranslateService, public translate: TranslateService,
private customTranslate: CustomTranslatePipe,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher) { @SkipSelf() private errorStateMatcher: ErrorStateMatcher) {
super(store); super(store);
} }
@ -135,7 +137,8 @@ export class QuickLinkComponent extends PageComponent implements OnInit, Control
this.updateView(modelValue); this.updateView(modelValue);
}), }),
map(value => value ? (typeof value === 'string' ? value : map(value => value ? (typeof value === 'string' ? value :
((value as any).translated ? value.name : this.translate.instant(value.name))) : ''), ((value as any).translated ? value.name
: value.customTranslate ? this.customTranslate.transform(value.name) : this.translate.instant(value.name))) : ''),
distinctUntilChanged(), distinctUntilChanged(),
switchMap(name => this.fetchLinks(name) ), switchMap(name => this.fetchLinks(name) ),
share() share()
@ -202,7 +205,8 @@ export class QuickLinkComponent extends PageComponent implements OnInit, Control
} }
displayLinkFn = (link?: MenuSection): string | undefined => displayLinkFn = (link?: MenuSection): string | undefined =>
link ? ((link as any).translated ? link.name : this.translate.instant(link.fullName || link.name)) : undefined; link ? ((link as any).translated ? link.name : link.customTranslate ? this.customTranslate.transform(link.fullName || link.name)
: this.translate.instant(link.fullName || link.name)) : undefined;
fetchLinks(searchText?: string): Observable<Array<MenuSection>> { fetchLinks(searchText?: string): Observable<Array<MenuSection>> {
this.searchText = searchText; this.searchText = searchText;
@ -228,7 +232,8 @@ export class QuickLinkComponent extends PageComponent implements OnInit, Control
map((links) => { map((links) => {
const result = deepClone(links); const result = deepClone(links);
for (const link of result) { for (const link of result) {
link.name = this.translate.instant(link.fullName || link.name); link.name = link.customTranslate ? this.customTranslate.transform(link.fullName || link.name)
: this.translate.instant(link.fullName || link.name);
(link as any).translated = true; (link as any).translated = true;
} }
return result; return result;

2
ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html

@ -26,7 +26,7 @@
<mat-grid-tile *ngFor="let place of sectionPlaces(section)"> <mat-grid-tile *ngFor="let place of sectionPlaces(section)">
<a mat-raised-button color="primary" class="tb-card-button" href="{{place.path}}" (click)="navigate($event, place.path)"> <a mat-raised-button color="primary" class="tb-card-button" href="{{place.path}}" (click)="navigate($event, place.path)">
<tb-icon matButtonIcon class="tb-mat-96">{{place.icon}}</tb-icon> <tb-icon matButtonIcon class="tb-mat-96">{{place.icon}}</tb-icon>
<span translate>{{place.name}}</span> <span>{{place.customTranslate ? (place.name | customTranslate) : (place.name | translate)}}</span>
</a> </a>
</mat-grid-tile> </mat-grid-tile>
</mat-grid-list> </mat-grid-list>

10
ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.ts

@ -20,7 +20,7 @@ import { WidgetContext } from '@home/models/widget-component.models';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { MenuService } from '@core/services/menu.service'; import { MenuService } from '@core/services/menu.service';
import { HomeSection, HomeSectionPlace } from '@core/services/menu.models'; import { HomeSection, MenuSection } from '@core/services/menu.models';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@ -38,9 +38,7 @@ export class NavigationCardsWidgetComponent extends PageComponent implements OnI
homeSections$ = this.menuService.homeSections(); homeSections$ = this.menuService.homeSections();
showHomeSections$ = this.homeSections$.pipe( showHomeSections$ = this.homeSections$.pipe(
map((sections) => { map((sections) => sections.filter((section) => this.sectionPlaces(section).length > 0))
return sections.filter((section) => this.sectionPlaces(section).length > 0);
})
); );
cols = null; cols = null;
@ -85,11 +83,11 @@ export class NavigationCardsWidgetComponent extends PageComponent implements OnI
}); });
} }
sectionPlaces(section: HomeSection): HomeSectionPlace[] { sectionPlaces(section: HomeSection): MenuSection[] {
return section && section.places ? section.places.filter((place) => this.filterPlace(place)) : []; return section && section.places ? section.places.filter((place) => this.filterPlace(place)) : [];
} }
private filterPlace(place: HomeSectionPlace): boolean { private filterPlace(place: MenuSection): boolean {
if (this.settings.filterType === 'include') { if (this.settings.filterType === 'include') {
return this.settings.filter.includes(place.path); return this.settings.filter.includes(place.path);
} else if (this.settings.filterType === 'exclude') { } else if (this.settings.filterType === 'exclude') {

2
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -113,7 +113,7 @@ export class WidgetComponentService {
hasBasicMode: this.utils.editWidgetInfo.hasBasicMode, hasBasicMode: this.utils.editWidgetInfo.hasBasicMode,
basicModeDirective: this.utils.editWidgetInfo.basicModeDirective, basicModeDirective: this.utils.editWidgetInfo.basicModeDirective,
defaultConfig: this.utils.editWidgetInfo.defaultConfig defaultConfig: this.utils.editWidgetInfo.defaultConfig
}, new WidgetTypeId('1'), new TenantId( NULL_UUID ), undefined }, new WidgetTypeId('1'), new TenantId( NULL_UUID ), undefined, undefined
); );
} }
const initSubject = new ReplaySubject<void>(); const initSubject = new ReplaySubject<void>();

2
ui-ngx/src/app/modules/home/menu/menu-link.component.html

@ -17,5 +17,5 @@
--> -->
<a mat-button routerLinkActive="tb-active" [routerLinkActiveOptions]="{paths: 'subset', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored'}" routerLink="{{section.path}}"> <a mat-button routerLinkActive="tb-active" [routerLinkActiveOptions]="{paths: 'subset', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored'}" routerLink="{{section.path}}">
<tb-icon matButtonIcon *ngIf="section.icon !== null">{{section.icon}}</tb-icon> <tb-icon matButtonIcon *ngIf="section.icon !== null">{{section.icon}}</tb-icon>
<span>{{section.name | translate}}</span> <span>{{section.customTranslate ? (section.name | customTranslate) : (section.name | translate)}}</span>
</a> </a>

2
ui-ngx/src/app/modules/home/menu/menu-toggle.component.html

@ -17,7 +17,7 @@
--> -->
<a mat-button class="tb-button-toggle" (click)="toggleSection($event)"> <a mat-button class="tb-button-toggle" (click)="toggleSection($event)">
<tb-icon matButtonIcon *ngIf="section.icon !== null">{{section.icon}}</tb-icon> <tb-icon matButtonIcon *ngIf="section.icon !== null">{{section.icon}}</tb-icon>
<span>{{section.name | translate}}</span> <span>{{section.customTranslate ? (section.name | customTranslate) : (section.name | translate)}}</span>
<span class=" pull-right fa fa-chevron-down tb-toggle-icon" <span class=" pull-right fa fa-chevron-down tb-toggle-icon"
[ngClass]="{'tb-toggled' : section.opened}"></span> [ngClass]="{'tb-toggled' : section.opened}"></span>
</a> </a>

7
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -669,7 +669,7 @@ export const detailsToWidgetInfo = (widgetTypeDetailsEntity: WidgetTypeDetails):
}; };
export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId, export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId,
createdTime: number): WidgetType => { createdTime: number, version: number): WidgetType => {
const descriptor: WidgetTypeDescriptor = { const descriptor: WidgetTypeDescriptor = {
type: widgetInfo.type, type: widgetInfo.type,
sizeX: widgetInfo.sizeX, sizeX: widgetInfo.sizeX,
@ -692,6 +692,7 @@ export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
id, id,
tenantId, tenantId,
createdTime, createdTime,
version,
fqn: widgetTypeFqn(widgetInfo.fullFqn), fqn: widgetTypeFqn(widgetInfo.fullFqn),
name: widgetInfo.widgetName, name: widgetInfo.widgetName,
deprecated: widgetInfo.deprecated, deprecated: widgetInfo.deprecated,
@ -701,8 +702,8 @@ export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
}; };
export const toWidgetTypeDetails = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId, export const toWidgetTypeDetails = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId,
createdTime: number): WidgetTypeDetails => { createdTime: number, version: number): WidgetTypeDetails => {
const widgetTypeEntity = toWidgetType(widgetInfo, id, tenantId, createdTime); const widgetTypeEntity = toWidgetType(widgetInfo, id, tenantId, createdTime, version);
return { return {
...widgetTypeEntity, ...widgetTypeEntity,
description: widgetInfo.description, description: widgetInfo.description,

49
ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts

@ -44,6 +44,7 @@ import { ImageResourceType, IMAGES_URL_PREFIX, ResourceSubType } from '@shared/m
import { ScadaSymbolComponent } from '@home/pages/scada-symbol/scada-symbol.component'; import { ScadaSymbolComponent } from '@home/pages/scada-symbol/scada-symbol.component';
import { ImageService } from '@core/http/image.service'; import { ImageService } from '@core/http/image.service';
import { ScadaSymbolData } from '@home/pages/scada-symbol/scada-symbol-editor.models'; import { ScadaSymbolData } from '@home/pages/scada-symbol/scada-symbol-editor.models';
import { MenuId } from '@core/services/menu.models';
export const scadaSymbolResolver: ResolveFn<ScadaSymbolData> = export const scadaSymbolResolver: ResolveFn<ScadaSymbolData> =
(route: ActivatedRouteSnapshot, (route: ActivatedRouteSnapshot,
@ -67,8 +68,7 @@ const routes: Routes = [
data: { data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
breadcrumb: { breadcrumb: {
label: 'admin.resources', menuId: MenuId.resources
icon: 'folder'
} }
}, },
children: [ children: [
@ -85,8 +85,7 @@ const routes: Routes = [
path: 'images', path: 'images',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'image.gallery', menuId: MenuId.images
icon: 'filter'
} }
}, },
children: [ children: [
@ -105,8 +104,7 @@ const routes: Routes = [
path: 'scada-symbols', path: 'scada-symbols',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'scada.symbols', menuId: MenuId.scada_symbols
icon: 'view_in_ar'
} }
}, },
children: [ children: [
@ -141,8 +139,7 @@ const routes: Routes = [
path: 'resources-library', path: 'resources-library',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'resource.resources-library', menuId: MenuId.resources_library
icon: 'mdi:rhombus-split'
} }
}, },
children: [ children: [
@ -184,8 +181,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
showMainLoadingBar: false, showMainLoadingBar: false,
breadcrumb: { breadcrumb: {
label: 'admin.settings', menuId: MenuId.settings
icon: 'settings'
} }
}, },
children: [ children: [
@ -208,8 +204,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN], auth: [Authority.SYS_ADMIN],
title: 'admin.general-settings', title: 'admin.general-settings',
breadcrumb: { breadcrumb: {
label: 'admin.general', menuId: MenuId.general
icon: 'settings_applications'
} }
} }
}, },
@ -221,8 +216,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN], auth: [Authority.SYS_ADMIN],
title: 'admin.outgoing-mail-settings', title: 'admin.outgoing-mail-settings',
breadcrumb: { breadcrumb: {
label: 'admin.outgoing-mail', menuId: MenuId.mail_server
icon: 'mail'
} }
} }
}, },
@ -234,8 +228,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
title: 'admin.notifications-settings', title: 'admin.notifications-settings',
breadcrumb: { breadcrumb: {
label: 'admin.notifications', menuId: MenuId.notification_settings
icon: 'mdi:message-badge'
} }
} }
}, },
@ -243,8 +236,7 @@ const routes: Routes = [
path: 'queues', path: 'queues',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'admin.queues', menuId: MenuId.queues
icon: 'swap_calls'
} }
}, },
children: [ children: [
@ -285,8 +277,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN], auth: [Authority.TENANT_ADMIN],
title: 'admin.home-settings', title: 'admin.home-settings',
breadcrumb: { breadcrumb: {
label: 'admin.home', menuId: MenuId.home_settings
icon: 'settings_applications'
} }
} }
}, },
@ -298,8 +289,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN], auth: [Authority.TENANT_ADMIN],
title: 'admin.repository-settings', title: 'admin.repository-settings',
breadcrumb: { breadcrumb: {
label: 'admin.repository', menuId: MenuId.repository_settings
icon: 'manage_history'
} }
} }
}, },
@ -311,8 +301,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN], auth: [Authority.TENANT_ADMIN],
title: 'admin.auto-commit-settings', title: 'admin.auto-commit-settings',
breadcrumb: { breadcrumb: {
label: 'admin.auto-commit', menuId: MenuId.auto_commit_settings
icon: 'settings_backup_restore'
} }
} }
}, },
@ -324,8 +313,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN], auth: [Authority.SYS_ADMIN],
title: 'admin.mobile-app.mobile-app', title: 'admin.mobile-app.mobile-app',
breadcrumb: { breadcrumb: {
label: 'admin.mobile-app.mobile-app', menuId: MenuId.mobile_app_settings
icon: 'smartphone'
} }
} }
}, },
@ -361,8 +349,7 @@ const routes: Routes = [
data: { data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
breadcrumb: { breadcrumb: {
label: 'security.security', menuId: MenuId.security_settings
icon: 'security'
} }
}, },
children: [ children: [
@ -385,8 +372,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN], auth: [Authority.SYS_ADMIN],
title: 'admin.general', title: 'admin.general',
breadcrumb: { breadcrumb: {
label: 'admin.general', menuId: MenuId.security_settings_general
icon: 'settings_applications'
} }
} }
}, },
@ -398,8 +384,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN], auth: [Authority.SYS_ADMIN],
title: 'admin.2fa.2fa', title: 'admin.2fa.2fa',
breadcrumb: { breadcrumb: {
label: 'admin.2fa.2fa', menuId: MenuId.two_fa
icon: 'mdi:two-factor-authentication'
} }
} }
}, },

4
ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts

@ -21,6 +21,7 @@ import { Observable } from 'rxjs';
import { OAuth2Service } from '@core/http/oauth2.service'; import { OAuth2Service } from '@core/http/oauth2.service';
import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component'; import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component';
import { AlarmsMode } from '@shared/models/alarm.models'; import { AlarmsMode } from '@shared/models/alarm.models';
import { MenuId } from '@core/services/menu.models';
@Injectable() @Injectable()
export class OAuth2LoginProcessingUrlResolver implements Resolve<string> { export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
@ -41,8 +42,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'alarm.alarms', title: 'alarm.alarms',
breadcrumb: { breadcrumb: {
label: 'alarm.alarms', menuId: MenuId.alarms
icon: 'mdi:alert-outline'
}, },
isPage: true, isPage: true,
alarmsMode: AlarmsMode.ALL alarmsMode: AlarmsMode.ALL

4
ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts

@ -21,6 +21,7 @@ import { ApiUsageComponent } from '@home/pages/api-usage/api-usage.component';
import { Dashboard } from '@shared/models/dashboard.models'; import { Dashboard } from '@shared/models/dashboard.models';
import { ResourcesService } from '@core/services/resources.service'; import { ResourcesService } from '@core/services/resources.service';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { MenuId } from '@core/services/menu.models';
const apiUsageDashboardJson = '/assets/dashboard/api_usage.json'; const apiUsageDashboardJson = '/assets/dashboard/api_usage.json';
@ -38,8 +39,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN], auth: [Authority.TENANT_ADMIN],
title: 'api-usage.api-usage', title: 'api-usage.api-usage',
breadcrumb: { breadcrumb: {
label: 'api-usage.api-usage', menuId: MenuId.api_usage
icon: 'insert_chart'
} }
}, },
resolve: { resolve: {

4
ui-ngx/src/app/modules/home/pages/asset-profile/asset-profile-routing.module.ts

@ -24,14 +24,14 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { AssetProfilesTableConfigResolver } from './asset-profiles-table-config.resolver'; import { AssetProfilesTableConfigResolver } from './asset-profiles-table-config.resolver';
import { MenuId } from '@core/services/menu.models';
export const assetProfilesRoutes: Routes = [ export const assetProfilesRoutes: Routes = [
{ {
path: 'assetProfiles', path: 'assetProfiles',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'asset-profile.asset-profiles', menuId: MenuId.asset_profiles
icon: 'mdi:alpha-a-box'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts

@ -24,14 +24,14 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai
import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { MenuId } from '@core/services/menu.models';
export const assetRoutes: Routes = [ export const assetRoutes: Routes = [
{ {
path: 'assets', path: 'assets',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'asset.assets', menuId: MenuId.assets
icon: 'domain'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts

@ -18,6 +18,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { AuditLogTableComponent } from '@home/components/audit-log/audit-log-table.component'; import { AuditLogTableComponent } from '@home/components/audit-log/audit-log-table.component';
import { MenuId } from '@core/services/menu.models';
export const auditLogsRoutes: Routes = [ export const auditLogsRoutes: Routes = [
{ {
@ -27,8 +28,7 @@ export const auditLogsRoutes: Routes = [
auth: [Authority.TENANT_ADMIN], auth: [Authority.TENANT_ADMIN],
title: 'audit-log.audit-logs', title: 'audit-log.audit-logs',
breadcrumb: { breadcrumb: {
label: 'audit-log.audit-logs', menuId: MenuId.audit_log
icon: 'track_changes'
}, },
isPage: true isPage: true
} }

4
ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts

@ -31,14 +31,14 @@ import { EdgesTableConfigResolver } from '@home/pages/edge/edges-table-config.re
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component'; import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [ const routes: Routes = [
{ {
path: 'customers', path: 'customers',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'customer.customers', menuId: MenuId.customers
icon: 'supervisor_account'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts

@ -33,6 +33,7 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { MenuId } from '@core/services/menu.models';
@Injectable() @Injectable()
export class DashboardResolver implements Resolve<Dashboard> { export class DashboardResolver implements Resolve<Dashboard> {
@ -66,8 +67,7 @@ const routes: Routes = [
path: 'dashboards', path: 'dashboards',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'dashboard.dashboards', menuId: MenuId.dashboards
icon: 'dashboard'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/device-profile/device-profile-routing.module.ts

@ -24,14 +24,14 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { MenuId } from '@core/services/menu.models';
export const deviceProfilesRoutes: Routes = [ export const deviceProfilesRoutes: Routes = [
{ {
path: 'deviceProfiles', path: 'deviceProfiles',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'device-profile.device-profiles', menuId: MenuId.device_profiles
icon: 'mdi:alpha-d-box'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts

@ -26,14 +26,14 @@ import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages
import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { assetRoutes } from '@home/pages/asset/asset-routing.module'; import { assetRoutes } from '@home/pages/asset/asset-routing.module';
import { entityViewRoutes } from '@home/pages/entity-view/entity-view-routing.module'; import { entityViewRoutes } from '@home/pages/entity-view/entity-view-routing.module';
import { MenuId } from '@core/services/menu.models';
export const deviceRoutes: Routes = [ export const deviceRoutes: Routes = [
{ {
path: 'devices', path: 'devices',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'device.devices', menuId: MenuId.devices
icon: 'devices_other'
} }
}, },
children: [ children: [

10
ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts

@ -41,14 +41,14 @@ import {
} from '@home/pages/rulechain/rulechain-routing.module'; } from '@home/pages/rulechain/rulechain-routing.module';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component'; import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [ const routes: Routes = [
{ {
path: 'edgeManagement', path: 'edgeManagement',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'edge.management', menuId: MenuId.edge_management
icon: 'settings_input_antenna'
} }
}, },
children: [ children: [
@ -64,8 +64,7 @@ const routes: Routes = [
path: 'instances', path: 'instances',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'edge.instances', menuId: MenuId.edges
icon: 'router'
} }
}, },
children: [ children: [
@ -307,8 +306,7 @@ const routes: Routes = [
path: 'ruleChains', path: 'ruleChains',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'edge.rulechain-templates', menuId: MenuId.rulechain_templates
icon: 'settings_ethernet'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts

@ -24,14 +24,14 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { MenuId } from '@core/services/menu.models';
export const entityViewRoutes: Routes = [ export const entityViewRoutes: Routes = [
{ {
path: 'entityViews', path: 'entityViews',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'entity-view.entity-views', menuId: MenuId.entity_views
icon: 'view_quilt'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/features/features-routing.module.ts

@ -19,6 +19,7 @@ import { Authority } from '@shared/models/authority.enum';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { otaUpdatesRoutes } from '@home/pages/ota-update/ota-update-routing.module'; import { otaUpdatesRoutes } from '@home/pages/ota-update/ota-update-routing.module';
import { vcRoutes } from '@home/pages/vc/vc-routing.module'; import { vcRoutes } from '@home/pages/vc/vc-routing.module';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [ const routes: Routes = [
{ {
@ -26,8 +27,7 @@ const routes: Routes = [
data: { data: {
auth: [Authority.TENANT_ADMIN], auth: [Authority.TENANT_ADMIN],
breadcrumb: { breadcrumb: {
label: 'feature.advanced-features', menuId: MenuId.features
icon: 'construction'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts

@ -34,6 +34,7 @@ import {
import { EntityKeyType } from '@shared/models/query/query.models'; import { EntityKeyType } from '@shared/models/query/query.models';
import { ResourcesService } from '@core/services/resources.service'; import { ResourcesService } from '@core/services/resources.service';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { MenuId } from '@core/services/menu.models';
const sysAdminHomePageJson = '/assets/dashboard/sys_admin_home_page.json'; const sysAdminHomePageJson = '/assets/dashboard/sys_admin_home_page.json';
const tenantAdminHomePageJson = '/assets/dashboard/tenant_admin_home_page.json'; const tenantAdminHomePageJson = '/assets/dashboard/tenant_admin_home_page.json';
@ -125,8 +126,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'home.home', title: 'home.home',
breadcrumb: { breadcrumb: {
label: 'home.home', menuId: MenuId.home
icon: 'home'
} }
}, },
resolve: { resolve: {

2
ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html

@ -28,7 +28,7 @@
<mat-grid-tile *ngFor="let place of section.places"> <mat-grid-tile *ngFor="let place of section.places">
<a mat-raised-button color="primary" class="tb-card-button" routerLink="{{place.path}}"> <a mat-raised-button color="primary" class="tb-card-button" routerLink="{{place.path}}">
<tb-icon matButtonIcon class="tb-mat-96">{{place.icon}}</tb-icon> <tb-icon matButtonIcon class="tb-mat-96">{{place.icon}}</tb-icon>
<span translate>{{place.name}}</span> <span>{{place.customTranslate ? (place.name | customTranslate) : (place.name | translate)}}</span>
</a> </a>
</mat-grid-tile> </mat-grid-tile>
</mat-grid-list> </mat-grid-list>

19
ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts

@ -25,6 +25,7 @@ import { RecipientTableConfigResolver } from '@home/pages/notification/recipient
import { TemplateTableConfigResolver } from '@home/pages/notification/template/template-table-config.resolver'; import { TemplateTableConfigResolver } from '@home/pages/notification/template/template-table-config.resolver';
import { RuleTableConfigResolver } from '@home/pages/notification/rule/rule-table-config.resolver'; import { RuleTableConfigResolver } from '@home/pages/notification/rule/rule-table-config.resolver';
import { SendNotificationButtonComponent } from '@home/components/notification/send-notification-button.component'; import { SendNotificationButtonComponent } from '@home/components/notification/send-notification-button.component';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [ const routes: Routes = [
{ {
@ -33,8 +34,7 @@ const routes: Routes = [
data: { data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN], auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN],
breadcrumb: { breadcrumb: {
label: 'notification.notification-center', menuId: MenuId.notifications_center
icon: 'mdi:message-badge'
}, },
routerTabsHeaderComponent: SendNotificationButtonComponent routerTabsHeaderComponent: SendNotificationButtonComponent
}, },
@ -54,8 +54,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN], auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN],
title: 'notification.inbox', title: 'notification.inbox',
breadcrumb: { breadcrumb: {
label: 'notification.inbox', menuId: MenuId.notification_inbox
icon: 'inbox'
} }
}, },
resolve: { resolve: {
@ -69,8 +68,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'notification.sent', title: 'notification.sent',
breadcrumb: { breadcrumb: {
label: 'notification.sent', menuId: MenuId.notification_sent
icon: 'outbox'
} }
}, },
resolve: { resolve: {
@ -84,8 +82,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'notification.templates', title: 'notification.templates',
breadcrumb: { breadcrumb: {
label: 'notification.templates', menuId: MenuId.notification_templates
icon: 'mdi:message-draw'
} }
}, },
resolve: { resolve: {
@ -99,8 +96,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'notification.recipients', title: 'notification.recipients',
breadcrumb: { breadcrumb: {
label: 'notification.recipients', menuId: MenuId.notification_recipients
icon: 'contacts'
}, },
}, },
resolve: { resolve: {
@ -114,8 +110,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'notification.rules', title: 'notification.rules',
breadcrumb: { breadcrumb: {
label: 'notification.rules', menuId: MenuId.notification_rules
icon: 'mdi:message-cog'
} }
}, },
resolve: { resolve: {

1
ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html

@ -104,6 +104,7 @@
</ng-template> </ng-template>
<ng-template #recipientsList> <ng-template #recipientsList>
<tb-entity-list <tb-entity-list
syncIdsWithDB
required required
formControlName="targets" formControlName="targets"
labelText="{{ 'notification.recipients' | translate }}" labelText="{{ 'notification.recipients' | translate }}"

4
ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts

@ -23,14 +23,14 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { MenuId } from '@core/services/menu.models';
export const otaUpdatesRoutes: Routes = [ export const otaUpdatesRoutes: Routes = [
{ {
path: 'otaUpdates', path: 'otaUpdates',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'ota-update.ota-updates', menuId: MenuId.otaUpdates
icon: 'memory'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/profiles/profiles-routing.module.ts

@ -19,6 +19,7 @@ import { Authority } from '@shared/models/authority.enum';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { deviceProfilesRoutes } from '@home/pages/device-profile/device-profile-routing.module'; import { deviceProfilesRoutes } from '@home/pages/device-profile/device-profile-routing.module';
import { assetProfilesRoutes } from '@home/pages/asset-profile/asset-profile-routing.module'; import { assetProfilesRoutes } from '@home/pages/asset-profile/asset-profile-routing.module';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [ const routes: Routes = [
{ {
@ -26,8 +27,7 @@ const routes: Routes = [
data: { data: {
auth: [Authority.TENANT_ADMIN], auth: [Authority.TENANT_ADMIN],
breadcrumb: { breadcrumb: {
label: 'profiles.profiles', menuId: MenuId.profiles
icon: 'badge'
} }
}, },
children: [ children: [

5
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts

@ -39,7 +39,7 @@ import { RuleChainService } from '@core/http/rule-chain.service';
import { RuleChainPageComponent } from '@home/pages/rulechain/rulechain-page.component'; import { RuleChainPageComponent } from '@home/pages/rulechain/rulechain-page.component';
import { RuleNodeComponentDescriptor } from '@shared/models/rule-node.models'; import { RuleNodeComponentDescriptor } from '@shared/models/rule-node.models';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { ItemBufferService } from '@core/public-api'; import { ItemBufferService, MenuId } from '@core/public-api';
import { MODULES_MAP } from '@shared/public-api'; import { MODULES_MAP } from '@shared/public-api';
import { IModulesMap } from '@modules/common/modules-map.models'; import { IModulesMap } from '@modules/common/modules-map.models';
@ -127,8 +127,7 @@ const routes: Routes = [
path: 'ruleChains', path: 'ruleChains',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'rulechain.rulechains', menuId: MenuId.rule_chains
icon: 'settings_ethernet'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-routing.module.ts

@ -24,14 +24,14 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [ const routes: Routes = [
{ {
path: 'tenantProfiles', path: 'tenantProfiles',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'tenant-profile.tenant-profiles', menuId: MenuId.tenant_profiles
icon: 'mdi:alpha-t-box'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/tenant/tenant-routing.module.ts

@ -25,14 +25,14 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [ const routes: Routes = [
{ {
path: 'tenants', path: 'tenants',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'tenant.tenants', menuId: MenuId.tenants
icon: 'supervisor_account'
} }
}, },
children: [ children: [

4
ui-ngx/src/app/modules/home/pages/vc/vc-routing.module.ts

@ -19,6 +19,7 @@ import { RouterModule, Routes } from '@angular/router';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { VersionControlComponent } from '@home/components/vc/version-control.component'; import { VersionControlComponent } from '@home/components/vc/version-control.component';
import { MenuId } from '@core/services/menu.models';
export const vcRoutes: Routes = [ export const vcRoutes: Routes = [
{ {
@ -29,8 +30,7 @@ export const vcRoutes: Routes = [
auth: [Authority.TENANT_ADMIN], auth: [Authority.TENANT_ADMIN],
title: 'version-control.version-control', title: 'version-control.version-control',
breadcrumb: { breadcrumb: {
label: 'version-control.version-control', menuId: MenuId.version_control
icon: 'history'
} }
} }
} }

20
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts

@ -59,12 +59,13 @@ import {
SaveWidgetTypeAsDialogComponent, SaveWidgetTypeAsDialogComponent,
SaveWidgetTypeAsDialogResult SaveWidgetTypeAsDialogResult
} from '@home/pages/widget/save-widget-type-as-dialog.component'; } from '@home/pages/widget/save-widget-type-as-dialog.component';
import { forkJoin, mergeMap, of, Subscription } from 'rxjs'; import { forkJoin, mergeMap, of, Subscription, throwError } from 'rxjs';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models'; import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { map, tap } from 'rxjs/operators'; import { catchError, map, tap } from 'rxjs/operators';
import { beautifyCss, beautifyHtml, beautifyJs } from '@shared/models/beautify.models'; import { beautifyCss, beautifyHtml, beautifyJs } from '@shared/models/beautify.models';
import { HttpStatusCode } from '@angular/common/http';
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
// @dynamic // @dynamic
@ -569,9 +570,12 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
private commitSaveWidget() { private commitSaveWidget() {
const id = (this.widgetTypeDetails && this.widgetTypeDetails.id) ? this.widgetTypeDetails.id : undefined; const id = (this.widgetTypeDetails && this.widgetTypeDetails.id) ? this.widgetTypeDetails.id : undefined;
const version = this.widgetTypeDetails?.version ?? null;
const createdTime = (this.widgetTypeDetails && this.widgetTypeDetails.createdTime) ? this.widgetTypeDetails.createdTime : undefined; const createdTime = (this.widgetTypeDetails && this.widgetTypeDetails.createdTime) ? this.widgetTypeDetails.createdTime : undefined;
this.widgetService.saveWidgetTypeDetails(this.widget, id, createdTime).pipe( this.saveWidgetPending = false;
this.widgetService.saveWidgetTypeDetails(this.widget, id, createdTime, version).pipe(
mergeMap((widgetTypeDetails) => { mergeMap((widgetTypeDetails) => {
this.saveWidgetPending = true;
const widgetsBundleId = this.route.snapshot.params.widgetsBundleId as string; const widgetsBundleId = this.route.snapshot.params.widgetsBundleId as string;
if (widgetsBundleId && !id) { if (widgetsBundleId && !id) {
return this.widgetService.addWidgetFqnToWidgetBundle(widgetsBundleId, widgetTypeDetails.fqn).pipe( return this.widgetService.addWidgetFqnToWidgetBundle(widgetsBundleId, widgetTypeDetails.fqn).pipe(
@ -579,7 +583,13 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
); );
} }
return of(widgetTypeDetails); return of(widgetTypeDetails);
}) }),
catchError((err) => {
if (id && err.status === HttpStatusCode.Conflict) {
return this.widgetService.getWidgetTypeById(id.id);
}
return throwError(() => err);
}),
).subscribe({ ).subscribe({
next: (widgetTypeDetails) => { next: (widgetTypeDetails) => {
this.saveWidgetPending = false; this.saveWidgetPending = false;
@ -612,7 +622,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
config.title = this.widget.widgetName; config.title = this.widget.widgetName;
this.widget.defaultConfig = JSON.stringify(config); this.widget.defaultConfig = JSON.stringify(config);
this.isDirty = false; this.isDirty = false;
this.widgetService.saveWidgetTypeDetails(this.widget, undefined, undefined).pipe( this.widgetService.saveWidgetTypeDetails(this.widget, undefined, undefined, undefined).pipe(
mergeMap((widget) => { mergeMap((widget) => {
if (saveWidgetAsData.widgetBundleId) { if (saveWidgetAsData.widgetBundleId) {
return this.widgetService.addWidgetFqnToWidgetBundle(saveWidgetAsData.widgetBundleId, widget.fqn).pipe( return this.widgetService.addWidgetFqnToWidgetBundle(saveWidgetAsData.widgetBundleId, widget.fqn).pipe(

10
ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts

@ -33,6 +33,7 @@ import { WidgetTypesTableConfigResolver } from '@home/pages/widget/widget-types-
import { WidgetsBundleWidgetsComponent } from '@home/pages/widget/widgets-bundle-widgets.component'; import { WidgetsBundleWidgetsComponent } from '@home/pages/widget/widgets-bundle-widgets.component';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component'; import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { MenuId } from '@core/services/menu.models';
export interface WidgetEditorData { export interface WidgetEditorData {
widgetTypeDetails: WidgetTypeDetails; widgetTypeDetails: WidgetTypeDetails;
@ -108,8 +109,7 @@ const widgetTypesRoutes: Routes = [
path: 'widget-types', path: 'widget-types',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'widget.widgets', menuId: MenuId.widget_types
icon: 'now_widgets'
} }
}, },
children: [ children: [
@ -157,8 +157,7 @@ const widgetsBundlesRoutes: Routes = [
path: 'widgets-bundles', path: 'widgets-bundles',
data: { data: {
breadcrumb: { breadcrumb: {
label: 'widgets-bundle.widgets-bundles', menuId: MenuId.widgets_bundles
icon: 'now_widgets'
} }
}, },
children: [ children: [
@ -235,8 +234,7 @@ export const widgetsLibraryRoutes: Routes = [
data: { data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
breadcrumb: { breadcrumb: {
label: 'widget.widget-library', menuId: MenuId.widget_library
icon: 'now_widgets'
} }
}, },
children: [ children: [

8
ui-ngx/src/app/shared/components/breadcrumb.component.html

@ -17,9 +17,7 @@
--> -->
<div fxFlex class="tb-breadcrumb" fxLayout="row"> <div fxFlex class="tb-breadcrumb" fxLayout="row">
<h1 fxFlex fxHide.gt-sm *ngIf="lastBreadcrumb$ | async; let breadcrumb"> <h1 fxFlex fxHide.gt-sm *ngIf="lastBreadcrumb$ | async; let breadcrumb">
{{ breadcrumb.ignoreTranslate {{ breadcrumb.labelFunction ? breadcrumb.labelFunction() : (breadcrumb.customTranslate ? (breadcrumb.label | customTranslate) : (breadcrumb.label | translate)) }}
? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : utils.customTranslation(breadcrumb.label, breadcrumb.label))
: (breadcrumb.label | translate) }}
</h1> </h1>
<span fxHide.lt-md fxLayout="row" *ngFor="let breadcrumb of breadcrumbs$ | async; trackBy: trackByBreadcrumbs; last as isLast;" [ngSwitch]="isLast"> <span fxHide.lt-md fxLayout="row" *ngFor="let breadcrumb of breadcrumbs$ | async; trackBy: trackByBreadcrumbs; last as isLast;" [ngSwitch]="isLast">
<a *ngSwitchCase="false" [routerLink]="breadcrumb.link" [queryParams]="breadcrumb.queryParams"> <a *ngSwitchCase="false" [routerLink]="breadcrumb.link" [queryParams]="breadcrumb.queryParams">
@ -40,7 +38,5 @@
<tb-icon *ngIf="!breadcrumb.iconUrl"> <tb-icon *ngIf="!breadcrumb.iconUrl">
{{ breadcrumb.icon }} {{ breadcrumb.icon }}
</tb-icon> </tb-icon>
{{ breadcrumb.ignoreTranslate {{ breadcrumb.labelFunction ? breadcrumb.labelFunction() : (breadcrumb.customTranslate ? (breadcrumb.label | customTranslate) : (breadcrumb.label | translate)) }}
? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : utils.customTranslation(breadcrumb.label, breadcrumb.label))
: (breadcrumb.label | translate) }}
</ng-template> </ng-template>

44
ui-ngx/src/app/shared/components/breadcrumb.component.ts

@ -14,16 +14,18 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { BehaviorSubject, Subject, Subscription } from 'rxjs'; import { BehaviorSubject, Subject, Subscription } from 'rxjs';
import { BreadCrumb, BreadCrumbConfig } from './breadcrumb'; import { BreadCrumb, BreadCrumbConfig } from './breadcrumb';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router';
import { distinctUntilChanged, filter, map, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, first, map, switchMap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { guid } from '@core/utils'; import { guid } from '@core/utils';
import { BroadcastService } from '@core/services/broadcast.service'; import { BroadcastService } from '@core/services/broadcast.service';
import { ActiveComponentService } from '@core/services/active-component.service'; import { ActiveComponentService } from '@core/services/active-component.service';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { MenuSection, menuSectionMap } from '@core/services/menu.models';
import { MenuService } from '@core/services/menu.service';
@Component({ @Component({
selector: 'tb-breadcrumb', selector: 'tb-breadcrumb',
@ -44,7 +46,9 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
this.activeComponentValue = activeComponent; this.activeComponentValue = activeComponent;
if (this.activeComponentValue && this.activeComponentValue.updateBreadcrumbs) { if (this.activeComponentValue && this.activeComponentValue.updateBreadcrumbs) {
this.updateBreadcrumbsSubscription = this.activeComponentValue.updateBreadcrumbs.subscribe(() => { this.updateBreadcrumbsSubscription = this.activeComponentValue.updateBreadcrumbs.subscribe(() => {
this.breadcrumbs$.next(this.buildBreadCrumbs(this.activatedRoute.snapshot)); this.menuService.availableMenuSections().pipe(first()).subscribe((sections) => {
this.breadcrumbs$.next(this.buildBreadCrumbs(this.activatedRoute.snapshot, sections));
});
}); });
} }
} }
@ -54,7 +58,8 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
routerEventsSubscription = this.router.events.pipe( routerEventsSubscription = this.router.events.pipe(
filter((event) => event instanceof NavigationEnd ), filter((event) => event instanceof NavigationEnd ),
distinctUntilChanged(), distinctUntilChanged(),
map( () => this.buildBreadCrumbs(this.activatedRoute.snapshot) ) switchMap(() => this.menuService.availableMenuSections().pipe(first())),
map( (sections) => this.buildBreadCrumbs(this.activatedRoute.snapshot, sections) )
).subscribe(breadcrumns => this.breadcrumbs$.next(breadcrumns) ); ).subscribe(breadcrumns => this.breadcrumbs$.next(breadcrumns) );
activeComponentSubscription = this.activeComponentService.onActiveComponentChanged().subscribe(comp => this.setActiveComponent(comp)); activeComponentSubscription = this.activeComponentService.onActiveComponentChanged().subscribe(comp => this.setActiveComponent(comp));
@ -69,6 +74,7 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
private activeComponentService: ActiveComponentService, private activeComponentService: ActiveComponentService,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef,
private translate: TranslateService, private translate: TranslateService,
private menuService: MenuService,
public utils: UtilsService) { public utils: UtilsService) {
} }
@ -96,7 +102,8 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
return child; return child;
} }
buildBreadCrumbs(route: ActivatedRouteSnapshot, breadcrumbs: Array<BreadCrumb> = [], buildBreadCrumbs(route: ActivatedRouteSnapshot, availableMenuSections: MenuSection[],
breadcrumbs: Array<BreadCrumb> = [],
lastChild?: ActivatedRouteSnapshot): Array<BreadCrumb> { lastChild?: ActivatedRouteSnapshot): Array<BreadCrumb> {
if (!lastChild) { if (!lastChild) {
lastChild = this.lastChild(route); lastChild = this.lastChild(route);
@ -105,24 +112,27 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
if (route.routeConfig && route.routeConfig.data) { if (route.routeConfig && route.routeConfig.data) {
const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig<any>; const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig<any>;
if (breadcrumbConfig && !breadcrumbConfig.skip) { if (breadcrumbConfig && !breadcrumbConfig.skip) {
let label; let labelFunction: () => string;
let labelFunction; let section: MenuSection = null;
let ignoreTranslate; if (breadcrumbConfig.menuId) {
section = availableMenuSections.find(menu => menu.id === breadcrumbConfig.menuId);
if (!section) {
section = menuSectionMap.get(breadcrumbConfig.menuId);
}
}
const label = section?.name || breadcrumbConfig.label || 'home.home';
const customTranslate = section?.customTranslate || false;
if (breadcrumbConfig.labelFunction) { if (breadcrumbConfig.labelFunction) {
labelFunction = () => this.activeComponentValue ? labelFunction = () => this.activeComponentValue ?
breadcrumbConfig.labelFunction(route, this.translate, this.activeComponentValue, lastChild.data) : breadcrumbConfig.label; breadcrumbConfig.labelFunction(route, this.translate, this.activeComponentValue, lastChild.data) : label;
ignoreTranslate = true;
} else {
label = breadcrumbConfig.label || 'home.home';
ignoreTranslate = false;
} }
const icon = breadcrumbConfig.icon || 'home'; const icon = section?.icon || breadcrumbConfig.icon || 'home';
const link = [ route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/') ]; const link = [ route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/') ];
const breadcrumb = { const breadcrumb = {
id: guid(), id: guid(),
label, label,
customTranslate,
labelFunction, labelFunction,
ignoreTranslate,
icon, icon,
link, link,
queryParams: null queryParams: null
@ -131,12 +141,12 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
} }
} }
if (route.firstChild) { if (route.firstChild) {
return this.buildBreadCrumbs(route.firstChild, newBreadcrumbs, lastChild); return this.buildBreadCrumbs(route.firstChild, availableMenuSections, newBreadcrumbs, lastChild);
} }
return newBreadcrumbs; return newBreadcrumbs;
} }
trackByBreadcrumbs(index: number, breadcrumb: BreadCrumb){ trackByBreadcrumbs(_index: number, breadcrumb: BreadCrumb){
return breadcrumb.id; return breadcrumb.id;
} }
} }

8
ui-ngx/src/app/shared/components/breadcrumb.ts

@ -17,11 +17,12 @@
import { ActivatedRouteSnapshot, Params } from '@angular/router'; import { ActivatedRouteSnapshot, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { HasUUID } from '@shared/models/id/has-uuid'; import { HasUUID } from '@shared/models/id/has-uuid';
import { MenuId } from '@core/services/menu.models';
export interface BreadCrumb extends HasUUID{ export interface BreadCrumb extends HasUUID{
label: string; label: string;
customTranslate: boolean;
labelFunction?: () => string; labelFunction?: () => string;
ignoreTranslate: boolean;
icon: string; icon: string;
link: any[]; link: any[];
queryParams: Params; queryParams: Params;
@ -31,7 +32,8 @@ export type BreadCrumbLabelFunction<C> = (route: ActivatedRouteSnapshot, transla
export interface BreadCrumbConfig<C> { export interface BreadCrumbConfig<C> {
labelFunction: BreadCrumbLabelFunction<C>; labelFunction: BreadCrumbLabelFunction<C>;
label: string; menuId?: MenuId;
icon: string; label?: string;
icon?: string;
skip: boolean; skip: boolean;
} }

10
ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html

@ -16,7 +16,9 @@
--> -->
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2 class="main-label">{{ 'entity.version-conflict.label' | translate }}</h2> <h2 class="main-label">
{{ data.message }}
</h2>
<span fxFlex></span> <span fxFlex></span>
<button mat-icon-button <button mat-icon-button
(click)="onCancel()" (click)="onCancel()"
@ -26,23 +28,23 @@
</mat-toolbar> </mat-toolbar>
<div mat-dialog-content> <div mat-dialog-content>
<div class="message-container"> <div class="message-container">
<span>{{ data.message }}.</span>
<span> <span>
{{ 'entity.version-conflict.link' | translate: {{ 'entity.version-conflict.link' | translate:
{ entityType: (entityTypeTranslations.get(data.entity.id.entityType).type | translate) } { entityType: (entityTypeTranslations.get(data.entity.id.entityType).type | translate) }
}} }}
<a class="cursor-pointer" (click)="onLinkClick($event)">{{ 'entity.link' | translate }}</a>. <a class="cursor-pointer" (click)="onLinkClick($event)">{{ 'entity.link' | translate }}</a>.
</span> </span>
<br/>
<span>{{ 'entity.version-conflict.message' | translate }}</span> <span>{{ 'entity.version-conflict.message' | translate }}</span>
</div> </div>
</div> </div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
(click)="onCancel()" (click)="onDiscard()"
cdkFocusInitial cdkFocusInitial
> >
{{ 'entity.version-conflict.cancel' | translate }} {{ 'entity.version-conflict.discard' | translate }}
</button> </button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
type="submit" type="submit"

2
ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.scss

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
$conflict-dialog-width: 700px; $conflict-dialog-width: 530px;
:host { :host {
.main-label { .main-label {

4
ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts

@ -47,6 +47,10 @@ export class EntityConflictDialogComponent {
) {} ) {}
onCancel(): void { onCancel(): void {
this.dialogRef.close();
}
onDiscard(): void {
this.dialogRef.close(false); this.dialogRef.close(false);
} }

9
ui-ngx/src/app/shared/components/entity/entity-list.component.ts

@ -45,6 +45,7 @@ import { MatAutocomplete } from '@angular/material/autocomplete';
import { MatChipGrid } from '@angular/material/chips'; import { MatChipGrid } from '@angular/material/chips';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { SubscriptSizing } from '@angular/material/form-field'; import { SubscriptSizing } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({ @Component({
selector: 'tb-entity-list', selector: 'tb-entity-list',
@ -106,6 +107,10 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
@Input() @Input()
hint: string; hint: string;
@Input()
@coerceBoolean()
syncIdsWithDB = false;
@ViewChild('entityInput') entityInput: ElementRef<HTMLInputElement>; @ViewChild('entityInput') entityInput: ElementRef<HTMLInputElement>;
@ViewChild('entityAutocomplete') matAutocomplete: MatAutocomplete; @ViewChild('entityAutocomplete') matAutocomplete: MatAutocomplete;
@ViewChild('chipList', {static: true}) chipList: MatChipGrid; @ViewChild('chipList', {static: true}) chipList: MatChipGrid;
@ -189,6 +194,10 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
(entities) => { (entities) => {
this.entities = entities; this.entities = entities;
this.entityListFormGroup.get('entities').setValue(this.entities); this.entityListFormGroup.get('entities').setValue(this.entities);
if (this.syncIdsWithDB && this.modelValue.length !== entities.length) {
this.modelValue = entities.map(entity => entity.id.id);
this.propagateChange(this.modelValue);
}
} }
); );
} else { } else {

8
ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.html

@ -24,23 +24,23 @@
<mat-icon class="material-icons">close</mat-icon> <mat-icon class="material-icons">close</mat-icon>
</button> </button>
</mat-toolbar> </mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> <mat-progress-bar color="warn" mode="indeterminate" *ngIf="(isLoading$ | async) && !ignoreLoading">
</mat-progress-bar> </mat-progress-bar>
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="(isLoading$ | async) && !ignoreLoading">
<mat-checkbox [formControl]="exportWidgetsFormControl">{{ 'widgets-bundle.export-widgets-bundle-widgets-prompt' | translate }}</mat-checkbox> <mat-checkbox [formControl]="exportWidgetsFormControl">{{ 'widgets-bundle.export-widgets-bundle-widgets-prompt' | translate }}</mat-checkbox>
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async) && !ignoreLoading"
(click)="cancel()"> (click)="cancel()">
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
(click)="export()" (click)="export()"
[disabled]="(isLoading$ | async)"> [disabled]="(isLoading$ | async) && !ignoreLoading">
{{ 'action.export' | translate }} {{ 'action.export' | translate }}
</button> </button>
</div> </div>

4
ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.ts

@ -27,6 +27,7 @@ import { isDefinedAndNotNull } from '@core/utils';
export interface ExportWidgetsBundleDialogData { export interface ExportWidgetsBundleDialogData {
widgetsBundle: WidgetsBundle; widgetsBundle: WidgetsBundle;
includeBundleWidgetsInExport: boolean; includeBundleWidgetsInExport: boolean;
ignoreLoading?: boolean;
} }
export interface ExportWidgetsBundleDialogResult { export interface ExportWidgetsBundleDialogResult {
@ -44,6 +45,8 @@ export class ExportWidgetsBundleDialogComponent extends DialogComponent<ExportWi
widgetsBundle: WidgetsBundle; widgetsBundle: WidgetsBundle;
ignoreLoading = false;
exportWidgetsFormControl = new FormControl(true); exportWidgetsFormControl = new FormControl(true);
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
@ -52,6 +55,7 @@ export class ExportWidgetsBundleDialogComponent extends DialogComponent<ExportWi
public dialogRef: MatDialogRef<ExportWidgetsBundleDialogComponent, ExportWidgetsBundleDialogResult>) { public dialogRef: MatDialogRef<ExportWidgetsBundleDialogComponent, ExportWidgetsBundleDialogResult>) {
super(store, router, dialogRef); super(store, router, dialogRef);
this.widgetsBundle = data.widgetsBundle; this.widgetsBundle = data.widgetsBundle;
this.ignoreLoading = data.ignoreLoading;
if (isDefinedAndNotNull(data.includeBundleWidgetsInExport)) { if (isDefinedAndNotNull(data.includeBundleWidgetsInExport)) {
this.exportWidgetsFormControl.patchValue(data.includeBundleWidgetsInExport, {emitEvent: false}); this.exportWidgetsFormControl.patchValue(data.includeBundleWidgetsInExport, {emitEvent: false});
} }

67
ui-ngx/src/app/shared/import-export/import-export.service.ts

@ -20,7 +20,7 @@ import { TranslateService } from '@ngx-translate/core';
import { select, Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { Dashboard, DashboardLayoutId } from '@shared/models/dashboard.models'; import { BreakpointId, Dashboard, DashboardLayoutId } from '@shared/models/dashboard.models';
import { deepClone, guid, isDefined, isNotEmptyStr, isObject, isString, isUndefined } from '@core/utils'; import { deepClone, guid, isDefined, isNotEmptyStr, isObject, isString, isUndefined } from '@core/utils';
import { WINDOW } from '@core/services/window.service'; import { WINDOW } from '@core/services/window.service';
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
@ -199,7 +199,7 @@ export class ImportExportService {
} }
public exportWidget(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget, public exportWidget(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget,
widgetTitle: string, breakpoint: string) { widgetTitle: string, breakpoint: BreakpointId) {
const widgetItem = this.itembuffer.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget, breakpoint); const widgetItem = this.itembuffer.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget, breakpoint);
const widgetDefaultName = this.widgetService.getWidgetInfoFromCache(widget.typeFullFqn).widgetName; const widgetDefaultName = this.widgetService.getWidgetInfoFromCache(widget.typeFullFqn).widgetName;
let fileName = widgetDefaultName + (isNotEmptyStr(widgetTitle) ? `_${widgetTitle}` : ''); let fileName = widgetDefaultName + (isNotEmptyStr(widgetTitle) ? `_${widgetTitle}` : '');
@ -352,28 +352,7 @@ export class ImportExportService {
forkJoin(tasks).subscribe({ forkJoin(tasks).subscribe({
next: ({includeBundleWidgetsInExport, widgetsBundle}) => { next: ({includeBundleWidgetsInExport, widgetsBundle}) => {
this.dialog.open<ExportWidgetsBundleDialogComponent, ExportWidgetsBundleDialogData, this.handleExportWidgetsBundle(widgetsBundle, includeBundleWidgetsInExport);
ExportWidgetsBundleDialogResult>(ExportWidgetsBundleDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
widgetsBundle,
includeBundleWidgetsInExport
}
}).afterClosed().subscribe(
(result) => {
if (result) {
if (includeBundleWidgetsInExport !== result.exportWidgets) {
this.store.dispatch(new ActionPreferencesPutUserSettings({includeBundleWidgetsInExport: result.exportWidgets}));
}
if (result.exportWidgets) {
this.exportWidgetsBundleWithWidgetTypes(widgetsBundle);
} else {
this.exportWidgetsBundleWithWidgetTypeFqns(widgetsBundle);
}
}
}
);
}, },
error: (e) => { error: (e) => {
this.handleExportError(e, 'widgets-bundle.export-failed-error'); this.handleExportError(e, 'widgets-bundle.export-failed-error');
@ -401,6 +380,9 @@ export class ImportExportService {
})) }))
.subscribe(ruleChainData => this.exportToPc(ruleChainData, entityData.name)); .subscribe(ruleChainData => this.exportToPc(ruleChainData, entityData.name));
return; return;
case EntityType.WIDGETS_BUNDLE:
this.exportSelectedWidgetsBundle(entityData as WidgetsBundle);
return;
case EntityType.DASHBOARD: case EntityType.DASHBOARD:
preparedData = this.prepareDashboardExport(entityData as Dashboard); preparedData = this.prepareDashboardExport(entityData as Dashboard);
break; break;
@ -410,6 +392,43 @@ export class ImportExportService {
this.exportToPc(preparedData, entityData.name); this.exportToPc(preparedData, entityData.name);
} }
private exportSelectedWidgetsBundle(widgetsBundle: WidgetsBundle): void {
this.store.pipe(select(selectUserSettingsProperty( 'includeBundleWidgetsInExport'))).pipe(take(1)).subscribe({
next: (includeBundleWidgetsInExport) => {
this.handleExportWidgetsBundle(widgetsBundle, includeBundleWidgetsInExport, true);
},
error: (e) => {
this.handleExportError(e, 'widgets-bundle.export-failed-error');
}
});
}
private handleExportWidgetsBundle(widgetsBundle: WidgetsBundle, includeBundleWidgetsInExport: boolean, ignoreLoading?: boolean): void {
this.dialog.open<ExportWidgetsBundleDialogComponent, ExportWidgetsBundleDialogData,
ExportWidgetsBundleDialogResult>(ExportWidgetsBundleDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
widgetsBundle,
includeBundleWidgetsInExport,
ignoreLoading
}
}).afterClosed().subscribe(
(result) => {
if (result) {
if (includeBundleWidgetsInExport !== result.exportWidgets) {
this.store.dispatch(new ActionPreferencesPutUserSettings({includeBundleWidgetsInExport: result.exportWidgets}));
}
if (result.exportWidgets) {
this.exportWidgetsBundleWithWidgetTypes(widgetsBundle);
} else {
this.exportWidgetsBundleWithWidgetTypeFqns(widgetsBundle);
}
}
}
);
}
private exportWidgetsBundleWithWidgetTypes(widgetsBundle: WidgetsBundle) { private exportWidgetsBundleWithWidgetTypes(widgetsBundle: WidgetsBundle) {
this.widgetService.exportBundleWidgetTypesDetails(widgetsBundle.id.id).subscribe({ this.widgetService.exportBundleWidgetTypesDetails(widgetsBundle.id.id).subscribe({
next: (widgetTypesDetails) => { next: (widgetTypesDetails) => {

2
ui-ngx/src/app/shared/models/dashboard.models.ts

@ -104,7 +104,7 @@ export interface DashboardLayout {
breakpoints?: {[breakpointId in BreakpointId]?: Omit<DashboardLayout, 'breakpoints'>}; breakpoints?: {[breakpointId in BreakpointId]?: Omit<DashboardLayout, 'breakpoints'>};
} }
export declare type DashboardLayoutInfo = {[breakpointId: string]: BreakpointLayoutInfo}; export declare type DashboardLayoutInfo = {[breakpointId in BreakpointId]?: BreakpointLayoutInfo};
export interface BreakpointLayoutInfo { export interface BreakpointLayoutInfo {
widgetIds?: string[]; widgetIds?: string[];

4
ui-ngx/src/app/shared/models/widget.models.ts

@ -41,7 +41,7 @@ import { isNotEmptyStr, mergeDeepIgnoreArray } from '@core/utils';
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models'; import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models';
import { NULL_UUID } from '@shared/models/id/has-uuid'; import { NULL_UUID } from '@shared/models/id/has-uuid';
import { HasTenantId } from '@shared/models/entity.models'; import { HasTenantId, HasVersion } from '@shared/models/entity.models';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
@ -199,7 +199,7 @@ export interface WidgetControllerDescriptor {
actionSources?: {[actionSourceId: string]: WidgetActionSource}; actionSources?: {[actionSourceId: string]: WidgetActionSource};
} }
export interface BaseWidgetType extends BaseData<WidgetTypeId>, HasTenantId { export interface BaseWidgetType extends BaseData<WidgetTypeId>, HasTenantId, HasVersion {
tenantId: TenantId; tenantId: TenantId;
fqn: string; fqn: string;
name: string; name: string;

20
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2335,11 +2335,10 @@
"list-of-edges": "{ count, plural, =1 {One edge} other {List of # edges} }", "list-of-edges": "{ count, plural, =1 {One edge} other {List of # edges} }",
"edge-name-starts-with": "Edges whose names start with '{{prefix}}'", "edge-name-starts-with": "Edges whose names start with '{{prefix}}'",
"version-conflict": { "version-conflict": {
"label": "Version conflict", "message": "Do you want to overwrite existing version or discard changes and load the latest version?",
"message": "Do you want to cancel your changes or overwrite existing version?",
"link": "You can download your version of the {{entityType}} using this", "link": "You can download your version of the {{entityType}} using this",
"overwrite": "Overwrite version", "overwrite": "Overwrite version",
"cancel": "Cancel changes" "discard": "Discard changes"
}, },
"type-tb-resource": "Resource", "type-tb-resource": "Resource",
"type-tb-resources": "Resources", "type-tb-resources": "Resources",
@ -3076,7 +3075,8 @@
"method-required": "Method name is required.", "method-required": "Method name is required.",
"min-pack-send-delay": "Min pack send delay (in ms)", "min-pack-send-delay": "Min pack send delay (in ms)",
"min-pack-send-delay-required": "Min pack send delay is required", "min-pack-send-delay-required": "Min pack send delay is required",
"min-pack-send-delay-min": "Min pack send delay can not be less then 0", "min-pack-send-delay-min": "Min pack send delay can not be less then 10",
"min-pack-send-delay-pattern": "Min pack send delay is not valid",
"mode": "Mode", "mode": "Mode",
"model-name": "Model name", "model-name": "Model name",
"mqtt-version": "MQTT version", "mqtt-version": "MQTT version",
@ -3100,6 +3100,7 @@
"password-required": "Password is required.", "password-required": "Password is required.",
"permit-without-calls": "Keep alive permit without calls", "permit-without-calls": "Keep alive permit without calls",
"poll-period": "Poll period (ms)", "poll-period": "Poll period (ms)",
"poll-period-error": "Poll period should be at least {{min}} (ms).",
"port": "Port", "port": "Port",
"port-required": "Port is required.", "port-required": "Port is required.",
"port-limits-error": "Port should be number from {{min}} to {{max}}.", "port-limits-error": "Port should be number from {{min}} to {{max}}.",
@ -3279,6 +3280,14 @@
"send-period-min": "Statistic send period can not be less then 60", "send-period-min": "Statistic send period can not be less then 60",
"send-period-pattern": "Statistic send period is not valid", "send-period-pattern": "Statistic send period is not valid",
"check-connectors-configuration": "Check connectors configuration (in sec)", "check-connectors-configuration": "Check connectors configuration (in sec)",
"max-payload-size-bytes": "Max payload size in bytes",
"max-payload-size-bytes-required": "Max payload size in bytes is required",
"max-payload-size-bytes-min": "Max payload size in bytes can not be less then 100",
"max-payload-size-bytes-pattern": "Max payload size in bytes is not valid",
"min-pack-size-to-send": "Min packet size to send",
"min-pack-size-to-send-required": "Min packet size to send is required",
"min-pack-size-to-send-min": "Min packet size to send can not be less then 100",
"min-pack-size-to-send-pattern": "Min packet size to send is not valid",
"check-connectors-configuration-required": "Check connectors configuration is required", "check-connectors-configuration-required": "Check connectors configuration is required",
"check-connectors-configuration-min": "Check connectors configuration can not be less then 1", "check-connectors-configuration-min": "Check connectors configuration can not be less then 1",
"check-connectors-configuration-pattern": "Check connectors configuration is not valid", "check-connectors-configuration-pattern": "Check connectors configuration is not valid",
@ -3445,6 +3454,9 @@
"show-map": "Show nodes on scanning.", "show-map": "Show nodes on scanning.",
"method-name": "Name of method on OPC-UA server.", "method-name": "Name of method on OPC-UA server.",
"arguments": "Arguments for the method (will be overwritten by arguments from the RPC request).", "arguments": "Arguments for the method (will be overwritten by arguments from the RPC request).",
"min-pack-size-to-send": "Minimum package size for sending.",
"max-payload-size-bytes": "Maximum package size in bytes",
"poll-period": "Period in milliseconds to read data from nodes.",
"modbus": { "modbus": {
"framer-type": "Type of a framer (Socket, RTU, or ASCII), if needed.", "framer-type": "Type of a framer (Socket, RTU, or ASCII), if needed.",
"host": "Hostname or IP address of Modbus server.", "host": "Hostname or IP address of Modbus server.",

3
ui-ngx/src/assets/metadata/connector-default-configs/opcua.json

@ -3,7 +3,8 @@
"name": "OPC-UA Default Server", "name": "OPC-UA Default Server",
"url": "localhost:4840/freeopcua/server/", "url": "localhost:4840/freeopcua/server/",
"timeoutInMillis": 5000, "timeoutInMillis": 5000,
"scanPeriodInMillis": 5000, "scanPeriodInMillis": 3600000,
"pollPeriodInMillis": 5000,
"enableSubscriptions": true, "enableSubscriptions": true,
"subCheckPeriodInMillis": 100, "subCheckPeriodInMillis": 100,
"showMap": false, "showMap": false,

5
ui-ngx/src/assets/metadata/connector-default-configs/opcua_asyncio.json

@ -3,7 +3,8 @@
"name": "OPC-UA Default Server", "name": "OPC-UA Default Server",
"url": "localhost:4840/freeopcua/server/", "url": "localhost:4840/freeopcua/server/",
"timeoutInMillis": 5000, "timeoutInMillis": 5000,
"scanPeriodInMillis": 5000, "scanPeriodInMillis": 3600000,
"pollPeriodInMillis": 5000,
"disableSubscriptions": false, "disableSubscriptions": false,
"subCheckPeriodInMillis": 100, "subCheckPeriodInMillis": 100,
"showMap": false, "showMap": false,
@ -49,4 +50,4 @@
} }
] ]
} }
} }

Loading…
Cancel
Save