Commit 2cd61fe6bb6ce141bed00ab48ed5e97c62b28396
Merge branch 'develop/3.2' of github.com:thingsboard/thingsboard into develop/3.2
Showing
18 changed files
with
78 additions
and
27 deletions
... | ... | @@ -56,7 +56,9 @@ public class TbRuleEngineProcessingStrategyFactory { |
56 | 56 | private final boolean retryTimeout; |
57 | 57 | private final int maxRetries; |
58 | 58 | private final double maxAllowedFailurePercentage; |
59 | - private final long pauseBetweenRetries; | |
59 | + private final long maxPauseBetweenRetries; | |
60 | + | |
61 | + private long pauseBetweenRetries; | |
60 | 62 | |
61 | 63 | private int initialTotalCount; |
62 | 64 | private int retryCount; |
... | ... | @@ -69,6 +71,7 @@ public class TbRuleEngineProcessingStrategyFactory { |
69 | 71 | this.maxRetries = configuration.getRetries(); |
70 | 72 | this.maxAllowedFailurePercentage = configuration.getFailurePercentage(); |
71 | 73 | this.pauseBetweenRetries = configuration.getPauseBetweenRetries(); |
74 | + this.maxPauseBetweenRetries = configuration.getMaxPauseBetweenRetries(); | |
72 | 75 | } |
73 | 76 | |
74 | 77 | @Override |
... | ... | @@ -108,6 +111,9 @@ public class TbRuleEngineProcessingStrategyFactory { |
108 | 111 | } catch (InterruptedException e) { |
109 | 112 | throw new RuntimeException(e); |
110 | 113 | } |
114 | + if (maxPauseBetweenRetries > pauseBetweenRetries) { | |
115 | + pauseBetweenRetries = Math.min(maxPauseBetweenRetries, pauseBetweenRetries * 2); | |
116 | + } | |
111 | 117 | } |
112 | 118 | return new TbRuleEngineProcessingDecision(false, toReprocess); |
113 | 119 | } | ... | ... |
... | ... | @@ -765,6 +765,7 @@ queue: |
765 | 765 | retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited |
766 | 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 | 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 | 769 | - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}" |
769 | 770 | topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}" |
770 | 771 | poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}" |
... | ... | @@ -780,6 +781,7 @@ queue: |
780 | 781 | retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited |
781 | 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 | 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 | 785 | - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}" |
784 | 786 | topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}" |
785 | 787 | poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}" |
... | ... | @@ -795,6 +797,7 @@ queue: |
795 | 797 | retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited |
796 | 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 | 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 | 801 | transport: |
799 | 802 | # For high priority notifications that require minimum latency and processing time |
800 | 803 | notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" | ... | ... |
... | ... | @@ -350,7 +350,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
350 | 350 | |
351 | 351 | private MqttMessage createUnSubAckMessage(int msgId) { |
352 | 352 | MqttFixedHeader mqttFixedHeader = |
353 | - new MqttFixedHeader(UNSUBACK, false, AT_LEAST_ONCE, false, 0); | |
353 | + new MqttFixedHeader(UNSUBACK, false, AT_MOST_ONCE, false, 0); | |
354 | 354 | MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId); |
355 | 355 | return new MqttMessage(mqttFixedHeader, mqttMessageIdVariableHeader); |
356 | 356 | } |
... | ... | @@ -455,7 +455,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
455 | 455 | |
456 | 456 | private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) { |
457 | 457 | MqttFixedHeader mqttFixedHeader = |
458 | - new MqttFixedHeader(SUBACK, false, AT_LEAST_ONCE, false, 0); | |
458 | + new MqttFixedHeader(SUBACK, false, AT_MOST_ONCE, false, 0); | |
459 | 459 | MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId); |
460 | 460 | MqttSubAckPayload mqttSubAckPayload = new MqttSubAckPayload(grantedQoSList); |
461 | 461 | return new MqttSubAckMessage(mqttFixedHeader, mqttMessageIdVariableHeader, mqttSubAckPayload); |
... | ... | @@ -467,7 +467,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
467 | 467 | |
468 | 468 | public static MqttPubAckMessage createMqttPubAckMsg(int requestId) { |
469 | 469 | MqttFixedHeader mqttFixedHeader = |
470 | - new MqttFixedHeader(PUBACK, false, AT_LEAST_ONCE, false, 0); | |
470 | + new MqttFixedHeader(PUBACK, false, AT_MOST_ONCE, false, 0); | |
471 | 471 | MqttMessageIdVariableHeader mqttMsgIdVariableHeader = |
472 | 472 | MqttMessageIdVariableHeader.from(requestId); |
473 | 473 | return new MqttPubAckMessage(mqttFixedHeader, mqttMsgIdVariableHeader); | ... | ... |
... | ... | @@ -1277,7 +1277,7 @@ export class WidgetSubscription implements IWidgetSubscription { |
1277 | 1277 | const index = startIndex + dataIndex * dataKeysCount + dataKeyIndex; |
1278 | 1278 | let update = true; |
1279 | 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 | 1281 | currentData = this.hiddenData[index]; |
1282 | 1282 | } else { |
1283 | 1283 | currentData = this.data[index]; | ... | ... |
... | ... | @@ -14,9 +14,11 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | +import { HasUUID } from '@shared/models/id/has-uuid'; | |
18 | + | |
17 | 19 | export declare type MenuSectionType = 'link' | 'toggle'; |
18 | 20 | |
19 | -export class MenuSection { | |
21 | +export interface MenuSection extends HasUUID{ | |
20 | 22 | name: string; |
21 | 23 | type: MenuSectionType; |
22 | 24 | path: string; |
... | ... | @@ -26,12 +28,12 @@ export class MenuSection { |
26 | 28 | pages?: Array<MenuSection>; |
27 | 29 | } |
28 | 30 | |
29 | -export class HomeSection { | |
31 | +export interface HomeSection { | |
30 | 32 | name: string; |
31 | 33 | places: Array<HomeSectionPlace>; |
32 | 34 | } |
33 | 35 | |
34 | -export class HomeSectionPlace { | |
36 | +export interface HomeSectionPlace { | |
35 | 37 | name: string; |
36 | 38 | icon: string; |
37 | 39 | isMdiIcon?: boolean; | ... | ... |
... | ... | @@ -24,6 +24,7 @@ import { HomeSection, MenuSection } from '@core/services/menu.models'; |
24 | 24 | import { BehaviorSubject, Observable, Subject } from 'rxjs'; |
25 | 25 | import { Authority } from '@shared/models/authority.enum'; |
26 | 26 | import { AuthUser } from '@shared/models/user.model'; |
27 | +import { guid } from '@core/utils'; | |
27 | 28 | |
28 | 29 | @Injectable({ |
29 | 30 | providedIn: 'root' |
... | ... | @@ -74,18 +75,21 @@ export class MenuService { |
74 | 75 | const sections: Array<MenuSection> = []; |
75 | 76 | sections.push( |
76 | 77 | { |
78 | + id: guid(), | |
77 | 79 | name: 'home.home', |
78 | 80 | type: 'link', |
79 | 81 | path: '/home', |
80 | 82 | icon: 'home' |
81 | 83 | }, |
82 | 84 | { |
85 | + id: guid(), | |
83 | 86 | name: 'tenant.tenants', |
84 | 87 | type: 'link', |
85 | 88 | path: '/tenants', |
86 | 89 | icon: 'supervisor_account' |
87 | 90 | }, |
88 | 91 | { |
92 | + id: guid(), | |
89 | 93 | name: 'tenant-profile.tenant-profiles', |
90 | 94 | type: 'link', |
91 | 95 | path: '/tenantProfiles', |
... | ... | @@ -93,12 +97,14 @@ export class MenuService { |
93 | 97 | isMdiIcon: true |
94 | 98 | }, |
95 | 99 | { |
100 | + id: guid(), | |
96 | 101 | name: 'widget.widget-library', |
97 | 102 | type: 'link', |
98 | 103 | path: '/widgets-bundles', |
99 | 104 | icon: 'now_widgets' |
100 | 105 | }, |
101 | 106 | { |
107 | + id: guid(), | |
102 | 108 | name: 'admin.system-settings', |
103 | 109 | type: 'toggle', |
104 | 110 | path: '/settings', |
... | ... | @@ -106,18 +112,21 @@ export class MenuService { |
106 | 112 | icon: 'settings', |
107 | 113 | pages: [ |
108 | 114 | { |
115 | + id: guid(), | |
109 | 116 | name: 'admin.general', |
110 | 117 | type: 'link', |
111 | 118 | path: '/settings/general', |
112 | 119 | icon: 'settings_applications' |
113 | 120 | }, |
114 | 121 | { |
122 | + id: guid(), | |
115 | 123 | name: 'admin.outgoing-mail', |
116 | 124 | type: 'link', |
117 | 125 | path: '/settings/outgoing-mail', |
118 | 126 | icon: 'mail' |
119 | 127 | }, |
120 | 128 | { |
129 | + id: guid(), | |
121 | 130 | name: 'admin.security-settings', |
122 | 131 | type: 'link', |
123 | 132 | path: '/settings/security-settings', |
... | ... | @@ -186,36 +195,42 @@ export class MenuService { |
186 | 195 | const sections: Array<MenuSection> = []; |
187 | 196 | sections.push( |
188 | 197 | { |
198 | + id: guid(), | |
189 | 199 | name: 'home.home', |
190 | 200 | type: 'link', |
191 | 201 | path: '/home', |
192 | 202 | icon: 'home' |
193 | 203 | }, |
194 | 204 | { |
205 | + id: guid(), | |
195 | 206 | name: 'rulechain.rulechains', |
196 | 207 | type: 'link', |
197 | 208 | path: '/ruleChains', |
198 | 209 | icon: 'settings_ethernet' |
199 | 210 | }, |
200 | 211 | { |
212 | + id: guid(), | |
201 | 213 | name: 'customer.customers', |
202 | 214 | type: 'link', |
203 | 215 | path: '/customers', |
204 | 216 | icon: 'supervisor_account' |
205 | 217 | }, |
206 | 218 | { |
219 | + id: guid(), | |
207 | 220 | name: 'asset.assets', |
208 | 221 | type: 'link', |
209 | 222 | path: '/assets', |
210 | 223 | icon: 'domain' |
211 | 224 | }, |
212 | 225 | { |
226 | + id: guid(), | |
213 | 227 | name: 'device.devices', |
214 | 228 | type: 'link', |
215 | 229 | path: '/devices', |
216 | 230 | icon: 'devices_other' |
217 | 231 | }, |
218 | 232 | { |
233 | + id: guid(), | |
219 | 234 | name: 'device-profile.device-profiles', |
220 | 235 | type: 'link', |
221 | 236 | path: '/deviceProfiles', |
... | ... | @@ -223,24 +238,28 @@ export class MenuService { |
223 | 238 | isMdiIcon: true |
224 | 239 | }, |
225 | 240 | { |
241 | + id: guid(), | |
226 | 242 | name: 'entity-view.entity-views', |
227 | 243 | type: 'link', |
228 | 244 | path: '/entityViews', |
229 | 245 | icon: 'view_quilt' |
230 | 246 | }, |
231 | 247 | { |
248 | + id: guid(), | |
232 | 249 | name: 'widget.widget-library', |
233 | 250 | type: 'link', |
234 | 251 | path: '/widgets-bundles', |
235 | 252 | icon: 'now_widgets' |
236 | 253 | }, |
237 | 254 | { |
255 | + id: guid(), | |
238 | 256 | name: 'dashboard.dashboards', |
239 | 257 | type: 'link', |
240 | 258 | path: '/dashboards', |
241 | 259 | icon: 'dashboards' |
242 | 260 | }, |
243 | 261 | { |
262 | + id: guid(), | |
244 | 263 | name: 'audit-log.audit-logs', |
245 | 264 | type: 'link', |
246 | 265 | path: '/auditLogs', |
... | ... | @@ -342,30 +361,35 @@ export class MenuService { |
342 | 361 | const sections: Array<MenuSection> = []; |
343 | 362 | sections.push( |
344 | 363 | { |
364 | + id: guid(), | |
345 | 365 | name: 'home.home', |
346 | 366 | type: 'link', |
347 | 367 | path: '/home', |
348 | 368 | icon: 'home' |
349 | 369 | }, |
350 | 370 | { |
371 | + id: guid(), | |
351 | 372 | name: 'asset.assets', |
352 | 373 | type: 'link', |
353 | 374 | path: '/assets', |
354 | 375 | icon: 'domain' |
355 | 376 | }, |
356 | 377 | { |
378 | + id: guid(), | |
357 | 379 | name: 'device.devices', |
358 | 380 | type: 'link', |
359 | 381 | path: '/devices', |
360 | 382 | icon: 'devices_other' |
361 | 383 | }, |
362 | 384 | { |
385 | + id: guid(), | |
363 | 386 | name: 'entity-view.entity-views', |
364 | 387 | type: 'link', |
365 | 388 | path: '/entityViews', |
366 | 389 | icon: 'view_quilt' |
367 | 390 | }, |
368 | 391 | { |
392 | + id: guid(), | |
369 | 393 | name: 'dashboard.dashboards', |
370 | 394 | type: 'link', |
371 | 395 | path: '/dashboards', | ... | ... |
... | ... | @@ -127,8 +127,8 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { |
127 | 127 | |
128 | 128 | updateForm(entity: DeviceProfile) { |
129 | 129 | this.entityForm.patchValue({name: entity.name}); |
130 | - this.entityForm.patchValue({type: entity.type}); | |
131 | - this.entityForm.patchValue({transportType: entity.transportType}); | |
130 | + this.entityForm.patchValue({type: entity.type}, {emitEvent: false}); | |
131 | + this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false}); | |
132 | 132 | this.entityForm.patchValue({profileData: entity.profileData}); |
133 | 133 | this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); |
134 | 134 | this.entityForm.patchValue({description: entity.description}); | ... | ... |
... | ... | @@ -86,8 +86,7 @@ export default abstract class LeafletMap { |
86 | 86 | |
87 | 87 | public initSettings(options: MapSettings) { |
88 | 88 | this.options.tinyColor = tinycolor(this.options.color || defaultSettings.color); |
89 | - const { disableScrollZooming, | |
90 | - useClusterMarkers, | |
89 | + const { useClusterMarkers, | |
91 | 90 | zoomOnClick, |
92 | 91 | showCoverageOnHover, |
93 | 92 | removeOutsideVisibleBounds, |
... | ... | @@ -95,9 +94,6 @@ export default abstract class LeafletMap { |
95 | 94 | chunkedLoading, |
96 | 95 | maxClusterRadius, |
97 | 96 | maxZoom }: MapSettings = options; |
98 | - if (disableScrollZooming) { | |
99 | - this.map.scrollWheelZoom.disable(); | |
100 | - } | |
101 | 97 | if (useClusterMarkers) { |
102 | 98 | const clusteringSettings: MarkerClusterGroupOptions = { |
103 | 99 | zoomToBoundsOnClick: zoomOnClick, |
... | ... | @@ -307,8 +303,11 @@ export default abstract class LeafletMap { |
307 | 303 | } else { |
308 | 304 | this.bounds = new L.LatLngBounds(null, null); |
309 | 305 | } |
306 | + if (this.options.disableScrollZooming) { | |
307 | + this.map.scrollWheelZoom.disable(); | |
308 | + } | |
310 | 309 | if (this.options.draggableMarker) { |
311 | - this.addMarkerControl(); | |
310 | + this.addMarkerControl(); | |
312 | 311 | } |
313 | 312 | if (this.options.editablePolygon) { |
314 | 313 | this.addPolygonControl(); |
... | ... | @@ -623,10 +622,10 @@ export default abstract class LeafletMap { |
623 | 622 | |
624 | 623 | // Polyline |
625 | 624 | |
626 | - updatePolylines(polyData: FormattedData[][], updateBounds = true, data?: FormattedData) { | |
625 | + updatePolylines(polyData: FormattedData[][], updateBounds = true, activePolyline?: FormattedData) { | |
627 | 626 | const keys: string[] = []; |
628 | 627 | polyData.forEach((dataSource: FormattedData[]) => { |
629 | - data = data || dataSource[0]; | |
628 | + const data = activePolyline || dataSource[0]; | |
630 | 629 | if (dataSource.length && data.entityName === dataSource[0].entityName) { |
631 | 630 | if (this.polylines.get(data.entityName)) { |
632 | 631 | this.updatePolyline(data, dataSource, this.options, updateBounds); | ... | ... |
... | ... | @@ -24,7 +24,7 @@ |
24 | 24 | [ngClass]="{'tb-toggled' : sectionActive()}"></span> |
25 | 25 | </a> |
26 | 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 | 28 | <tb-menu-link [section]="page"></tb-menu-link> |
29 | 29 | </li> |
30 | 30 | </ul> | ... | ... |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | |
17 | 17 | --> |
18 | 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 | 20 | <tb-menu-link *ngSwitchCase="true" [section]="section"></tb-menu-link> |
21 | 21 | <tb-menu-toggle *ngSwitchCase="false" [section]="section"></tb-menu-toggle> |
22 | 22 | </li> | ... | ... |
... | ... | @@ -16,6 +16,7 @@ |
16 | 16 | |
17 | 17 | import { Component, OnInit } from '@angular/core'; |
18 | 18 | import { MenuService } from '@core/services/menu.service'; |
19 | +import { MenuSection } from '@core/services/menu.models'; | |
19 | 20 | |
20 | 21 | @Component({ |
21 | 22 | selector: 'tb-side-menu', |
... | ... | @@ -29,6 +30,10 @@ export class SideMenuComponent implements OnInit { |
29 | 30 | constructor(private menuService: MenuService) { |
30 | 31 | } |
31 | 32 | |
33 | + trackByMenuSection(index: number, section: MenuSection){ | |
34 | + return section.id; | |
35 | + } | |
36 | + | |
32 | 37 | ngOnInit() { |
33 | 38 | } |
34 | 39 | ... | ... |
... | ... | @@ -19,7 +19,7 @@ |
19 | 19 | <h1 fxFlex fxHide.gt-sm *ngIf="lastBreadcrumb$ | async; let breadcrumb"> |
20 | 20 | {{ breadcrumb.ignoreTranslate ? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : breadcrumb.label) : (breadcrumb.label | translate) }} |
21 | 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 | 23 | <a *ngSwitchCase="false" [routerLink]="breadcrumb.link" [queryParams]="breadcrumb.queryParams"> |
24 | 24 | <mat-icon *ngIf="breadcrumb.isMdiIcon" [svgIcon]="breadcrumb.icon"> |
25 | 25 | </mat-icon> | ... | ... |
... | ... | @@ -20,6 +20,7 @@ import { BreadCrumb, BreadCrumbConfig } from './breadcrumb'; |
20 | 20 | import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; |
21 | 21 | import { distinctUntilChanged, filter, map } from 'rxjs/operators'; |
22 | 22 | import { TranslateService } from '@ngx-translate/core'; |
23 | +import { guid } from '@core/utils'; | |
23 | 24 | |
24 | 25 | @Component({ |
25 | 26 | selector: 'tb-breadcrumb', |
... | ... | @@ -94,6 +95,7 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { |
94 | 95 | const isMdiIcon = icon.startsWith('mdi:'); |
95 | 96 | const link = [ route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/') ]; |
96 | 97 | const breadcrumb = { |
98 | + id: guid(), | |
97 | 99 | label, |
98 | 100 | labelFunction, |
99 | 101 | ignoreTranslate, |
... | ... | @@ -110,4 +112,8 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { |
110 | 112 | } |
111 | 113 | return newBreadcrumbs; |
112 | 114 | } |
115 | + | |
116 | + trackByBreadcrumbs(index: number, breadcrumb: BreadCrumb){ | |
117 | + return breadcrumb.id; | |
118 | + } | |
113 | 119 | } | ... | ... |
... | ... | @@ -16,8 +16,9 @@ |
16 | 16 | |
17 | 17 | import { ActivatedRouteSnapshot, Params } from '@angular/router'; |
18 | 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 | 22 | label: string; |
22 | 23 | labelFunction?: () => string; |
23 | 24 | ignoreTranslate: boolean; | ... | ... |
... | ... | @@ -5770,10 +5770,10 @@ leaflet.markercluster@^1.4.1: |
5770 | 5770 | resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.4.1.tgz#b53f2c4f2ca7306ddab1dbb6f1861d5e8aa6c5e5" |
5771 | 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 | 5778 | less-loader@6.1.0: |
5779 | 5779 | version "6.1.0" | ... | ... |