@fleetbase/fleetops-engine 0.6.54 → 0.6.56

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 (220) hide show
  1. package/addon/components/admin/map-settings.hbs +1 -1
  2. package/addon/components/admin/routing-settings.hbs +2 -1
  3. package/addon/components/cell/attached-vehicle.hbs +10 -0
  4. package/addon/components/cell/attached-vehicle.js +58 -0
  5. package/addon/components/cell/device-identity.hbs +24 -0
  6. package/addon/components/cell/device-identity.js +129 -0
  7. package/addon/components/cell/driver-identity.hbs +33 -0
  8. package/addon/components/cell/driver-identity.js +143 -0
  9. package/addon/components/cell/equipment-identity.hbs +5 -0
  10. package/addon/components/cell/equipment-identity.js +48 -0
  11. package/addon/components/cell/part-identity.hbs +5 -0
  12. package/addon/components/cell/part-identity.js +63 -0
  13. package/addon/components/cell/telematic-device.hbs +38 -0
  14. package/addon/components/cell/telematic-device.js +44 -0
  15. package/addon/components/cell/telematic-provider.hbs +33 -16
  16. package/addon/components/cell/telematic-provider.js +50 -8
  17. package/addon/components/cell/vehicle-identity.hbs +33 -0
  18. package/addon/components/cell/vehicle-identity.js +138 -0
  19. package/addon/components/device/details.js +1 -1
  20. package/addon/components/device/form.hbs +2 -2
  21. package/addon/components/device/form.js +7 -0
  22. package/addon/components/device/panel-header.hbs +45 -20
  23. package/addon/components/device/panel-header.js +49 -1
  24. package/addon/components/device/panel-tabs/events.hbs +30 -0
  25. package/addon/components/device/panel-tabs/events.js +128 -0
  26. package/addon/components/device/panel-tabs/sensors.hbs +30 -0
  27. package/addon/components/device/panel-tabs/sensors.js +93 -0
  28. package/addon/components/device/panel-tabs/vehicle.hbs +68 -0
  29. package/addon/components/device/panel-tabs/vehicle.js +90 -0
  30. package/addon/components/device-event/details.hbs +192 -0
  31. package/addon/components/device-event/details.js +151 -0
  32. package/addon/components/driver/schedule.js +1 -1
  33. package/addon/components/equipment/form.js +8 -1
  34. package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
  35. package/addon/components/layout/fleet-ops-sidebar.js +20 -6
  36. package/addon/components/map/drawer/device-event-listing.js +2 -0
  37. package/addon/components/map/drawer/driver-listing.js +2 -0
  38. package/addon/components/map/drawer/place-listing.js +2 -0
  39. package/addon/components/map/drawer/position-listing.hbs +11 -6
  40. package/addon/components/map/drawer/position-listing.js +17 -4
  41. package/addon/components/map/drawer/vehicle-listing.js +2 -0
  42. package/addon/components/sensor/panel-header.hbs +47 -15
  43. package/addon/components/sensor/panel-header.js +59 -1
  44. package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
  45. package/addon/components/table/cell/dropdown/action-item.js +62 -0
  46. package/addon/components/table/cell/dropdown.hbs +28 -0
  47. package/addon/components/table/cell/dropdown.js +91 -0
  48. package/addon/components/telematic/details.hbs +1 -1
  49. package/addon/components/telematic/form.hbs +2 -2
  50. package/addon/components/telematic/settings.hbs +1 -1
  51. package/addon/components/vendor/panel-header.hbs +82 -0
  52. package/addon/components/vendor/panel-header.js +48 -0
  53. package/addon/controllers/connectivity/devices/index/details/events.js +1 -1
  54. package/addon/controllers/connectivity/devices/index/details/vehicle.js +50 -2
  55. package/addon/controllers/connectivity/devices/index.js +144 -129
  56. package/addon/controllers/connectivity/events/details.js +70 -0
  57. package/addon/controllers/connectivity/events/index.js +187 -11
  58. package/addon/controllers/connectivity/sensors/index.js +160 -14
  59. package/addon/controllers/connectivity/telematics/details/devices.js +126 -134
  60. package/addon/controllers/connectivity/telematics/details/events.js +1 -1
  61. package/addon/controllers/connectivity/telematics/details/sensors.js +4 -0
  62. package/addon/controllers/maintenance/equipment/index.js +1 -2
  63. package/addon/controllers/maintenance/parts/index.js +1 -2
  64. package/addon/controllers/management/drivers/index.js +82 -42
  65. package/addon/controllers/management/fuel-reports/index.js +63 -4
  66. package/addon/controllers/management/issues/index.js +63 -4
  67. package/addon/controllers/management/vehicles/index.js +33 -6
  68. package/addon/controllers/settings/map.js +15 -0
  69. package/addon/instance-initializers/register-vroom-allocation.js +1 -1
  70. package/addon/routes/connectivity/devices/index/details/vehicle.js +24 -2
  71. package/addon/routes/connectivity/devices/index.js +7 -0
  72. package/addon/routes/connectivity/events/details.js +28 -0
  73. package/addon/routes/connectivity/events/index.js +6 -2
  74. package/addon/routes/connectivity/sensors/index.js +7 -2
  75. package/addon/routes/connectivity/telematics/details/devices.js +2 -0
  76. package/addon/routes/management/vehicles/index/details.js +3 -1
  77. package/addon/services/device-actions.js +30 -0
  78. package/addon/services/device-event-actions.js +1 -1
  79. package/addon/services/equipment-actions.js +1 -0
  80. package/addon/services/map-adapter/google.js +25 -0
  81. package/addon/services/map-adapter/leaflet.js +1 -1
  82. package/addon/services/map-adapter-interface.js +1 -1
  83. package/addon/services/map-manager.js +4 -1
  84. package/addon/services/map-settings.js +15 -0
  85. package/addon/services/sensor-actions.js +3 -0
  86. package/addon/services/service-rate-actions.js +39 -1
  87. package/addon/services/vehicle-actions.js +24 -12
  88. package/addon/services/vendor-actions.js +3 -0
  89. package/addon/styles/fleetops-engine.css +9 -1
  90. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +66 -2
  91. package/addon/templates/connectivity/events/details.hbs +40 -0
  92. package/addon/templates/management/vendors/index/details.hbs +2 -2
  93. package/addon/templates/settings/map.hbs +38 -9
  94. package/addon/templates/settings/notifications.hbs +2 -1
  95. package/addon/templates/settings/routing.hbs +1 -0
  96. package/addon/utils/device-table-columns.js +288 -0
  97. package/addon/utils/identity-cell-resource.js +20 -0
  98. package/addon/utils/leaflet-plugin-loader.js +5 -5
  99. package/addon/utils/map-drawer-dropdown-position.js +47 -0
  100. package/addon/utils/route-colors.js +1 -1
  101. package/addon/utils/trackable-option.js +93 -0
  102. package/app/components/cell/attached-vehicle.js +1 -0
  103. package/app/components/cell/device-identity.js +1 -0
  104. package/app/components/cell/driver-identity.js +1 -0
  105. package/app/components/cell/equipment-identity.js +1 -0
  106. package/app/components/cell/part-identity.js +1 -0
  107. package/app/components/cell/telematic-device.js +1 -0
  108. package/app/components/cell/vehicle-identity.js +1 -0
  109. package/app/components/device/panel-tabs/events.hbs +30 -0
  110. package/app/components/device/panel-tabs/events.js +128 -0
  111. package/app/components/device/panel-tabs/sensors.hbs +30 -0
  112. package/app/components/device/panel-tabs/sensors.js +93 -0
  113. package/app/components/device/panel-tabs/vehicle.hbs +68 -0
  114. package/app/components/device/panel-tabs/vehicle.js +90 -0
  115. package/app/components/device-event/details.hbs +192 -0
  116. package/app/components/device-event/details.js +1 -0
  117. package/app/components/table/cell/dropdown/action-item.js +1 -0
  118. package/app/components/table/cell/dropdown.js +1 -0
  119. package/app/components/vendor/panel-header.js +1 -0
  120. package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
  121. package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
  122. package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
  123. package/app/utils/map-drawer-dropdown-position.js +1 -0
  124. package/app/utils/trackable-option.js +1 -0
  125. package/composer.json +1 -1
  126. package/extension.json +1 -1
  127. package/package.json +4 -4
  128. package/server/migrations/2026_06_23_000001_delete_bad_afaqy_telematics_sensors.php +34 -0
  129. package/server/src/Console/Commands/SyncTelematics.php +4 -4
  130. package/server/src/Exports/ContactExport.php +12 -5
  131. package/server/src/Exports/DeviceExport.php +92 -0
  132. package/server/src/Exports/DriverExport.php +28 -3
  133. package/server/src/Exports/EquipmentExport.php +94 -0
  134. package/server/src/Exports/FleetExport.php +22 -7
  135. package/server/src/Exports/FuelReportExport.php +16 -1
  136. package/server/src/Exports/IssueExport.php +25 -3
  137. package/server/src/Exports/MaintenanceExport.php +106 -0
  138. package/server/src/Exports/MaintenanceScheduleExport.php +99 -0
  139. package/server/src/Exports/OrderExport.php +24 -5
  140. package/server/src/Exports/PartExport.php +99 -0
  141. package/server/src/Exports/PlaceExport.php +33 -7
  142. package/server/src/Exports/SensorExport.php +94 -0
  143. package/server/src/Exports/ServiceAreaExport.php +31 -7
  144. package/server/src/Exports/ServiceRateExport.php +41 -1
  145. package/server/src/Exports/TelematicExport.php +90 -0
  146. package/server/src/Exports/VehicleExport.php +274 -8
  147. package/server/src/Exports/VendorExport.php +15 -2
  148. package/server/src/Exports/WorkOrderExport.php +92 -0
  149. package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +176 -0
  150. package/server/src/Http/Controllers/Api/v1/DeviceController.php +225 -0
  151. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +114 -0
  152. package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +185 -0
  153. package/server/src/Http/Controllers/Api/v1/PartController.php +120 -0
  154. package/server/src/Http/Controllers/Api/v1/SensorController.php +132 -0
  155. package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +161 -0
  156. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +36 -1
  157. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +1 -1
  158. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +17 -0
  159. package/server/src/Http/Controllers/Internal/v1/LiveController.php +1 -1
  160. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +17 -0
  161. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +17 -0
  162. package/server/src/Http/Controllers/Internal/v1/PartController.php +17 -0
  163. package/server/src/Http/Controllers/Internal/v1/SensorController.php +18 -0
  164. package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
  165. package/server/src/Http/Controllers/Internal/v1/SettingController.php +29 -5
  166. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +18 -0
  167. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +17 -0
  168. package/server/src/Http/Filter/DeviceEventFilter.php +21 -0
  169. package/server/src/Http/Filter/DeviceFilter.php +50 -4
  170. package/server/src/Http/Filter/EquipmentFilter.php +23 -0
  171. package/server/src/Http/Filter/FuelProviderTransactionFilter.php +57 -2
  172. package/server/src/Http/Filter/PartFilter.php +47 -0
  173. package/server/src/Http/Filter/SensorFilter.php +34 -6
  174. package/server/src/Http/Filter/WorkOrderFilter.php +23 -0
  175. package/server/src/Http/Requests/CreateDeviceRequest.php +51 -0
  176. package/server/src/Http/Requests/CreateEquipmentRequest.php +35 -0
  177. package/server/src/Http/Requests/CreateFuelTransactionRequest.php +50 -0
  178. package/server/src/Http/Requests/CreatePartRequest.php +40 -0
  179. package/server/src/Http/Requests/CreateSensorRequest.php +47 -0
  180. package/server/src/Http/Requests/CreateWorkOrderRequest.php +42 -0
  181. package/server/src/Http/Requests/UpdateDeviceRequest.php +7 -0
  182. package/server/src/Http/Requests/UpdateEquipmentRequest.php +7 -0
  183. package/server/src/Http/Requests/UpdateFuelTransactionRequest.php +7 -0
  184. package/server/src/Http/Requests/UpdatePartRequest.php +7 -0
  185. package/server/src/Http/Requests/UpdateSensorRequest.php +7 -0
  186. package/server/src/Http/Requests/UpdateWorkOrderRequest.php +7 -0
  187. package/server/src/Http/Resources/v1/Device.php +72 -0
  188. package/server/src/Http/Resources/v1/Equipment.php +57 -0
  189. package/server/src/Http/Resources/v1/FuelTransaction.php +69 -0
  190. package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
  191. package/server/src/Http/Resources/v1/Part.php +64 -0
  192. package/server/src/Http/Resources/v1/Sensor.php +68 -0
  193. package/server/src/Jobs/SyncTelematicDevicesJob.php +194 -38
  194. package/server/src/Models/Device.php +2 -0
  195. package/server/src/Models/DeviceEvent.php +81 -0
  196. package/server/src/Models/Equipment.php +55 -0
  197. package/server/src/Models/Order.php +2 -1
  198. package/server/src/Observers/PurchaseRateObserver.php +2 -1
  199. package/server/src/Support/Analytics/RevenueTrend.php +3 -8
  200. package/server/src/Support/Metrics/ActiveRevenueQuery.php +121 -0
  201. package/server/src/Support/Metrics/AvgOrderValueMetric.php +1 -7
  202. package/server/src/Support/Metrics/EarningsMetric.php +1 -10
  203. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +105 -5
  204. package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
  205. package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
  206. package/server/src/Support/Telematics/Providers/SafeeProvider.php +567 -47
  207. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
  208. package/server/src/Support/Telematics/TelematicService.php +28 -9
  209. package/server/src/routes.php +67 -15
  210. package/server/tests/DeviceFilterTest.php +9 -0
  211. package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
  212. package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
  213. package/server/tests/ExportCoverageTest.php +79 -0
  214. package/server/tests/MetricsRegistryTest.php +45 -5
  215. package/server/tests/PublicFleetOpsRestEndpointsTest.php +191 -0
  216. package/server/tests/ServiceRateRouteValidationTest.php +101 -0
  217. package/server/tests/TelematicsHardeningTest.php +873 -7
  218. package/addon/controllers/connectivity/events/index/details.js +0 -3
  219. package/addon/routes/connectivity/events/index/details.js +0 -3
  220. package/addon/templates/connectivity/events/index/details.hbs +0 -1
@@ -112,7 +112,7 @@
112
112
  {{/if}}
113
113
  {{else}}
114
114
  <div class="rounded-md border border-dashed border-gray-300 p-3 text-xs text-gray-500 dark:border-gray-700 dark:text-gray-300">
115
- This provider does not require FleetOps webhook configuration.
115
+ This provider does not require Fleet-Ops webhook configuration.
116
116
  </div>
117
117
  {{/if}}
118
118
  </section>
@@ -0,0 +1,82 @@
1
+ <div class="px-4 py-3" ...attributes>
2
+ <div class="flex flex-1 flex-row items-start justify-between gap-4">
3
+ <div class="flex min-w-0 flex-row items-start gap-3">
4
+ <div class="relative flex flex-shrink-0 items-start justify-start">
5
+ <Image
6
+ src={{this.imageUrl}}
7
+ @fallbackSrc={{config "defaultValues.vendorImage"}}
8
+ alt={{this.name}}
9
+ height="48"
10
+ width="48"
11
+ class="h-14 w-14 rounded-md border border-gray-200 object-cover shadow-sm dark:border-gray-700"
12
+ />
13
+ <Attach::Tooltip @class="clean" @animation="scale" @placement="top">
14
+ <InputInfo @text={{this.name}} />
15
+ </Attach::Tooltip>
16
+ </div>
17
+ <div class="flex min-w-0 flex-col">
18
+ <div class="flex min-w-0 flex-wrap items-center gap-2">
19
+ <h1 class="truncate text-base font-semibold leading-5 text-gray-900 dark:text-gray-100">{{this.name}}</h1>
20
+ <Badge @status={{this.status}} @size="xs">{{smart-humanize this.status}}</Badge>
21
+ </div>
22
+
23
+ <div class="mt-1 flex max-w-2xl flex-wrap items-center gap-1.5">
24
+ {{#if this.businessId}}
25
+ <span class="inline-flex max-w-[12rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
26
+ <FaIcon @icon="id-card" class="text-gray-400 dark:text-gray-300" />
27
+ <span class="truncate">{{this.businessId}}</span>
28
+ </span>
29
+ {{/if}}
30
+ {{#if this.type}}
31
+ <span class="inline-flex max-w-[10rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
32
+ <FaIcon @icon="building" class="text-gray-400 dark:text-gray-300" />
33
+ <span class="truncate">{{smart-humanize this.type}}</span>
34
+ </span>
35
+ {{/if}}
36
+ {{#if this.email}}
37
+ <span class="inline-flex max-w-[14rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
38
+ <FaIcon @icon="envelope" class="text-gray-400 dark:text-gray-300" />
39
+ <span class="truncate">{{this.email}}</span>
40
+ </span>
41
+ {{/if}}
42
+ {{#if this.phone}}
43
+ <span class="inline-flex max-w-[10rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
44
+ <FaIcon @icon="phone" class="text-gray-400 dark:text-gray-300" />
45
+ <span class="truncate">{{this.phone}}</span>
46
+ </span>
47
+ {{/if}}
48
+ {{#if this.country}}
49
+ <span class="inline-flex max-w-[10rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
50
+ <FaIcon @icon="globe" class="text-gray-400 dark:text-gray-300" />
51
+ <span class="truncate"><CountryName @country={{this.country}} @showFlag={{true}} /></span>
52
+ </span>
53
+ {{/if}}
54
+ {{#if this.address}}
55
+ <span class="inline-flex max-w-[16rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
56
+ <FaIcon @icon="location-dot" class="text-gray-400 dark:text-gray-300" />
57
+ <span class="truncate">{{this.address}}</span>
58
+ </span>
59
+ {{/if}}
60
+ {{#if this.website}}
61
+ <span class="inline-flex max-w-[14rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
62
+ <FaIcon @icon="link" class="text-gray-400 dark:text-gray-300" />
63
+ <span class="truncate">{{this.website}}</span>
64
+ </span>
65
+ {{/if}}
66
+ </div>
67
+ </div>
68
+ </div>
69
+ <div class="next-view-header-right flex-shrink-0">
70
+ <Layout::Resource::Panel::HeaderActions
71
+ @resource={{@resource}}
72
+ @saveTask={{@saveTask}}
73
+ @saveOptions={{@saveOptions}}
74
+ @saveDisabled={{@saveDisabled}}
75
+ @pojoResource={{@pojoResource}}
76
+ @authSchema={{@authSchema}}
77
+ @actionButtons={{@actionButtons}}
78
+ @onPressCancel={{@onPressCancel}}
79
+ />
80
+ </div>
81
+ </div>
82
+ </div>
@@ -0,0 +1,48 @@
1
+ import Component from '@glimmer/component';
2
+ import { get } from '@ember/object';
3
+
4
+ export default class VendorPanelHeaderComponent extends Component {
5
+ get resource() {
6
+ return this.args.resource;
7
+ }
8
+
9
+ get imageUrl() {
10
+ return get(this.resource, 'photo_url') ?? get(this.resource, 'logo_url');
11
+ }
12
+
13
+ get name() {
14
+ return get(this.resource, 'name') ?? get(this.resource, 'displayName') ?? get(this.resource, 'business_id') ?? get(this.resource, 'public_id') ?? '-';
15
+ }
16
+
17
+ get status() {
18
+ return get(this.resource, 'status') ?? 'active';
19
+ }
20
+
21
+ get businessId() {
22
+ return get(this.resource, 'business_id') ?? get(this.resource, 'internal_id') ?? get(this.resource, 'public_id');
23
+ }
24
+
25
+ get type() {
26
+ return get(this.resource, 'type');
27
+ }
28
+
29
+ get email() {
30
+ return get(this.resource, 'email');
31
+ }
32
+
33
+ get phone() {
34
+ return get(this.resource, 'phone');
35
+ }
36
+
37
+ get country() {
38
+ return get(this.resource, 'country');
39
+ }
40
+
41
+ get address() {
42
+ return get(this.resource, 'address_street') ?? get(this.resource, 'address');
43
+ }
44
+
45
+ get website() {
46
+ return get(this.resource, 'website_url');
47
+ }
48
+ }
@@ -56,7 +56,7 @@ export default class ConnectivityDevicesIndexDetailsEventsController extends Con
56
56
  }
57
57
 
58
58
  get unprocessedEventsCount() {
59
- return this.events.filter((event) => !event.processed_at && !event.is_processed).length;
59
+ return this.events.filter((event) => !event.processed_at).length;
60
60
  }
61
61
 
62
62
  get processedEventsCount() {
@@ -7,17 +7,27 @@ export default class ConnectivityDevicesIndexDetailsVehicleController extends Co
7
7
  @service deviceActions;
8
8
  @service hostRouter;
9
9
  @service intl;
10
+ @service mapManager;
11
+ @service vehicleActions;
10
12
 
11
13
  @tracked queryParams = [];
12
14
 
13
15
  get device() {
14
- return this.model;
16
+ return this.model?.device ?? this.model;
15
17
  }
16
18
 
17
19
  get vehicle() {
18
20
  return this.device?.attachable;
19
21
  }
20
22
 
23
+ get positions() {
24
+ return Array.from(this.model?.positions ?? []);
25
+ }
26
+
27
+ get hasPositions() {
28
+ return this.positions.length > 0;
29
+ }
30
+
21
31
  get vehicleName() {
22
32
  return this.device?.attached_to_name ?? this.vehicle?.displayName ?? this.vehicle?.display_name ?? this.vehicle?.name;
23
33
  }
@@ -58,6 +68,14 @@ export default class ConnectivityDevicesIndexDetailsVehicleController extends Co
58
68
  return Boolean(this.vehicleName || this.device?.attachable_uuid);
59
69
  }
60
70
 
71
+ get canOpenVehicle() {
72
+ return Boolean(this.vehicle?.id);
73
+ }
74
+
75
+ get canLocateVehicle() {
76
+ return Boolean(this.vehicle?.id && (this.vehicle?.location || this.vehicle?.last_position || this.hasPositions));
77
+ }
78
+
61
79
  @action attachToVehicle() {
62
80
  return this.deviceActions.attachToVehicle(this.device, { callback: () => this.hostRouter.refresh() });
63
81
  }
@@ -68,7 +86,37 @@ export default class ConnectivityDevicesIndexDetailsVehicleController extends Co
68
86
 
69
87
  @action openVehicle() {
70
88
  if (this.vehicle?.id) {
71
- return this.hostRouter.transitionTo('console.fleet-ops.management.vehicles.index.details', this.vehicle);
89
+ return this.hostRouter.transitionTo('console.fleet-ops.management.vehicles.index.details', this.vehicle.public_id);
90
+ }
91
+ }
92
+
93
+ @action openVehiclePositions() {
94
+ if (this.vehicle?.id) {
95
+ return this.hostRouter.transitionTo('console.fleet-ops.management.vehicles.index.details.positions', this.vehicle.public_id);
96
+ }
97
+ }
98
+
99
+ @action async locateVehicle() {
100
+ if (!this.vehicle?.id) {
101
+ return;
102
+ }
103
+
104
+ await this.transitionToLiveMap();
105
+ await this.mapManager.waitForMap({ timeoutMs: 8000 });
106
+
107
+ this.mapManager.focusResource(this.vehicle, 16, {
108
+ paddingBottomRight: [300, 200],
109
+ moveend: () => {
110
+ this.vehicleActions.panel?.view?.(this.vehicle, { closeOnTransition: true });
111
+ },
112
+ });
113
+ }
114
+
115
+ async transitionToLiveMap() {
116
+ try {
117
+ await this.hostRouter.transitionTo('console.fleet-ops.operations.orders.index', { queryParams: { layout: 'map' } });
118
+ } catch (_) {
119
+ // Keep locate actions usable even if the current transition is already in-flight.
72
120
  }
73
121
  }
74
122
  }
@@ -1,15 +1,41 @@
1
1
  import Controller from '@ember/controller';
2
+ import { action } from '@ember/object';
2
3
  import { inject as service } from '@ember/service';
3
4
  import { tracked } from '@glimmer/tracking';
5
+ import buildDeviceTableColumns from '../../../utils/device-table-columns';
4
6
  import fleetOpsOptions from '../../../utils/fleet-ops-options';
5
7
 
6
8
  export default class ConnectivityDevicesIndexController extends Controller {
7
9
  @service deviceActions;
10
+ @service hostRouter;
8
11
  @service telematicActions;
9
12
  @service intl;
13
+ @service mapManager;
14
+ @service notifications;
15
+ @service store;
16
+ @service vehicleActions;
10
17
 
11
18
  /** query params */
12
- @tracked queryParams = ['name', 'status', 'attachment_state', 'telematic', 'page', 'limit', 'sort', 'query', 'public_id', 'created_at', 'updated_at'];
19
+ @tracked queryParams = [
20
+ 'name',
21
+ 'status',
22
+ 'attachment_state',
23
+ 'telematic',
24
+ 'provider',
25
+ 'vehicle',
26
+ 'connection_status',
27
+ 'device_id',
28
+ 'type',
29
+ 'serial_number',
30
+ 'last_online_at',
31
+ 'page',
32
+ 'limit',
33
+ 'sort',
34
+ 'query',
35
+ 'public_id',
36
+ 'created_at',
37
+ 'updated_at',
38
+ ];
13
39
  @tracked page = 1;
14
40
  @tracked limit;
15
41
  @tracked sort = '-created_at';
@@ -18,6 +44,13 @@ export default class ConnectivityDevicesIndexController extends Controller {
18
44
  @tracked status;
19
45
  @tracked attachment_state;
20
46
  @tracked telematic;
47
+ @tracked provider;
48
+ @tracked vehicle;
49
+ @tracked connection_status;
50
+ @tracked device_id;
51
+ @tracked type;
52
+ @tracked serial_number;
53
+ @tracked last_online_at;
21
54
 
22
55
  /** action buttons */
23
56
  @tracked actionButtons = [
@@ -56,133 +89,115 @@ export default class ConnectivityDevicesIndexController extends Controller {
56
89
  },
57
90
  ];
58
91
 
59
- /** columns */
60
- @tracked columns = [
61
- {
62
- sticky: true,
63
- label: this.intl.t('column.name'),
64
- valuePath: 'displayName',
65
- cellComponent: 'table/cell/anchor',
66
- action: this.deviceActions.transition.view,
67
- permission: 'fleet-ops view device',
68
- resizable: true,
69
- sortable: true,
70
- filterable: true,
71
- filterParam: 'name',
72
- filterComponent: 'filter/string',
73
- },
74
- {
75
- label: 'Telematic',
76
- valuePath: 'telematic.provider',
77
- cellComponent: 'table/cell/anchor',
78
- action: this.telematicActions.transition.view,
79
- permission: 'fleet-ops view telematic',
80
- resizable: true,
81
- sortable: true,
82
- filterable: true,
83
- filterComponent: 'filter/model',
84
- filterComponentPlaceholder: 'Select telematic',
85
- filterParam: 'telematic',
86
- model: 'telematic',
87
- },
88
- {
89
- label: 'Type',
90
- valuePath: 'type',
91
- resizable: true,
92
- sortable: true,
93
- filterable: true,
94
- filterParam: 'type',
95
- filterComponent: 'filter/multi-option',
96
- filterOptions: fleetOpsOptions('deviceTypes'),
97
- },
98
- {
99
- label: 'Serial Number',
100
- valuePath: 'serial_number',
101
- resizable: true,
102
- sortable: true,
103
- filterable: true,
104
- filterParam: 'serial_number',
105
- filterComponent: 'filter/string',
106
- },
107
- {
108
- label: this.intl.t('column.status'),
109
- valuePath: 'status',
110
- cellComponent: 'table/cell/status',
111
- resizable: true,
112
- sortable: true,
113
- filterable: true,
114
- filterComponent: 'filter/multi-option',
115
- filterOptions: fleetOpsOptions('deviceStatuses'),
116
- },
117
- {
118
- label: this.intl.t('column.created-at'),
119
- valuePath: 'createdAt',
120
- sortParam: 'created_at',
121
- resizable: true,
122
- sortable: true,
123
- filterable: true,
124
- filterComponent: 'filter/date',
125
- },
126
- {
127
- label: this.intl.t('column.updated-at'),
128
- valuePath: 'updatedAt',
129
- sortParam: 'updated_at',
130
- resizable: true,
131
- sortable: true,
132
- hidden: true,
133
- filterable: true,
134
- filterComponent: 'filter/date',
135
- },
92
+ get deviceTypeOptions() {
93
+ return fleetOpsOptions('deviceTypes');
94
+ }
136
95
 
137
- {
138
- label: '',
139
- cellComponent: 'table/cell/dropdown',
140
- ddButtonText: false,
141
- ddButtonIcon: 'ellipsis-h',
142
- ddButtonIconPrefix: 'fas',
143
- ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.device') }),
144
- cellClassNames: 'overflow-visible',
145
- wrapperClass: 'flex items-center justify-end mx-2',
146
- sticky: 'right',
147
- width: 60,
148
- actions: [
149
- {
150
- label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.device') }),
151
- fn: this.deviceActions.transition.view,
152
- permission: 'fleet-ops view device',
153
- },
154
- {
155
- label: this.intl.t('common.edit-resource', { resource: this.intl.t('resource.device') }),
156
- fn: this.deviceActions.transition.edit,
157
- permission: 'fleet-ops update device',
158
- },
159
- {
160
- separator: true,
161
- },
162
- {
163
- label: this.intl.t('device.actions.attach-to-vehicle'),
164
- fn: this.deviceActions.attachToVehicle,
165
- permission: 'fleet-ops update device',
166
- },
167
- {
168
- label: this.intl.t('device.actions.detach-from-vehicle'),
169
- fn: this.deviceActions.detachFromVehicle,
170
- permission: 'fleet-ops update device',
171
- isVisible: (device) => Boolean(device.attachable_uuid || device.attached_to_name || device.attachable),
172
- },
173
- {
174
- separator: true,
175
- },
176
- {
177
- label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.device') }),
178
- fn: this.deviceActions.delete,
179
- permission: 'fleet-ops delete device',
180
- },
181
- ],
182
- sortable: false,
183
- filterable: false,
184
- resizable: false,
185
- searchable: false,
186
- },
187
- ];
96
+ get deviceStatusOptions() {
97
+ return fleetOpsOptions('deviceStatuses');
98
+ }
99
+
100
+ get columns() {
101
+ const columns = buildDeviceTableColumns(this, { deviceActionMode: 'route', showDeviceStatus: false });
102
+ const actionsColumn = columns.find((column) => column.cellComponent === 'table/cell/dropdown');
103
+
104
+ actionsColumn?.actions.push(
105
+ {
106
+ separator: true,
107
+ },
108
+ {
109
+ label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.device') }),
110
+ fn: this.deviceActions.delete,
111
+ permission: 'fleet-ops delete device',
112
+ }
113
+ );
114
+
115
+ return columns;
116
+ }
117
+
118
+ @action openTelematic(telematic) {
119
+ if (telematic?.id) {
120
+ return this.telematicActions.transition.view(telematic);
121
+ }
122
+ }
123
+
124
+ @action hasAttachedVehicle(device) {
125
+ return Boolean(device?.attachable_uuid && this.isVehicleAttachment(device));
126
+ }
127
+
128
+ @action async viewAttachedVehicle(device) {
129
+ const vehicle = await this.resolveAttachedVehicle(device);
130
+
131
+ if (!vehicle) {
132
+ return;
133
+ }
134
+
135
+ if (this.vehicleActions.panel?.view) {
136
+ return this.vehicleActions.panel.view(vehicle);
137
+ }
138
+
139
+ return this.hostRouter.transitionTo('console.fleet-ops.management.vehicles.index.details', vehicle);
140
+ }
141
+
142
+ @action async locateAttachedVehicle(device) {
143
+ const vehicle = await this.resolveAttachedVehicle(device);
144
+
145
+ if (!vehicle) {
146
+ return;
147
+ }
148
+
149
+ await this.transitionToLiveMap();
150
+ await this.mapManager.waitForMap({ timeoutMs: 8000 });
151
+
152
+ this.mapManager.focusResource(vehicle, 16, {
153
+ paddingBottomRight: [300, 200],
154
+ moveend: () => {
155
+ this.vehicleActions.panel?.view?.(vehicle, { closeOnTransition: true });
156
+ },
157
+ });
158
+ }
159
+
160
+ async transitionToLiveMap() {
161
+ try {
162
+ await this.hostRouter.transitionTo('console.fleet-ops.operations.orders.index', { queryParams: { layout: 'map' } });
163
+ } catch (_) {
164
+ // Keep locate actions usable even if another map transition is already active.
165
+ }
166
+ }
167
+
168
+ isVehicleAttachment(device) {
169
+ const attachableType = `${device?.attachable_type ?? ''}`.toLowerCase();
170
+
171
+ return !attachableType || attachableType.includes('vehicle');
172
+ }
173
+
174
+ async resolveAttachedVehicle(device) {
175
+ if (!device?.attachable_uuid || !this.isVehicleAttachment(device)) {
176
+ return null;
177
+ }
178
+
179
+ const attachable = device.attachable;
180
+
181
+ if (attachable && typeof attachable.then !== 'function') {
182
+ return attachable;
183
+ }
184
+
185
+ if (attachable && typeof attachable.then === 'function') {
186
+ return await attachable;
187
+ }
188
+
189
+ const cachedVehicle = this.store.peekRecord('vehicle', device.attachable_uuid);
190
+
191
+ if (cachedVehicle) {
192
+ return cachedVehicle;
193
+ }
194
+
195
+ try {
196
+ return await this.store.findRecord('vehicle', device.attachable_uuid);
197
+ } catch (error) {
198
+ this.notifications.serverError(error);
199
+ }
200
+
201
+ return null;
202
+ }
188
203
  }
@@ -0,0 +1,70 @@
1
+ import Controller from '@ember/controller';
2
+ import { action, set } from '@ember/object';
3
+ import { inject as service } from '@ember/service';
4
+ import { tracked } from '@glimmer/tracking';
5
+
6
+ function firstPresent(...values) {
7
+ return values.find((value) => value !== undefined && value !== null && value !== '');
8
+ }
9
+
10
+ function toDate(value) {
11
+ if (value instanceof Date) {
12
+ return value;
13
+ }
14
+
15
+ const date = new Date(value);
16
+
17
+ return Number.isNaN(date.getTime()) ? value : date;
18
+ }
19
+
20
+ export default class ConnectivityEventsDetailsController extends Controller {
21
+ @service deviceEventActions;
22
+ @service hostRouter;
23
+
24
+ @tracked isMarkingProcessed = false;
25
+
26
+ get event() {
27
+ return this.model;
28
+ }
29
+
30
+ get isProcessed() {
31
+ return Boolean(firstPresent(this.event?.processed_at, this.event?.processedAt));
32
+ }
33
+
34
+ get processedStatus() {
35
+ return this.isProcessed ? 'success' : 'warning';
36
+ }
37
+
38
+ get processedLabel() {
39
+ return this.isProcessed ? 'Processed' : 'Unprocessed';
40
+ }
41
+
42
+ @action goBack() {
43
+ return this.hostRouter.transitionTo('console.fleet-ops.connectivity.events.index');
44
+ }
45
+
46
+ @action refresh() {
47
+ return this.hostRouter.refresh();
48
+ }
49
+
50
+ @action async markProcessed() {
51
+ if (this.isMarkingProcessed) {
52
+ return;
53
+ }
54
+
55
+ this.isMarkingProcessed = true;
56
+
57
+ try {
58
+ const response = await this.deviceEventActions.markProcessed(this.event);
59
+ const processedAt = firstPresent(response?.device_event?.processed_at, response?.device_event?.processedAt, new Date());
60
+
61
+ if (this.event) {
62
+ set(this.event, 'processed_at', toDate(processedAt));
63
+ }
64
+
65
+ return response;
66
+ } finally {
67
+ this.isMarkingProcessed = false;
68
+ }
69
+ }
70
+ }