Commit 692f575661dc6d8a1b31fdfd891c7370bf56e02e

Authored by Igor Kulikov
2 parents 70ca190f 2897ea44

Merge with master

@@ -56,7 +56,9 @@ public class TbRuleEngineProcessingStrategyFactory { @@ -56,7 +56,9 @@ public class TbRuleEngineProcessingStrategyFactory {
56 private final boolean retryTimeout; 56 private final boolean retryTimeout;
57 private final int maxRetries; 57 private final int maxRetries;
58 private final double maxAllowedFailurePercentage; 58 private final double maxAllowedFailurePercentage;
59 - private final long pauseBetweenRetries; 59 + private final long maxPauseBetweenRetries;
  60 +
  61 + private long pauseBetweenRetries;
60 62
61 private int initialTotalCount; 63 private int initialTotalCount;
62 private int retryCount; 64 private int retryCount;
@@ -69,6 +71,7 @@ public class TbRuleEngineProcessingStrategyFactory { @@ -69,6 +71,7 @@ public class TbRuleEngineProcessingStrategyFactory {
69 this.maxRetries = configuration.getRetries(); 71 this.maxRetries = configuration.getRetries();
70 this.maxAllowedFailurePercentage = configuration.getFailurePercentage(); 72 this.maxAllowedFailurePercentage = configuration.getFailurePercentage();
71 this.pauseBetweenRetries = configuration.getPauseBetweenRetries(); 73 this.pauseBetweenRetries = configuration.getPauseBetweenRetries();
  74 + this.maxPauseBetweenRetries = configuration.getMaxPauseBetweenRetries();
72 } 75 }
73 76
74 @Override 77 @Override
@@ -108,6 +111,9 @@ public class TbRuleEngineProcessingStrategyFactory { @@ -108,6 +111,9 @@ public class TbRuleEngineProcessingStrategyFactory {
108 } catch (InterruptedException e) { 111 } catch (InterruptedException e) {
109 throw new RuntimeException(e); 112 throw new RuntimeException(e);
110 } 113 }
  114 + if (maxPauseBetweenRetries > pauseBetweenRetries) {
  115 + pauseBetweenRetries = Math.min(maxPauseBetweenRetries, pauseBetweenRetries * 2);
  116 + }
111 } 117 }
112 return new TbRuleEngineProcessingDecision(false, toReprocess); 118 return new TbRuleEngineProcessingDecision(false, toReprocess);
113 } 119 }
@@ -765,6 +765,7 @@ queue: @@ -765,6 +765,7 @@ queue:
765 retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited 765 retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
766 failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; 766 failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
767 pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}"# Time in seconds to wait in consumer thread before retries; 767 pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}"# Time in seconds to wait in consumer thread before retries;
  768 + max-pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:3}"# Max allowed time in seconds for pause between retries.
768 - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}" 769 - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
769 topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}" 770 topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
770 poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}" 771 poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
@@ -780,6 +781,7 @@ queue: @@ -780,6 +781,7 @@ queue:
780 retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited 781 retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
781 failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; 782 failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
782 pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; 783 pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  784 + max-pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries.
783 - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}" 785 - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
784 topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}" 786 topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
785 poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}" 787 poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
@@ -795,6 +797,7 @@ queue: @@ -795,6 +797,7 @@ queue:
795 retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited 797 retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
796 failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; 798 failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
797 pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; 799 pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  800 + max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries.
798 transport: 801 transport:
799 # For high priority notifications that require minimum latency and processing time 802 # For high priority notifications that require minimum latency and processing time
800 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" 803 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
@@ -24,5 +24,6 @@ public class TbRuleEngineQueueAckStrategyConfiguration { @@ -24,5 +24,6 @@ public class TbRuleEngineQueueAckStrategyConfiguration {
24 private int retries; 24 private int retries;
25 private double failurePercentage; 25 private double failurePercentage;
26 private long pauseBetweenRetries; 26 private long pauseBetweenRetries;
  27 + private long maxPauseBetweenRetries;
27 28
28 } 29 }
@@ -54,7 +54,7 @@ @@ -54,7 +54,7 @@
54 "jstree": "^3.3.10", 54 "jstree": "^3.3.10",
55 "jstree-bootstrap-theme": "^1.0.1", 55 "jstree-bootstrap-theme": "^1.0.1",
56 "jszip": "^3.5.0", 56 "jszip": "^3.5.0",
57 - "leaflet": "^1.6.0", 57 + "leaflet": "^1.7.1",
58 "leaflet-editable": "^1.2.0", 58 "leaflet-editable": "^1.2.0",
59 "leaflet-polylinedecorator": "^1.6.0", 59 "leaflet-polylinedecorator": "^1.6.0",
60 "leaflet-providers": "^1.10.2", 60 "leaflet-providers": "^1.10.2",
@@ -1277,7 +1277,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1277,7 +1277,7 @@ export class WidgetSubscription implements IWidgetSubscription {
1277 const index = startIndex + dataIndex * dataKeysCount + dataKeyIndex; 1277 const index = startIndex + dataIndex * dataKeysCount + dataKeyIndex;
1278 let update = true; 1278 let update = true;
1279 let currentData: DataSetHolder; 1279 let currentData: DataSetHolder;
1280 - if (this.displayLegend && this.legendData.keys[index].dataKey.hidden) { 1280 + if (this.displayLegend && this.legendData.keys.find(key => key.dataIndex === index).dataKey.hidden) {
1281 currentData = this.hiddenData[index]; 1281 currentData = this.hiddenData[index];
1282 } else { 1282 } else {
1283 currentData = this.data[index]; 1283 currentData = this.data[index];
@@ -14,9 +14,11 @@ @@ -14,9 +14,11 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
  17 +import { HasUUID } from '@shared/models/id/has-uuid';
  18 +
17 export declare type MenuSectionType = 'link' | 'toggle'; 19 export declare type MenuSectionType = 'link' | 'toggle';
18 20
19 -export class MenuSection { 21 +export interface MenuSection extends HasUUID{
20 name: string; 22 name: string;
21 type: MenuSectionType; 23 type: MenuSectionType;
22 path: string; 24 path: string;
@@ -26,12 +28,12 @@ export class MenuSection { @@ -26,12 +28,12 @@ export class MenuSection {
26 pages?: Array<MenuSection>; 28 pages?: Array<MenuSection>;
27 } 29 }
28 30
29 -export class HomeSection { 31 +export interface HomeSection {
30 name: string; 32 name: string;
31 places: Array<HomeSectionPlace>; 33 places: Array<HomeSectionPlace>;
32 } 34 }
33 35
34 -export class HomeSectionPlace { 36 +export interface HomeSectionPlace {
35 name: string; 37 name: string;
36 icon: string; 38 icon: string;
37 isMdiIcon?: boolean; 39 isMdiIcon?: boolean;
@@ -24,6 +24,7 @@ import { HomeSection, MenuSection } from '@core/services/menu.models'; @@ -24,6 +24,7 @@ import { HomeSection, MenuSection } from '@core/services/menu.models';
24 import { BehaviorSubject, Observable, Subject } from 'rxjs'; 24 import { BehaviorSubject, Observable, Subject } from 'rxjs';
25 import { Authority } from '@shared/models/authority.enum'; 25 import { Authority } from '@shared/models/authority.enum';
26 import { AuthUser } from '@shared/models/user.model'; 26 import { AuthUser } from '@shared/models/user.model';
  27 +import { guid } from '@core/utils';
27 28
28 @Injectable({ 29 @Injectable({
29 providedIn: 'root' 30 providedIn: 'root'
@@ -74,18 +75,21 @@ export class MenuService { @@ -74,18 +75,21 @@ export class MenuService {
74 const sections: Array<MenuSection> = []; 75 const sections: Array<MenuSection> = [];
75 sections.push( 76 sections.push(
76 { 77 {
  78 + id: guid(),
77 name: 'home.home', 79 name: 'home.home',
78 type: 'link', 80 type: 'link',
79 path: '/home', 81 path: '/home',
80 icon: 'home' 82 icon: 'home'
81 }, 83 },
82 { 84 {
  85 + id: guid(),
83 name: 'tenant.tenants', 86 name: 'tenant.tenants',
84 type: 'link', 87 type: 'link',
85 path: '/tenants', 88 path: '/tenants',
86 icon: 'supervisor_account' 89 icon: 'supervisor_account'
87 }, 90 },
88 { 91 {
  92 + id: guid(),
89 name: 'tenant-profile.tenant-profiles', 93 name: 'tenant-profile.tenant-profiles',
90 type: 'link', 94 type: 'link',
91 path: '/tenantProfiles', 95 path: '/tenantProfiles',
@@ -93,12 +97,14 @@ export class MenuService { @@ -93,12 +97,14 @@ export class MenuService {
93 isMdiIcon: true 97 isMdiIcon: true
94 }, 98 },
95 { 99 {
  100 + id: guid(),
96 name: 'widget.widget-library', 101 name: 'widget.widget-library',
97 type: 'link', 102 type: 'link',
98 path: '/widgets-bundles', 103 path: '/widgets-bundles',
99 icon: 'now_widgets' 104 icon: 'now_widgets'
100 }, 105 },
101 { 106 {
  107 + id: guid(),
102 name: 'admin.system-settings', 108 name: 'admin.system-settings',
103 type: 'toggle', 109 type: 'toggle',
104 path: '/settings', 110 path: '/settings',
@@ -106,18 +112,21 @@ export class MenuService { @@ -106,18 +112,21 @@ export class MenuService {
106 icon: 'settings', 112 icon: 'settings',
107 pages: [ 113 pages: [
108 { 114 {
  115 + id: guid(),
109 name: 'admin.general', 116 name: 'admin.general',
110 type: 'link', 117 type: 'link',
111 path: '/settings/general', 118 path: '/settings/general',
112 icon: 'settings_applications' 119 icon: 'settings_applications'
113 }, 120 },
114 { 121 {
  122 + id: guid(),
115 name: 'admin.outgoing-mail', 123 name: 'admin.outgoing-mail',
116 type: 'link', 124 type: 'link',
117 path: '/settings/outgoing-mail', 125 path: '/settings/outgoing-mail',
118 icon: 'mail' 126 icon: 'mail'
119 }, 127 },
120 { 128 {
  129 + id: guid(),
121 name: 'admin.security-settings', 130 name: 'admin.security-settings',
122 type: 'link', 131 type: 'link',
123 path: '/settings/security-settings', 132 path: '/settings/security-settings',
@@ -186,36 +195,42 @@ export class MenuService { @@ -186,36 +195,42 @@ export class MenuService {
186 const sections: Array<MenuSection> = []; 195 const sections: Array<MenuSection> = [];
187 sections.push( 196 sections.push(
188 { 197 {
  198 + id: guid(),
189 name: 'home.home', 199 name: 'home.home',
190 type: 'link', 200 type: 'link',
191 path: '/home', 201 path: '/home',
192 icon: 'home' 202 icon: 'home'
193 }, 203 },
194 { 204 {
  205 + id: guid(),
195 name: 'rulechain.rulechains', 206 name: 'rulechain.rulechains',
196 type: 'link', 207 type: 'link',
197 path: '/ruleChains', 208 path: '/ruleChains',
198 icon: 'settings_ethernet' 209 icon: 'settings_ethernet'
199 }, 210 },
200 { 211 {
  212 + id: guid(),
201 name: 'customer.customers', 213 name: 'customer.customers',
202 type: 'link', 214 type: 'link',
203 path: '/customers', 215 path: '/customers',
204 icon: 'supervisor_account' 216 icon: 'supervisor_account'
205 }, 217 },
206 { 218 {
  219 + id: guid(),
207 name: 'asset.assets', 220 name: 'asset.assets',
208 type: 'link', 221 type: 'link',
209 path: '/assets', 222 path: '/assets',
210 icon: 'domain' 223 icon: 'domain'
211 }, 224 },
212 { 225 {
  226 + id: guid(),
213 name: 'device.devices', 227 name: 'device.devices',
214 type: 'link', 228 type: 'link',
215 path: '/devices', 229 path: '/devices',
216 icon: 'devices_other' 230 icon: 'devices_other'
217 }, 231 },
218 { 232 {
  233 + id: guid(),
219 name: 'device-profile.device-profiles', 234 name: 'device-profile.device-profiles',
220 type: 'link', 235 type: 'link',
221 path: '/deviceProfiles', 236 path: '/deviceProfiles',
@@ -223,24 +238,28 @@ export class MenuService { @@ -223,24 +238,28 @@ export class MenuService {
223 isMdiIcon: true 238 isMdiIcon: true
224 }, 239 },
225 { 240 {
  241 + id: guid(),
226 name: 'entity-view.entity-views', 242 name: 'entity-view.entity-views',
227 type: 'link', 243 type: 'link',
228 path: '/entityViews', 244 path: '/entityViews',
229 icon: 'view_quilt' 245 icon: 'view_quilt'
230 }, 246 },
231 { 247 {
  248 + id: guid(),
232 name: 'widget.widget-library', 249 name: 'widget.widget-library',
233 type: 'link', 250 type: 'link',
234 path: '/widgets-bundles', 251 path: '/widgets-bundles',
235 icon: 'now_widgets' 252 icon: 'now_widgets'
236 }, 253 },
237 { 254 {
  255 + id: guid(),
238 name: 'dashboard.dashboards', 256 name: 'dashboard.dashboards',
239 type: 'link', 257 type: 'link',
240 path: '/dashboards', 258 path: '/dashboards',
241 icon: 'dashboards' 259 icon: 'dashboards'
242 }, 260 },
243 { 261 {
  262 + id: guid(),
244 name: 'audit-log.audit-logs', 263 name: 'audit-log.audit-logs',
245 type: 'link', 264 type: 'link',
246 path: '/auditLogs', 265 path: '/auditLogs',
@@ -342,30 +361,35 @@ export class MenuService { @@ -342,30 +361,35 @@ export class MenuService {
342 const sections: Array<MenuSection> = []; 361 const sections: Array<MenuSection> = [];
343 sections.push( 362 sections.push(
344 { 363 {
  364 + id: guid(),
345 name: 'home.home', 365 name: 'home.home',
346 type: 'link', 366 type: 'link',
347 path: '/home', 367 path: '/home',
348 icon: 'home' 368 icon: 'home'
349 }, 369 },
350 { 370 {
  371 + id: guid(),
351 name: 'asset.assets', 372 name: 'asset.assets',
352 type: 'link', 373 type: 'link',
353 path: '/assets', 374 path: '/assets',
354 icon: 'domain' 375 icon: 'domain'
355 }, 376 },
356 { 377 {
  378 + id: guid(),
357 name: 'device.devices', 379 name: 'device.devices',
358 type: 'link', 380 type: 'link',
359 path: '/devices', 381 path: '/devices',
360 icon: 'devices_other' 382 icon: 'devices_other'
361 }, 383 },
362 { 384 {
  385 + id: guid(),
363 name: 'entity-view.entity-views', 386 name: 'entity-view.entity-views',
364 type: 'link', 387 type: 'link',
365 path: '/entityViews', 388 path: '/entityViews',
366 icon: 'view_quilt' 389 icon: 'view_quilt'
367 }, 390 },
368 { 391 {
  392 + id: guid(),
369 name: 'dashboard.dashboards', 393 name: 'dashboard.dashboards',
370 type: 'link', 394 type: 'link',
371 path: '/dashboards', 395 path: '/dashboards',
@@ -86,8 +86,7 @@ export default abstract class LeafletMap { @@ -86,8 +86,7 @@ export default abstract class LeafletMap {
86 86
87 public initSettings(options: MapSettings) { 87 public initSettings(options: MapSettings) {
88 this.options.tinyColor = tinycolor(this.options.color || defaultSettings.color); 88 this.options.tinyColor = tinycolor(this.options.color || defaultSettings.color);
89 - const { disableScrollZooming,  
90 - useClusterMarkers, 89 + const { useClusterMarkers,
91 zoomOnClick, 90 zoomOnClick,
92 showCoverageOnHover, 91 showCoverageOnHover,
93 removeOutsideVisibleBounds, 92 removeOutsideVisibleBounds,
@@ -95,9 +94,6 @@ export default abstract class LeafletMap { @@ -95,9 +94,6 @@ export default abstract class LeafletMap {
95 chunkedLoading, 94 chunkedLoading,
96 maxClusterRadius, 95 maxClusterRadius,
97 maxZoom }: MapSettings = options; 96 maxZoom }: MapSettings = options;
98 - if (disableScrollZooming) {  
99 - this.map.scrollWheelZoom.disable();  
100 - }  
101 if (useClusterMarkers) { 97 if (useClusterMarkers) {
102 const clusteringSettings: MarkerClusterGroupOptions = { 98 const clusteringSettings: MarkerClusterGroupOptions = {
103 zoomToBoundsOnClick: zoomOnClick, 99 zoomToBoundsOnClick: zoomOnClick,
@@ -307,8 +303,11 @@ export default abstract class LeafletMap { @@ -307,8 +303,11 @@ export default abstract class LeafletMap {
307 } else { 303 } else {
308 this.bounds = new L.LatLngBounds(null, null); 304 this.bounds = new L.LatLngBounds(null, null);
309 } 305 }
  306 + if (this.options.disableScrollZooming) {
  307 + this.map.scrollWheelZoom.disable();
  308 + }
310 if (this.options.draggableMarker) { 309 if (this.options.draggableMarker) {
311 - this.addMarkerControl(); 310 + this.addMarkerControl();
312 } 311 }
313 if (this.options.editablePolygon) { 312 if (this.options.editablePolygon) {
314 this.addPolygonControl(); 313 this.addPolygonControl();
@@ -623,10 +622,10 @@ export default abstract class LeafletMap { @@ -623,10 +622,10 @@ export default abstract class LeafletMap {
623 622
624 // Polyline 623 // Polyline
625 624
626 - updatePolylines(polyData: FormattedData[][], updateBounds = true, data?: FormattedData) { 625 + updatePolylines(polyData: FormattedData[][], updateBounds = true, activePolyline?: FormattedData) {
627 const keys: string[] = []; 626 const keys: string[] = [];
628 polyData.forEach((dataSource: FormattedData[]) => { 627 polyData.forEach((dataSource: FormattedData[]) => {
629 - data = data || dataSource[0]; 628 + const data = activePolyline || dataSource[0];
630 if (dataSource.length && data.entityName === dataSource[0].entityName) { 629 if (dataSource.length && data.entityName === dataSource[0].entityName) {
631 if (this.polylines.get(data.entityName)) { 630 if (this.polylines.get(data.entityName)) {
632 this.updatePolyline(data, dataSource, this.options, updateBounds); 631 this.updatePolyline(data, dataSource, this.options, updateBounds);
@@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
24 [ngClass]="{'tb-toggled' : sectionActive()}"></span> 24 [ngClass]="{'tb-toggled' : sectionActive()}"></span>
25 </a> 25 </a>
26 <ul id="docs-menu-{{section.name | nospace}}" class="tb-menu-toggle-list" [ngStyle]="{height: sectionHeight()}"> 26 <ul id="docs-menu-{{section.name | nospace}}" class="tb-menu-toggle-list" [ngStyle]="{height: sectionHeight()}">
27 - <li *ngFor="let page of section.pages"> 27 + <li *ngFor="let page of section.pages; trackBy: trackBySectionPages">
28 <tb-menu-link [section]="page"></tb-menu-link> 28 <tb-menu-link [section]="page"></tb-menu-link>
29 </li> 29 </li>
30 </ul> 30 </ul>
@@ -44,4 +44,8 @@ export class MenuToggleComponent implements OnInit { @@ -44,4 +44,8 @@ export class MenuToggleComponent implements OnInit {
44 return '0px'; 44 return '0px';
45 } 45 }
46 } 46 }
  47 +
  48 + trackBySectionPages(index: number, section: MenuSection){
  49 + return section.id;
  50 + }
47 } 51 }
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 16
17 --> 17 -->
18 <ul fxFlex fxLayout="column" fxLayoutAlign="start stretch" class="tb-side-menu"> 18 <ul fxFlex fxLayout="column" fxLayoutAlign="start stretch" class="tb-side-menu">
19 - <li *ngFor="let section of menuSections$| async" [ngSwitch]="section.type === 'link'"> 19 + <li *ngFor="let section of menuSections$ | async; trackBy: trackByMenuSection" [ngSwitch]="section.type === 'link'">
20 <tb-menu-link *ngSwitchCase="true" [section]="section"></tb-menu-link> 20 <tb-menu-link *ngSwitchCase="true" [section]="section"></tb-menu-link>
21 <tb-menu-toggle *ngSwitchCase="false" [section]="section"></tb-menu-toggle> 21 <tb-menu-toggle *ngSwitchCase="false" [section]="section"></tb-menu-toggle>
22 </li> 22 </li>
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 16
17 import { Component, OnInit } from '@angular/core'; 17 import { Component, OnInit } from '@angular/core';
18 import { MenuService } from '@core/services/menu.service'; 18 import { MenuService } from '@core/services/menu.service';
  19 +import { MenuSection } from '@core/services/menu.models';
19 20
20 @Component({ 21 @Component({
21 selector: 'tb-side-menu', 22 selector: 'tb-side-menu',
@@ -29,6 +30,10 @@ export class SideMenuComponent implements OnInit { @@ -29,6 +30,10 @@ export class SideMenuComponent implements OnInit {
29 constructor(private menuService: MenuService) { 30 constructor(private menuService: MenuService) {
30 } 31 }
31 32
  33 + trackByMenuSection(index: number, section: MenuSection){
  34 + return section.id;
  35 + }
  36 +
32 ngOnInit() { 37 ngOnInit() {
33 } 38 }
34 39
@@ -19,7 +19,7 @@ @@ -19,7 +19,7 @@
19 <h1 fxFlex fxHide.gt-sm *ngIf="lastBreadcrumb$ | async; let breadcrumb"> 19 <h1 fxFlex fxHide.gt-sm *ngIf="lastBreadcrumb$ | async; let breadcrumb">
20 {{ breadcrumb.ignoreTranslate ? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : breadcrumb.label) : (breadcrumb.label | translate) }} 20 {{ breadcrumb.ignoreTranslate ? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : breadcrumb.label) : (breadcrumb.label | translate) }}
21 </h1> 21 </h1>
22 - <span fxHide.lt-md fxLayout="row" *ngFor="let breadcrumb of breadcrumbs$ | async; last as isLast;" [ngSwitch]="isLast"> 22 + <span fxHide.lt-md fxLayout="row" *ngFor="let breadcrumb of breadcrumbs$ | async; trackBy: trackByBreadcrumbs; last as isLast;" [ngSwitch]="isLast">
23 <a *ngSwitchCase="false" [routerLink]="breadcrumb.link" [queryParams]="breadcrumb.queryParams"> 23 <a *ngSwitchCase="false" [routerLink]="breadcrumb.link" [queryParams]="breadcrumb.queryParams">
24 <mat-icon *ngIf="breadcrumb.isMdiIcon" [svgIcon]="breadcrumb.icon"> 24 <mat-icon *ngIf="breadcrumb.isMdiIcon" [svgIcon]="breadcrumb.icon">
25 </mat-icon> 25 </mat-icon>
@@ -20,6 +20,7 @@ import { BreadCrumb, BreadCrumbConfig } from './breadcrumb'; @@ -20,6 +20,7 @@ import { BreadCrumb, BreadCrumbConfig } from './breadcrumb';
20 import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; 20 import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router';
21 import { distinctUntilChanged, filter, map } from 'rxjs/operators'; 21 import { distinctUntilChanged, filter, map } from 'rxjs/operators';
22 import { TranslateService } from '@ngx-translate/core'; 22 import { TranslateService } from '@ngx-translate/core';
  23 +import { guid } from '@core/utils';
23 24
24 @Component({ 25 @Component({
25 selector: 'tb-breadcrumb', 26 selector: 'tb-breadcrumb',
@@ -94,6 +95,7 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { @@ -94,6 +95,7 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
94 const isMdiIcon = icon.startsWith('mdi:'); 95 const isMdiIcon = icon.startsWith('mdi:');
95 const link = [ route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/') ]; 96 const link = [ route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/') ];
96 const breadcrumb = { 97 const breadcrumb = {
  98 + id: guid(),
97 label, 99 label,
98 labelFunction, 100 labelFunction,
99 ignoreTranslate, 101 ignoreTranslate,
@@ -110,4 +112,8 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { @@ -110,4 +112,8 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
110 } 112 }
111 return newBreadcrumbs; 113 return newBreadcrumbs;
112 } 114 }
  115 +
  116 + trackByBreadcrumbs(index: number, breadcrumb: BreadCrumb){
  117 + return breadcrumb.id;
  118 + }
113 } 119 }
@@ -16,8 +16,9 @@ @@ -16,8 +16,9 @@
16 16
17 import { ActivatedRouteSnapshot, Params } from '@angular/router'; 17 import { ActivatedRouteSnapshot, Params } from '@angular/router';
18 import { TranslateService } from '@ngx-translate/core'; 18 import { TranslateService } from '@ngx-translate/core';
  19 +import { HasUUID } from '@shared/models/id/has-uuid';
19 20
20 -export interface BreadCrumb { 21 +export interface BreadCrumb extends HasUUID{
21 label: string; 22 label: string;
22 labelFunction?: () => string; 23 labelFunction?: () => string;
23 ignoreTranslate: boolean; 24 ignoreTranslate: boolean;
@@ -5770,10 +5770,10 @@ leaflet.markercluster@^1.4.1: @@ -5770,10 +5770,10 @@ leaflet.markercluster@^1.4.1:
5770 resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.4.1.tgz#b53f2c4f2ca7306ddab1dbb6f1861d5e8aa6c5e5" 5770 resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.4.1.tgz#b53f2c4f2ca7306ddab1dbb6f1861d5e8aa6c5e5"
5771 integrity sha512-ZSEpE/EFApR0bJ1w/dUGwTSUvWlpalKqIzkaYdYB7jaftQA/Y2Jav+eT4CMtEYFj+ZK4mswP13Q2acnPBnhGOw== 5771 integrity sha512-ZSEpE/EFApR0bJ1w/dUGwTSUvWlpalKqIzkaYdYB7jaftQA/Y2Jav+eT4CMtEYFj+ZK4mswP13Q2acnPBnhGOw==
5772 5772
5773 -leaflet@^1.6.0:  
5774 - version "1.6.0"  
5775 - resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.6.0.tgz#aecbb044b949ec29469eeb31c77a88e2f448f308"  
5776 - integrity sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ== 5773 +leaflet@^1.7.1:
  5774 + version "1.7.1"
  5775 + resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19"
  5776 + integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==
5777 5777
5778 less-loader@6.1.0: 5778 less-loader@6.1.0:
5779 version "6.1.0" 5779 version "6.1.0"