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,
id: WidgetTypeId,
createdTime: number,
version: number,
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,
defaultHttpOptionsFromConfig(config)).pipe(
tap((savedWidgetType) => {

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

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

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

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

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

@ -15,7 +15,7 @@
///
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 {
Datasource,
@ -87,7 +87,7 @@ export class ItemBufferService {
private utils: UtilsService) {}
public prepareWidgetItem(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId,
widget: Widget, breakpoint: string): WidgetItem {
widget: Widget, breakpoint: BreakpointId): WidgetItem {
const aliasesInfo: AliasesInfo = {
datasourceAliases: {},
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);
this.storeSet(WIDGET_ITEM, widgetItem);
}
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);
this.storeSet(WIDGET_REFERENCE, widgetReference);
}
@ -412,7 +412,7 @@ export class ItemBufferService {
}
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 originalSize = this.dashboardUtils.getOriginalSize(dashboard, sourceState, sourceLayout, widget, breakpoint);
return {

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

@ -14,11 +14,14 @@
/// 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 interface MenuSection extends HasUUID{
export interface MenuSection {
id: MenuId | string;
name: string;
fullName?: string;
type: MenuSectionType;
@ -26,17 +29,901 @@ export interface MenuSection extends HasUUID{
icon: string;
pages?: Array<MenuSection>;
opened?: boolean;
disabled?: boolean;
rootOnly?: boolean;
customTranslate?: boolean;
}
export interface HomeSection {
export interface MenuReference {
id: MenuId;
pages?: Array<MenuReference>;
}
export interface HomeSectionReference {
name: string;
places: Array<HomeSectionPlace>;
places: Array<MenuId>;
}
export interface HomeSectionPlace {
export interface HomeSection {
name: string;
icon: string;
path: string;
places: Array<MenuSection>;
}
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 { EntityService } from '@core/http/entity.service';
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 { DashboardService } from '@core/http/dashboard.service';
import {
@ -147,7 +147,7 @@ import { IAliasController } from '@core/api/widget-api.models';
import { MatButton } from '@angular/material/button';
import { VersionControlComponent } from '@home/components/vc/version-control.component';
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 { TbPopoverComponent } from '@shared/components/popover.component';
import { ResizeObserver } from '@juggle/resize-observer';
@ -156,6 +156,7 @@ import {
MoveWidgetsDialogComponent,
MoveWidgetsDialogResult
} from '@home/components/dashboard-page/layout/move-widgets-dialog.component';
import { HttpStatusCode } from '@angular/common/http';
// @dynamic
@Component({
@ -1092,7 +1093,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
public saveDashboard() {
this.translatedDashboardTitle = this.getTranslatedDashboardTitle();
this.setEditMode(false, false);
this.notifyDashboardUpdated();
}
@ -1204,8 +1204,33 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
data: widget
};
this.window.parent.postMessage(JSON.stringify(message), '*');
this.setEditMode(false, false);
} 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>
</button>
</mat-toolbar>
<div mat-dialog-content style="width: 400px">
<div mat-dialog-content style="width: 400px; max-width: 100%;">
<form [formGroup]="addBreakpointFormGroup">
<mat-form-field appearance="outline">
<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>
</button>
</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">
<div class="tb-form-row space-between">
<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 {
width: 120%;
width: 100%;
background-color: rgba(mat.get-color-from-palette($tb-primary, 50), 0.6);
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 {
/* Alarm tooltip with side-to-side movement */
.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;
descriptionSize?: string;
layout: DashboardLayout;
breakpoint: string;
breakpoint: BreakpointId;
}
@Component({
@ -103,8 +103,8 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
private submitted = false;
allowBreakpointIds = [];
selectedBreakpointIds = ['default'];
allowBreakpointIds: BreakpointId[] = [];
selectedBreakpointIds: BreakpointId[] = ['default'];
constructor(protected store: Store<AppState>,
protected router: Router,
@ -293,8 +293,10 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
this.layouts.main.gridSettings.layoutType = layoutType;
if (!this.isDividerLayout) {
delete this.layouts.right;
for (const breakpoint of Object.values(this.layouts.main.breakpoints)) {
breakpoint.gridSettings.layoutType = layoutType;
if (this.layouts.main.breakpoints) {
for (const breakpoint of Object.values(this.layouts.main.breakpoints)) {
breakpoint.gridSettings.layoutType = layoutType;
}
}
} else {
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) {
$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';
breakpointIds: Array<string> = ['default'];
breakpointIds: Array<BreakpointId> = ['default'];
private layoutDataChanged$: Subscription;
@ -42,7 +42,7 @@ export class SelectDashboardBreakpointComponent implements OnInit, OnDestroy {
ngOnInit() {
this.layoutDataChanged$ = this.dashboardCtrl.layouts.main.layoutCtx.layoutDataChanged.subscribe(() => {
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) => {
const aMaxWidth = this.dashboardUtils.getBreakpointInfoById(a)?.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();
}
updateData(closeDetails: boolean = true) {
updateData(closeDetails: boolean = true, reloadEntity: boolean = true) {
if (closeDetails) {
this.isDetailsOpen = false;
}
@ -428,7 +428,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
timePageLink.endTime = interval.endTime;
}
this.dataSource.loadEntities(this.pageLink);
if (this.isDetailsOpen && this.entityDetailsPanel) {
if (reloadEntity && this.isDetailsOpen && this.entityDetailsPanel) {
this.entityDetailsPanel.reloadEntity();
}
}
@ -512,7 +512,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
}
onEntityUpdated(entity: BaseData<HasId>) {
this.updateData(false);
this.updateData(false, false);
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 { TbAnchorComponent } from '@shared/components/tb-anchor.component';
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 { EntityTabsComponent } from '@home/components/entity/entity-tabs.component';
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({
selector: 'tb-entity-details-panel',
@ -288,7 +289,16 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
editingEntity.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) => {
this.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) {
const rootPath = sectionPath.substring(0, sectionPath.length - found.path.length);
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 [];

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>
</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="fixed-title-width" tb-hint-tooltip-icon="{{ 'gateway.hints.sub-check-period' | translate }}">
<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 {
SecurityConfigComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component';
import { HOUR } from '@shared/models/time/time.models';
@Component({
selector: 'tb-opc-server-config',
@ -78,7 +79,8 @@ export class OpcServerConfigComponent implements ControlValueAccessor, Validator
name: ['', []],
url: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
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, []],
subCheckPeriodInMillis: [100, [Validators.required, Validators.min(100)]],
showMap: [false, []],
@ -116,7 +118,8 @@ export class OpcServerConfigComponent implements ControlValueAccessor, Validator
writeValue(serverConfig: ServerConfig): void {
const {
timeoutInMillis = 1000,
scanPeriodInMillis = 1000,
scanPeriodInMillis = HOUR,
pollPeriodInMillis = 5000,
enableSubscriptions = true,
subCheckPeriodInMillis = 100,
showMap = false,
@ -128,6 +131,7 @@ export class OpcServerConfigComponent implements ControlValueAccessor, Validator
...serverConfig,
timeoutInMillis,
scanPeriodInMillis,
pollPeriodInMillis,
enableSubscriptions,
subCheckPeriodInMillis,
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')">
{{ 'gateway.min-pack-send-delay-min' | translate }}
</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;"
matTooltip="{{ 'gateway.hints.minimal-pack-delay' | translate }}">info_outlined
</mat-icon>
@ -747,22 +751,64 @@
</mat-icon>
</mat-form-field>
</section>
<mat-form-field appearance="outline" class="mat-block">
<mat-label translate>gateway.statistics.check-connectors-configuration</mat-label>
<input matInput formControlName="checkConnectorsConfigurationInSeconds" type="number" min="0"/>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')">
{{ 'gateway.statistics.check-connectors-configuration-required' | translate }}
</mat-error>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('min')">
{{ 'gateway.statistics.check-connectors-configuration-min' | translate }}
</mat-error>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('pattern')">
{{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }}
</mat-error>
</mat-form-field>
<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.check-connectors-configuration</mat-label>
<input matInput formControlName="checkConnectorsConfigurationInSeconds" type="number" min="0"/>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')">
{{ 'gateway.statistics.check-connectors-configuration-required' | translate }}
</mat-error>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('min')">
{{ 'gateway.statistics.check-connectors-configuration-min' | translate }}
</mat-error>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('pattern')">
{{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }}
</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>
</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]+$/)]],
commands: this.fb.array([], [])
}),
maxPayloadSizeBytes: [1024, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]],
minPackSendDelayMS: [200, [Validators.required, Validators.min(0), Validators.pattern(/^-?[0-9]+$/)]],
minPackSizeToSend: [500, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]],
maxPayloadSizeBytes: [8196, [Validators.required, Validators.min(100), Validators.pattern(/^-?[0-9]+$/)]],
minPackSendDelayMS: [50, [Validators.required, Validators.min(10), Validators.pattern(/^-?[0-9]+$/)]],
minPackSizeToSend: [500, [Validators.required, Validators.min(100), Validators.pattern(/^-?[0-9]+$/)]],
handleDeviceRenaming: [true, []],
checkingDeviceActivity: this.fb.group({
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;
timeoutInMillis: number;
scanPeriodInMillis: number;
pollPeriodInMillis: number;
enableSubscriptions: boolean;
subCheckPeriodInMillis: number;
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 { emptyPageData, PageData } from '@shared/models/page/page-data';
import { deepClone } from '@core/utils';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
@Component({
selector: 'tb-quick-link',
@ -114,6 +115,7 @@ export class QuickLinkComponent extends PageComponent implements OnInit, Control
private fb: UntypedFormBuilder,
private menuService: MenuService,
public translate: TranslateService,
private customTranslate: CustomTranslatePipe,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher) {
super(store);
}
@ -135,7 +137,8 @@ export class QuickLinkComponent extends PageComponent implements OnInit, Control
this.updateView(modelValue);
}),
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(),
switchMap(name => this.fetchLinks(name) ),
share()
@ -202,7 +205,8 @@ export class QuickLinkComponent extends PageComponent implements OnInit, Control
}
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>> {
this.searchText = searchText;
@ -228,7 +232,8 @@ export class QuickLinkComponent extends PageComponent implements OnInit, Control
map((links) => {
const result = deepClone(links);
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;
}
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)">
<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>
<span translate>{{place.name}}</span>
<span>{{place.customTranslate ? (place.name | customTranslate) : (place.name | translate)}}</span>
</a>
</mat-grid-tile>
</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 { AppState } from '@core/core.state';
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 { map } from 'rxjs/operators';
@ -38,9 +38,7 @@ export class NavigationCardsWidgetComponent extends PageComponent implements OnI
homeSections$ = this.menuService.homeSections();
showHomeSections$ = this.homeSections$.pipe(
map((sections) => {
return sections.filter((section) => this.sectionPlaces(section).length > 0);
})
map((sections) => sections.filter((section) => this.sectionPlaces(section).length > 0))
);
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)) : [];
}
private filterPlace(place: HomeSectionPlace): boolean {
private filterPlace(place: MenuSection): boolean {
if (this.settings.filterType === 'include') {
return this.settings.filter.includes(place.path);
} 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,
basicModeDirective: this.utils.editWidgetInfo.basicModeDirective,
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>();

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}}">
<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>

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)">
<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"
[ngClass]="{'tb-toggled' : section.opened}"></span>
</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,
createdTime: number): WidgetType => {
createdTime: number, version: number): WidgetType => {
const descriptor: WidgetTypeDescriptor = {
type: widgetInfo.type,
sizeX: widgetInfo.sizeX,
@ -692,6 +692,7 @@ export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
id,
tenantId,
createdTime,
version,
fqn: widgetTypeFqn(widgetInfo.fullFqn),
name: widgetInfo.widgetName,
deprecated: widgetInfo.deprecated,
@ -701,8 +702,8 @@ export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
};
export const toWidgetTypeDetails = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId,
createdTime: number): WidgetTypeDetails => {
const widgetTypeEntity = toWidgetType(widgetInfo, id, tenantId, createdTime);
createdTime: number, version: number): WidgetTypeDetails => {
const widgetTypeEntity = toWidgetType(widgetInfo, id, tenantId, createdTime, version);
return {
...widgetTypeEntity,
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 { ImageService } from '@core/http/image.service';
import { ScadaSymbolData } from '@home/pages/scada-symbol/scada-symbol-editor.models';
import { MenuId } from '@core/services/menu.models';
export const scadaSymbolResolver: ResolveFn<ScadaSymbolData> =
(route: ActivatedRouteSnapshot,
@ -67,8 +68,7 @@ const routes: Routes = [
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
breadcrumb: {
label: 'admin.resources',
icon: 'folder'
menuId: MenuId.resources
}
},
children: [
@ -85,8 +85,7 @@ const routes: Routes = [
path: 'images',
data: {
breadcrumb: {
label: 'image.gallery',
icon: 'filter'
menuId: MenuId.images
}
},
children: [
@ -105,8 +104,7 @@ const routes: Routes = [
path: 'scada-symbols',
data: {
breadcrumb: {
label: 'scada.symbols',
icon: 'view_in_ar'
menuId: MenuId.scada_symbols
}
},
children: [
@ -141,8 +139,7 @@ const routes: Routes = [
path: 'resources-library',
data: {
breadcrumb: {
label: 'resource.resources-library',
icon: 'mdi:rhombus-split'
menuId: MenuId.resources_library
}
},
children: [
@ -184,8 +181,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
showMainLoadingBar: false,
breadcrumb: {
label: 'admin.settings',
icon: 'settings'
menuId: MenuId.settings
}
},
children: [
@ -208,8 +204,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN],
title: 'admin.general-settings',
breadcrumb: {
label: 'admin.general',
icon: 'settings_applications'
menuId: MenuId.general
}
}
},
@ -221,8 +216,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN],
title: 'admin.outgoing-mail-settings',
breadcrumb: {
label: 'admin.outgoing-mail',
icon: 'mail'
menuId: MenuId.mail_server
}
}
},
@ -234,8 +228,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
title: 'admin.notifications-settings',
breadcrumb: {
label: 'admin.notifications',
icon: 'mdi:message-badge'
menuId: MenuId.notification_settings
}
}
},
@ -243,8 +236,7 @@ const routes: Routes = [
path: 'queues',
data: {
breadcrumb: {
label: 'admin.queues',
icon: 'swap_calls'
menuId: MenuId.queues
}
},
children: [
@ -285,8 +277,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN],
title: 'admin.home-settings',
breadcrumb: {
label: 'admin.home',
icon: 'settings_applications'
menuId: MenuId.home_settings
}
}
},
@ -298,8 +289,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN],
title: 'admin.repository-settings',
breadcrumb: {
label: 'admin.repository',
icon: 'manage_history'
menuId: MenuId.repository_settings
}
}
},
@ -311,8 +301,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN],
title: 'admin.auto-commit-settings',
breadcrumb: {
label: 'admin.auto-commit',
icon: 'settings_backup_restore'
menuId: MenuId.auto_commit_settings
}
}
},
@ -324,8 +313,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN],
title: 'admin.mobile-app.mobile-app',
breadcrumb: {
label: 'admin.mobile-app.mobile-app',
icon: 'smartphone'
menuId: MenuId.mobile_app_settings
}
}
},
@ -361,8 +349,7 @@ const routes: Routes = [
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
breadcrumb: {
label: 'security.security',
icon: 'security'
menuId: MenuId.security_settings
}
},
children: [
@ -385,8 +372,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN],
title: 'admin.general',
breadcrumb: {
label: 'admin.general',
icon: 'settings_applications'
menuId: MenuId.security_settings_general
}
}
},
@ -398,8 +384,7 @@ const routes: Routes = [
auth: [Authority.SYS_ADMIN],
title: 'admin.2fa.2fa',
breadcrumb: {
label: 'admin.2fa.2fa',
icon: 'mdi:two-factor-authentication'
menuId: MenuId.two_fa
}
}
},

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 { AlarmTableComponent } from '@home/components/alarm/alarm-table.component';
import { AlarmsMode } from '@shared/models/alarm.models';
import { MenuId } from '@core/services/menu.models';
@Injectable()
export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
@ -41,8 +42,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'alarm.alarms',
breadcrumb: {
label: 'alarm.alarms',
icon: 'mdi:alert-outline'
menuId: MenuId.alarms
},
isPage: true,
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 { ResourcesService } from '@core/services/resources.service';
import { Observable } from 'rxjs';
import { MenuId } from '@core/services/menu.models';
const apiUsageDashboardJson = '/assets/dashboard/api_usage.json';
@ -38,8 +39,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN],
title: 'api-usage.api-usage',
breadcrumb: {
label: 'api-usage.api-usage',
icon: 'insert_chart'
menuId: MenuId.api_usage
}
},
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 { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { AssetProfilesTableConfigResolver } from './asset-profiles-table-config.resolver';
import { MenuId } from '@core/services/menu.models';
export const assetProfilesRoutes: Routes = [
{
path: 'assetProfiles',
data: {
breadcrumb: {
label: 'asset-profile.asset-profiles',
icon: 'mdi:alpha-a-box'
menuId: MenuId.asset_profiles
}
},
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 { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { MenuId } from '@core/services/menu.models';
export const assetRoutes: Routes = [
{
path: 'assets',
data: {
breadcrumb: {
label: 'asset.assets',
icon: 'domain'
menuId: MenuId.assets
}
},
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 { Authority } from '@shared/models/authority.enum';
import { AuditLogTableComponent } from '@home/components/audit-log/audit-log-table.component';
import { MenuId } from '@core/services/menu.models';
export const auditLogsRoutes: Routes = [
{
@ -27,8 +28,7 @@ export const auditLogsRoutes: Routes = [
auth: [Authority.TENANT_ADMIN],
title: 'audit-log.audit-logs',
breadcrumb: {
label: 'audit-log.audit-logs',
icon: 'track_changes'
menuId: MenuId.audit_log
},
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 { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [
{
path: 'customers',
data: {
breadcrumb: {
label: 'customer.customers',
icon: 'supervisor_account'
menuId: MenuId.customers
}
},
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 { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { MenuId } from '@core/services/menu.models';
@Injectable()
export class DashboardResolver implements Resolve<Dashboard> {
@ -66,8 +67,7 @@ const routes: Routes = [
path: 'dashboards',
data: {
breadcrumb: {
label: 'dashboard.dashboards',
icon: 'dashboard'
menuId: MenuId.dashboards
}
},
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 { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { MenuId } from '@core/services/menu.models';
export const deviceProfilesRoutes: Routes = [
{
path: 'deviceProfiles',
data: {
breadcrumb: {
label: 'device-profile.device-profiles',
icon: 'mdi:alpha-d-box'
menuId: MenuId.device_profiles
}
},
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 { assetRoutes } from '@home/pages/asset/asset-routing.module';
import { entityViewRoutes } from '@home/pages/entity-view/entity-view-routing.module';
import { MenuId } from '@core/services/menu.models';
export const deviceRoutes: Routes = [
{
path: 'devices',
data: {
breadcrumb: {
label: 'device.devices',
icon: 'devices_other'
menuId: MenuId.devices
}
},
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';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [
{
path: 'edgeManagement',
data: {
breadcrumb: {
label: 'edge.management',
icon: 'settings_input_antenna'
menuId: MenuId.edge_management
}
},
children: [
@ -64,8 +64,7 @@ const routes: Routes = [
path: 'instances',
data: {
breadcrumb: {
label: 'edge.instances',
icon: 'router'
menuId: MenuId.edges
}
},
children: [
@ -307,8 +306,7 @@ const routes: Routes = [
path: 'ruleChains',
data: {
breadcrumb: {
label: 'edge.rulechain-templates',
icon: 'settings_ethernet'
menuId: MenuId.rulechain_templates
}
},
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 { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { MenuId } from '@core/services/menu.models';
export const entityViewRoutes: Routes = [
{
path: 'entityViews',
data: {
breadcrumb: {
label: 'entity-view.entity-views',
icon: 'view_quilt'
menuId: MenuId.entity_views
}
},
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 { otaUpdatesRoutes } from '@home/pages/ota-update/ota-update-routing.module';
import { vcRoutes } from '@home/pages/vc/vc-routing.module';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [
{
@ -26,8 +27,7 @@ const routes: Routes = [
data: {
auth: [Authority.TENANT_ADMIN],
breadcrumb: {
label: 'feature.advanced-features',
icon: 'construction'
menuId: MenuId.features
}
},
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 { ResourcesService } from '@core/services/resources.service';
import { isDefinedAndNotNull } from '@core/utils';
import { MenuId } from '@core/services/menu.models';
const sysAdminHomePageJson = '/assets/dashboard/sys_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],
title: 'home.home',
breadcrumb: {
label: 'home.home',
icon: 'home'
menuId: MenuId.home
}
},
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">
<a mat-raised-button color="primary" class="tb-card-button" routerLink="{{place.path}}">
<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>
</mat-grid-tile>
</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 { RuleTableConfigResolver } from '@home/pages/notification/rule/rule-table-config.resolver';
import { SendNotificationButtonComponent } from '@home/components/notification/send-notification-button.component';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [
{
@ -33,8 +34,7 @@ const routes: Routes = [
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN],
breadcrumb: {
label: 'notification.notification-center',
icon: 'mdi:message-badge'
menuId: MenuId.notifications_center
},
routerTabsHeaderComponent: SendNotificationButtonComponent
},
@ -54,8 +54,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN],
title: 'notification.inbox',
breadcrumb: {
label: 'notification.inbox',
icon: 'inbox'
menuId: MenuId.notification_inbox
}
},
resolve: {
@ -69,8 +68,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'notification.sent',
breadcrumb: {
label: 'notification.sent',
icon: 'outbox'
menuId: MenuId.notification_sent
}
},
resolve: {
@ -84,8 +82,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'notification.templates',
breadcrumb: {
label: 'notification.templates',
icon: 'mdi:message-draw'
menuId: MenuId.notification_templates
}
},
resolve: {
@ -99,8 +96,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'notification.recipients',
breadcrumb: {
label: 'notification.recipients',
icon: 'contacts'
menuId: MenuId.notification_recipients
},
},
resolve: {
@ -114,8 +110,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'notification.rules',
breadcrumb: {
label: 'notification.rules',
icon: 'mdi:message-cog'
menuId: MenuId.notification_rules
}
},
resolve: {

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

@ -104,6 +104,7 @@
</ng-template>
<ng-template #recipientsList>
<tb-entity-list
syncIdsWithDB
required
formControlName="targets"
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 { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { MenuId } from '@core/services/menu.models';
export const otaUpdatesRoutes: Routes = [
{
path: 'otaUpdates',
data: {
breadcrumb: {
label: 'ota-update.ota-updates',
icon: 'memory'
menuId: MenuId.otaUpdates
}
},
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 { deviceProfilesRoutes } from '@home/pages/device-profile/device-profile-routing.module';
import { assetProfilesRoutes } from '@home/pages/asset-profile/asset-profile-routing.module';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [
{
@ -26,8 +27,7 @@ const routes: Routes = [
data: {
auth: [Authority.TENANT_ADMIN],
breadcrumb: {
label: 'profiles.profiles',
icon: 'badge'
menuId: MenuId.profiles
}
},
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 { RuleNodeComponentDescriptor } from '@shared/models/rule-node.models';
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 { IModulesMap } from '@modules/common/modules-map.models';
@ -127,8 +127,7 @@ const routes: Routes = [
path: 'ruleChains',
data: {
breadcrumb: {
label: 'rulechain.rulechains',
icon: 'settings_ethernet'
menuId: MenuId.rule_chains
}
},
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 { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [
{
path: 'tenantProfiles',
data: {
breadcrumb: {
label: 'tenant-profile.tenant-profiles',
icon: 'mdi:alpha-t-box'
menuId: MenuId.tenant_profiles
}
},
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 { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { MenuId } from '@core/services/menu.models';
const routes: Routes = [
{
path: 'tenants',
data: {
breadcrumb: {
label: 'tenant.tenants',
icon: 'supervisor_account'
menuId: MenuId.tenants
}
},
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 { Authority } from '@shared/models/authority.enum';
import { VersionControlComponent } from '@home/components/vc/version-control.component';
import { MenuId } from '@core/services/menu.models';
export const vcRoutes: Routes = [
{
@ -29,8 +30,7 @@ export const vcRoutes: Routes = [
auth: [Authority.TENANT_ADMIN],
title: 'version-control.version-control',
breadcrumb: {
label: 'version-control.version-control',
icon: 'history'
menuId: MenuId.version_control
}
}
}

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

@ -59,12 +59,13 @@ import {
SaveWidgetTypeAsDialogComponent,
SaveWidgetTypeAsDialogResult
} 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 { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';
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 { HttpStatusCode } from '@angular/common/http';
import Timeout = NodeJS.Timeout;
// @dynamic
@ -569,9 +570,12 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
private commitSaveWidget() {
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;
this.widgetService.saveWidgetTypeDetails(this.widget, id, createdTime).pipe(
this.saveWidgetPending = false;
this.widgetService.saveWidgetTypeDetails(this.widget, id, createdTime, version).pipe(
mergeMap((widgetTypeDetails) => {
this.saveWidgetPending = true;
const widgetsBundleId = this.route.snapshot.params.widgetsBundleId as string;
if (widgetsBundleId && !id) {
return this.widgetService.addWidgetFqnToWidgetBundle(widgetsBundleId, widgetTypeDetails.fqn).pipe(
@ -579,7 +583,13 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
);
}
return of(widgetTypeDetails);
})
}),
catchError((err) => {
if (id && err.status === HttpStatusCode.Conflict) {
return this.widgetService.getWidgetTypeById(id.id);
}
return throwError(() => err);
}),
).subscribe({
next: (widgetTypeDetails) => {
this.saveWidgetPending = false;
@ -612,7 +622,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
config.title = this.widget.widgetName;
this.widget.defaultConfig = JSON.stringify(config);
this.isDirty = false;
this.widgetService.saveWidgetTypeDetails(this.widget, undefined, undefined).pipe(
this.widgetService.saveWidgetTypeDetails(this.widget, undefined, undefined, undefined).pipe(
mergeMap((widget) => {
if (saveWidgetAsData.widgetBundleId) {
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 { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { MenuId } from '@core/services/menu.models';
export interface WidgetEditorData {
widgetTypeDetails: WidgetTypeDetails;
@ -108,8 +109,7 @@ const widgetTypesRoutes: Routes = [
path: 'widget-types',
data: {
breadcrumb: {
label: 'widget.widgets',
icon: 'now_widgets'
menuId: MenuId.widget_types
}
},
children: [
@ -157,8 +157,7 @@ const widgetsBundlesRoutes: Routes = [
path: 'widgets-bundles',
data: {
breadcrumb: {
label: 'widgets-bundle.widgets-bundles',
icon: 'now_widgets'
menuId: MenuId.widgets_bundles
}
},
children: [
@ -235,8 +234,7 @@ export const widgetsLibraryRoutes: Routes = [
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
breadcrumb: {
label: 'widget.widget-library',
icon: 'now_widgets'
menuId: MenuId.widget_library
}
},
children: [

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

@ -17,9 +17,7 @@
-->
<div fxFlex class="tb-breadcrumb" fxLayout="row">
<h1 fxFlex fxHide.gt-sm *ngIf="lastBreadcrumb$ | async; let breadcrumb">
{{ breadcrumb.ignoreTranslate
? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : utils.customTranslation(breadcrumb.label, breadcrumb.label))
: (breadcrumb.label | translate) }}
{{ breadcrumb.labelFunction ? breadcrumb.labelFunction() : (breadcrumb.customTranslate ? (breadcrumb.label | customTranslate) : (breadcrumb.label | translate)) }}
</h1>
<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">
@ -40,7 +38,5 @@
<tb-icon *ngIf="!breadcrumb.iconUrl">
{{ breadcrumb.icon }}
</tb-icon>
{{ breadcrumb.ignoreTranslate
? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : utils.customTranslation(breadcrumb.label, breadcrumb.label))
: (breadcrumb.label | translate) }}
{{ breadcrumb.labelFunction ? breadcrumb.labelFunction() : (breadcrumb.customTranslate ? (breadcrumb.label | customTranslate) : (breadcrumb.label | translate)) }}
</ng-template>

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

@ -14,16 +14,18 @@
/// 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 { BreadCrumb, BreadCrumbConfig } from './breadcrumb';
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 { guid } from '@core/utils';
import { BroadcastService } from '@core/services/broadcast.service';
import { ActiveComponentService } from '@core/services/active-component.service';
import { UtilsService } from '@core/services/utils.service';
import { MenuSection, menuSectionMap } from '@core/services/menu.models';
import { MenuService } from '@core/services/menu.service';
@Component({
selector: 'tb-breadcrumb',
@ -44,7 +46,9 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
this.activeComponentValue = activeComponent;
if (this.activeComponentValue && this.activeComponentValue.updateBreadcrumbs) {
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(
filter((event) => event instanceof NavigationEnd ),
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) );
activeComponentSubscription = this.activeComponentService.onActiveComponentChanged().subscribe(comp => this.setActiveComponent(comp));
@ -69,6 +74,7 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
private activeComponentService: ActiveComponentService,
private cd: ChangeDetectorRef,
private translate: TranslateService,
private menuService: MenuService,
public utils: UtilsService) {
}
@ -96,7 +102,8 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
return child;
}
buildBreadCrumbs(route: ActivatedRouteSnapshot, breadcrumbs: Array<BreadCrumb> = [],
buildBreadCrumbs(route: ActivatedRouteSnapshot, availableMenuSections: MenuSection[],
breadcrumbs: Array<BreadCrumb> = [],
lastChild?: ActivatedRouteSnapshot): Array<BreadCrumb> {
if (!lastChild) {
lastChild = this.lastChild(route);
@ -105,24 +112,27 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
if (route.routeConfig && route.routeConfig.data) {
const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig<any>;
if (breadcrumbConfig && !breadcrumbConfig.skip) {
let label;
let labelFunction;
let ignoreTranslate;
let labelFunction: () => string;
let section: MenuSection = null;
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) {
labelFunction = () => this.activeComponentValue ?
breadcrumbConfig.labelFunction(route, this.translate, this.activeComponentValue, lastChild.data) : breadcrumbConfig.label;
ignoreTranslate = true;
} else {
label = breadcrumbConfig.label || 'home.home';
ignoreTranslate = false;
breadcrumbConfig.labelFunction(route, this.translate, this.activeComponentValue, lastChild.data) : label;
}
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 breadcrumb = {
id: guid(),
label,
customTranslate,
labelFunction,
ignoreTranslate,
icon,
link,
queryParams: null
@ -131,12 +141,12 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
}
}
if (route.firstChild) {
return this.buildBreadCrumbs(route.firstChild, newBreadcrumbs, lastChild);
return this.buildBreadCrumbs(route.firstChild, availableMenuSections, newBreadcrumbs, lastChild);
}
return newBreadcrumbs;
}
trackByBreadcrumbs(index: number, breadcrumb: BreadCrumb){
trackByBreadcrumbs(_index: number, breadcrumb: BreadCrumb){
return breadcrumb.id;
}
}

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

@ -17,11 +17,12 @@
import { ActivatedRouteSnapshot, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { HasUUID } from '@shared/models/id/has-uuid';
import { MenuId } from '@core/services/menu.models';
export interface BreadCrumb extends HasUUID{
label: string;
customTranslate: boolean;
labelFunction?: () => string;
ignoreTranslate: boolean;
icon: string;
link: any[];
queryParams: Params;
@ -31,7 +32,8 @@ export type BreadCrumbLabelFunction<C> = (route: ActivatedRouteSnapshot, transla
export interface BreadCrumbConfig<C> {
labelFunction: BreadCrumbLabelFunction<C>;
label: string;
icon: string;
menuId?: MenuId;
label?: string;
icon?: string;
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">
<h2 class="main-label">{{ 'entity.version-conflict.label' | translate }}</h2>
<h2 class="main-label">
{{ data.message }}
</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="onCancel()"
@ -26,23 +28,23 @@
</mat-toolbar>
<div mat-dialog-content>
<div class="message-container">
<span>{{ data.message }}.</span>
<span>
{{ 'entity.version-conflict.link' | translate:
{ entityType: (entityTypeTranslations.get(data.entity.id.entityType).type | translate) }
}}
<a class="cursor-pointer" (click)="onLinkClick($event)">{{ 'entity.link' | translate }}</a>.
</span>
<br/>
<span>{{ 'entity.version-conflict.message' | translate }}</span>
</div>
</div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
(click)="onCancel()"
(click)="onDiscard()"
cdkFocusInitial
>
{{ 'entity.version-conflict.cancel' | translate }}
{{ 'entity.version-conflict.discard' | translate }}
</button>
<button mat-raised-button color="primary"
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
* limitations under the License.
*/
$conflict-dialog-width: 700px;
$conflict-dialog-width: 530px;
:host {
.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 {
this.dialogRef.close();
}
onDiscard(): void {
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 { coerceBooleanProperty } from '@angular/cdk/coercion';
import { SubscriptSizing } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-entity-list',
@ -106,6 +107,10 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
@Input()
hint: string;
@Input()
@coerceBoolean()
syncIdsWithDB = false;
@ViewChild('entityInput') entityInput: ElementRef<HTMLInputElement>;
@ViewChild('entityAutocomplete') matAutocomplete: MatAutocomplete;
@ViewChild('chipList', {static: true}) chipList: MatChipGrid;
@ -189,6 +194,10 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
(entities) => {
this.entities = 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 {

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>
</button>
</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>
<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>
</fieldset>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
[disabled]="(isLoading$ | async) && !ignoreLoading"
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
(click)="export()"
[disabled]="(isLoading$ | async)">
[disabled]="(isLoading$ | async) && !ignoreLoading">
{{ 'action.export' | translate }}
</button>
</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 {
widgetsBundle: WidgetsBundle;
includeBundleWidgetsInExport: boolean;
ignoreLoading?: boolean;
}
export interface ExportWidgetsBundleDialogResult {
@ -44,6 +45,8 @@ export class ExportWidgetsBundleDialogComponent extends DialogComponent<ExportWi
widgetsBundle: WidgetsBundle;
ignoreLoading = false;
exportWidgetsFormControl = new FormControl(true);
constructor(protected store: Store<AppState>,
@ -52,6 +55,7 @@ export class ExportWidgetsBundleDialogComponent extends DialogComponent<ExportWi
public dialogRef: MatDialogRef<ExportWidgetsBundleDialogComponent, ExportWidgetsBundleDialogResult>) {
super(store, router, dialogRef);
this.widgetsBundle = data.widgetsBundle;
this.ignoreLoading = data.ignoreLoading;
if (isDefinedAndNotNull(data.includeBundleWidgetsInExport)) {
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 { AppState } from '@core/core.state';
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 { WINDOW } from '@core/services/window.service';
import { DOCUMENT } from '@angular/common';
@ -199,7 +199,7 @@ export class ImportExportService {
}
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 widgetDefaultName = this.widgetService.getWidgetInfoFromCache(widget.typeFullFqn).widgetName;
let fileName = widgetDefaultName + (isNotEmptyStr(widgetTitle) ? `_${widgetTitle}` : '');
@ -352,28 +352,7 @@ export class ImportExportService {
forkJoin(tasks).subscribe({
next: ({includeBundleWidgetsInExport, widgetsBundle}) => {
this.dialog.open<ExportWidgetsBundleDialogComponent, ExportWidgetsBundleDialogData,
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);
}
}
}
);
this.handleExportWidgetsBundle(widgetsBundle, includeBundleWidgetsInExport);
},
error: (e) => {
this.handleExportError(e, 'widgets-bundle.export-failed-error');
@ -401,6 +380,9 @@ export class ImportExportService {
}))
.subscribe(ruleChainData => this.exportToPc(ruleChainData, entityData.name));
return;
case EntityType.WIDGETS_BUNDLE:
this.exportSelectedWidgetsBundle(entityData as WidgetsBundle);
return;
case EntityType.DASHBOARD:
preparedData = this.prepareDashboardExport(entityData as Dashboard);
break;
@ -410,6 +392,43 @@ export class ImportExportService {
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) {
this.widgetService.exportBundleWidgetTypesDetails(widgetsBundle.id.id).subscribe({
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'>};
}
export declare type DashboardLayoutInfo = {[breakpointId: string]: BreakpointLayoutInfo};
export declare type DashboardLayoutInfo = {[breakpointId in BreakpointId]?: BreakpointLayoutInfo};
export interface BreakpointLayoutInfo {
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 { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models';
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 { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
@ -199,7 +199,7 @@ export interface WidgetControllerDescriptor {
actionSources?: {[actionSourceId: string]: WidgetActionSource};
}
export interface BaseWidgetType extends BaseData<WidgetTypeId>, HasTenantId {
export interface BaseWidgetType extends BaseData<WidgetTypeId>, HasTenantId, HasVersion {
tenantId: TenantId;
fqn: 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} }",
"edge-name-starts-with": "Edges whose names start with '{{prefix}}'",
"version-conflict": {
"label": "Version conflict",
"message": "Do you want to cancel your changes or overwrite existing version?",
"message": "Do you want to overwrite existing version or discard changes and load the latest version?",
"link": "You can download your version of the {{entityType}} using this",
"overwrite": "Overwrite version",
"cancel": "Cancel changes"
"discard": "Discard changes"
},
"type-tb-resource": "Resource",
"type-tb-resources": "Resources",
@ -3076,7 +3075,8 @@
"method-required": "Method name is required.",
"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-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",
"model-name": "Model name",
"mqtt-version": "MQTT version",
@ -3100,6 +3100,7 @@
"password-required": "Password is required.",
"permit-without-calls": "Keep alive permit without calls",
"poll-period": "Poll period (ms)",
"poll-period-error": "Poll period should be at least {{min}} (ms).",
"port": "Port",
"port-required": "Port is required.",
"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-pattern": "Statistic send period is not valid",
"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-min": "Check connectors configuration can not be less then 1",
"check-connectors-configuration-pattern": "Check connectors configuration is not valid",
@ -3445,6 +3454,9 @@
"show-map": "Show nodes on scanning.",
"method-name": "Name of method on OPC-UA server.",
"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": {
"framer-type": "Type of a framer (Socket, RTU, or ASCII), if needed.",
"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",
"url": "localhost:4840/freeopcua/server/",
"timeoutInMillis": 5000,
"scanPeriodInMillis": 5000,
"scanPeriodInMillis": 3600000,
"pollPeriodInMillis": 5000,
"enableSubscriptions": true,
"subCheckPeriodInMillis": 100,
"showMap": false,

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

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

Loading…
Cancel
Save