@fleetbase/fleetops-engine 0.6.20 → 0.6.22

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 (162) hide show
  1. package/addon/components/custom-entity/form.hbs +14 -14
  2. package/addon/components/device/card.hbs +1 -0
  3. package/addon/components/device/card.js +3 -0
  4. package/addon/components/device/details.hbs +92 -43
  5. package/addon/components/device/form.hbs +108 -60
  6. package/addon/components/device/form.js +36 -8
  7. package/addon/components/device/manager.hbs +29 -0
  8. package/addon/components/device/manager.js +95 -0
  9. package/addon/components/device/panel-header.hbs +32 -0
  10. package/addon/components/device/panel-header.js +3 -0
  11. package/addon/components/device/pill.hbs +16 -0
  12. package/addon/components/device/pill.js +3 -0
  13. package/addon/components/driver/details.hbs +4 -0
  14. package/addon/components/driver/details.js +19 -1
  15. package/addon/components/driver/form.hbs +14 -3
  16. package/addon/components/driver/form.js +49 -47
  17. package/addon/components/driver/pill.hbs +17 -0
  18. package/addon/components/driver/pill.js +3 -0
  19. package/addon/components/entity/form.hbs +7 -5
  20. package/addon/components/layout/fleet-ops-sidebar.js +12 -12
  21. package/addon/components/map/drawer/device-event-listing.hbs +64 -0
  22. package/addon/components/map/drawer/device-event-listing.js +181 -0
  23. package/addon/components/map/drawer/position-listing.hbs +100 -0
  24. package/addon/components/map/drawer/position-listing.js +455 -0
  25. package/addon/components/map/drawer.js +2 -0
  26. package/addon/components/map/leaflet-live-map.hbs +7 -2
  27. package/addon/components/modals/attach-device.hbs +18 -0
  28. package/addon/components/modals/attach-device.js +3 -0
  29. package/addon/components/order/details/detail.hbs +2 -54
  30. package/addon/components/order/details/detail.js +1 -0
  31. package/addon/components/order/details/payload.hbs +6 -4
  32. package/addon/components/order/details/payload.js +2 -0
  33. package/addon/components/order/pill.hbs +34 -0
  34. package/addon/components/order/pill.js +3 -0
  35. package/addon/components/order-config-manager/custom-fields.js +1 -1
  36. package/addon/components/positions-replay.hbs +339 -0
  37. package/addon/components/positions-replay.js +409 -0
  38. package/addon/components/sensor/details.hbs +64 -38
  39. package/addon/components/sensor/form.hbs +112 -63
  40. package/addon/components/sensor/form.js +36 -24
  41. package/addon/components/sensor/panel-header.hbs +32 -0
  42. package/addon/components/sensor/panel-header.js +3 -0
  43. package/addon/components/telematic/details.hbs +40 -16
  44. package/addon/components/telematic/form.hbs +63 -64
  45. package/addon/components/telematic/form.js +73 -4
  46. package/addon/components/vehicle/card.hbs +2 -2
  47. package/addon/components/vehicle/details.hbs +4 -0
  48. package/addon/components/vehicle/details.js +19 -1
  49. package/addon/components/vehicle/form.hbs +4 -0
  50. package/addon/components/vehicle/pill.hbs +34 -0
  51. package/addon/components/vehicle/pill.js +3 -0
  52. package/addon/controllers/analytics/reports/index/edit.js +1 -1
  53. package/addon/controllers/connectivity/devices/index/details.js +22 -1
  54. package/addon/controllers/connectivity/devices/index/edit.js +66 -1
  55. package/addon/controllers/connectivity/devices/index.js +51 -9
  56. package/addon/controllers/connectivity/events/index.js +65 -16
  57. package/addon/controllers/connectivity/sensors/index/details.js +22 -1
  58. package/addon/controllers/connectivity/sensors/index/edit.js +66 -1
  59. package/addon/controllers/connectivity/sensors/index.js +66 -6
  60. package/addon/controllers/connectivity/telematics/index/details.js +22 -1
  61. package/addon/controllers/connectivity/telematics/index/edit.js +66 -1
  62. package/addon/controllers/connectivity/telematics/index.js +20 -11
  63. package/addon/controllers/management/fleets/index/details.js +26 -21
  64. package/addon/controllers/management/fleets/index/edit.js +9 -6
  65. package/addon/controllers/management/vehicles/index/details.js +26 -13
  66. package/addon/controllers/settings/custom-fields.js +6 -0
  67. package/addon/helpers/get-fleet-ops-option-label.js +11 -0
  68. package/addon/routes/connectivity/devices/index/details.js +27 -1
  69. package/addon/routes/connectivity/devices/index/edit.js +27 -1
  70. package/addon/routes/connectivity/sensors/index/details.js +27 -1
  71. package/addon/routes/connectivity/sensors/index/edit.js +27 -1
  72. package/addon/routes/connectivity/telematics/index/details.js +27 -1
  73. package/addon/routes/connectivity/telematics/index/edit.js +27 -1
  74. package/addon/routes/management/drivers/index/details/positions.js +3 -0
  75. package/addon/routes/management/vehicles/index/details/positions.js +3 -0
  76. package/addon/routes.js +4 -0
  77. package/addon/services/movement-tracker.js +81 -30
  78. package/addon/services/position-playback.js +486 -0
  79. package/addon/services/resource-metadata.js +46 -0
  80. package/addon/styles/fleetops-engine.css +157 -0
  81. package/addon/templates/connectivity/devices/index/details/index.hbs +2 -2
  82. package/addon/templates/connectivity/devices/index/details.hbs +15 -2
  83. package/addon/templates/connectivity/devices/index/edit.hbs +1 -1
  84. package/addon/templates/connectivity/events/index.hbs +1 -1
  85. package/addon/templates/connectivity/sensors/index/details/index.hbs +2 -2
  86. package/addon/templates/connectivity/sensors/index/details.hbs +15 -2
  87. package/addon/templates/connectivity/sensors/index/edit.hbs +1 -1
  88. package/addon/templates/connectivity/telematics/index/details/index.hbs +2 -2
  89. package/addon/templates/connectivity/telematics/index/details.hbs +14 -2
  90. package/addon/templates/connectivity/telematics/index/edit.hbs +1 -1
  91. package/addon/templates/management/drivers/index/details/positions.hbs +2 -0
  92. package/addon/templates/management/vehicles/index/details/devices.hbs +1 -2
  93. package/addon/templates/management/vehicles/index/details/positions.hbs +1 -0
  94. package/addon/utils/fleet-ops-options.js +95 -0
  95. package/app/components/device/card.js +1 -0
  96. package/app/components/device/manager.js +1 -0
  97. package/app/components/device/panel-header.js +1 -0
  98. package/app/components/device/pill.js +1 -0
  99. package/app/components/driver/pill.js +1 -0
  100. package/app/components/map/drawer/device-event-listing.js +1 -0
  101. package/app/components/map/drawer/position-listing.js +1 -0
  102. package/app/components/modals/attach-device.js +1 -0
  103. package/app/components/order/pill.js +1 -0
  104. package/app/components/positions-replay.js +1 -0
  105. package/app/components/sensor/panel-header.js +1 -0
  106. package/app/components/vehicle/pill.js +1 -0
  107. package/app/helpers/get-fleet-ops-option-label.js +1 -0
  108. package/app/routes/management/drivers/index/details/positions.js +1 -0
  109. package/app/routes/management/vehicles/index/details/positions.js +1 -0
  110. package/app/services/position-playback.js +1 -0
  111. package/app/services/resource-metadata.js +1 -0
  112. package/app/templates/management/drivers/index/details/positions.js +1 -0
  113. package/app/templates/management/vehicles/index/details/positions.js +1 -0
  114. package/composer.json +1 -1
  115. package/extension.json +1 -1
  116. package/package.json +4 -4
  117. package/server/config/telematics.php +111 -0
  118. package/server/migrations/2025_10_27_000001_add_telematics_integration_fields.php +70 -0
  119. package/server/migrations/2025_10_27_171322_fix_device_column_names.php +107 -0
  120. package/server/migrations/2025_10_27_203023_add_company_uuid_to_device_events_table.php +28 -0
  121. package/server/src/Console/Commands/ReplayVehicleLocations.php +225 -0
  122. package/server/src/Contracts/TelematicProviderDescriptor.php +72 -0
  123. package/server/src/Contracts/TelematicProviderInterface.php +119 -0
  124. package/server/src/Exceptions/TelematicProviderException.php +14 -0
  125. package/server/src/Exceptions/TelematicRateLimitExceededException.php +12 -0
  126. package/server/src/Http/Controllers/Api/v1/DriverController.php +24 -14
  127. package/server/src/Http/Controllers/Api/v1/VehicleController.php +27 -7
  128. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +22 -0
  129. package/server/src/Http/Controllers/Internal/v1/PositionController.php +240 -0
  130. package/server/src/Http/Controllers/Internal/v1/SensorController.php +11 -0
  131. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +141 -0
  132. package/server/src/Http/Controllers/TelematicWebhookController.php +169 -0
  133. package/server/src/Http/Filter/DeviceEventFilter.php +68 -0
  134. package/server/src/Http/Filter/PositionFilter.php +35 -0
  135. package/server/src/Http/Resources/v1/Position.php +44 -0
  136. package/server/src/Jobs/ReplayPositions.php +64 -0
  137. package/server/src/Jobs/SendPositionReplay.php +65 -0
  138. package/server/src/Jobs/SyncTelematicDevicesJob.php +106 -0
  139. package/server/src/Jobs/TestTelematicConnectionJob.php +102 -0
  140. package/server/src/Models/Asset.php +10 -8
  141. package/server/src/Models/Device.php +79 -12
  142. package/server/src/Models/DeviceEvent.php +33 -3
  143. package/server/src/Models/Driver.php +28 -1
  144. package/server/src/Models/Maintenance.php +15 -12
  145. package/server/src/Models/Part.php +2 -0
  146. package/server/src/Models/Payload.php +0 -1
  147. package/server/src/Models/Place.php +4 -1
  148. package/server/src/Models/Position.php +27 -17
  149. package/server/src/Models/Sensor.php +78 -13
  150. package/server/src/Models/Telematic.php +116 -6
  151. package/server/src/Models/TrackingNumber.php +3 -1
  152. package/server/src/Models/Vehicle.php +8 -11
  153. package/server/src/Models/WorkOrder.php +8 -5
  154. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  155. package/server/src/Support/Telematics/Providers/AbstractProvider.php +151 -0
  156. package/server/src/Support/Telematics/Providers/FlespiProvider.php +182 -0
  157. package/server/src/Support/Telematics/Providers/GeotabProvider.php +181 -0
  158. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +177 -0
  159. package/server/src/Support/Telematics/TelematicProviderRegistry.php +147 -0
  160. package/server/src/Support/Telematics/TelematicService.php +223 -0
  161. package/server/src/Support/Utils.php +1 -1
  162. package/server/src/routes.php +24 -1
@@ -0,0 +1,95 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { action, get } from '@ember/object';
4
+ import { inject as service } from '@ember/service';
5
+ import { debug } from '@ember/debug';
6
+ import { task } from 'ember-concurrency';
7
+ import getModelName from '@fleetbase/ember-core/utils/get-model-name';
8
+
9
+ export default class DeviceManagerComponent extends Component {
10
+ @service store;
11
+ @service modalsManager;
12
+ @service notifications;
13
+ @tracked devices = [];
14
+
15
+ get resourceName() {
16
+ const record = this.args.resource;
17
+ if (!record) return 'resource';
18
+
19
+ return (
20
+ get(record, this.args.namePath ?? 'name') ??
21
+ get(record, 'display_name') ??
22
+ get(record, 'displayName') ??
23
+ get(record, 'tracking') ??
24
+ get(record, 'public_id') ??
25
+ getModelName(record)
26
+ );
27
+ }
28
+
29
+ constructor() {
30
+ super(...arguments);
31
+ this.loadDevices.perform();
32
+ }
33
+
34
+ @action addDevice() {
35
+ this.modalsManager.show('modals/attach-device', {
36
+ title: 'Select device to attach',
37
+ acceptButtonText: 'Confirm & Attach Device',
38
+ selectedDevice: null,
39
+ confirm: async (modal) => {
40
+ const selectedDevice = modal.getOption('selectedDevice');
41
+ if (!selectedDevice) return;
42
+
43
+ selectedDevice.setProperties({
44
+ attachable_uuid: this.args.resource.id,
45
+ attachable_type: `fleet-ops:${getModelName(this.args.resource)}`,
46
+ });
47
+
48
+ modal.startLoading();
49
+
50
+ try {
51
+ await selectedDevice.save();
52
+ await this.loadDevices.perform();
53
+ this.notifications.success('Device attached successfully.');
54
+ modal.done();
55
+ } catch (err) {
56
+ this.notifications.serverError(err);
57
+ modal.stopLoading();
58
+ }
59
+ },
60
+ });
61
+ }
62
+
63
+ @action removeDevice(device) {
64
+ this.modalsManager.confirm({
65
+ title: `Are you sure you want to detach this device (${device.name}) from (${this.resourceName})?`,
66
+ body: `Removing this device will stop all telematic updates and events for this ${getModelName(this.args.resource)}.`,
67
+ confirm: async (modal) => {
68
+ modal.startLoading();
69
+
70
+ device.setProperties({ attachable_uuid: null, attachable_type: null });
71
+
72
+ try {
73
+ await device.save();
74
+ await this.loadDevices.perform();
75
+ this.notifications.success('Device removed.');
76
+ modal.done();
77
+ } catch (error) {
78
+ this.notifications.serverError(error);
79
+ modal.stopLoading();
80
+ }
81
+ },
82
+ });
83
+ }
84
+
85
+ @task *loadDevices() {
86
+ if (!this.args.resource) return;
87
+
88
+ try {
89
+ const devices = yield this.store.query('device', { attachable_uuid: this.args.resource.id });
90
+ this.devices = devices;
91
+ } catch (err) {
92
+ debug('Unable to load devices: ' + err.message);
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,32 @@
1
+ <div class="px-4 py-2">
2
+ <div class="flex flex-1 flex-row items-start justify-between">
3
+ <div class="flex flex-row space-x-3">
4
+ <div class="flex items-start justify-start rounded-full">
5
+ <Image src={{@resource.photo_url}} @fallbackSrc={{config "defaultValues.placeholderImage"}} alt={{@resource.displayName}} height="48" width="48" class="h-14 w-14 rounded-full shadow-sm" />
6
+ <Attach::Tooltip @class="clean" @animation="scale" @placement="top">
7
+ <InputInfo @text={{@resource.displayName}} />
8
+ </Attach::Tooltip>
9
+ </div>
10
+ <div class="flex flex-col">
11
+ <h1 class="text-gray-900 dark:text-white font-semibold text-lg">{{@resource.displayName}}</h1>
12
+ <div>
13
+ <div class="text-xs text-gray-400 dark:text-gray-500">{{concat (n-a (titleize @resource.provider)) " " (n-a (get-fleet-ops-option-label "deviceTypes" @resource.type))}}</div>
14
+ <div class="text-xs text-gray-400 dark:text-gray-500">{{n-a @resource.serial_number}}</div>
15
+ </div>
16
+ <Badge @status={{if @resource.online "online" "offline"}}>{{if @resource.online (t "common.online") (t "common.offline")}}</Badge>
17
+ </div>
18
+ </div>
19
+ <div class="next-view-header-right">
20
+ <Layout::Resource::Panel::HeaderActions
21
+ @resource={{@resource}}
22
+ @saveTask={{@saveTask}}
23
+ @saveOptions={{@saveOptions}}
24
+ @saveDisabled={{@saveDisabled}}
25
+ @pojoResource={{@pojoResource}}
26
+ @authSchema={{@authSchema}}
27
+ @actionButtons={{@actionButtons}}
28
+ @onPressCancel={{@onPressCancel}}
29
+ />
30
+ </div>
31
+ </div>
32
+ </div>
@@ -0,0 +1,3 @@
1
+ import Component from '@glimmer/component';
2
+
3
+ export default class DevicePanelHeaderComponent extends Component {}
@@ -0,0 +1,16 @@
1
+ {{#let (or @device @resource) as |resource|}}
2
+ <Pill
3
+ @resource={{resource}}
4
+ @imageSrc={{resource.photo_url}}
5
+ @subtitle={{or resource.device_id resource.serial_number resource.imei resource.public_id}}
6
+ @showOnlineIndicator={{true}}
7
+ @onClick={{@onClick}}
8
+ @anchorClass={{@anchorClass}}
9
+ @imageClass={{@imageClass}}
10
+ @imageWrapperClass={{@imageWrapperClass}}
11
+ @contentWrapperClass={{@contentWrapperClass}}
12
+ @titleClass={{@titleClass}}
13
+ @subtitleClass={{@subtitleClass}}
14
+ ...attributes
15
+ />
16
+ {{/let}}
@@ -0,0 +1,3 @@
1
+ import Component from '@glimmer/component';
2
+
3
+ export default class DevicePillComponent extends Component {}
@@ -53,4 +53,8 @@
53
53
  </ContentPanel>
54
54
 
55
55
  <CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
56
+
57
+ <ContentPanel @title={{t "common.metadata"}} @open={{true}} @actionButtons={{this.metadataButtons}} @wrapperClass="bordered-top" @panelBodyWrapperClass={{unless (is-object-empty @resource.meta) "px-0i" ""}}>
58
+ <MetadataViewer @metadata={{@resource.meta}} />
59
+ </ContentPanel>
56
60
  </div>
@@ -1,3 +1,21 @@
1
1
  import Component from '@glimmer/component';
2
+ import { inject as service } from '@ember/service';
2
3
 
3
- export default class DriverDetailsComponent extends Component {}
4
+ export default class DriverDetailsComponent extends Component {
5
+ @service resourceMetadata;
6
+
7
+ get metadataButtons() {
8
+ return [
9
+ {
10
+ type: 'default',
11
+ text: 'Edit',
12
+ icon: 'pencil',
13
+ iconPrefix: 'fas',
14
+ permission: 'fleet-ops update driver',
15
+ onClick: () => {
16
+ this.resourceMetadata.edit(this.args.resource);
17
+ },
18
+ },
19
+ ];
20
+ }
21
+ }
@@ -51,9 +51,16 @@
51
51
  <ContentPanel @title={{t "driver.fields.driver-details"}} @open={{true}} @wrapperClass="bordered-top">
52
52
  <div class="flex flex-row space-x-3 mb-3">
53
53
  <div class="flex items-center justify-start rounded-full">
54
- <Image src={{@resource.photo_url}} @fallbackSrc={{config "defaultValues.driverImage"}} alt={{@resource.name}} height="48" width="48" class="h-14 w-14 rounded-full shadow-sm" />
54
+ <Image
55
+ src={{@resource.photo_url}}
56
+ @fallbackSrc={{config "defaultValues.driverImage"}}
57
+ alt={{@resource.name}}
58
+ height="48"
59
+ width="48"
60
+ class="h-14 w-14 rounded-full shadow-sm"
61
+ />
55
62
  <Attach::Tooltip @class="clean" @animation="scale" @placement="top">
56
- <InputInfo @text={{t "driver.fields.upload-new-photo"}} />
63
+ <InputInfo @text={{t "common.upload-new-photo"}} />
57
64
  </Attach::Tooltip>
58
65
  </div>
59
66
  <div>
@@ -133,7 +140,7 @@
133
140
  </InputGroup>
134
141
 
135
142
  <InputGroup @name={{t "common.status"}}>
136
- <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
143
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
137
144
  <PowerSelect
138
145
  @options={{get-fleet-ops-options "driverStatuses"}}
139
146
  @selected={{find-by "value" @resource.status (get-fleet-ops-options "driverStatuses")}}
@@ -168,4 +175,8 @@
168
175
  <ContentPanel @title={{t "avatar-picker.avatar"}} @open={{true}} @wrapperClass="bordered-top">
169
176
  <AvatarPicker @model={{@resource}} @defaultAvatar={{config "defaultValues.driverAvatar"}} />
170
177
  </ContentPanel>
178
+
179
+ <ContentPanel @title={{t "common.metadata"}} @open={{true}} @wrapperClass="bordered-top">
180
+ <MetadataEditor @value={{@resource.meta}} @onChange={{fn (mut @resource.meta)}} />
181
+ </ContentPanel>
171
182
  </div>
@@ -1,5 +1,4 @@
1
1
  import Component from '@glimmer/component';
2
- import { tracked } from '@glimmer/tracking';
3
2
  import { inject as service } from '@ember/service';
4
3
  import { task } from 'ember-concurrency';
5
4
 
@@ -9,55 +8,58 @@ export default class DriverFormComponent extends Component {
9
8
  @service currentUser;
10
9
  @service notifications;
11
10
  @service modalsManager;
12
- @tracked userAccountActionButtons = [
13
- {
14
- icon: 'user-plus',
15
- size: 'xs',
16
- permission: 'iam create user',
17
- onClick: () => {
18
- const user = this.store.createRecord('user', {
19
- status: 'pending',
20
- type: 'user',
21
- });
22
11
 
23
- this.modalsManager.show('modals/user-form', {
24
- title: 'Create a new user',
25
- user,
26
- formPermission: 'iam create user',
27
- uploadNewPhoto: (file) => {
28
- this.fetch.uploadFile.perform(
29
- file,
30
- {
31
- path: `uploads/${this.currentUser.companyId}/users/${user.slug}`,
32
- subject_uui: user.id,
33
- subject_type: 'user',
34
- type: 'user_photo',
35
- },
36
- (uploadedFile) => {
37
- user.setProperties({
38
- avatar_uuid: uploadedFile.id,
39
- avatar_url: uploadedFile.url,
40
- avatar: uploadedFile,
41
- });
42
- }
43
- );
44
- },
45
- confirm: async (modal) => {
46
- modal.startLoading();
12
+ get userAccountActionButtons() {
13
+ return [
14
+ {
15
+ icon: 'user-plus',
16
+ size: 'xs',
17
+ permission: 'iam create user',
18
+ onClick: () => {
19
+ const user = this.store.createRecord('user', {
20
+ status: 'pending',
21
+ type: 'user',
22
+ });
47
23
 
48
- try {
49
- await user.save();
50
- this.notifications.success('New user created successfully!');
51
- modal.done();
52
- } catch (error) {
53
- this.notifications.serverError(error);
54
- modal.stopLoading();
55
- }
56
- },
57
- });
24
+ this.modalsManager.show('modals/user-form', {
25
+ title: 'Create a new user',
26
+ user,
27
+ formPermission: 'iam create user',
28
+ uploadNewPhoto: (file) => {
29
+ this.fetch.uploadFile.perform(
30
+ file,
31
+ {
32
+ path: `uploads/${this.currentUser.companyId}/users/${user.slug}`,
33
+ subject_uui: user.id,
34
+ subject_type: 'user',
35
+ type: 'user_photo',
36
+ },
37
+ (uploadedFile) => {
38
+ user.setProperties({
39
+ avatar_uuid: uploadedFile.id,
40
+ avatar_url: uploadedFile.url,
41
+ avatar: uploadedFile,
42
+ });
43
+ }
44
+ );
45
+ },
46
+ confirm: async (modal) => {
47
+ modal.startLoading();
48
+
49
+ try {
50
+ await user.save();
51
+ this.notifications.success('New user created successfully!');
52
+ modal.done();
53
+ } catch (error) {
54
+ this.notifications.serverError(error);
55
+ modal.stopLoading();
56
+ }
57
+ },
58
+ });
59
+ },
58
60
  },
59
- },
60
- ];
61
+ ];
62
+ }
61
63
 
62
64
  @task *handlePhotoUpload(file) {
63
65
  try {
@@ -0,0 +1,17 @@
1
+ {{#let (or @driver @resource) as |resource|}}
2
+ <Pill
3
+ @resource={{resource}}
4
+ @imageSrc={{avatar-url resource.photo_url}}
5
+ @subtitle={{n-a resource.phone "No Phone"}}
6
+ @fallbackImageType="driverImage"
7
+ @showOnlineIndicator={{true}}
8
+ @onClick={{@onClick}}
9
+ @anchorClass={{@anchorClass}}
10
+ @imageClass={{@imageClass}}
11
+ @imageWrapperClass={{@imageWrapperClass}}
12
+ @contentWrapperClass={{@contentWrapperClass}}
13
+ @titleClass={{@titleClass}}
14
+ @subtitleClass={{@subtitleClass}}
15
+ ...attributes
16
+ />
17
+ {{/let}}
@@ -0,0 +1,3 @@
1
+ import Component from '@glimmer/component';
2
+
3
+ export default class DriverPillComponent extends Component {}
@@ -25,11 +25,7 @@
25
25
  <InputGroup @name={{t "common.internal-id"}} @value={{@resource.internal_id}} @helpText={{t "modals.entity-form.id-text"}} />
26
26
  <InputGroup @name={{t "modals.entity-form.sku"}} @value={{@resource.sku}} @helpText={{t "modals.entity-form.sku-text"}} />
27
27
  <div></div>
28
- <InputGroup
29
- @name={{t "modals.entity-form.description"}}
30
- @helpText={{t "modals.entity-form.description-text"}}
31
- @wrapperClass="col-span-2"
32
- >
28
+ <InputGroup @name={{t "modals.entity-form.description"}} @helpText={{t "modals.entity-form.description-text"}} @wrapperClass="col-span-2">
33
29
  <Textarea @value={{@resource.description}} type="text" class="w-full form-input" placeholder={{t "modals.entity-form.description"}} />
34
30
  </InputGroup>
35
31
  </div>
@@ -83,4 +79,10 @@
83
79
  </div>
84
80
  </div>
85
81
  </ContentPanel>
82
+
83
+ <CustomField::Yield @subject={{@resource}} @wrapperClass="bordered-top" />
84
+
85
+ <RegistryYield @registry="fleet-ops:component:entity:form" as |RegistryComponent|>
86
+ <RegistryComponent @resource={{@resource}} @controller={{this.controller}} @permission={{get-write-permission @resource}} />
87
+ </RegistryYield>
86
88
  </div>
@@ -171,14 +171,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
171
171
  permission: 'fleet-ops list device-event',
172
172
  visible: this.abilities.can('fleet-ops see device-event'),
173
173
  },
174
- {
175
- intl: 'menu.tracking',
176
- title: this.intl.t('menu.tracking'),
177
- icon: 'map-marked-alt',
178
- route: 'connectivity.tracking',
179
- permission: 'fleet-ops list device',
180
- visible: this.abilities.can('fleet-ops see device'),
181
- },
174
+ // {
175
+ // intl: 'menu.tracking',
176
+ // title: this.intl.t('menu.tracking'),
177
+ // icon: 'map-marked-alt',
178
+ // route: 'connectivity.tracking',
179
+ // permission: 'fleet-ops list device',
180
+ // visible: this.abilities.can('fleet-ops see device'),
181
+ // },
182
182
  ];
183
183
 
184
184
  const maintenanceItems = [
@@ -284,10 +284,10 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
284
284
  // open: this.appCache.get('fleet-ops:sidebar:maintenance:open', false),
285
285
  // onToggle: (open) => this.appCache.set('fleet-ops:sidebar:maintenance:open', open),
286
286
  // }),
287
- // createPanel('menu.connectivity', 'connectivity', connectivityItems, {
288
- // open: this.appCache.get('fleet-ops:sidebar:connectivity:open', false),
289
- // onToggle: (open) => this.appCache.set('fleet-ops:sidebar:connectivity:open', open),
290
- // }),
287
+ createPanel('menu.connectivity', 'connectivity', connectivityItems, {
288
+ open: this.appCache.get('fleet-ops:sidebar:connectivity:open', false),
289
+ onToggle: (open) => this.appCache.set('fleet-ops:sidebar:connectivity:open', open),
290
+ }),
291
291
  createPanel('menu.analytics', 'analytics', analyticsItems, {
292
292
  open: this.appCache.get('fleet-ops:sidebar:analytics:open', false),
293
293
  onToggle: (open) => this.appCache.set('fleet-ops:sidebar:analytics:open', open),
@@ -0,0 +1,64 @@
1
+ <div class="flex flex-row items-center justify-between w-full px-5 py-2 border-b border-gray-200 dark:border-gray-700">
2
+ <div class="flex flex-row items-center space-x-2">
3
+ <div>
4
+ <ModelSelect
5
+ @modelName="telematic"
6
+ @selectedModel={{this.telematic}}
7
+ @placeholder="Filter by Telematic"
8
+ @triggerClass="form-select form-input form-input-sm w-48"
9
+ @infiniteScroll={{false}}
10
+ @renderInPlace={{true}}
11
+ @onChange={{this.onTelematicSelected}}
12
+ as |model|
13
+ >
14
+ <div class="space-x-2 text-sm">
15
+ <div class="inline-block align-top">
16
+ <div class="hide-from-trigger h-1.5 w-full" />
17
+ <Image src={{model.provider_descriptor.icon}} class="w-5 h-5" />
18
+ </div>
19
+ <div class="inline-block">
20
+ <div class="font-semibold normalize-in-trigger">{{or model.name model.public_id}}</div>
21
+ <div class="hide-from-trigger">{{n-a model.provider_descriptor.label (titleize model.provider)}}</div>
22
+ </div>
23
+ </div>
24
+ </ModelSelect>
25
+ </div>
26
+
27
+ <div>
28
+ <ModelSelect
29
+ @modelName="device"
30
+ @selectedModel={{this.device}}
31
+ @placeholder="Filter by Device"
32
+ @triggerClass="form-select form-input form-input-sm w-48"
33
+ @infiniteScroll={{false}}
34
+ @renderInPlace={{true}}
35
+ @onChange={{this.onDeviceSelected}}
36
+ as |model|
37
+ >
38
+ <div class="text-sm">
39
+ <div class="font-semibold normalize-in-trigger">{{model.name}}</div>
40
+ <div class="hide-from-trigger">{{n-a model.serial_number}}</div>
41
+ </div>
42
+ </ModelSelect>
43
+ </div>
44
+
45
+ <div class="w-48">
46
+ <DatePicker
47
+ @value={{this.dateFilter}}
48
+ @range={{true}}
49
+ @onSelect={{this.onDateRangeChanged}}
50
+ @autoClose={{true}}
51
+ @placeholder="Select date range"
52
+ class="w-full form-input form-input-sm w-48"
53
+ />
54
+ </div>
55
+
56
+ {{#if this.loadEvents.isRunning}}
57
+ <div>
58
+ <Spinner />
59
+ </div>
60
+ {{/if}}
61
+ </div>
62
+ </div>
63
+ <Table @rows={{this.events}} @columns={{this.columns}} />
64
+ <Spacer @height="200px" />
@@ -0,0 +1,181 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { action } from '@ember/object';
5
+ import { task } from 'ember-concurrency';
6
+ import { isArray } from '@ember/array';
7
+ import { startOfWeek, endOfWeek, format } from 'date-fns';
8
+
9
+ export default class MapDrawerDeviceEventListingComponent extends Component {
10
+ @service store;
11
+ @service hostRouter;
12
+ @service notifications;
13
+ @service deviceEventActions;
14
+ @service deviceActions;
15
+ @service intl;
16
+
17
+ @tracked events = [];
18
+ @tracked telematic = null;
19
+ @tracked device = null;
20
+ @tracked dateFilter = [format(startOfWeek(new Date(), { weekStartsOn: 1 }), 'yyyy-MM-dd'), format(endOfWeek(new Date(), { weekStartsOn: 1 }), 'yyyy-MM-dd')];
21
+
22
+ get columns() {
23
+ return [
24
+ {
25
+ label: 'Event',
26
+ valuePath: 'event_type',
27
+ cellComponent: 'table/cell/anchor',
28
+ cellClassNames: 'uppercase',
29
+ action: this.deviceEventActions.panel.view,
30
+ permission: 'fleet-ops view device-event',
31
+ resizable: true,
32
+ sortable: true,
33
+ filterable: true,
34
+ filterParam: 'name',
35
+ filterComponent: 'filter/string',
36
+ },
37
+ {
38
+ label: 'Device',
39
+ valuePath: 'device.displayName',
40
+ cellComponent: 'table/cell/anchor',
41
+ action: this.deviceActions.panel.view,
42
+ permission: 'fleet-ops view device',
43
+ resizable: true,
44
+ sortable: true,
45
+ filterable: true,
46
+ filterComponent: 'filter/model',
47
+ filterComponentPlaceholder: 'Select device',
48
+ filterParam: 'device',
49
+ model: 'device',
50
+ },
51
+ {
52
+ label: 'Provider',
53
+ valuePath: 'provider',
54
+ resizable: true,
55
+ sortable: true,
56
+ filterable: true,
57
+ filterParam: 'provider',
58
+ filterComponent: 'filter/string',
59
+ },
60
+ {
61
+ label: 'Severity',
62
+ valuePath: 'severity',
63
+ resizable: true,
64
+ sortable: true,
65
+ filterable: true,
66
+ filterParam: 'severity',
67
+ filterComponent: 'filter/string',
68
+ },
69
+ {
70
+ label: 'IDENT',
71
+ valuePath: 'ident',
72
+ hidden: true,
73
+ resizable: true,
74
+ sortable: true,
75
+ },
76
+ {
77
+ label: 'Protocol',
78
+ valuePath: 'protocol',
79
+ hidden: true,
80
+ resizable: true,
81
+ sortable: true,
82
+ },
83
+ {
84
+ label: 'State',
85
+ valuePath: 'state',
86
+ hidden: true,
87
+ resizable: true,
88
+ sortable: true,
89
+ },
90
+ {
91
+ label: 'Code',
92
+ valuePath: 'code',
93
+ resizable: true,
94
+ sortable: true,
95
+ filterable: true,
96
+ filterParam: 'code',
97
+ filterComponent: 'filter/string',
98
+ },
99
+ {
100
+ label: this.intl.t('column.created-at'),
101
+ valuePath: 'createdAt',
102
+ sortParam: 'created_at',
103
+ width: '10%',
104
+ resizable: true,
105
+ sortable: true,
106
+ filterable: true,
107
+ filterComponent: 'filter/date',
108
+ },
109
+ {
110
+ label: '',
111
+ cellComponent: 'table/cell/dropdown',
112
+ ddButtonText: false,
113
+ ddButtonIcon: 'ellipsis-h',
114
+ ddButtonIconPrefix: 'fas',
115
+ ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.device-event') }),
116
+ cellClassNames: 'overflow-visible',
117
+ wrapperClass: 'flex items-center justify-end mx-2',
118
+ width: '10%',
119
+ actions: [
120
+ {
121
+ label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.device-event') }),
122
+ fn: this.deviceEventActions.panel.view,
123
+ permission: 'fleet-ops view device-event',
124
+ },
125
+ ],
126
+ sortable: false,
127
+ filterable: false,
128
+ resizable: false,
129
+ searchable: false,
130
+ },
131
+ ];
132
+ }
133
+
134
+ constructor() {
135
+ super(...arguments);
136
+ this.loadEvents.perform();
137
+ }
138
+
139
+ @action onDeviceSelected(device) {
140
+ this.device = device;
141
+ this.loadEvents.perform();
142
+ }
143
+
144
+ @action onTelematicSelected(telematic) {
145
+ this.telematic = telematic;
146
+ this.loadEvents.perform();
147
+ }
148
+
149
+ @action onDateRangeChanged({ formattedDate }) {
150
+ if (isArray(formattedDate) && formattedDate.length === 2) {
151
+ this.dateFilter = formattedDate;
152
+ this.loadEvents.perform();
153
+ }
154
+ }
155
+
156
+ @task *loadEvents() {
157
+ try {
158
+ const params = {
159
+ limit: 900,
160
+ sort: 'created_at',
161
+ };
162
+
163
+ if (this.telematic) {
164
+ params.telematic = this.telematic.id;
165
+ }
166
+
167
+ if (this.device) {
168
+ params.device = this.device.id;
169
+ }
170
+
171
+ if (isArray(this.dateFilter) && this.dateFilter.length === 2) {
172
+ params.created_at = this.dateFilter.join(',');
173
+ }
174
+
175
+ const events = yield this.store.query('device-event', params);
176
+ this.positions = isArray(events) ? events : [];
177
+ } catch (error) {
178
+ this.notifications.serverError(error);
179
+ }
180
+ }
181
+ }