@c8y/ngx-components 1023.71.1 → 1023.75.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/asset-properties/index.d.ts +19 -1
  2. package/asset-properties/index.d.ts.map +1 -1
  3. package/cockpit-config/index.d.ts +4 -3
  4. package/cockpit-config/index.d.ts.map +1 -1
  5. package/context-dashboard/index.d.ts +202 -4
  6. package/context-dashboard/index.d.ts.map +1 -1
  7. package/datapoints-export-selector/index.d.ts +8 -1
  8. package/datapoints-export-selector/index.d.ts.map +1 -1
  9. package/device-profile/index.d.ts +8 -1
  10. package/device-profile/index.d.ts.map +1 -1
  11. package/events/cockpit/index.d.ts +6 -0
  12. package/events/cockpit/index.d.ts.map +1 -0
  13. package/events/devicemanagement/index.d.ts +6 -0
  14. package/events/devicemanagement/index.d.ts.map +1 -0
  15. package/events/events-timeline/index.d.ts +10 -10
  16. package/events/events-timeline/index.d.ts.map +1 -1
  17. package/events/index.d.ts +363 -5
  18. package/events/index.d.ts.map +1 -1
  19. package/fesm2022/c8y-ngx-components-alarm-event-selector.mjs +1 -1
  20. package/fesm2022/c8y-ngx-components-alarm-event-selector.mjs.map +1 -1
  21. package/fesm2022/c8y-ngx-components-alarms.mjs +1 -1
  22. package/fesm2022/c8y-ngx-components-alarms.mjs.map +1 -1
  23. package/fesm2022/c8y-ngx-components-asset-properties.mjs +2 -2
  24. package/fesm2022/c8y-ngx-components-asset-properties.mjs.map +1 -1
  25. package/fesm2022/c8y-ngx-components-auth-configuration.mjs +1 -1
  26. package/fesm2022/c8y-ngx-components-auth-configuration.mjs.map +1 -1
  27. package/fesm2022/c8y-ngx-components-branding-shared-lazy-add-branding-modal.mjs +1 -1
  28. package/fesm2022/c8y-ngx-components-branding-shared-lazy-add-branding-modal.mjs.map +1 -1
  29. package/fesm2022/c8y-ngx-components-branding-shared-lazy.mjs +2 -2
  30. package/fesm2022/c8y-ngx-components-branding-shared-lazy.mjs.map +1 -1
  31. package/fesm2022/c8y-ngx-components-cockpit-config.mjs +8 -11
  32. package/fesm2022/c8y-ngx-components-cockpit-config.mjs.map +1 -1
  33. package/fesm2022/{c8y-ngx-components-computed-asset-properties-alarm-count-config.component-CPLDClTp.mjs → c8y-ngx-components-computed-asset-properties-alarm-count-config.component-DX9Rgjgl.mjs} +2 -2
  34. package/fesm2022/{c8y-ngx-components-computed-asset-properties-alarm-count-config.component-CPLDClTp.mjs.map → c8y-ngx-components-computed-asset-properties-alarm-count-config.component-DX9Rgjgl.mjs.map} +1 -1
  35. package/fesm2022/{c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-9be_iMQg.mjs → c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-CRpLJ5H7.mjs} +8 -8
  36. package/fesm2022/{c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-9be_iMQg.mjs.map → c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-CRpLJ5H7.mjs.map} +1 -1
  37. package/fesm2022/{c8y-ngx-components-computed-asset-properties-configuration-snapshot-config.component-B2em01_W.mjs → c8y-ngx-components-computed-asset-properties-configuration-snapshot-config.component-2rDsrxcs.mjs} +2 -2
  38. package/fesm2022/{c8y-ngx-components-computed-asset-properties-configuration-snapshot-config.component-B2em01_W.mjs.map → c8y-ngx-components-computed-asset-properties-configuration-snapshot-config.component-2rDsrxcs.mjs.map} +1 -1
  39. package/fesm2022/{c8y-ngx-components-computed-asset-properties-event-count-config.component-CQuGa1RI.mjs → c8y-ngx-components-computed-asset-properties-event-count-config.component-BJNoqWZf.mjs} +2 -2
  40. package/fesm2022/{c8y-ngx-components-computed-asset-properties-event-count-config.component-CQuGa1RI.mjs.map → c8y-ngx-components-computed-asset-properties-event-count-config.component-BJNoqWZf.mjs.map} +1 -1
  41. package/fesm2022/{c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-CkmurxJv.mjs → c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-DYac6foX.mjs} +3 -3
  42. package/fesm2022/{c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-CkmurxJv.mjs.map → c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-DYac6foX.mjs.map} +1 -1
  43. package/fesm2022/{c8y-ngx-components-computed-asset-properties-last-measurement-config.component-CTK9zNUh.mjs → c8y-ngx-components-computed-asset-properties-last-measurement-config.component-3yTe6lIr.mjs} +3 -3
  44. package/fesm2022/{c8y-ngx-components-computed-asset-properties-last-measurement-config.component-CTK9zNUh.mjs.map → c8y-ngx-components-computed-asset-properties-last-measurement-config.component-3yTe6lIr.mjs.map} +1 -1
  45. package/fesm2022/c8y-ngx-components-computed-asset-properties.mjs +1 -1
  46. package/fesm2022/{c8y-ngx-components-context-dashboard-dashboard-appearance-settings.component-DsCDppJx.mjs → c8y-ngx-components-context-dashboard-dashboard-appearance-settings.component-C7yXSDYC.mjs} +3 -3
  47. package/fesm2022/{c8y-ngx-components-context-dashboard-dashboard-appearance-settings.component-DsCDppJx.mjs.map → c8y-ngx-components-context-dashboard-dashboard-appearance-settings.component-C7yXSDYC.mjs.map} +1 -1
  48. package/fesm2022/{c8y-ngx-components-context-dashboard-dashboard-general-settings.component-RdLW5nde.mjs → c8y-ngx-components-context-dashboard-dashboard-general-settings.component-w8N16Z3t.mjs} +4 -4
  49. package/fesm2022/{c8y-ngx-components-context-dashboard-dashboard-general-settings.component-RdLW5nde.mjs.map → c8y-ngx-components-context-dashboard-dashboard-general-settings.component-w8N16Z3t.mjs.map} +1 -1
  50. package/fesm2022/c8y-ngx-components-context-dashboard.mjs +561 -21
  51. package/fesm2022/c8y-ngx-components-context-dashboard.mjs.map +1 -1
  52. package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs +2 -2
  53. package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs.map +1 -1
  54. package/fesm2022/c8y-ngx-components-datapoint-library-details.mjs +1 -1
  55. package/fesm2022/c8y-ngx-components-datapoint-library-details.mjs.map +1 -1
  56. package/fesm2022/c8y-ngx-components-datapoint-selector.mjs +1 -1
  57. package/fesm2022/c8y-ngx-components-datapoint-selector.mjs.map +1 -1
  58. package/fesm2022/c8y-ngx-components-datapoints-export-selector.mjs +41 -8
  59. package/fesm2022/c8y-ngx-components-datapoints-export-selector.mjs.map +1 -1
  60. package/fesm2022/c8y-ngx-components-device-profile.mjs +38 -12
  61. package/fesm2022/c8y-ngx-components-device-profile.mjs.map +1 -1
  62. package/fesm2022/c8y-ngx-components-ecosystem-license-confirm.mjs +1 -1
  63. package/fesm2022/c8y-ngx-components-ecosystem-license-confirm.mjs.map +1 -1
  64. package/fesm2022/c8y-ngx-components-ecosystem-shared.mjs +1 -1
  65. package/fesm2022/c8y-ngx-components-ecosystem-shared.mjs.map +1 -1
  66. package/fesm2022/c8y-ngx-components-ecosystem.mjs +1 -1
  67. package/fesm2022/c8y-ngx-components-ecosystem.mjs.map +1 -1
  68. package/fesm2022/c8y-ngx-components-events-cockpit.mjs +54 -0
  69. package/fesm2022/c8y-ngx-components-events-cockpit.mjs.map +1 -0
  70. package/fesm2022/c8y-ngx-components-events-devicemanagement.mjs +79 -0
  71. package/fesm2022/c8y-ngx-components-events-devicemanagement.mjs.map +1 -0
  72. package/fesm2022/c8y-ngx-components-events-events-timeline.mjs +30 -23
  73. package/fesm2022/c8y-ngx-components-events-events-timeline.mjs.map +1 -1
  74. package/fesm2022/c8y-ngx-components-events.mjs +1080 -4
  75. package/fesm2022/c8y-ngx-components-events.mjs.map +1 -1
  76. package/fesm2022/c8y-ngx-components-file-preview.mjs +48 -41
  77. package/fesm2022/c8y-ngx-components-file-preview.mjs.map +1 -1
  78. package/fesm2022/c8y-ngx-components-files-repository.mjs +1 -1
  79. package/fesm2022/c8y-ngx-components-files-repository.mjs.map +1 -1
  80. package/fesm2022/c8y-ngx-components-global-context.mjs +68 -34
  81. package/fesm2022/c8y-ngx-components-global-context.mjs.map +1 -1
  82. package/fesm2022/c8y-ngx-components-interval-picker.mjs +3 -3
  83. package/fesm2022/c8y-ngx-components-interval-picker.mjs.map +1 -1
  84. package/fesm2022/c8y-ngx-components-location.mjs +1 -1
  85. package/fesm2022/c8y-ngx-components-location.mjs.map +1 -1
  86. package/fesm2022/c8y-ngx-components-operation-picker.mjs +1 -1
  87. package/fesm2022/c8y-ngx-components-operation-picker.mjs.map +1 -1
  88. package/fesm2022/c8y-ngx-components-operations-bulk-operation-scheduler.mjs +1 -1
  89. package/fesm2022/c8y-ngx-components-operations-bulk-operation-scheduler.mjs.map +1 -1
  90. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-device-profile.mjs +1 -1
  91. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-device-profile.mjs.map +1 -1
  92. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-software.mjs +1 -1
  93. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-software.mjs.map +1 -1
  94. package/fesm2022/c8y-ngx-components-protocol-lpwan.mjs +5 -5
  95. package/fesm2022/c8y-ngx-components-protocol-lpwan.mjs.map +1 -1
  96. package/fesm2022/c8y-ngx-components-protocol-opcua.mjs +2 -2
  97. package/fesm2022/c8y-ngx-components-protocol-opcua.mjs.map +1 -1
  98. package/fesm2022/c8y-ngx-components-remote-access-shared.mjs +1 -1
  99. package/fesm2022/c8y-ngx-components-remote-access-shared.mjs.map +1 -1
  100. package/fesm2022/c8y-ngx-components-remote-access-ssh-remote-access-ssh-endpoint-modal.mjs +1 -1
  101. package/fesm2022/c8y-ngx-components-remote-access-ssh-remote-access-ssh-endpoint-modal.mjs.map +1 -1
  102. package/fesm2022/c8y-ngx-components-remote-access-vnc-remote-access-vnc-endpoint-modal.mjs +1 -1
  103. package/fesm2022/c8y-ngx-components-remote-access-vnc-remote-access-vnc-endpoint-modal.mjs.map +1 -1
  104. package/fesm2022/c8y-ngx-components-remote-access-vnc-vnc-viewer.mjs +1 -1
  105. package/fesm2022/c8y-ngx-components-remote-access-vnc-vnc-viewer.mjs.map +1 -1
  106. package/fesm2022/c8y-ngx-components-repository-firmware.mjs +1 -1
  107. package/fesm2022/c8y-ngx-components-repository-firmware.mjs.map +1 -1
  108. package/fesm2022/c8y-ngx-components-static-assets-modal.mjs +1 -1
  109. package/fesm2022/c8y-ngx-components-static-assets-modal.mjs.map +1 -1
  110. package/fesm2022/c8y-ngx-components-time-context.mjs +1 -1
  111. package/fesm2022/c8y-ngx-components-time-context.mjs.map +1 -1
  112. package/fesm2022/c8y-ngx-components-translation-editor-lazy.mjs +1 -1
  113. package/fesm2022/c8y-ngx-components-translation-editor-lazy.mjs.map +1 -1
  114. package/fesm2022/c8y-ngx-components-widgets-definitions-event-list.mjs +39 -1
  115. package/fesm2022/c8y-ngx-components-widgets-definitions-event-list.mjs.map +1 -1
  116. package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget.mjs +76 -4
  117. package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget.mjs.map +1 -1
  118. package/fesm2022/c8y-ngx-components-widgets-implementations-alarms.mjs +1 -1
  119. package/fesm2022/c8y-ngx-components-widgets-implementations-alarms.mjs.map +1 -1
  120. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs +1 -1
  121. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs.map +1 -1
  122. package/fesm2022/c8y-ngx-components-widgets-implementations-events.mjs +236 -0
  123. package/fesm2022/c8y-ngx-components-widgets-implementations-events.mjs.map +1 -0
  124. package/fesm2022/c8y-ngx-components-widgets-implementations-html-widget.mjs +271 -31
  125. package/fesm2022/c8y-ngx-components-widgets-implementations-html-widget.mjs.map +1 -1
  126. package/fesm2022/c8y-ngx-components-widgets-implementations-image.mjs +1 -1
  127. package/fesm2022/c8y-ngx-components-widgets-implementations-image.mjs.map +1 -1
  128. package/fesm2022/c8y-ngx-components-widgets-implementations-info-gauge.mjs +1 -1
  129. package/fesm2022/c8y-ngx-components-widgets-implementations-info-gauge.mjs.map +1 -1
  130. package/fesm2022/c8y-ngx-components-widgets-implementations-kpi.mjs +1 -1
  131. package/fesm2022/c8y-ngx-components-widgets-implementations-kpi.mjs.map +1 -1
  132. package/fesm2022/c8y-ngx-components-widgets-implementations-linear-gauge.mjs +1 -1
  133. package/fesm2022/c8y-ngx-components-widgets-implementations-linear-gauge.mjs.map +1 -1
  134. package/fesm2022/c8y-ngx-components-widgets-implementations-map.mjs +1 -1
  135. package/fesm2022/c8y-ngx-components-widgets-implementations-map.mjs.map +1 -1
  136. package/fesm2022/c8y-ngx-components.mjs +248 -26
  137. package/fesm2022/c8y-ngx-components.mjs.map +1 -1
  138. package/file-preview/index.d.ts +7 -6
  139. package/file-preview/index.d.ts.map +1 -1
  140. package/global-context/index.d.ts +3 -0
  141. package/global-context/index.d.ts.map +1 -1
  142. package/index.d.ts +106 -11
  143. package/index.d.ts.map +1 -1
  144. package/locales/de.po +117 -3
  145. package/locales/es.po +117 -3
  146. package/locales/fr.po +117 -3
  147. package/locales/ja_JP.po +117 -3
  148. package/locales/ko.po +117 -3
  149. package/locales/locales.pot +117 -3
  150. package/locales/nl.po +117 -3
  151. package/locales/pl.po +117 -3
  152. package/locales/pt_BR.po +117 -3
  153. package/locales/zh_CN.po +117 -3
  154. package/locales/zh_TW.po +117 -3
  155. package/package.json +1 -1
  156. package/widgets/definitions/event-list/index.d.ts +44 -1
  157. package/widgets/definitions/event-list/index.d.ts.map +1 -1
  158. package/widgets/implementations/alarms/index.d.ts +2 -0
  159. package/widgets/implementations/alarms/index.d.ts.map +1 -1
  160. package/widgets/implementations/events/index.d.ts +89 -0
  161. package/widgets/implementations/events/index.d.ts.map +1 -0
  162. package/widgets/implementations/html-widget/index.d.ts +69 -9
  163. package/widgets/implementations/html-widget/index.d.ts.map +1 -1
@@ -1,11 +1,33 @@
1
- import { gettext } from '@c8y/ngx-components/gettext';
1
+ import { JsonPipe, KeyValuePipe } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { Injectable } from '@angular/core';
3
+ import { inject, Injectable, signal, computed, DestroyRef, ChangeDetectionStrategy, Component, Pipe, input, output, viewChild, effect, untracked, ElementRef, ViewChildren } from '@angular/core';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { ActivatedRoute, RouterLink, Router, NavigationEnd, RouterLinkActive, RouterOutlet } from '@angular/router';
6
+ import * as i1 from '@c8y/client';
7
+ import { EventBinaryService, EventService, InventoryService } from '@c8y/client';
8
+ import { FilesService, GenericFileIconPipe, AlertService, ColorService, IconDirective, IconPanelComponent, LoadingComponent, AssetLinkPipe, C8yTranslatePipe, DatePipe, HumanizePipe, ViewContext, ContextRouteService, FormGroupComponent, C8yTranslateDirective, DateTimePickerComponent, MessagesComponent, MessageDirective, CountdownIntervalComponent, ForOfDirective, ListGroupComponent, ListItemBodyComponent, ListItemComponent, ListItemIconComponent, ListItemTimelineComponent, SplitViewAlertsComponent, SplitViewHeaderActionsComponent, SplitViewListComponent, EmptyStateComponent, TitleComponent, HelpComponent, ActionBarItemComponent, SplitViewComponent, SplitViewDetailsComponent } from '@c8y/ngx-components';
9
+ import { gettext } from '@c8y/ngx-components/gettext';
10
+ import { saveAs } from 'file-saver';
11
+ import { PopoverDirective } from 'ngx-bootstrap/popover';
4
12
  import { pickBy, has, without, difference, keys, includes } from 'lodash-es';
13
+ import { INTERVAL_TITLES, INTERVALS, IntervalPickerComponent } from '@c8y/ngx-components/interval-picker';
14
+ import * as i2 from '@c8y/ngx-components/global-context';
15
+ import { Subject, take, timer, fromEvent } from 'rxjs';
16
+ import { filter, switchMap, map, distinctUntilChanged, debounceTime } from 'rxjs/operators';
17
+ import * as i1$1 from '@angular/forms';
18
+ import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
19
+ import { BsDropdownDirective, BsDropdownToggleDirective, BsDropdownMenuDirective } from 'ngx-bootstrap/dropdown';
20
+ import { TooltipDirective } from 'ngx-bootstrap/tooltip';
21
+ import * as i1$2 from '@c8y/ngx-components/file-preview';
22
+ import { FilePreviewModule } from '@c8y/ngx-components/file-preview';
23
+ import { BsModalService } from 'ngx-bootstrap/modal';
24
+ import { CdkTrapFocus } from '@angular/cdk/a11y';
25
+ import { AlarmEventSelectorService } from '@c8y/ngx-components/alarm-event-selector';
5
26
 
6
27
  const EVENT_RESERVED_KEYS = [
7
28
  'creationTime',
8
29
  'id',
30
+ 'lastUpdated',
9
31
  'self',
10
32
  'source',
11
33
  'text',
@@ -15,16 +37,71 @@ const EVENT_RESERVED_KEYS = [
15
37
  ];
16
38
  const EVENT_STANDARD_KEYS = {
17
39
  type: gettext('Type'),
18
- text: gettext('Text')
40
+ text: gettext('Text'),
41
+ lastUpdated: gettext('Last updated')
19
42
  };
43
+ const EVENTS_PATH = 'events';
44
+ /**
45
+ * Extended interval titles with an additional title for the case when no date is selected.
46
+ */
47
+ const INTERVAL_TITLES_EXTENDED = {
48
+ ...INTERVAL_TITLES,
49
+ none: gettext('No date filter')
50
+ };
51
+ const INTERVALS_EXTENDED = [
52
+ {
53
+ id: 'none',
54
+ title: gettext('No date filter')
55
+ },
56
+ ...INTERVALS
57
+ ];
20
58
 
59
+ const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
21
60
  class EventsService {
61
+ constructor() {
62
+ this.filesService = inject(FilesService);
63
+ this.fileIconPipe = new GenericFileIconPipe(this.filesService);
64
+ }
65
+ resolveFileIcon(fileName) {
66
+ const extension = this.filesService.getFileExtension(fileName);
67
+ if (!extension) {
68
+ return 'file-o';
69
+ }
70
+ const genericType = this.fileIconPipe.getGenericType({ name: fileName });
71
+ return this.fileIconPipe.getIcon(genericType);
72
+ }
22
73
  getStandardKeys(event) {
23
74
  return pickBy(EVENT_STANDARD_KEYS, (_, key) => has(event, key));
24
75
  }
25
76
  getNonStandardKeys(event, excluding = []) {
26
77
  return without(difference(this.getKeys(event), keys(this.getStandardKeys(event))), ...excluding);
27
78
  }
79
+ isImageBinary(binaryInfo) {
80
+ if (!binaryInfo?.name) {
81
+ return false;
82
+ }
83
+ const extension = this.filesService.getFileExtension(binaryInfo.name);
84
+ return extension ? IMAGE_EXTENSIONS.includes(extension) : false;
85
+ }
86
+ getCustomFragments(event) {
87
+ const nonStandardKeys = this.getNonStandardKeys(event);
88
+ if (nonStandardKeys.length === 0) {
89
+ return null;
90
+ }
91
+ const fragments = {};
92
+ for (const key of nonStandardKeys) {
93
+ fragments[key] = event[key];
94
+ }
95
+ return fragments;
96
+ }
97
+ arrayBufferToBase64(buffer) {
98
+ const bytes = new Uint8Array(buffer);
99
+ const chunks = [];
100
+ for (let i = 0; i < bytes.length; i += 8192) {
101
+ chunks.push(String.fromCharCode(...bytes.subarray(i, i + 8192)));
102
+ }
103
+ return btoa(chunks.join(''));
104
+ }
28
105
  getKeys(managedObject) {
29
106
  return Object.keys({ ...managedObject }).filter(key => !includes(EVENT_RESERVED_KEYS, key));
30
107
  }
@@ -36,9 +113,1008 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
36
113
  args: [{ providedIn: 'root' }]
37
114
  }] });
38
115
 
116
+ class EventDetailsComponent {
117
+ constructor() {
118
+ this.CREATION_TIME_HELP_TEXT = gettext("Time in which the event was created on the server. The time shown corresponds to the server's time.");
119
+ this.EVENT_ICON = 'online1';
120
+ this.TIME_HELP_TEXT = gettext('Time in which the event was created on the device. Device time can be different from server time.');
121
+ this.binaryPreviewLoading = signal(false, ...(ngDevMode ? [{ debugName: "binaryPreviewLoading" }] : []));
122
+ this.binaryPreviewUrl = signal(null, ...(ngDevMode ? [{ debugName: "binaryPreviewUrl" }] : []));
123
+ this.binaryPreviewFailed = signal(false, ...(ngDevMode ? [{ debugName: "binaryPreviewFailed" }] : []));
124
+ this.customFragments = signal(null, ...(ngDevMode ? [{ debugName: "customFragments" }] : []));
125
+ this.isDownloading = signal(false, ...(ngDevMode ? [{ debugName: "isDownloading" }] : []));
126
+ this.isLoading = signal(true, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
127
+ this.selectedEvent = signal(null, ...(ngDevMode ? [{ debugName: "selectedEvent" }] : []));
128
+ this.selectedEventSource = signal(null, ...(ngDevMode ? [{ debugName: "selectedEventSource" }] : []));
129
+ this.typeColor = signal('', ...(ngDevMode ? [{ debugName: "typeColor" }] : []));
130
+ this.binaryInfo = computed(() => this.selectedEvent()?.['c8y_IsBinary'] ?? null, ...(ngDevMode ? [{ debugName: "binaryInfo" }] : []));
131
+ this.hasBinary = computed(() => !!this.binaryInfo(), ...(ngDevMode ? [{ debugName: "hasBinary" }] : []));
132
+ this.canDownload = computed(() => this.binaryInfo()?.length !== undefined, ...(ngDevMode ? [{ debugName: "canDownload" }] : []));
133
+ this.binaryIcon = computed(() => {
134
+ const info = this.binaryInfo();
135
+ if (!info?.name) {
136
+ return 'file-o';
137
+ }
138
+ return this.eventsService.resolveFileIcon(info.name);
139
+ }, ...(ngDevMode ? [{ debugName: "binaryIcon" }] : []));
140
+ this.activatedRoute = inject(ActivatedRoute);
141
+ this.alertService = inject(AlertService);
142
+ this.colorService = inject(ColorService);
143
+ this.destroyRef = inject(DestroyRef);
144
+ this.eventBinaryService = inject(EventBinaryService);
145
+ this.eventService = inject(EventService);
146
+ this.eventsService = inject(EventsService);
147
+ this.inventoryService = inject(InventoryService);
148
+ }
149
+ ngOnInit() {
150
+ this.activatedRoute.params.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(params => {
151
+ if (params['id']) {
152
+ void this.loadEventDetails(params['id']);
153
+ }
154
+ });
155
+ }
156
+ isComplexValue(value) {
157
+ return typeof value === 'object' && value !== null;
158
+ }
159
+ async downloadBinary() {
160
+ const event = this.selectedEvent();
161
+ const binaryInfo = this.binaryInfo();
162
+ if (!event || !binaryInfo) {
163
+ return;
164
+ }
165
+ try {
166
+ this.isDownloading.set(true);
167
+ const response = await this.eventBinaryService.download(event);
168
+ const arrayBuffer = await response.arrayBuffer();
169
+ const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
170
+ const blob = new Blob([arrayBuffer], { type: contentType });
171
+ saveAs(blob, binaryInfo.name || 'attachment');
172
+ }
173
+ catch (error) {
174
+ this.alertService.addServerFailure(error);
175
+ }
176
+ finally {
177
+ this.isDownloading.set(false);
178
+ }
179
+ }
180
+ async loadEventDetails(eventId) {
181
+ try {
182
+ this.isLoading.set(true);
183
+ this.binaryPreviewUrl.set(null);
184
+ this.binaryPreviewFailed.set(false);
185
+ const { data: event } = await this.eventService.detail(eventId);
186
+ this.selectedEvent.set(event);
187
+ const [source, color] = await Promise.all([
188
+ this.loadEventSource(event.source?.id),
189
+ this.colorService.generateColor(event.type)
190
+ ]);
191
+ this.selectedEventSource.set(source);
192
+ this.typeColor.set(color);
193
+ this.customFragments.set(this.eventsService.getCustomFragments(event));
194
+ if (event['c8y_IsBinary'] && this.eventsService.isImageBinary(event['c8y_IsBinary'])) {
195
+ void this.loadBinaryPreview(event);
196
+ }
197
+ }
198
+ catch (error) {
199
+ this.alertService.addServerFailure(error);
200
+ }
201
+ finally {
202
+ this.isLoading.set(false);
203
+ }
204
+ }
205
+ async loadEventSource(sourceId) {
206
+ if (!sourceId) {
207
+ return null;
208
+ }
209
+ try {
210
+ const { data } = await this.inventoryService.detail(sourceId);
211
+ return data;
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ }
217
+ async loadBinaryPreview(event) {
218
+ try {
219
+ this.binaryPreviewLoading.set(true);
220
+ const response = await this.eventBinaryService.download(event);
221
+ const arrayBuffer = await response.arrayBuffer();
222
+ const contentType = response.headers.get('Content-Type') || 'image/png';
223
+ const base64 = this.eventsService.arrayBufferToBase64(arrayBuffer);
224
+ this.binaryPreviewUrl.set(`data:${contentType};base64,${base64}`);
225
+ }
226
+ catch {
227
+ this.binaryPreviewFailed.set(true);
228
+ }
229
+ finally {
230
+ this.binaryPreviewLoading.set(false);
231
+ }
232
+ }
233
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventDetailsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
234
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: EventDetailsComponent, isStandalone: true, selector: "c8y-event-details", ngImport: i0, template: "@if (isLoading()) {\n <c8y-loading />\n} @else if (selectedEvent(); as event) {\n <!-- Header with event text -->\n <div\n class=\"card-header p-24 m-b-16 bg-component separator-bottom sticky-top\"\n style=\"margin: 0 -24px\"\n >\n <h4\n class=\"m-0\"\n data-cy=\"c8y-event-details-title\"\n >\n {{ event.text | translate }}\n </h4>\n </div>\n\n <c8y-icon-panel [sections]=\"[]\">\n <!-- Source section -->\n <div\n class=\"col-xs-12 col-md-6 d-flex p-b-8\"\n data-cy=\"c8y-event-details--source-wrapper\"\n >\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <i\n class=\"icon-24 m-t-4 stroked-icon status\"\n c8yIcon=\"contactless-payment\"\n ></i>\n </div>\n <div class=\"p-t-8 p-b-8 p-r-8 min-width-0\">\n <p class=\"text-label-small m-b-0 m-r-8\">{{ 'Source' | translate }}</p>\n <p class=\"small\">\n @if (selectedEventSource(); as source) {\n <button\n class=\"btn-link p-0 m-r-8 text-left text-truncate\"\n [title]=\"source.name || event.source?.id\"\n type=\"button\"\n [routerLink]=\"source | assetLink\"\n >\n <i c8yIcon=\"exchange\"></i>\n {{ source.name || event.source?.id }}\n </button>\n } @else {\n <span class=\"text-muted\">{{ event.source?.id }}</span>\n }\n </p>\n </div>\n </div>\n </div>\n\n <!-- Type section -->\n <div\n class=\"col-xs-12 col-md-6 d-flex p-b-8\"\n data-cy=\"c8y-event-details--type-wrapper\"\n >\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <span\n class=\"circle-icon-wrapper\"\n [style.background-color]=\"typeColor()\"\n >\n <i\n class=\"stroked-icon\"\n [c8yIcon]=\"EVENT_ICON\"\n ></i>\n </span>\n </div>\n <div class=\"p-t-8 p-b-8 p-r-8 min-width-0\">\n <p class=\"text-label-small m-b-0 m-r-8\">{{ 'Type' | translate }}</p>\n <p\n class=\"small text-truncate\"\n [title]=\"event.type\"\n >\n <code>{{ event.type }}</code>\n </p>\n </div>\n </div>\n </div>\n\n <!-- Time, Server creation time, and Last updated -->\n <div class=\"col-xs-12 col-md-12 p-b-16\">\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <i\n class=\"icon-24 text-gray-dark m-t-4\"\n c8yIcon=\"calendar\"\n data-cy=\"c8y-event-details--calendar-icon\"\n ></i>\n </div>\n <div class=\"p-t-8 p-b-0 p-r-8 flex-grow\">\n <div class=\"content-flex-50\">\n <!-- Device time -->\n <div\n class=\"col-4 p-b-8\"\n data-cy=\"c8y-event-details--time-wrapper\"\n >\n <p class=\"text-label-small m-b-0 m-r-8\">{{ 'Time' | translate }}</p>\n <p class=\"small\">\n {{ event.time | c8yDate: 'medium' }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"TIME_HELP_TEXT | translate\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </p>\n </div>\n <!-- Server creation time -->\n <div\n class=\"col-4 p-b-8\"\n data-cy=\"c8y-event-details--creation-time-wrapper\"\n >\n <p class=\"text-label-small m-b-0 m-r-8\">\n {{ 'Server creation time' | translate }}\n </p>\n <p class=\"small\">\n {{ event.creationTime | c8yDate: 'medium' }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"CREATION_TIME_HELP_TEXT | translate\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </p>\n </div>\n <!-- Last updated -->\n @if (event['lastUpdated']) {\n <div\n class=\"col-4 p-b-8\"\n data-cy=\"c8y-event-details--last-updated-wrapper\"\n >\n <p class=\"text-label-small m-b-0 m-r-8\">\n {{ 'Last updated' | translate }}\n </p>\n <p class=\"small\">\n {{ event['lastUpdated'] | c8yDate: 'medium' }}\n </p>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Custom fragments section -->\n @if (customFragments(); as fragments) {\n <div\n class=\"col-xs-12 col-md-12 p-b-16\"\n data-cy=\"c8y-event-details--custom-fragments-wrapper\"\n >\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <i\n class=\"icon-24 text-gray-dark m-t-4\"\n c8yIcon=\"outgoing-data\"\n ></i>\n </div>\n <div\n class=\"p-t-8 p-b-0 p-r-8 flex-grow\"\n data-cy=\"event-details-custom-data\"\n >\n <ul class=\"list-unstyled small m-b-0\">\n @for (item of fragments | keyvalue; track item.key) {\n <li\n class=\"p-t-4 p-b-4 separator-bottom\"\n data-cy=\"event-details-custom-data-item\"\n >\n <label class=\"small m-b-4 text-label-small d-block\">\n {{ item.key | humanize }}\n </label>\n @if (isComplexValue(item.value)) {\n <pre class=\"m-b-0\"><code>{{ item.value | json }}</code></pre>\n } @else {\n <span>{{ item.value }}</span>\n }\n </li>\n }\n </ul>\n </div>\n </div>\n </div>\n }\n\n <!-- Attachment section -->\n @if (hasBinary()) {\n <div\n class=\"col-xs-12 col-md-12 p-b-16\"\n data-cy=\"c8y-event-details--attachment-wrapper\"\n >\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <i\n class=\"icon-24 text-gray-dark m-t-4\"\n [c8yIcon]=\"binaryIcon()\"\n ></i>\n </div>\n <div class=\"p-t-8 p-b-8 p-r-8 flex-grow\">\n <p class=\"text-label-small m-b-4 m-r-8\">{{ 'Attachment' | translate }}</p>\n\n <!-- Image preview -->\n @if (binaryPreviewLoading()) {\n <div class=\"m-b-16\">\n <c8y-loading></c8y-loading>\n </div>\n } @else if (binaryPreviewUrl(); as previewUrl) {\n @let imgAltText = 'Attachment preview' | translate;\n <div class=\"m-b-16\">\n <img\n class=\"max-width-100\"\n [src]=\"previewUrl\"\n [alt]=\"binaryInfo()?.name || imgAltText\"\n data-cy=\"c8y-event-details--attachment-preview\"\n />\n </div>\n } @else if (binaryPreviewFailed()) {\n <p\n class=\"text-muted small m-b-8\"\n translate\n >\n Preview unavailable.\n </p>\n }\n\n <!-- Download button -->\n @if (canDownload()) {\n <button\n class=\"btn btn-primary btn-xs\"\n [title]=\"'Download' | translate\"\n [attr.aria-busy]=\"isDownloading()\"\n type=\"button\"\n [disabled]=\"isDownloading()\"\n (click)=\"downloadBinary()\"\n data-cy=\"c8y-event-details--download-btn\"\n >\n <i [c8yIcon]=\"isDownloading() ? 'spinner' : 'download'\"></i>\n <span>{{ 'Download' | translate }}</span>\n </button>\n }\n </div>\n </div>\n </div>\n }\n </c8y-icon-panel>\n}\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: IconPanelComponent, selector: "c8y-icon-panel", inputs: ["sections", "ariaLabel"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "directive", type: PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: AssetLinkPipe, name: "assetLink" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }, { kind: "pipe", type: HumanizePipe, name: "humanize" }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
235
+ }
236
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventDetailsComponent, decorators: [{
237
+ type: Component,
238
+ args: [{ selector: 'c8y-event-details', imports: [
239
+ AssetLinkPipe,
240
+ C8yTranslatePipe,
241
+ DatePipe,
242
+ HumanizePipe,
243
+ IconDirective,
244
+ IconPanelComponent,
245
+ JsonPipe,
246
+ KeyValuePipe,
247
+ LoadingComponent,
248
+ PopoverDirective,
249
+ RouterLink
250
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (isLoading()) {\n <c8y-loading />\n} @else if (selectedEvent(); as event) {\n <!-- Header with event text -->\n <div\n class=\"card-header p-24 m-b-16 bg-component separator-bottom sticky-top\"\n style=\"margin: 0 -24px\"\n >\n <h4\n class=\"m-0\"\n data-cy=\"c8y-event-details-title\"\n >\n {{ event.text | translate }}\n </h4>\n </div>\n\n <c8y-icon-panel [sections]=\"[]\">\n <!-- Source section -->\n <div\n class=\"col-xs-12 col-md-6 d-flex p-b-8\"\n data-cy=\"c8y-event-details--source-wrapper\"\n >\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <i\n class=\"icon-24 m-t-4 stroked-icon status\"\n c8yIcon=\"contactless-payment\"\n ></i>\n </div>\n <div class=\"p-t-8 p-b-8 p-r-8 min-width-0\">\n <p class=\"text-label-small m-b-0 m-r-8\">{{ 'Source' | translate }}</p>\n <p class=\"small\">\n @if (selectedEventSource(); as source) {\n <button\n class=\"btn-link p-0 m-r-8 text-left text-truncate\"\n [title]=\"source.name || event.source?.id\"\n type=\"button\"\n [routerLink]=\"source | assetLink\"\n >\n <i c8yIcon=\"exchange\"></i>\n {{ source.name || event.source?.id }}\n </button>\n } @else {\n <span class=\"text-muted\">{{ event.source?.id }}</span>\n }\n </p>\n </div>\n </div>\n </div>\n\n <!-- Type section -->\n <div\n class=\"col-xs-12 col-md-6 d-flex p-b-8\"\n data-cy=\"c8y-event-details--type-wrapper\"\n >\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <span\n class=\"circle-icon-wrapper\"\n [style.background-color]=\"typeColor()\"\n >\n <i\n class=\"stroked-icon\"\n [c8yIcon]=\"EVENT_ICON\"\n ></i>\n </span>\n </div>\n <div class=\"p-t-8 p-b-8 p-r-8 min-width-0\">\n <p class=\"text-label-small m-b-0 m-r-8\">{{ 'Type' | translate }}</p>\n <p\n class=\"small text-truncate\"\n [title]=\"event.type\"\n >\n <code>{{ event.type }}</code>\n </p>\n </div>\n </div>\n </div>\n\n <!-- Time, Server creation time, and Last updated -->\n <div class=\"col-xs-12 col-md-12 p-b-16\">\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <i\n class=\"icon-24 text-gray-dark m-t-4\"\n c8yIcon=\"calendar\"\n data-cy=\"c8y-event-details--calendar-icon\"\n ></i>\n </div>\n <div class=\"p-t-8 p-b-0 p-r-8 flex-grow\">\n <div class=\"content-flex-50\">\n <!-- Device time -->\n <div\n class=\"col-4 p-b-8\"\n data-cy=\"c8y-event-details--time-wrapper\"\n >\n <p class=\"text-label-small m-b-0 m-r-8\">{{ 'Time' | translate }}</p>\n <p class=\"small\">\n {{ event.time | c8yDate: 'medium' }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"TIME_HELP_TEXT | translate\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </p>\n </div>\n <!-- Server creation time -->\n <div\n class=\"col-4 p-b-8\"\n data-cy=\"c8y-event-details--creation-time-wrapper\"\n >\n <p class=\"text-label-small m-b-0 m-r-8\">\n {{ 'Server creation time' | translate }}\n </p>\n <p class=\"small\">\n {{ event.creationTime | c8yDate: 'medium' }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"CREATION_TIME_HELP_TEXT | translate\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </p>\n </div>\n <!-- Last updated -->\n @if (event['lastUpdated']) {\n <div\n class=\"col-4 p-b-8\"\n data-cy=\"c8y-event-details--last-updated-wrapper\"\n >\n <p class=\"text-label-small m-b-0 m-r-8\">\n {{ 'Last updated' | translate }}\n </p>\n <p class=\"small\">\n {{ event['lastUpdated'] | c8yDate: 'medium' }}\n </p>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Custom fragments section -->\n @if (customFragments(); as fragments) {\n <div\n class=\"col-xs-12 col-md-12 p-b-16\"\n data-cy=\"c8y-event-details--custom-fragments-wrapper\"\n >\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <i\n class=\"icon-24 text-gray-dark m-t-4\"\n c8yIcon=\"outgoing-data\"\n ></i>\n </div>\n <div\n class=\"p-t-8 p-b-0 p-r-8 flex-grow\"\n data-cy=\"event-details-custom-data\"\n >\n <ul class=\"list-unstyled small m-b-0\">\n @for (item of fragments | keyvalue; track item.key) {\n <li\n class=\"p-t-4 p-b-4 separator-bottom\"\n data-cy=\"event-details-custom-data-item\"\n >\n <label class=\"small m-b-4 text-label-small d-block\">\n {{ item.key | humanize }}\n </label>\n @if (isComplexValue(item.value)) {\n <pre class=\"m-b-0\"><code>{{ item.value | json }}</code></pre>\n } @else {\n <span>{{ item.value }}</span>\n }\n </li>\n }\n </ul>\n </div>\n </div>\n </div>\n }\n\n <!-- Attachment section -->\n @if (hasBinary()) {\n <div\n class=\"col-xs-12 col-md-12 p-b-16\"\n data-cy=\"c8y-event-details--attachment-wrapper\"\n >\n <div class=\"border-all fit-w d-flex\">\n <div class=\"p-8\">\n <i\n class=\"icon-24 text-gray-dark m-t-4\"\n [c8yIcon]=\"binaryIcon()\"\n ></i>\n </div>\n <div class=\"p-t-8 p-b-8 p-r-8 flex-grow\">\n <p class=\"text-label-small m-b-4 m-r-8\">{{ 'Attachment' | translate }}</p>\n\n <!-- Image preview -->\n @if (binaryPreviewLoading()) {\n <div class=\"m-b-16\">\n <c8y-loading></c8y-loading>\n </div>\n } @else if (binaryPreviewUrl(); as previewUrl) {\n @let imgAltText = 'Attachment preview' | translate;\n <div class=\"m-b-16\">\n <img\n class=\"max-width-100\"\n [src]=\"previewUrl\"\n [alt]=\"binaryInfo()?.name || imgAltText\"\n data-cy=\"c8y-event-details--attachment-preview\"\n />\n </div>\n } @else if (binaryPreviewFailed()) {\n <p\n class=\"text-muted small m-b-8\"\n translate\n >\n Preview unavailable.\n </p>\n }\n\n <!-- Download button -->\n @if (canDownload()) {\n <button\n class=\"btn btn-primary btn-xs\"\n [title]=\"'Download' | translate\"\n [attr.aria-busy]=\"isDownloading()\"\n type=\"button\"\n [disabled]=\"isDownloading()\"\n (click)=\"downloadBinary()\"\n data-cy=\"c8y-event-details--download-btn\"\n >\n <i [c8yIcon]=\"isDownloading() ? 'spinner' : 'download'\"></i>\n <span>{{ 'Download' | translate }}</span>\n </button>\n }\n </div>\n </div>\n </div>\n }\n </c8y-icon-panel>\n}\n" }]
251
+ }] });
252
+
253
+ const DEFAULT_EVENT_ICON = 'online1';
254
+ class EventIconPipe {
255
+ constructor() {
256
+ this.eventsService = inject(EventsService);
257
+ }
258
+ transform(event) {
259
+ const binaryInfo = event['c8y_IsBinary'];
260
+ return binaryInfo?.name
261
+ ? this.eventsService.resolveFileIcon(binaryInfo.name)
262
+ : DEFAULT_EVENT_ICON;
263
+ }
264
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventIconPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
265
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.18", ngImport: i0, type: EventIconPipe, isStandalone: true, name: "eventIcon" }); }
266
+ }
267
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventIconPipe, decorators: [{
268
+ type: Pipe,
269
+ args: [{
270
+ name: 'eventIcon',
271
+ standalone: true
272
+ }]
273
+ }] });
274
+
275
+ class EventIsImagePipe {
276
+ constructor() {
277
+ this.eventsService = inject(EventsService);
278
+ }
279
+ transform(event) {
280
+ return this.eventsService.isImageBinary(event['c8y_IsBinary']);
281
+ }
282
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventIsImagePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
283
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.18", ngImport: i0, type: EventIsImagePipe, isStandalone: true, name: "eventIsImage" }); }
284
+ }
285
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventIsImagePipe, decorators: [{
286
+ type: Pipe,
287
+ args: [{
288
+ name: 'eventIsImage',
289
+ standalone: true
290
+ }]
291
+ }] });
292
+
293
+ class EventsViewService {
294
+ constructor(eventService, dateTimeContextPickerService) {
295
+ this.eventService = eventService;
296
+ this.dateTimeContextPickerService = dateTimeContextPickerService;
297
+ this.DEFAULT_PAGE_SIZE = 50;
298
+ this.DEFAULT_REFRESH_INTERVAL = 30_000;
299
+ this.reloadEventsList$ = new Subject();
300
+ }
301
+ /**
302
+ * Emits a subject to trigger events list reload.
303
+ */
304
+ updateEventsList() {
305
+ this.reloadEventsList$.next();
306
+ }
307
+ /**
308
+ * Retrieves a list of events with optional filters.
309
+ */
310
+ async retrieveEvents(filter) {
311
+ const queryFilter = {
312
+ pageSize: filter?.pageSize ?? this.DEFAULT_PAGE_SIZE,
313
+ withTotalPages: true
314
+ };
315
+ if (filter?.source) {
316
+ queryFilter['source'] = filter.source;
317
+ queryFilter['withSourceChildren'] = filter.withSourceChildren ?? true;
318
+ }
319
+ if (filter?.type) {
320
+ queryFilter['type'] = filter.type;
321
+ }
322
+ if (filter?.dateFrom) {
323
+ queryFilter['dateFrom'] = filter.dateFrom.toISOString();
324
+ }
325
+ if (filter?.dateTo) {
326
+ queryFilter['dateTo'] = filter.dateTo.toISOString();
327
+ }
328
+ return this.eventService.list(queryFilter);
329
+ }
330
+ /**
331
+ * Returns the correct link based on the provided context data.
332
+ */
333
+ getRouterLink(contextData, event) {
334
+ let detailUrl = `/${EVENTS_PATH}`;
335
+ if (event) {
336
+ detailUrl = `/${EVENTS_PATH}/${event.id}`;
337
+ }
338
+ if (!contextData) {
339
+ return detailUrl;
340
+ }
341
+ switch (contextData.context) {
342
+ case ViewContext.Device:
343
+ return `/device/${contextData.contextData.id}${detailUrl}`;
344
+ case ViewContext.Group:
345
+ return `/group/${contextData.contextData.id}${detailUrl}`;
346
+ default:
347
+ return detailUrl;
348
+ }
349
+ }
350
+ /**
351
+ * Returns the correct from and to dates based on the selected interval
352
+ * @param intervalId the selected interval. E.g. 'none', 'hours', 'custom' ...
353
+ * @returns The calculated date context based on the selected interval.
354
+ */
355
+ buildEventsFilter(params) {
356
+ const dateFrom = params.dateTimeContext?.dateFrom ?? params.dateRange?.[0];
357
+ const dateTo = params.dateTimeContext?.dateTo ?? params.dateRange?.[1];
358
+ return {
359
+ ...(params.source && {
360
+ source: params.source,
361
+ withSourceChildren: params.withSourceChildren ?? true
362
+ }),
363
+ ...(params.type && { type: params.type }),
364
+ ...(dateFrom && { dateFrom: new Date(dateFrom) }),
365
+ ...(dateTo && { dateTo: new Date(dateTo) })
366
+ };
367
+ }
368
+ getDateTimeContextByInterval(intervalId) {
369
+ return this.dateTimeContextPickerService.getDateTimeContextByInterval(intervalId);
370
+ }
371
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsViewService, deps: [{ token: i1.EventService }, { token: i2.DateTimeContextPickerService }], target: i0.ɵɵFactoryTarget.Injectable }); }
372
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsViewService, providedIn: 'root' }); }
373
+ }
374
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsViewService, decorators: [{
375
+ type: Injectable,
376
+ args: [{
377
+ providedIn: 'root'
378
+ }]
379
+ }], ctorParameters: () => [{ type: i1.EventService }, { type: i2.DateTimeContextPickerService }] });
380
+
381
+ class EventRouterLinkPipe {
382
+ constructor() {
383
+ this.activatedRoute = inject(ActivatedRoute);
384
+ this.contextRouteService = inject(ContextRouteService);
385
+ this.eventsViewService = inject(EventsViewService);
386
+ }
387
+ transform(event, alwaysNavigateToAllEvents = false) {
388
+ if (alwaysNavigateToAllEvents) {
389
+ return this.eventsViewService.getRouterLink(null, event);
390
+ }
391
+ const contextData = this.contextRouteService.getContextData(this.activatedRoute);
392
+ return this.eventsViewService.getRouterLink(contextData, event);
393
+ }
394
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventRouterLinkPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
395
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.18", ngImport: i0, type: EventRouterLinkPipe, isStandalone: true, name: "eventRouterLink" }); }
396
+ }
397
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventRouterLinkPipe, decorators: [{
398
+ type: Pipe,
399
+ args: [{
400
+ name: 'eventRouterLink',
401
+ standalone: true
402
+ }]
403
+ }] });
404
+
405
+ class EventsDateFilterComponent {
406
+ constructor() {
407
+ this.INTERVALS = INTERVALS_EXTENDED;
408
+ this.INTERVAL_TITLES = INTERVAL_TITLES_EXTENDED;
409
+ this.DATE_FORMAT = 'short';
410
+ this.defaultInterval = input('none', ...(ngDevMode ? [{ debugName: "defaultInterval" }] : []));
411
+ this.updateQueryParams = input(true, ...(ngDevMode ? [{ debugName: "updateQueryParams" }] : []));
412
+ this.date = signal(undefined, ...(ngDevMode ? [{ debugName: "date" }] : []));
413
+ this.noFilterLabel = gettext('No date filter');
414
+ this.dateFilterChange = output();
415
+ this.dropdown = viewChild(BsDropdownDirective, ...(ngDevMode ? [{ debugName: "dropdown" }] : []));
416
+ this.activatedRoute = inject(ActivatedRoute);
417
+ this.destroyRef = inject(DestroyRef);
418
+ this.eventsViewService = inject(EventsViewService);
419
+ this.formBuilder = inject(FormBuilder);
420
+ this.router = inject(Router);
421
+ }
422
+ ngOnInit() {
423
+ const context = this.getDefaultContext();
424
+ this.form = this.createForm(context);
425
+ this.date.set([
426
+ this.form.value.currentDateContextFromDate,
427
+ this.form.value.currentDateContextToDate
428
+ ]);
429
+ this.activatedRoute.queryParams
430
+ .pipe(take(1), takeUntilDestroyed(this.destroyRef))
431
+ .subscribe(params => {
432
+ if (!params.interval) {
433
+ return;
434
+ }
435
+ if (params.interval !== 'custom') {
436
+ this.updateDateTime(params.interval);
437
+ }
438
+ else {
439
+ this.form.patchValue({
440
+ currentDateContextInterval: params.interval,
441
+ temporaryUserSelectedFromDate: params.dateFrom,
442
+ temporaryUserSelectedToDate: params.dateTo
443
+ });
444
+ this.date.set([params.dateFrom, params.dateTo]);
445
+ }
446
+ });
447
+ this.subscribeToIntervalChange();
448
+ }
449
+ applyDateFilter() {
450
+ const interval = this.form.value.currentDateContextInterval;
451
+ const isNoDateFilter = interval === 'none';
452
+ const combinedFormEvent = {
453
+ interval,
454
+ selectedDates: isNoDateFilter
455
+ ? undefined
456
+ : [
457
+ new Date(this.form.value.temporaryUserSelectedFromDate),
458
+ new Date(this.form.value.temporaryUserSelectedToDate)
459
+ ]
460
+ };
461
+ // needed for custom interval
462
+ this.date.set([
463
+ this.form.value.temporaryUserSelectedFromDate,
464
+ this.form.value.temporaryUserSelectedToDate
465
+ ]);
466
+ this.router.navigate([], {
467
+ relativeTo: this.activatedRoute,
468
+ queryParams: {
469
+ interval,
470
+ dateFrom: isNoDateFilter ? null : combinedFormEvent.selectedDates[0].toISOString(),
471
+ dateTo: isNoDateFilter ? null : combinedFormEvent.selectedDates[1].toISOString()
472
+ },
473
+ queryParamsHandling: 'merge'
474
+ });
475
+ this.dateFilterChange.emit(combinedFormEvent);
476
+ }
477
+ updateDateTime(interval) {
478
+ const date = this.eventsViewService.getDateTimeContextByInterval(interval);
479
+ const dropdown = this.dropdown();
480
+ if (dropdown) {
481
+ dropdown.isOpen = false;
482
+ }
483
+ this.date.set(date.map(d => d.toISOString()));
484
+ this.form.patchValue({
485
+ temporaryUserSelectedFromDate: date[0].toISOString(),
486
+ temporaryUserSelectedToDate: date[1].toISOString(),
487
+ currentDateContextInterval: interval
488
+ }, { emitEvent: false });
489
+ this.applyDateFilter();
490
+ }
491
+ getDefaultContext() {
492
+ return {
493
+ date: this.eventsViewService.getDateTimeContextByInterval(this.defaultInterval()),
494
+ interval: this.defaultInterval()
495
+ };
496
+ }
497
+ subscribeToIntervalChange() {
498
+ this.form.controls.currentDateContextInterval.valueChanges
499
+ .pipe(takeUntilDestroyed(this.destroyRef))
500
+ .subscribe(interval => {
501
+ if (interval === 'custom') {
502
+ this.form.patchValue({
503
+ temporaryUserSelectedFromDate: this.form.controls.temporaryUserSelectedFromDate.value === new Date(0).toISOString()
504
+ ? this.form.controls.currentDateContextToDate.value
505
+ : this.form.controls.temporaryUserSelectedFromDate.value,
506
+ currentDateContextInterval: interval
507
+ }, { emitEvent: false });
508
+ return;
509
+ }
510
+ this.updateDateTime(interval);
511
+ });
512
+ }
513
+ createForm(context) {
514
+ return this.formBuilder.group({
515
+ temporaryUserSelectedFromDate: context.date[0].toISOString(),
516
+ temporaryUserSelectedToDate: context.date[1].toISOString(),
517
+ currentDateContextFromDate: context.date[0].toISOString(),
518
+ currentDateContextToDate: context.date[1].toISOString(),
519
+ currentDateContextInterval: context.interval || 'custom'
520
+ });
521
+ }
522
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsDateFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
523
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: EventsDateFilterComponent, isStandalone: true, selector: "c8y-events-date-filter", inputs: { defaultInterval: { classPropertyName: "defaultInterval", publicName: "defaultInterval", isSignal: true, isRequired: false, transformFunction: null }, updateQueryParams: { classPropertyName: "updateQueryParams", publicName: "updateQueryParams", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateFilterChange: "dateFilterChange" }, viewQueries: [{ propertyName: "dropdown", first: true, predicate: BsDropdownDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<form\n class=\"d-flex gap-16 p-l-xs-16 p-r-xs-16 m-t-xs-8 m-b-xs-8\"\n [formGroup]=\"form\"\n>\n <div\n class=\"dropdown flex-grow\"\n #dropDirection=\"bs-dropdown\"\n dropdown\n [insideClick]=\"true\"\n >\n <button\n class=\"dropdown-toggle form-control l-h-tight d-flex a-i-center\"\n attr.aria-label=\"{{\n (form.value.currentDateContextInterval === 'none'\n ? noFilterLabel\n : (date()?.[0] | c8yDate: DATE_FORMAT) + ' \u2014 ' + (date()?.[1] | c8yDate: DATE_FORMAT)\n ) | translate\n }}\"\n tooltip=\"{{\n (form.value.currentDateContextInterval === 'none'\n ? noFilterLabel\n : (date()?.[0] | c8yDate: DATE_FORMAT) + ' \u2014 ' + (date()?.[1] | c8yDate: DATE_FORMAT)\n ) | translate\n }}\"\n placement=\"top\"\n container=\"body\"\n data-cy=\"events-date-filter--date-picker-dropdown-button\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n dropdownToggle\n >\n <i\n class=\"m-r-4\"\n c8yIcon=\"schedule1\"\n ></i>\n <div class=\"d-col text-left fit-w\">\n <span\n class=\"text-12\"\n data-cy=\"widget-time-context--selected-interval\"\n >\n {{\n INTERVAL_TITLES[form.controls.currentDateContextInterval.value ?? 'none'] | translate\n }}\n </span>\n @if (form.controls.currentDateContextInterval.value !== 'none') {\n <span\n class=\"text-10 text-muted text-truncate\"\n data-cy=\"events-date-filter--selected-time-range\"\n >\n {{ date()?.[0] | c8yDate: DATE_FORMAT }} \u2014 {{ date()?.[1] | c8yDate: DATE_FORMAT }}\n </span>\n }\n </div>\n <span class=\"caret m-r-16 m-l-4\"></span>\n </button>\n\n <ul\n class=\"dropdown-menu dropdown-menu--date-range\"\n *dropdownMenu\n >\n <c8y-interval-picker\n class=\"d-contents\"\n formControlName=\"currentDateContextInterval\"\n [INTERVALS]=\"INTERVALS\"\n ></c8y-interval-picker>\n\n @if (form.controls.currentDateContextInterval.value === 'custom') {\n <div class=\"p-l-16 p-r-16\">\n <c8y-form-group\n class=\"m-b-8\"\n [class.has-error]=\"form.controls.temporaryUserSelectedFromDate.errors\"\n >\n <label\n [title]=\"'From`date`' | translate\"\n for=\"temporaryUserSelectedFromDate\"\n translate\n >\n From`date`\n </label>\n <c8y-date-time-picker\n [class.has-error]=\"form.controls.temporaryUserSelectedFromDate.errors\"\n id=\"temporaryUserSelectedFromDate\"\n [maxDate]=\"form.value.temporaryUserSelectedToDate ?? ''\"\n [placeholder]=\"'From`date`' | translate\"\n [formControl]=\"form.controls.temporaryUserSelectedFromDate\"\n ></c8y-date-time-picker>\n <c8y-messages [show]=\"form.controls.temporaryUserSelectedFromDate.errors ?? {}\">\n <c8y-message\n name=\"dateAfterRangeMax\"\n [text]=\"'This date is after the latest allowed date.' | translate\"\n ></c8y-message>\n <c8y-message\n name=\"invalidDateTime\"\n [text]=\"'This date is invalid.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <c8y-form-group\n class=\"m-b-8\"\n [class.has-error]=\"form.controls.temporaryUserSelectedToDate.errors\"\n >\n <label\n [title]=\"'To`date`' | translate\"\n for=\"temporaryUserSelectedToDate\"\n translate\n >\n To`date`\n </label>\n <c8y-date-time-picker\n [class.has-error]=\"form.controls.temporaryUserSelectedToDate.errors\"\n id=\"temporaryUserSelectedToDate\"\n [minDate]=\"form.value.temporaryUserSelectedFromDate ?? ''\"\n [placeholder]=\"'To`date`' | translate\"\n [formControl]=\"form.controls.temporaryUserSelectedToDate\"\n ></c8y-date-time-picker>\n <c8y-messages [show]=\"form.controls.temporaryUserSelectedToDate.errors ?? {}\">\n <c8y-message\n name=\"dateBeforeRangeMin\"\n [text]=\"'This date is before the earliest allowed date.' | translate\"\n ></c8y-message>\n <c8y-message\n name=\"invalidDateTime\"\n [text]=\"'This date is invalid.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"p-16 d-flex gap-8 separator-top\">\n <button\n class=\"btn btn-primary btn-sm flex-grow\"\n title=\"{{ 'Apply' | translate }}\"\n type=\"button\"\n data-cy=\"events-date-filter--apply-button\"\n (click)=\"applyDateFilter(); dropDirection.isOpen = false\"\n [disabled]=\"(form.pristine && form.untouched) || form.invalid\"\n translate\n >\n Apply\n </button>\n </div>\n }\n </ul>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: BsDropdownDirective, selector: "[bsDropdown], [dropdown]", inputs: ["placement", "triggers", "container", "dropup", "autoClose", "isAnimated", "insideClick", "isDisabled", "isOpen"], outputs: ["isOpenChange", "onShown", "onHidden"], exportAs: ["bs-dropdown"] }, { kind: "directive", type: BsDropdownToggleDirective, selector: "[bsDropdownToggle],[dropdownToggle]", exportAs: ["bs-dropdown-toggle"] }, { kind: "directive", type: TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: BsDropdownMenuDirective, selector: "[bsDropdownMenu],[dropdownMenu]", exportAs: ["bs-dropdown-menu"] }, { kind: "component", type: IntervalPickerComponent, selector: "c8y-interval-picker", inputs: ["INTERVALS"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: DateTimePickerComponent, selector: "c8y-date-time-picker", inputs: ["minDate", "maxDate", "placeholder", "dateInputFormat", "adaptivePosition", "size", "dateType", "config"], outputs: ["onDateSelected"] }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage", "additionalMessages"] }, { kind: "directive", type: MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
524
+ }
525
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsDateFilterComponent, decorators: [{
526
+ type: Component,
527
+ args: [{ selector: 'c8y-events-date-filter', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
528
+ FormsModule,
529
+ ReactiveFormsModule,
530
+ BsDropdownDirective,
531
+ BsDropdownToggleDirective,
532
+ TooltipDirective,
533
+ IconDirective,
534
+ BsDropdownMenuDirective,
535
+ IntervalPickerComponent,
536
+ FormGroupComponent,
537
+ C8yTranslateDirective,
538
+ DateTimePickerComponent,
539
+ MessagesComponent,
540
+ MessageDirective,
541
+ C8yTranslatePipe,
542
+ DatePipe
543
+ ], template: "<form\n class=\"d-flex gap-16 p-l-xs-16 p-r-xs-16 m-t-xs-8 m-b-xs-8\"\n [formGroup]=\"form\"\n>\n <div\n class=\"dropdown flex-grow\"\n #dropDirection=\"bs-dropdown\"\n dropdown\n [insideClick]=\"true\"\n >\n <button\n class=\"dropdown-toggle form-control l-h-tight d-flex a-i-center\"\n attr.aria-label=\"{{\n (form.value.currentDateContextInterval === 'none'\n ? noFilterLabel\n : (date()?.[0] | c8yDate: DATE_FORMAT) + ' \u2014 ' + (date()?.[1] | c8yDate: DATE_FORMAT)\n ) | translate\n }}\"\n tooltip=\"{{\n (form.value.currentDateContextInterval === 'none'\n ? noFilterLabel\n : (date()?.[0] | c8yDate: DATE_FORMAT) + ' \u2014 ' + (date()?.[1] | c8yDate: DATE_FORMAT)\n ) | translate\n }}\"\n placement=\"top\"\n container=\"body\"\n data-cy=\"events-date-filter--date-picker-dropdown-button\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n dropdownToggle\n >\n <i\n class=\"m-r-4\"\n c8yIcon=\"schedule1\"\n ></i>\n <div class=\"d-col text-left fit-w\">\n <span\n class=\"text-12\"\n data-cy=\"widget-time-context--selected-interval\"\n >\n {{\n INTERVAL_TITLES[form.controls.currentDateContextInterval.value ?? 'none'] | translate\n }}\n </span>\n @if (form.controls.currentDateContextInterval.value !== 'none') {\n <span\n class=\"text-10 text-muted text-truncate\"\n data-cy=\"events-date-filter--selected-time-range\"\n >\n {{ date()?.[0] | c8yDate: DATE_FORMAT }} \u2014 {{ date()?.[1] | c8yDate: DATE_FORMAT }}\n </span>\n }\n </div>\n <span class=\"caret m-r-16 m-l-4\"></span>\n </button>\n\n <ul\n class=\"dropdown-menu dropdown-menu--date-range\"\n *dropdownMenu\n >\n <c8y-interval-picker\n class=\"d-contents\"\n formControlName=\"currentDateContextInterval\"\n [INTERVALS]=\"INTERVALS\"\n ></c8y-interval-picker>\n\n @if (form.controls.currentDateContextInterval.value === 'custom') {\n <div class=\"p-l-16 p-r-16\">\n <c8y-form-group\n class=\"m-b-8\"\n [class.has-error]=\"form.controls.temporaryUserSelectedFromDate.errors\"\n >\n <label\n [title]=\"'From`date`' | translate\"\n for=\"temporaryUserSelectedFromDate\"\n translate\n >\n From`date`\n </label>\n <c8y-date-time-picker\n [class.has-error]=\"form.controls.temporaryUserSelectedFromDate.errors\"\n id=\"temporaryUserSelectedFromDate\"\n [maxDate]=\"form.value.temporaryUserSelectedToDate ?? ''\"\n [placeholder]=\"'From`date`' | translate\"\n [formControl]=\"form.controls.temporaryUserSelectedFromDate\"\n ></c8y-date-time-picker>\n <c8y-messages [show]=\"form.controls.temporaryUserSelectedFromDate.errors ?? {}\">\n <c8y-message\n name=\"dateAfterRangeMax\"\n [text]=\"'This date is after the latest allowed date.' | translate\"\n ></c8y-message>\n <c8y-message\n name=\"invalidDateTime\"\n [text]=\"'This date is invalid.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <c8y-form-group\n class=\"m-b-8\"\n [class.has-error]=\"form.controls.temporaryUserSelectedToDate.errors\"\n >\n <label\n [title]=\"'To`date`' | translate\"\n for=\"temporaryUserSelectedToDate\"\n translate\n >\n To`date`\n </label>\n <c8y-date-time-picker\n [class.has-error]=\"form.controls.temporaryUserSelectedToDate.errors\"\n id=\"temporaryUserSelectedToDate\"\n [minDate]=\"form.value.temporaryUserSelectedFromDate ?? ''\"\n [placeholder]=\"'To`date`' | translate\"\n [formControl]=\"form.controls.temporaryUserSelectedToDate\"\n ></c8y-date-time-picker>\n <c8y-messages [show]=\"form.controls.temporaryUserSelectedToDate.errors ?? {}\">\n <c8y-message\n name=\"dateBeforeRangeMin\"\n [text]=\"'This date is before the earliest allowed date.' | translate\"\n ></c8y-message>\n <c8y-message\n name=\"invalidDateTime\"\n [text]=\"'This date is invalid.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"p-16 d-flex gap-8 separator-top\">\n <button\n class=\"btn btn-primary btn-sm flex-grow\"\n title=\"{{ 'Apply' | translate }}\"\n type=\"button\"\n data-cy=\"events-date-filter--apply-button\"\n (click)=\"applyDateFilter(); dropDirection.isOpen = false\"\n [disabled]=\"(form.pristine && form.untouched) || form.invalid\"\n translate\n >\n Apply\n </button>\n </div>\n }\n </ul>\n </div>\n</form>\n" }]
544
+ }], propDecorators: { defaultInterval: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultInterval", required: false }] }], updateQueryParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "updateQueryParams", required: false }] }], dateFilterChange: [{ type: i0.Output, args: ["dateFilterChange"] }], dropdown: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BsDropdownDirective), { isSignal: true }] }] } });
545
+
546
+ class EventsIntervalRefreshComponent {
547
+ constructor() {
548
+ this.DEFAULT_INTERVAL_VALUES = [5_000, 10_000, 15_000, 30_000, 60_000];
549
+ this.DEFAULT_INTERVAL_VALUE = 30_000;
550
+ this.DISABLE_AUTO_REFRESH = gettext('Disable auto refresh');
551
+ this.ENABLE_AUTO_REFRESH = gettext('Enable auto refresh');
552
+ this.SECONDS_UNTIL_REFRESH = gettext('{{ seconds }} s');
553
+ this.isLoading = input(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
554
+ this.isDisabled = input(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
555
+ this.isIntervalToggleEnabled = input(true, ...(ngDevMode ? [{ debugName: "isIntervalToggleEnabled" }] : []));
556
+ this.onCountdownEnded = output();
557
+ this.countdownIntervalComponent = viewChild(CountdownIntervalComponent, ...(ngDevMode ? [{ debugName: "countdownIntervalComponent" }] : []));
558
+ this.fb = inject(FormBuilder);
559
+ this.toggleIntervalForm = this.initForm();
560
+ this.destroyRef = inject(DestroyRef);
561
+ this.doesUserCheckedIntervalToggle = false;
562
+ effect(() => {
563
+ const externalValue = this.isIntervalToggleEnabled();
564
+ const intervalEnabledControl = this.toggleIntervalForm.get('intervalEnabled');
565
+ const shouldUpdate = !intervalEnabledControl.dirty || (this.doesUserCheckedIntervalToggle && externalValue);
566
+ if (shouldUpdate && intervalEnabledControl.value !== externalValue) {
567
+ intervalEnabledControl.setValue(externalValue);
568
+ }
569
+ });
570
+ effect(() => {
571
+ const loading = this.isLoading();
572
+ untracked(() => {
573
+ if (loading) {
574
+ this.countdownIntervalComponent()?.stop();
575
+ }
576
+ else {
577
+ this.countdownIntervalComponent()?.reset();
578
+ }
579
+ });
580
+ });
581
+ }
582
+ get isToggleEnabled() {
583
+ return !this.isDisabled() && this.toggleIntervalForm.get('intervalEnabled').value;
584
+ }
585
+ ngAfterViewInit() {
586
+ this.onIntervalToggleChange();
587
+ this.listenToRefreshIntervalChange();
588
+ }
589
+ resetCountdown() {
590
+ this.countdownIntervalComponent()?.reset();
591
+ }
592
+ trackUserClickOnIntervalToggle(target) {
593
+ this.doesUserCheckedIntervalToggle = target.checked;
594
+ }
595
+ getTooltip() {
596
+ return this.isDisabled()
597
+ ? gettext('Disabled')
598
+ : this.isToggleEnabled
599
+ ? this.DISABLE_AUTO_REFRESH
600
+ : this.ENABLE_AUTO_REFRESH;
601
+ }
602
+ startCountdown() {
603
+ this.countdownIntervalComponent()?.start();
604
+ }
605
+ onIntervalToggleChange() {
606
+ this.toggleIntervalForm
607
+ .get('intervalEnabled')
608
+ .valueChanges.pipe(takeUntilDestroyed(this.destroyRef), filter(Boolean))
609
+ .subscribe(() => setTimeout(() => this.startCountdown()));
610
+ }
611
+ initForm() {
612
+ return this.fb.group({
613
+ intervalEnabled: true,
614
+ refreshInterval: this.DEFAULT_INTERVAL_VALUE
615
+ });
616
+ }
617
+ listenToRefreshIntervalChange() {
618
+ this.toggleIntervalForm
619
+ .get('refreshInterval')
620
+ .valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
621
+ .subscribe(() => this.resetCountdown());
622
+ }
623
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsIntervalRefreshComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
624
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: EventsIntervalRefreshComponent, isStandalone: true, selector: "c8y-events-interval-refresh", inputs: { isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, isIntervalToggleEnabled: { classPropertyName: "isIntervalToggleEnabled", publicName: "isIntervalToggleEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onCountdownEnded: "onCountdownEnded" }, viewQueries: [{ propertyName: "countdownIntervalComponent", first: true, predicate: CountdownIntervalComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<form\n class=\"d-flex a-i-center fit-w fit-h\"\n [formGroup]=\"toggleIntervalForm\"\n>\n <label class=\"m-b-0 m-r-8 text-normal text-muted flex-no-shrink\">\n {{ 'Auto refresh' | translate }}\n </label>\n <div class=\"input-group\">\n <label\n class=\"toggle-countdown\"\n [class.toggle-countdown-disabled]=\"isDisabled()\"\n [attr.aria-label]=\"getTooltip() | translate\"\n [tooltip]=\"getTooltip() | translate\"\n placement=\"bottom\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n data-cy=\"c8y-events-interval-refresh--toggle-countdown\"\n >\n <input\n type=\"checkbox\"\n data-cy=\"c8y-events-interval-toggle\"\n formControlName=\"intervalEnabled\"\n (click)=\"trackUserClickOnIntervalToggle($event.target)\"\n />\n @if (isToggleEnabled) {\n <c8y-countdown-interval\n [countdownInterval]=\"toggleIntervalForm.value.refreshInterval ?? DEFAULT_INTERVAL_VALUE\"\n (countdownEnded)=\"onCountdownEnded.emit()\"\n />\n } @else {\n <i\n c8yIcon=\"pause\"\n data-cy=\"c8y-events-interval-refresh--pause\"\n ></i>\n }\n </label>\n @if (!isDisabled()) {\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control text-12\"\n [attr.aria-label]=\"'Refresh interval in seconds' | translate\"\n [tooltip]=\"'Refresh interval in seconds' | translate\"\n placement=\"bottom\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n [container]=\"'body'\"\n formControlName=\"refreshInterval\"\n data-cy=\"c8y-events-interval-refresh--selector\"\n >\n @for (refreshInterval of DEFAULT_INTERVAL_VALUES; track refreshInterval) {\n <option\n [disabled]=\"isDisabled()\"\n [ngValue]=\"refreshInterval\"\n [attr.data-cy]=\"'c8y-interval-' + refreshInterval\"\n >\n {{ SECONDS_UNTIL_REFRESH | translate: { seconds: refreshInterval / 1000 } }}\n </option>\n }\n </select>\n <span></span>\n </div>\n }\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default\"\n style=\"border-left: 0\"\n [attr.aria-label]=\"'Refresh' | translate\"\n [tooltip]=\"'Refresh' | translate\"\n placement=\"bottom\"\n type=\"button\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n [disabled]=\"isDisabled() || isLoading()\"\n (click)=\"onCountdownEnded.emit()\"\n data-cy=\"c8y-events-interval-refresh--btn\"\n >\n <i\n [class.icon-spin]=\"isLoading()\"\n c8yIcon=\"refresh\"\n ></i>\n </button>\n </div>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "component", type: CountdownIntervalComponent, selector: "c8y-countdown-interval", inputs: ["countdownInterval", "config"], outputs: ["countdownEnded"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
625
+ }
626
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsIntervalRefreshComponent, decorators: [{
627
+ type: Component,
628
+ args: [{ selector: 'c8y-events-interval-refresh', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
629
+ FormsModule,
630
+ ReactiveFormsModule,
631
+ TooltipDirective,
632
+ CountdownIntervalComponent,
633
+ IconDirective,
634
+ C8yTranslatePipe
635
+ ], template: "<form\n class=\"d-flex a-i-center fit-w fit-h\"\n [formGroup]=\"toggleIntervalForm\"\n>\n <label class=\"m-b-0 m-r-8 text-normal text-muted flex-no-shrink\">\n {{ 'Auto refresh' | translate }}\n </label>\n <div class=\"input-group\">\n <label\n class=\"toggle-countdown\"\n [class.toggle-countdown-disabled]=\"isDisabled()\"\n [attr.aria-label]=\"getTooltip() | translate\"\n [tooltip]=\"getTooltip() | translate\"\n placement=\"bottom\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n data-cy=\"c8y-events-interval-refresh--toggle-countdown\"\n >\n <input\n type=\"checkbox\"\n data-cy=\"c8y-events-interval-toggle\"\n formControlName=\"intervalEnabled\"\n (click)=\"trackUserClickOnIntervalToggle($event.target)\"\n />\n @if (isToggleEnabled) {\n <c8y-countdown-interval\n [countdownInterval]=\"toggleIntervalForm.value.refreshInterval ?? DEFAULT_INTERVAL_VALUE\"\n (countdownEnded)=\"onCountdownEnded.emit()\"\n />\n } @else {\n <i\n c8yIcon=\"pause\"\n data-cy=\"c8y-events-interval-refresh--pause\"\n ></i>\n }\n </label>\n @if (!isDisabled()) {\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control text-12\"\n [attr.aria-label]=\"'Refresh interval in seconds' | translate\"\n [tooltip]=\"'Refresh interval in seconds' | translate\"\n placement=\"bottom\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n [container]=\"'body'\"\n formControlName=\"refreshInterval\"\n data-cy=\"c8y-events-interval-refresh--selector\"\n >\n @for (refreshInterval of DEFAULT_INTERVAL_VALUES; track refreshInterval) {\n <option\n [disabled]=\"isDisabled()\"\n [ngValue]=\"refreshInterval\"\n [attr.data-cy]=\"'c8y-interval-' + refreshInterval\"\n >\n {{ SECONDS_UNTIL_REFRESH | translate: { seconds: refreshInterval / 1000 } }}\n </option>\n }\n </select>\n <span></span>\n </div>\n }\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default\"\n style=\"border-left: 0\"\n [attr.aria-label]=\"'Refresh' | translate\"\n [tooltip]=\"'Refresh' | translate\"\n placement=\"bottom\"\n type=\"button\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n [disabled]=\"isDisabled() || isLoading()\"\n (click)=\"onCountdownEnded.emit()\"\n data-cy=\"c8y-events-interval-refresh--btn\"\n >\n <i\n [class.icon-spin]=\"isLoading()\"\n c8yIcon=\"refresh\"\n ></i>\n </button>\n </div>\n </div>\n</form>\n" }]
636
+ }], ctorParameters: () => [], propDecorators: { isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDisabled", required: false }] }], isIntervalToggleEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isIntervalToggleEnabled", required: false }] }], onCountdownEnded: [{ type: i0.Output, args: ["onCountdownEnded"] }], countdownIntervalComponent: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CountdownIntervalComponent), { isSignal: true }] }] } });
637
+
638
+ class EventsListComponent {
639
+ constructor() {
640
+ this.EMPTY_STATE_TITLE = gettext('No events to display.');
641
+ this.LIST_TITLE = gettext('Events list');
642
+ /**
643
+ * The paginated result list of events to display.
644
+ */
645
+ this.events = input(...(ngDevMode ? [undefined, { debugName: "events" }] : []));
646
+ /**
647
+ * Whether the events are currently being fetched.
648
+ */
649
+ this.isLoading = input(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
650
+ /**
651
+ * Controls the "load more" button behavior at the bottom of the list.
652
+ */
653
+ this.loadMoreMode = input('hidden', ...(ngDevMode ? [{ debugName: "loadMoreMode" }] : []));
654
+ /**
655
+ * Whether to show a file preview button for image events.
656
+ */
657
+ this.showPreview = input(false, ...(ngDevMode ? [{ debugName: "showPreview" }] : []));
658
+ /**
659
+ * Defines options for how the events list should navigate when a user clicks on an event.
660
+ */
661
+ this.navigationOptions = input({
662
+ allowNavigationToEventsView: true,
663
+ alwaysNavigateToAllEvents: false,
664
+ queryParamsHandling: 'merge'
665
+ }, ...(ngDevMode ? [{ debugName: "navigationOptions" }] : []));
666
+ /**
667
+ * Emits `true` when the list is scrolled past the threshold, `false` when scrolled back.
668
+ * Used to hide the countdown interval refresh control.
669
+ */
670
+ this.onScrollingStateChange = output();
671
+ /**
672
+ * Emits `true` when a file preview is opened, `false` when closed.
673
+ */
674
+ this.onPreviewStateChange = output();
675
+ /**
676
+ * Emits the event that was clicked by the user.
677
+ */
678
+ this.onSelectedEvent = output();
679
+ this.activeEvent = signal(null, ...(ngDevMode ? [{ debugName: "activeEvent" }] : []));
680
+ this.activeChildParamId = signal(null, ...(ngDevMode ? [{ debugName: "activeChildParamId" }] : []));
681
+ this.pendingActiveCheck = signal(true, ...(ngDevMode ? [{ debugName: "pendingActiveCheck" }] : []));
682
+ this.showEmptyState = computed(() => !!this.events() && !this.events()?.data?.length && !this.isLoading(), ...(ngDevMode ? [{ debugName: "showEmptyState" }] : []));
683
+ this.showFilterWarning = computed(() => !this.pendingActiveCheck() &&
684
+ !!this.activeChildParamId() &&
685
+ this.activeEvent()?.id !== this.activeChildParamId(), ...(ngDevMode ? [{ debugName: "showFilterWarning" }] : []));
686
+ this.svListComponent = viewChild('scrollWrapper', ...(ngDevMode ? [{ debugName: "svListComponent" }] : []));
687
+ this.isPreviewOpen = signal(false, ...(ngDevMode ? [{ debugName: "isPreviewOpen" }] : []));
688
+ /** Scroll threshold in pixels after which the countdown interval is hidden. */
689
+ this.HIDE_INTERVAL_COUNTDOWN_SCROLL = 50;
690
+ this.activatedRoute = inject(ActivatedRoute);
691
+ this.bsModalService = inject(BsModalService);
692
+ this.destroyRef = inject(DestroyRef);
693
+ this.eventBinaryService = inject(EventBinaryService);
694
+ this.router = inject(Router);
695
+ this.setupActiveChildParamTracking();
696
+ this.setupPreviewModalCloseListener();
697
+ effect(() => {
698
+ this.events();
699
+ untracked(() => {
700
+ this.activeEvent.set(null);
701
+ this.pendingActiveCheck.set(true);
702
+ if (this.activeChildParamId()) {
703
+ // Wait for routerLinkActive to confirm the match via activeRouteChanged.
704
+ // Fallback clears pending state so warning can show if event is not in the list.
705
+ timer(500)
706
+ .pipe(takeUntilDestroyed(this.destroyRef))
707
+ .subscribe(() => this.pendingActiveCheck.set(false));
708
+ }
709
+ else {
710
+ this.pendingActiveCheck.set(false);
711
+ }
712
+ });
713
+ });
714
+ }
715
+ ngAfterViewInit() {
716
+ this.setupScrollListener();
717
+ }
718
+ onEventClick(event) {
719
+ this.onSelectedEvent.emit(event);
720
+ }
721
+ activeRouteChanged(isActive, scrollAnchor, event) {
722
+ if (isActive) {
723
+ scrollAnchor.element.nativeElement.scrollIntoView({
724
+ behavior: 'smooth',
725
+ block: 'nearest'
726
+ });
727
+ this.activeEvent.set(event);
728
+ this.pendingActiveCheck.set(false);
729
+ }
730
+ }
731
+ onPreviewClick(mouseEvent) {
732
+ mouseEvent.stopPropagation();
733
+ this.isPreviewOpen.set(true);
734
+ this.onPreviewStateChange.emit(true);
735
+ }
736
+ toBinaryMo(event) {
737
+ const binaryInfo = event['c8y_IsBinary'];
738
+ return {
739
+ id: `${event.id}`,
740
+ name: binaryInfo?.name ?? event.text,
741
+ contentType: binaryInfo?.type ?? 'application/octet-stream',
742
+ type: binaryInfo?.type ?? 'application/octet-stream',
743
+ length: 0,
744
+ c8y_IsBinary: {}
745
+ };
746
+ }
747
+ getEventDownloadFn(event) {
748
+ return () => this.eventBinaryService.download(event);
749
+ }
750
+ setupPreviewModalCloseListener() {
751
+ this.bsModalService.onHidden
752
+ .pipe(filter(() => this.isPreviewOpen()), takeUntilDestroyed(this.destroyRef))
753
+ .subscribe(() => {
754
+ this.isPreviewOpen.set(false);
755
+ this.onPreviewStateChange.emit(false);
756
+ });
757
+ }
758
+ setupActiveChildParamTracking() {
759
+ this.router.events
760
+ .pipe(filter(e => e instanceof NavigationEnd && this.activatedRoute.children.length > 0), switchMap(() => this.activatedRoute.children[0].params), map(params => params['id']), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
761
+ .subscribe(id => this.activeChildParamId.set(id ?? null));
762
+ }
763
+ setupScrollListener() {
764
+ const scrollElement = this.svListComponent()?.innerScrollDiv?.nativeElement;
765
+ if (scrollElement) {
766
+ fromEvent(scrollElement, 'scroll')
767
+ .pipe(takeUntilDestroyed(this.destroyRef), debounceTime(300))
768
+ .subscribe((scrollEvent) => {
769
+ if (!(scrollEvent.target instanceof HTMLElement)) {
770
+ return;
771
+ }
772
+ const isScrolledPastThreshold = this.shouldCountdownIntervalBeHidden(scrollEvent.target);
773
+ this.onScrollingStateChange.emit(isScrolledPastThreshold);
774
+ });
775
+ }
776
+ }
777
+ shouldCountdownIntervalBeHidden(target) {
778
+ return target.scrollTop > this.HIDE_INTERVAL_COUNTDOWN_SCROLL;
779
+ }
780
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
781
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: EventsListComponent, isStandalone: true, selector: "c8y-events-list", inputs: { events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, loadMoreMode: { classPropertyName: "loadMoreMode", publicName: "loadMoreMode", isSignal: true, isRequired: false, transformFunction: null }, showPreview: { classPropertyName: "showPreview", publicName: "showPreview", isSignal: true, isRequired: false, transformFunction: null }, navigationOptions: { classPropertyName: "navigationOptions", publicName: "navigationOptions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScrollingStateChange: "onScrollingStateChange", onPreviewStateChange: "onPreviewStateChange", onSelectedEvent: "onSelectedEvent" }, viewQueries: [{ propertyName: "svListComponent", first: true, predicate: ["scrollWrapper"], descendants: true, isSignal: true }], ngImport: i0, template: "<c8y-sv-list\n [emptyStateIcon]=\"'online1'\"\n [title]=\"LIST_TITLE | translate\"\n [emptyStateTitle]=\"EMPTY_STATE_TITLE | translate\"\n #scrollWrapper\n [loading]=\"isLoading()\"\n [showEmptyState]=\"showEmptyState()\"\n [docsUrl]=\"\n '/docs/device-management-application/monitoring-and-controlling-devices/#to-view-events'\n \"\n data-cy=\"c8y-events-list\"\n>\n <c8y-sv-header-actions>\n <ng-content />\n </c8y-sv-header-actions>\n\n <c8y-sv-alerts>\n @if (showFilterWarning()) {\n <div\n class=\"alert alert-warning m-b-0\"\n role=\"alert\"\n translate\n >\n The selected event is not currently in the list, change your filter.\n </div>\n }\n </c8y-sv-alerts>\n\n <c8y-list-group class=\"c8y-list--timeline d-block p-r-16 p-t-16\">\n @let navOpts = navigationOptions();\n <c8y-li-timeline\n class=\"pointer c8y-list--timeline__item\"\n role=\"button\"\n data-cy=\"c8y-events-list--timeline-item\"\n *c8yFor=\"let event of events()!; loadMore: loadMoreMode()\"\n [routerLink]=\"\n navOpts.allowNavigationToEventsView\n ? (event | eventRouterLink: navOpts.alwaysNavigateToAllEvents)\n : null\n \"\n [queryParamsHandling]=\"navOpts.queryParamsHandling\"\n routerLinkActive=\"active\"\n (isActiveChange)=\"activeRouteChanged($event, liScrollAnchor, event)\"\n (click)=\"onEventClick(event)\"\n >\n <!-- Date column -->\n <span data-cy=\"c8y-events-list--event-date\">\n {{ event.time | c8yDate: 'mediumDate' }}\n {{ event.time | c8yDate: 'mediumTime' }}\n </span>\n\n <c8y-li\n style=\"scroll-margin-top: 56px\"\n #liScrollAnchor\n >\n <c8y-li-icon class=\"a-s-start\">\n <i [c8yIcon]=\"event | eventIcon\"></i>\n </c8y-li-icon>\n\n <c8y-li-body class=\"a-s-stretch\">\n <div class=\"d-flex a-i-start fit-h\">\n <div class=\"min-width-0 flex-grow\">\n <!-- Event column (text) -->\n <p\n class=\"text-truncate-wrap p-b-4\"\n data-cy=\"c8y-events-list--event-text\"\n >\n {{ event.text | translate }}\n </p>\n\n <!-- Source column -->\n <p\n class=\"small text-muted text-truncate\"\n [title]=\"event.source?.name\"\n data-cy=\"c8y-events-list--event-source\"\n >\n <i [c8yIcon]=\"'exchange'\"></i>\n {{ event.source?.name }}\n </p>\n </div>\n @if (showPreview() && (event | eventIsImage)) {\n <c8y-file-preview\n data-cy=\"c8y-events-list--image-preview\"\n (click)=\"onPreviewClick($event)\"\n [mo]=\"toBinaryMo(event)\"\n [downloadFn]=\"getEventDownloadFn(event)\"\n >\n <button\n class=\"btn btn-default btn-icon btn-xs\"\n [title]=\"'Preview' | translate\"\n type=\"button\"\n customButton\n data-cy=\"c8y-events-list--preview-btn\"\n >\n <i c8yIcon=\"search\"></i>\n </button>\n </c8y-file-preview>\n }\n </div>\n </c8y-li-body>\n </c8y-li>\n </c8y-li-timeline>\n </c8y-list-group>\n</c8y-sv-list>\n", dependencies: [{ kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: ForOfDirective, selector: "[c8yFor]", inputs: ["c8yForOf", "c8yForLoadMore", "c8yForPipe", "c8yForNotFound", "c8yForMaxIterations", "c8yForLoadingTemplate", "c8yForLoadNextLabel", "c8yForLoadingLabel", "c8yForRealtime", "c8yForRealtimeOptions", "c8yForComparator", "c8yForEnableVirtualScroll", "c8yForVirtualScrollElementSize", "c8yForVirtualScrollStrategy", "c8yForVirtualScrollContainerHeight"], outputs: ["c8yForCount", "c8yForChange", "c8yForLoadMoreComponent"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: ListItemTimelineComponent, selector: "c8y-list-item-timeline, c8y-li-timeline" }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: FilePreviewModule }, { kind: "component", type: i1$2.FilePreviewComponent, selector: "c8y-file-preview", inputs: ["mo", "downloadFn"] }, { kind: "component", type: SplitViewAlertsComponent, selector: "c8y-sv-alerts" }, { kind: "component", type: SplitViewHeaderActionsComponent, selector: "c8y-sv-header-actions" }, { kind: "component", type: SplitViewListComponent, selector: "c8y-sv-list", inputs: ["title", "loading", "showEmptyState", "emptyStateIcon", "emptyStateTitle", "emptyStateSubtitle", "docsUrl", "showTitle", "listOpacity"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }, { kind: "pipe", type: EventIconPipe, name: "eventIcon" }, { kind: "pipe", type: EventIsImagePipe, name: "eventIsImage" }, { kind: "pipe", type: EventRouterLinkPipe, name: "eventRouterLink" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
782
+ }
783
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsListComponent, decorators: [{
784
+ type: Component,
785
+ args: [{ selector: 'c8y-events-list', imports: [
786
+ C8yTranslateDirective,
787
+ C8yTranslatePipe,
788
+ DatePipe,
789
+ EventIconPipe,
790
+ EventIsImagePipe,
791
+ EventRouterLinkPipe,
792
+ ForOfDirective,
793
+ IconDirective,
794
+ ListGroupComponent,
795
+ ListItemBodyComponent,
796
+ ListItemComponent,
797
+ ListItemIconComponent,
798
+ ListItemTimelineComponent,
799
+ RouterLink,
800
+ RouterLinkActive,
801
+ FilePreviewModule,
802
+ SplitViewAlertsComponent,
803
+ SplitViewHeaderActionsComponent,
804
+ SplitViewListComponent
805
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<c8y-sv-list\n [emptyStateIcon]=\"'online1'\"\n [title]=\"LIST_TITLE | translate\"\n [emptyStateTitle]=\"EMPTY_STATE_TITLE | translate\"\n #scrollWrapper\n [loading]=\"isLoading()\"\n [showEmptyState]=\"showEmptyState()\"\n [docsUrl]=\"\n '/docs/device-management-application/monitoring-and-controlling-devices/#to-view-events'\n \"\n data-cy=\"c8y-events-list\"\n>\n <c8y-sv-header-actions>\n <ng-content />\n </c8y-sv-header-actions>\n\n <c8y-sv-alerts>\n @if (showFilterWarning()) {\n <div\n class=\"alert alert-warning m-b-0\"\n role=\"alert\"\n translate\n >\n The selected event is not currently in the list, change your filter.\n </div>\n }\n </c8y-sv-alerts>\n\n <c8y-list-group class=\"c8y-list--timeline d-block p-r-16 p-t-16\">\n @let navOpts = navigationOptions();\n <c8y-li-timeline\n class=\"pointer c8y-list--timeline__item\"\n role=\"button\"\n data-cy=\"c8y-events-list--timeline-item\"\n *c8yFor=\"let event of events()!; loadMore: loadMoreMode()\"\n [routerLink]=\"\n navOpts.allowNavigationToEventsView\n ? (event | eventRouterLink: navOpts.alwaysNavigateToAllEvents)\n : null\n \"\n [queryParamsHandling]=\"navOpts.queryParamsHandling\"\n routerLinkActive=\"active\"\n (isActiveChange)=\"activeRouteChanged($event, liScrollAnchor, event)\"\n (click)=\"onEventClick(event)\"\n >\n <!-- Date column -->\n <span data-cy=\"c8y-events-list--event-date\">\n {{ event.time | c8yDate: 'mediumDate' }}\n {{ event.time | c8yDate: 'mediumTime' }}\n </span>\n\n <c8y-li\n style=\"scroll-margin-top: 56px\"\n #liScrollAnchor\n >\n <c8y-li-icon class=\"a-s-start\">\n <i [c8yIcon]=\"event | eventIcon\"></i>\n </c8y-li-icon>\n\n <c8y-li-body class=\"a-s-stretch\">\n <div class=\"d-flex a-i-start fit-h\">\n <div class=\"min-width-0 flex-grow\">\n <!-- Event column (text) -->\n <p\n class=\"text-truncate-wrap p-b-4\"\n data-cy=\"c8y-events-list--event-text\"\n >\n {{ event.text | translate }}\n </p>\n\n <!-- Source column -->\n <p\n class=\"small text-muted text-truncate\"\n [title]=\"event.source?.name\"\n data-cy=\"c8y-events-list--event-source\"\n >\n <i [c8yIcon]=\"'exchange'\"></i>\n {{ event.source?.name }}\n </p>\n </div>\n @if (showPreview() && (event | eventIsImage)) {\n <c8y-file-preview\n data-cy=\"c8y-events-list--image-preview\"\n (click)=\"onPreviewClick($event)\"\n [mo]=\"toBinaryMo(event)\"\n [downloadFn]=\"getEventDownloadFn(event)\"\n >\n <button\n class=\"btn btn-default btn-icon btn-xs\"\n [title]=\"'Preview' | translate\"\n type=\"button\"\n customButton\n data-cy=\"c8y-events-list--preview-btn\"\n >\n <i c8yIcon=\"search\"></i>\n </button>\n </c8y-file-preview>\n }\n </div>\n </c8y-li-body>\n </c8y-li>\n </c8y-li-timeline>\n </c8y-list-group>\n</c8y-sv-list>\n" }]
806
+ }], ctorParameters: () => [], propDecorators: { events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], loadMoreMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadMoreMode", required: false }] }], showPreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPreview", required: false }] }], navigationOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigationOptions", required: false }] }], onScrollingStateChange: [{ type: i0.Output, args: ["onScrollingStateChange"] }], onPreviewStateChange: [{ type: i0.Output, args: ["onPreviewStateChange"] }], onSelectedEvent: [{ type: i0.Output, args: ["onSelectedEvent"] }], svListComponent: [{ type: i0.ViewChild, args: ['scrollWrapper', { isSignal: true }] }] } });
807
+
808
+ class EventsTypeFilterComponent {
809
+ constructor() {
810
+ /** The latest page of events used to derive available filter types. */
811
+ this.events = input(...(ngDevMode ? [undefined, { debugName: "events" }] : []));
812
+ /** Emits the selected {@link EventDetails} when the active filter changes, or `null` when cleared. */
813
+ this.onFilterChanged = output();
814
+ this.possibleFilters = signal([], ...(ngDevMode ? [{ debugName: "possibleFilters" }] : []));
815
+ this.activeFilter = signal(null, ...(ngDevMode ? [{ debugName: "activeFilter" }] : []));
816
+ this.customEventTypeInput = signal('', ...(ngDevMode ? [{ debugName: "customEventTypeInput" }] : []));
817
+ this.isCustomEventTypeInputInvalid = computed(() => !this.customEventTypeInput().trim(), ...(ngDevMode ? [{ debugName: "isCustomEventTypeInputInvalid" }] : []));
818
+ this.queryParamName = 'eventTypeFilter';
819
+ /**
820
+ * localStorage key storing user-added custom event types.
821
+ * Schema: `EventDetails[]` serialised as JSON (only entries where `__target === null`).
822
+ */
823
+ this.STORAGE_ACCESS_KEY = 'customEventTypes';
824
+ this.activatedRoute = inject(ActivatedRoute);
825
+ this.alarmEventSelectorService = inject(AlarmEventSelectorService);
826
+ this.colorService = inject(ColorService);
827
+ this.destroyRef = inject(DestroyRef);
828
+ this.router = inject(Router);
829
+ this.currentQueryParam = '';
830
+ effect(() => {
831
+ const eventsResult = this.events();
832
+ if (eventsResult) {
833
+ untracked(() => {
834
+ if (!this.activeFilter()) {
835
+ this.setPossibleFilters()
836
+ .then(() => this.applyFilterChange())
837
+ // eslint-disable-next-line no-console
838
+ .catch(err => console.error('[EventsTypeFilter] Failed to load filters', err));
839
+ }
840
+ });
841
+ }
842
+ });
843
+ }
844
+ ngOnInit() {
845
+ this.setQueryParameterObservable();
846
+ }
847
+ setQueryParameterObservable() {
848
+ this.activatedRoute.queryParams
849
+ .pipe(map(params => {
850
+ const events = this.possibleFilters();
851
+ const possibleFilters = this.setActiveEventFiltersFromQueryParameter(events, params[this.queryParamName]);
852
+ return possibleFilters;
853
+ }), takeUntilDestroyed(this.destroyRef))
854
+ .subscribe((possibleFilters) => {
855
+ this.possibleFilters.set(possibleFilters);
856
+ this.applyFilterChange();
857
+ });
858
+ }
859
+ selectEventType(eventType) {
860
+ this.possibleFilters.update(filters => filters.map(f => ({ ...f, __active: f.filters.type === eventType.filters.type })));
861
+ }
862
+ /** Selects an event type, applies the filter, and closes the dropdown in one action. */
863
+ selectAndClose(eventType, dropdown) {
864
+ this.selectEventType(eventType);
865
+ this.applyFilterChange();
866
+ dropdown.hide();
867
+ }
868
+ deselect() {
869
+ this.possibleFilters.update(filters => filters.map(f => ({ ...f, __active: false })));
870
+ this.applyFilterChange();
871
+ }
872
+ applyFilterChange() {
873
+ const active = this.possibleFilters().find((eventFilter) => eventFilter.__active);
874
+ const newQueryParam = active?.filters.type ?? '';
875
+ const hasChanged = newQueryParam !== this.currentQueryParam;
876
+ if (hasChanged) {
877
+ this.activeFilter.set(active ?? null);
878
+ this.onFilterChanged.emit(this.activeFilter());
879
+ this.router.navigate([], {
880
+ queryParams: {
881
+ [this.queryParamName]: newQueryParam || null
882
+ },
883
+ queryParamsHandling: 'merge'
884
+ });
885
+ this.currentQueryParam = newQueryParam;
886
+ }
887
+ }
888
+ restoreActiveFilter() {
889
+ const activeType = this.activeFilter()?.filters.type;
890
+ this.possibleFilters.update(filters => filters.map(f => ({ ...f, __active: f.filters.type === activeType })));
891
+ }
892
+ removeCustomEvent(eventDetails) {
893
+ const wasActive = eventDetails.filters.type === this.activeFilter()?.filters.type;
894
+ this.possibleFilters.update(filters => filters.filter(f => f.filters.type !== eventDetails.filters.type));
895
+ this.storeCustomEventTypes();
896
+ if (wasActive) {
897
+ this.applyFilterChange();
898
+ }
899
+ }
900
+ navigateOption(event, step) {
901
+ event.preventDefault();
902
+ const items = this.optionItems.toArray();
903
+ const focused = document.activeElement;
904
+ const currentIndex = items.findIndex(item => item.nativeElement === focused);
905
+ const nextIndex = (currentIndex + step + items.length) % items.length;
906
+ items[nextIndex]?.nativeElement.focus();
907
+ }
908
+ confirmWithEnter(event) {
909
+ if (event.key === 'Enter') {
910
+ this.addCustomEventType();
911
+ }
912
+ }
913
+ async addCustomEventType() {
914
+ const inputValue = this.customEventTypeInput().trim();
915
+ if (!inputValue) {
916
+ return;
917
+ }
918
+ let color;
919
+ try {
920
+ color = await this.colorService.generateColor(inputValue);
921
+ }
922
+ catch {
923
+ color = '#000000';
924
+ }
925
+ const newFilter = {
926
+ label: inputValue,
927
+ color,
928
+ filters: { type: inputValue },
929
+ timelineType: 'EVENT',
930
+ __active: true,
931
+ __target: null
932
+ };
933
+ this.possibleFilters.update(filters => [
934
+ newFilter,
935
+ ...filters.map(f => ({ ...f, __active: false }))
936
+ ]);
937
+ this.customEventTypeInput.set('');
938
+ this.storeCustomEventTypes();
939
+ this.applyFilterChange();
940
+ }
941
+ storeCustomEventTypes() {
942
+ try {
943
+ const customTypes = this.possibleFilters().filter((filter) => !filter.__target);
944
+ window.localStorage.setItem(this.STORAGE_ACCESS_KEY, JSON.stringify(customTypes));
945
+ }
946
+ catch {
947
+ // localStorage may be unavailable or quota exceeded — skip persistence silently
948
+ }
949
+ }
950
+ getCustomEventTypeFromStorage() {
951
+ try {
952
+ const types = window.localStorage.getItem(this.STORAGE_ACCESS_KEY);
953
+ return types ? JSON.parse(types) : [];
954
+ }
955
+ catch {
956
+ return [];
957
+ }
958
+ }
959
+ async setPossibleFilters() {
960
+ const queryParameters = this.activatedRoute.snapshot.queryParamMap.get(this.queryParamName);
961
+ const eventsData = this.events()?.data ?? [];
962
+ const eventTypesFromCurrentlyShownEvents = await this.alarmEventSelectorService.getUniqueEventsOnly(eventsData);
963
+ const customEventTypesFromLocalStorage = this.getCustomEventTypeFromStorage();
964
+ const selectableEventTypes = this.setActiveEventFiltersFromQueryParameter([...customEventTypesFromLocalStorage, ...eventTypesFromCurrentlyShownEvents], queryParameters);
965
+ this.possibleFilters.set(selectableEventTypes);
966
+ }
967
+ setActiveEventFiltersFromQueryParameter(eventFilters, filterTypeQuery = '') {
968
+ const selectedType = filterTypeQuery ?? '';
969
+ return eventFilters.map((event) => ({
970
+ ...event,
971
+ __active: event.filters.type === selectedType
972
+ }));
973
+ }
974
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsTypeFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
975
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: EventsTypeFilterComponent, isStandalone: true, selector: "c8y-events-type-filter", inputs: { events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onFilterChanged: "onFilterChanged" }, viewQueries: [{ propertyName: "optionItems", predicate: ["optionItem"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"d-flex a-i-center\">\n <div\n class=\"dropdown\"\n title=\"{{ 'Filter by event types' | translate }}\"\n dropdown\n [insideClick]=\"true\"\n #filtersDropdown=\"bs-dropdown\"\n [cdkTrapFocus]=\"filtersDropdown.isOpen\"\n (onHidden)=\"restoreActiveFilter()\"\n >\n <div class=\"input-group fit-w\">\n @let active = activeFilter();\n <div\n class=\"form-control d-flex a-i-center inner-scroll\"\n role=\"status\"\n >\n @if (active) {\n <span\n class=\"text-truncate\"\n style=\"max-width: 150px !important\"\n [title]=\"active.filters.type\"\n >\n {{ active.filters.type }}\n </span>\n } @else {\n <span class=\"text-nowrap\">\n {{ 'All event types' | translate }}\n </span>\n }\n </div>\n @if (active) {\n <div class=\"input-group-btn text-center\">\n <button\n class=\"btn-default btn\"\n [title]=\"'Clear filter' | translate\"\n [attr.aria-label]=\"'Clear filter' | translate\"\n (click)=\"deselect()\"\n >\n <i\n c8yIcon=\"times\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n }\n <div class=\"input-group-btn input-group-btn--last\">\n <button\n class=\"btn-default m-l-0 btn btn--caret\"\n [attr.aria-label]=\"'Event types' | translate\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"filtersDropdown.isOpen\"\n [attr.aria-controls]=\"'event-types-listbox'\"\n data-cy=\"c8y-event-type-filter\"\n dropdownToggle\n >\n <i\n class=\"caret\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n </div>\n <div\n class=\"dropdown-menu dropdown-menu-action-bar\"\n *dropdownMenu\n >\n <div\n class=\"p-16 bg-level-2\"\n style=\"min-width: 250px\"\n >\n <div>\n <p class=\"icon-flex\">\n <i\n class=\"text-info m-r-4\"\n [c8yIcon]=\"'info-circle'\"\n aria-hidden=\"true\"\n ></i>\n <strong translate>The list may be incomplete</strong>\n </p>\n <p class=\"text-12\">\n <span translate>\n Recent events are displayed below, but older ones may not be shown.\n </span>\n <span translate>Optionally, you can add a custom event type.</span>\n </p>\n </div>\n </div>\n <div class=\"input-group p-16 separator-bottom\">\n <input\n class=\"form-control\"\n [attr.aria-label]=\"'Custom event type' | translate\"\n type=\"text\"\n [placeholder]=\"'Custom event type' | translate\"\n [ngModel]=\"customEventTypeInput()\"\n (ngModelChange)=\"customEventTypeInput.set($event)\"\n (keydown)=\"confirmWithEnter($event)\"\n />\n <div class=\"input-group-btn\">\n <button\n class=\"btn-dot text-primary\"\n [attr.aria-label]=\"'Add custom event' | translate\"\n [tooltip]=\"'Add' | translate\"\n placement=\"top\"\n [delay]=\"500\"\n [disabled]=\"isCustomEventTypeInputInvalid()\"\n (click)=\"addCustomEventType()\"\n >\n <i\n c8yIcon=\"plus-circle\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n </div>\n <c8y-list-group\n [attr.aria-label]=\"'Event types' | translate\"\n id=\"event-types-listbox\"\n role=\"listbox\"\n >\n @for (eventType of possibleFilters(); track eventType.filters.type) {\n <c8y-li\n class=\"pointer p-0\"\n tabindex=\"0\"\n [attr.aria-selected]=\"eventType.__active\"\n role=\"option\"\n [active]=\"eventType.__active\"\n #optionItem\n (keydown.arrowdown)=\"navigateOption($event, 1)\"\n (keydown.arrowup)=\"navigateOption($event, -1)\"\n (keydown.enter)=\"selectAndClose(eventType, filtersDropdown)\"\n (click)=\"selectAndClose(eventType, filtersDropdown)\"\n >\n <div class=\"d-flex gap-8 a-i-center flex-grow p-t-4 p-b-4\">\n <span\n class=\"circle-icon-wrapper\"\n [style.background-color]=\"eventType.color\"\n >\n <i\n class=\"stroked-icon\"\n [c8yIcon]=\"'online1'\"\n aria-hidden=\"true\"\n ></i>\n </span>\n <span\n class=\"text-truncate text-12 flex-grow\"\n [title]=\"eventType.label\"\n [attr.aria-label]=\"eventType.label\"\n >\n {{ eventType.label }}\n </span>\n @if (eventType.__target === null) {\n <button\n class=\"btn-dot btn-dot--danger\"\n [attr.aria-label]=\"'Remove' | translate\"\n [tooltip]=\"'Remove' | translate\"\n placement=\"top\"\n [delay]=\"500\"\n (click)=\"removeCustomEvent(eventType); $event.stopPropagation()\"\n >\n <i\n c8yIcon=\"minus-circle\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n </div>\n </c8y-li>\n }\n @if (possibleFilters().length === 0) {\n <c8y-li aria-live=\"polite\">\n <c8y-ui-empty-state\n class=\"p-t-8\"\n icon=\"online1\"\n [title]=\"'No events found' | translate\"\n [subtitle]=\"\n 'There are no events to filter. You can still add a custom event type.' | translate\n \"\n [horizontal]=\"true\"\n />\n </c8y-li>\n }\n </c8y-list-group>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: BsDropdownDirective, selector: "[bsDropdown], [dropdown]", inputs: ["placement", "triggers", "container", "dropup", "autoClose", "isAnimated", "insideClick", "isDisabled", "isOpen"], outputs: ["isOpenChange", "onShown", "onHidden"], exportAs: ["bs-dropdown"] }, { kind: "directive", type: BsDropdownToggleDirective, selector: "[bsDropdownToggle],[dropdownToggle]", exportAs: ["bs-dropdown-toggle"] }, { kind: "directive", type: BsDropdownMenuDirective, selector: "[bsDropdownMenu],[dropdownMenu]", exportAs: ["bs-dropdown-menu"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
976
+ }
977
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsTypeFilterComponent, decorators: [{
978
+ type: Component,
979
+ args: [{ selector: 'c8y-events-type-filter', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
980
+ BsDropdownDirective,
981
+ BsDropdownToggleDirective,
982
+ BsDropdownMenuDirective,
983
+ CdkTrapFocus,
984
+ IconDirective,
985
+ ListGroupComponent,
986
+ ListItemComponent,
987
+ FormsModule,
988
+ TooltipDirective,
989
+ EmptyStateComponent,
990
+ C8yTranslateDirective,
991
+ C8yTranslatePipe
992
+ ], template: "<div class=\"d-flex a-i-center\">\n <div\n class=\"dropdown\"\n title=\"{{ 'Filter by event types' | translate }}\"\n dropdown\n [insideClick]=\"true\"\n #filtersDropdown=\"bs-dropdown\"\n [cdkTrapFocus]=\"filtersDropdown.isOpen\"\n (onHidden)=\"restoreActiveFilter()\"\n >\n <div class=\"input-group fit-w\">\n @let active = activeFilter();\n <div\n class=\"form-control d-flex a-i-center inner-scroll\"\n role=\"status\"\n >\n @if (active) {\n <span\n class=\"text-truncate\"\n style=\"max-width: 150px !important\"\n [title]=\"active.filters.type\"\n >\n {{ active.filters.type }}\n </span>\n } @else {\n <span class=\"text-nowrap\">\n {{ 'All event types' | translate }}\n </span>\n }\n </div>\n @if (active) {\n <div class=\"input-group-btn text-center\">\n <button\n class=\"btn-default btn\"\n [title]=\"'Clear filter' | translate\"\n [attr.aria-label]=\"'Clear filter' | translate\"\n (click)=\"deselect()\"\n >\n <i\n c8yIcon=\"times\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n }\n <div class=\"input-group-btn input-group-btn--last\">\n <button\n class=\"btn-default m-l-0 btn btn--caret\"\n [attr.aria-label]=\"'Event types' | translate\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"filtersDropdown.isOpen\"\n [attr.aria-controls]=\"'event-types-listbox'\"\n data-cy=\"c8y-event-type-filter\"\n dropdownToggle\n >\n <i\n class=\"caret\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n </div>\n <div\n class=\"dropdown-menu dropdown-menu-action-bar\"\n *dropdownMenu\n >\n <div\n class=\"p-16 bg-level-2\"\n style=\"min-width: 250px\"\n >\n <div>\n <p class=\"icon-flex\">\n <i\n class=\"text-info m-r-4\"\n [c8yIcon]=\"'info-circle'\"\n aria-hidden=\"true\"\n ></i>\n <strong translate>The list may be incomplete</strong>\n </p>\n <p class=\"text-12\">\n <span translate>\n Recent events are displayed below, but older ones may not be shown.\n </span>\n <span translate>Optionally, you can add a custom event type.</span>\n </p>\n </div>\n </div>\n <div class=\"input-group p-16 separator-bottom\">\n <input\n class=\"form-control\"\n [attr.aria-label]=\"'Custom event type' | translate\"\n type=\"text\"\n [placeholder]=\"'Custom event type' | translate\"\n [ngModel]=\"customEventTypeInput()\"\n (ngModelChange)=\"customEventTypeInput.set($event)\"\n (keydown)=\"confirmWithEnter($event)\"\n />\n <div class=\"input-group-btn\">\n <button\n class=\"btn-dot text-primary\"\n [attr.aria-label]=\"'Add custom event' | translate\"\n [tooltip]=\"'Add' | translate\"\n placement=\"top\"\n [delay]=\"500\"\n [disabled]=\"isCustomEventTypeInputInvalid()\"\n (click)=\"addCustomEventType()\"\n >\n <i\n c8yIcon=\"plus-circle\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n </div>\n <c8y-list-group\n [attr.aria-label]=\"'Event types' | translate\"\n id=\"event-types-listbox\"\n role=\"listbox\"\n >\n @for (eventType of possibleFilters(); track eventType.filters.type) {\n <c8y-li\n class=\"pointer p-0\"\n tabindex=\"0\"\n [attr.aria-selected]=\"eventType.__active\"\n role=\"option\"\n [active]=\"eventType.__active\"\n #optionItem\n (keydown.arrowdown)=\"navigateOption($event, 1)\"\n (keydown.arrowup)=\"navigateOption($event, -1)\"\n (keydown.enter)=\"selectAndClose(eventType, filtersDropdown)\"\n (click)=\"selectAndClose(eventType, filtersDropdown)\"\n >\n <div class=\"d-flex gap-8 a-i-center flex-grow p-t-4 p-b-4\">\n <span\n class=\"circle-icon-wrapper\"\n [style.background-color]=\"eventType.color\"\n >\n <i\n class=\"stroked-icon\"\n [c8yIcon]=\"'online1'\"\n aria-hidden=\"true\"\n ></i>\n </span>\n <span\n class=\"text-truncate text-12 flex-grow\"\n [title]=\"eventType.label\"\n [attr.aria-label]=\"eventType.label\"\n >\n {{ eventType.label }}\n </span>\n @if (eventType.__target === null) {\n <button\n class=\"btn-dot btn-dot--danger\"\n [attr.aria-label]=\"'Remove' | translate\"\n [tooltip]=\"'Remove' | translate\"\n placement=\"top\"\n [delay]=\"500\"\n (click)=\"removeCustomEvent(eventType); $event.stopPropagation()\"\n >\n <i\n c8yIcon=\"minus-circle\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n </div>\n </c8y-li>\n }\n @if (possibleFilters().length === 0) {\n <c8y-li aria-live=\"polite\">\n <c8y-ui-empty-state\n class=\"p-t-8\"\n icon=\"online1\"\n [title]=\"'No events found' | translate\"\n [subtitle]=\"\n 'There are no events to filter. You can still add a custom event type.' | translate\n \"\n [horizontal]=\"true\"\n />\n </c8y-li>\n }\n </c8y-list-group>\n </div>\n </div>\n</div>\n" }]
993
+ }], ctorParameters: () => [], propDecorators: { optionItems: [{
994
+ type: ViewChildren,
995
+ args: ['optionItem', { read: ElementRef }]
996
+ }], events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], onFilterChanged: [{ type: i0.Output, args: ["onFilterChanged"] }] } });
997
+
998
+ class EventsComponent {
999
+ constructor() {
1000
+ this.TITLE = gettext('Events');
1001
+ this.events = signal(null, ...(ngDevMode ? [{ debugName: "events" }] : []));
1002
+ this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
1003
+ this.shouldShowIntervalToggle = signal(true, ...(ngDevMode ? [{ debugName: "shouldShowIntervalToggle" }] : []));
1004
+ this.contextSourceId = null;
1005
+ this.isListScrolled = signal(false, ...(ngDevMode ? [{ debugName: "isListScrolled" }] : []));
1006
+ this.typeFilter = null;
1007
+ this.dateFilter = null;
1008
+ this.dateInterval = null;
1009
+ this.activatedRoute = inject(ActivatedRoute);
1010
+ this.alertService = inject(AlertService);
1011
+ this.contextRouteService = inject(ContextRouteService);
1012
+ this.destroyRef = inject(DestroyRef);
1013
+ this.eventsViewService = inject(EventsViewService);
1014
+ this.initializeContextSourceId();
1015
+ }
1016
+ ngOnInit() {
1017
+ this.setupEventListReload();
1018
+ // Pause interval if already in detail view
1019
+ const isInDetailView = !!this.activatedRoute.children[0]?.snapshot.params['id'];
1020
+ if (isInDetailView) {
1021
+ this.changeInterval(false);
1022
+ }
1023
+ // Initial load
1024
+ this.eventsViewService.updateEventsList();
1025
+ }
1026
+ refresh() {
1027
+ this.eventsViewService.updateEventsList();
1028
+ }
1029
+ changeInterval(value = true) {
1030
+ this.shouldShowIntervalToggle.set(value);
1031
+ }
1032
+ onScrollingStateChange(isScrolling) {
1033
+ this.isListScrolled.set(isScrolling);
1034
+ this.changeInterval(!isScrolling);
1035
+ }
1036
+ onPreviewStateChange(isOpen) {
1037
+ if (isOpen || !this.isListScrolled()) {
1038
+ this.changeInterval(!isOpen);
1039
+ }
1040
+ }
1041
+ onEventSelected() {
1042
+ this.changeInterval(false);
1043
+ }
1044
+ applyTypeFilter(filter) {
1045
+ this.typeFilter = filter;
1046
+ this.refresh();
1047
+ }
1048
+ applyDateFilter(filter) {
1049
+ this.dateInterval = filter.interval ?? null;
1050
+ this.dateFilter = filter.selectedDates ?? null;
1051
+ this.refresh();
1052
+ }
1053
+ setupEventListReload() {
1054
+ this.eventsViewService.reloadEventsList$
1055
+ .pipe(debounceTime(100), takeUntilDestroyed(this.destroyRef))
1056
+ .subscribe(() => this.loadEvents());
1057
+ }
1058
+ async loadEvents() {
1059
+ try {
1060
+ this.isLoading.set(true);
1061
+ const filter = this.eventsViewService.buildEventsFilter({
1062
+ source: this.contextSourceId,
1063
+ type: this.typeFilter?.filters.type ?? null,
1064
+ dateRange: this.resolveCurrentDateRange()
1065
+ });
1066
+ const events = await this.eventsViewService.retrieveEvents(filter);
1067
+ this.events.set(events);
1068
+ }
1069
+ catch (error) {
1070
+ this.alertService.addServerFailure(error);
1071
+ }
1072
+ finally {
1073
+ this.isLoading.set(false);
1074
+ }
1075
+ }
1076
+ resolveCurrentDateRange() {
1077
+ if (!this.dateInterval || this.dateInterval === 'none') {
1078
+ return null;
1079
+ }
1080
+ if (this.dateInterval === 'custom') {
1081
+ return this.dateFilter;
1082
+ }
1083
+ return this.eventsViewService.getDateTimeContextByInterval(this.dateInterval);
1084
+ }
1085
+ initializeContextSourceId() {
1086
+ const routeContext = this.contextRouteService.getContextData(this.activatedRoute);
1087
+ if (!routeContext) {
1088
+ return;
1089
+ }
1090
+ const { context, contextData } = routeContext;
1091
+ if ([ViewContext.Device, ViewContext.Group].includes(context)) {
1092
+ this.contextSourceId = contextData?.id;
1093
+ }
1094
+ }
1095
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1096
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: EventsComponent, isStandalone: true, selector: "c8y-events", ngImport: i0, template: "<c8y-title>{{ TITLE | translate }}</c8y-title>\n<c8y-help\n src=\"/docs/device-management-application/monitoring-and-controlling-devices/#troubleshooting-devices\"\n></c8y-help>\n\n<c8y-action-bar-item\n [placement]=\"'left'\"\n itemClass=\"navbar-form\"\n>\n <c8y-events-type-filter\n [events]=\"events()\"\n (onFilterChanged)=\"applyTypeFilter($event)\"\n />\n</c8y-action-bar-item>\n\n<c8y-action-bar-item\n [placement]=\"'left'\"\n itemClass=\"navbar-form\"\n>\n <c8y-events-date-filter (dateFilterChange)=\"applyDateFilter($event)\" />\n</c8y-action-bar-item>\n\n<c8y-sv [resizableConfig]=\"{ trackId: 'events-split-view', collapsible: false }\">\n <c8y-events-list\n [events]=\"events()\"\n [isLoading]=\"isLoading()\"\n (onScrollingStateChange)=\"onScrollingStateChange($event)\"\n (onPreviewStateChange)=\"onPreviewStateChange($event)\"\n (onSelectedEvent)=\"onEventSelected()\"\n >\n <c8y-events-interval-refresh\n [isLoading]=\"isLoading()\"\n [isIntervalToggleEnabled]=\"shouldShowIntervalToggle()\"\n (onCountdownEnded)=\"refresh()\"\n />\n </c8y-events-list>\n\n <c8y-sv-details\n emptyStateIcon=\"online1\"\n [emptyStateTitle]=\"'No event selected' | translate\"\n [emptyStateSubtitle]=\"'Select an event from the list to view its details.' | translate\"\n >\n <router-outlet />\n </c8y-sv-details>\n</c8y-sv>\n", dependencies: [{ kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "component", type: HelpComponent, selector: "c8y-help", inputs: ["src", "isCollapsed", "priority", "icon"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "component", type: SplitViewComponent, selector: "c8y-sv", inputs: ["showDefaultRouterOutlet", "isResizable", "initialSelection", "resizableBreakpoint", "resizableConfig"], outputs: ["selectionChange"] }, { kind: "component", type: SplitViewDetailsComponent, selector: "c8y-sv-details", inputs: ["title", "ariaLabel", "cssClass", "emptyStateIcon", "emptyStateTitle", "emptyStateSubtitle"], outputs: ["backClick"] }, { kind: "component", type: EventsListComponent, selector: "c8y-events-list", inputs: ["events", "isLoading", "loadMoreMode", "showPreview", "navigationOptions"], outputs: ["onScrollingStateChange", "onPreviewStateChange", "onSelectedEvent"] }, { kind: "component", type: EventsDateFilterComponent, selector: "c8y-events-date-filter", inputs: ["defaultInterval", "updateQueryParams"], outputs: ["dateFilterChange"] }, { kind: "component", type: EventsIntervalRefreshComponent, selector: "c8y-events-interval-refresh", inputs: ["isLoading", "isDisabled", "isIntervalToggleEnabled"], outputs: ["onCountdownEnded"] }, { kind: "component", type: EventsTypeFilterComponent, selector: "c8y-events-type-filter", inputs: ["events"], outputs: ["onFilterChanged"] }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1097
+ }
1098
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: EventsComponent, decorators: [{
1099
+ type: Component,
1100
+ args: [{ selector: 'c8y-events', imports: [
1101
+ C8yTranslatePipe,
1102
+ TitleComponent,
1103
+ HelpComponent,
1104
+ ActionBarItemComponent,
1105
+ SplitViewComponent,
1106
+ SplitViewDetailsComponent,
1107
+ EventsListComponent,
1108
+ EventsDateFilterComponent,
1109
+ EventsIntervalRefreshComponent,
1110
+ EventsTypeFilterComponent,
1111
+ RouterOutlet
1112
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<c8y-title>{{ TITLE | translate }}</c8y-title>\n<c8y-help\n src=\"/docs/device-management-application/monitoring-and-controlling-devices/#troubleshooting-devices\"\n></c8y-help>\n\n<c8y-action-bar-item\n [placement]=\"'left'\"\n itemClass=\"navbar-form\"\n>\n <c8y-events-type-filter\n [events]=\"events()\"\n (onFilterChanged)=\"applyTypeFilter($event)\"\n />\n</c8y-action-bar-item>\n\n<c8y-action-bar-item\n [placement]=\"'left'\"\n itemClass=\"navbar-form\"\n>\n <c8y-events-date-filter (dateFilterChange)=\"applyDateFilter($event)\" />\n</c8y-action-bar-item>\n\n<c8y-sv [resizableConfig]=\"{ trackId: 'events-split-view', collapsible: false }\">\n <c8y-events-list\n [events]=\"events()\"\n [isLoading]=\"isLoading()\"\n (onScrollingStateChange)=\"onScrollingStateChange($event)\"\n (onPreviewStateChange)=\"onPreviewStateChange($event)\"\n (onSelectedEvent)=\"onEventSelected()\"\n >\n <c8y-events-interval-refresh\n [isLoading]=\"isLoading()\"\n [isIntervalToggleEnabled]=\"shouldShowIntervalToggle()\"\n (onCountdownEnded)=\"refresh()\"\n />\n </c8y-events-list>\n\n <c8y-sv-details\n emptyStateIcon=\"online1\"\n [emptyStateTitle]=\"'No event selected' | translate\"\n [emptyStateSubtitle]=\"'Select an event from the list to view its details.' | translate\"\n >\n <router-outlet />\n </c8y-sv-details>\n</c8y-sv>\n" }]
1113
+ }], ctorParameters: () => [] });
1114
+
39
1115
  /**
40
1116
  * Generated bundle index. Do not edit.
41
1117
  */
42
1118
 
43
- export { EVENT_RESERVED_KEYS, EVENT_STANDARD_KEYS, EventsService };
1119
+ export { EVENTS_PATH, EVENT_RESERVED_KEYS, EVENT_STANDARD_KEYS, EventDetailsComponent, EventIconPipe, EventIsImagePipe, EventRouterLinkPipe, EventsComponent, EventsDateFilterComponent, EventsIntervalRefreshComponent, EventsListComponent, EventsService, EventsTypeFilterComponent, EventsViewService, INTERVALS_EXTENDED, INTERVAL_TITLES_EXTENDED };
44
1120
  //# sourceMappingURL=c8y-ngx-components-events.mjs.map