@fleetbase/fleetops-engine 0.6.55 → 0.6.57

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 (161) hide show
  1. package/addon/components/admin/map-settings.hbs +1 -1
  2. package/addon/components/ai/create-order-preview.hbs +297 -0
  3. package/addon/components/ai/create-order-preview.js +362 -0
  4. package/addon/components/ai/route-preview-map.hbs +79 -0
  5. package/addon/components/ai/route-preview-map.js +527 -0
  6. package/addon/components/device/details.js +1 -1
  7. package/addon/components/device-event/details.hbs +192 -0
  8. package/addon/components/device-event/details.js +151 -0
  9. package/addon/components/driver/schedule.js +1 -1
  10. package/addon/components/fleet/details.hbs +21 -8
  11. package/addon/components/fleet/details.js +55 -1
  12. package/addon/components/fleet/hierarchy-tree.hbs +137 -0
  13. package/addon/components/fleet/hierarchy-tree.js +528 -0
  14. package/addon/components/layout/fleet-ops-sidebar.js +15 -9
  15. package/addon/components/map/drawer/device-event-listing.js +1 -0
  16. package/addon/components/map/drawer/driver-listing.js +1 -0
  17. package/addon/components/map/drawer/place-listing.js +1 -0
  18. package/addon/components/map/drawer/vehicle-listing.js +1 -0
  19. package/addon/components/order/details.hbs +34 -1
  20. package/addon/components/order/form/details.hbs +4 -0
  21. package/addon/components/order/form.hbs +9 -1
  22. package/addon/components/telematic/details.hbs +1 -1
  23. package/addon/components/telematic/form.hbs +2 -2
  24. package/addon/components/telematic/settings.hbs +1 -1
  25. package/addon/components/vehicle/card.hbs +14 -2
  26. package/addon/components/vehicle/form.hbs +13 -1
  27. package/addon/controllers/connectivity/devices/index/details/events.js +1 -1
  28. package/addon/controllers/connectivity/devices/index/details/vehicle.js +2 -2
  29. package/addon/controllers/connectivity/events/details.js +68 -1
  30. package/addon/controllers/connectivity/sensors/index.js +4 -0
  31. package/addon/controllers/connectivity/telematics/details/events.js +1 -1
  32. package/addon/controllers/connectivity/telematics/details/sensors.js +4 -0
  33. package/addon/controllers/settings/map.js +19 -0
  34. package/addon/extension.js +14 -0
  35. package/addon/instance-initializers/register-vroom-allocation.js +1 -1
  36. package/addon/routes/connectivity/events/details.js +26 -1
  37. package/addon/routes/management/fleets/index/details.js +1 -1
  38. package/addon/routes/management/vehicles/index/details.js +3 -1
  39. package/addon/services/map-adapter/google.js +66 -1
  40. package/addon/services/map-adapter/leaflet.js +1 -1
  41. package/addon/services/map-adapter-interface.js +1 -1
  42. package/addon/services/map-manager.js +16 -1
  43. package/addon/services/map-settings.js +15 -0
  44. package/addon/styles/fleetops-engine.css +487 -1
  45. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +2 -1
  46. package/addon/templates/connectivity/events/details.hbs +40 -1
  47. package/addon/templates/settings/map.hbs +38 -9
  48. package/addon/utils/leaflet-plugin-loader.js +5 -5
  49. package/addon/utils/map-drawer-dropdown-position.js +11 -13
  50. package/addon/utils/route-colors.js +1 -1
  51. package/app/components/ai/create-order-preview.js +1 -0
  52. package/app/components/ai/route-preview-map.js +1 -0
  53. package/app/components/device-event/details.hbs +192 -0
  54. package/app/components/device-event/details.js +1 -0
  55. package/app/components/fleet/hierarchy-tree.js +1 -0
  56. package/app/services/route-engine.js +1 -0
  57. package/composer.json +1 -1
  58. package/extension.json +1 -1
  59. package/package.json +6 -4
  60. package/scripts/normalize-rgba-alpha.js +90 -0
  61. package/server/migrations/2026_06_23_000001_delete_bad_afaqy_telematics_sensors.php +34 -0
  62. package/server/src/Exports/ContactExport.php +12 -5
  63. package/server/src/Exports/DeviceExport.php +92 -0
  64. package/server/src/Exports/DriverExport.php +28 -3
  65. package/server/src/Exports/EquipmentExport.php +94 -0
  66. package/server/src/Exports/FleetExport.php +22 -7
  67. package/server/src/Exports/FuelReportExport.php +16 -1
  68. package/server/src/Exports/IssueExport.php +25 -3
  69. package/server/src/Exports/MaintenanceExport.php +106 -0
  70. package/server/src/Exports/MaintenanceScheduleExport.php +99 -0
  71. package/server/src/Exports/OrderExport.php +24 -5
  72. package/server/src/Exports/PartExport.php +99 -0
  73. package/server/src/Exports/PlaceExport.php +33 -7
  74. package/server/src/Exports/SensorExport.php +94 -0
  75. package/server/src/Exports/ServiceAreaExport.php +31 -7
  76. package/server/src/Exports/ServiceRateExport.php +41 -1
  77. package/server/src/Exports/TelematicExport.php +90 -0
  78. package/server/src/Exports/VehicleExport.php +274 -8
  79. package/server/src/Exports/VendorExport.php +15 -2
  80. package/server/src/Exports/WorkOrderExport.php +92 -0
  81. package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +176 -0
  82. package/server/src/Http/Controllers/Api/v1/DeviceController.php +225 -0
  83. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +114 -0
  84. package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +185 -0
  85. package/server/src/Http/Controllers/Api/v1/PartController.php +120 -0
  86. package/server/src/Http/Controllers/Api/v1/SensorController.php +132 -0
  87. package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +161 -0
  88. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +18 -0
  89. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +17 -0
  90. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +17 -0
  91. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +17 -0
  92. package/server/src/Http/Controllers/Internal/v1/PartController.php +17 -0
  93. package/server/src/Http/Controllers/Internal/v1/SensorController.php +18 -0
  94. package/server/src/Http/Controllers/Internal/v1/SettingController.php +29 -5
  95. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +18 -0
  96. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +17 -0
  97. package/server/src/Http/Filter/DeviceFilter.php +33 -10
  98. package/server/src/Http/Filter/EquipmentFilter.php +23 -0
  99. package/server/src/Http/Filter/FuelProviderTransactionFilter.php +57 -2
  100. package/server/src/Http/Filter/PartFilter.php +47 -0
  101. package/server/src/Http/Filter/SensorFilter.php +34 -6
  102. package/server/src/Http/Filter/WorkOrderFilter.php +23 -0
  103. package/server/src/Http/Requests/CreateDeviceRequest.php +51 -0
  104. package/server/src/Http/Requests/CreateEquipmentRequest.php +35 -0
  105. package/server/src/Http/Requests/CreateFuelTransactionRequest.php +50 -0
  106. package/server/src/Http/Requests/CreatePartRequest.php +40 -0
  107. package/server/src/Http/Requests/CreateSensorRequest.php +47 -0
  108. package/server/src/Http/Requests/CreateWorkOrderRequest.php +42 -0
  109. package/server/src/Http/Requests/UpdateDeviceRequest.php +7 -0
  110. package/server/src/Http/Requests/UpdateEquipmentRequest.php +7 -0
  111. package/server/src/Http/Requests/UpdateFuelTransactionRequest.php +7 -0
  112. package/server/src/Http/Requests/UpdatePartRequest.php +7 -0
  113. package/server/src/Http/Requests/UpdateSensorRequest.php +7 -0
  114. package/server/src/Http/Requests/UpdateWorkOrderRequest.php +7 -0
  115. package/server/src/Http/Resources/v1/Device.php +72 -0
  116. package/server/src/Http/Resources/v1/Equipment.php +57 -0
  117. package/server/src/Http/Resources/v1/Fleet.php +8 -2
  118. package/server/src/Http/Resources/v1/FuelTransaction.php +69 -0
  119. package/server/src/Http/Resources/v1/Part.php +64 -0
  120. package/server/src/Http/Resources/v1/Sensor.php +68 -0
  121. package/server/src/Jobs/SyncTelematicDevicesJob.php +194 -38
  122. package/server/src/Models/Driver.php +6 -0
  123. package/server/src/Models/Order.php +20 -7
  124. package/server/src/Models/Place.php +11 -3
  125. package/server/src/Observers/PurchaseRateObserver.php +2 -1
  126. package/server/src/Providers/FleetOpsServiceProvider.php +26 -0
  127. package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +88 -0
  128. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +108 -0
  129. package/server/src/Support/Ai/Capabilities/ConsoleNavigationCapability.php +55 -0
  130. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +508 -0
  131. package/server/src/Support/Ai/Capabilities/DocsHelpCapability.php +49 -0
  132. package/server/src/Support/Ai/Capabilities/ImportOrdersPreviewCapability.php +66 -0
  133. package/server/src/Support/Ai/Capabilities/OperationalQueryCapability.php +319 -0
  134. package/server/src/Support/Ai/Capabilities/OptimizeOrderRouteCapability.php +240 -0
  135. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +95 -0
  136. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +195 -0
  137. package/server/src/Support/Ai/FleetOpsAiQueryResources.php +150 -0
  138. package/server/src/Support/Analytics/RevenueTrend.php +3 -8
  139. package/server/src/Support/Metrics/ActiveRevenueQuery.php +121 -0
  140. package/server/src/Support/Metrics/AvgOrderValueMetric.php +1 -7
  141. package/server/src/Support/Metrics/EarningsMetric.php +1 -10
  142. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +104 -5
  143. package/server/src/Support/Telematics/Providers/SafeeProvider.php +550 -54
  144. package/server/src/Support/Telematics/TelematicService.php +28 -9
  145. package/server/src/Support/Utils.php +52 -0
  146. package/server/src/routes.php +67 -14
  147. package/server/tests/AiCreateOrderPreviewCapabilityTest.php +131 -0
  148. package/server/tests/AiOperationalQueryCapabilityTest.php +75 -0
  149. package/server/tests/ExportCoverageTest.php +79 -0
  150. package/server/tests/FleetHierarchyResourceTest.php +12 -0
  151. package/server/tests/MetricsRegistryTest.php +45 -5
  152. package/server/tests/OrderPayloadPlaceResolutionTest.php +53 -0
  153. package/server/tests/PointResolutionTest.php +63 -0
  154. package/server/tests/PublicFleetOpsRestEndpointsTest.php +191 -0
  155. package/server/tests/TelematicsHardeningTest.php +636 -1
  156. package/addon/components/table/cell/dropdown/action-item.hbs +0 -31
  157. package/addon/components/table/cell/dropdown/action-item.js +0 -62
  158. package/addon/components/table/cell/dropdown.hbs +0 -28
  159. package/addon/components/table/cell/dropdown.js +0 -91
  160. package/app/components/table/cell/dropdown/action-item.js +0 -1
  161. package/app/components/table/cell/dropdown.js +0 -1
@@ -2,8 +2,24 @@
2
2
  {{#if (has-block)}}
3
3
  {{yield
4
4
  (hash
5
+ RegistryYieldStart=(component
6
+ "registry-yield"
7
+ registry="fleet-ops:component:order:details:start"
8
+ resource=@resource
9
+ controller=@controller
10
+ permission=(get-write-permission @resource)
11
+ isLoading=@isLoading
12
+ )
5
13
  Activity=(component "order/details/activity" resource=@resource onChange=this.handleActivityChanged isLoadingActivity=@isLoadingActivity isLoading=@isLoading)
6
14
  Detail=(component "order/details/detail" resource=@resource onChange=@onDetailsChanged isLoading=@isLoading)
15
+ RegistryYieldAfterDetails=(component
16
+ "registry-yield"
17
+ registry="fleet-ops:component:order:details:after-details"
18
+ resource=@resource
19
+ controller=@controller
20
+ permission=(get-write-permission @resource)
21
+ isLoading=@isLoading
22
+ )
7
23
  CustomFields=(component "order/details/custom-fields" resource=@resource onChange=@onCustomFieldsChanged isLoading=@isLoading)
8
24
  PurchaseRate=(component "order/details/purchase-rate" resource=@resource isLoading=@isLoading)
9
25
  Tracking=(component "order/details/tracking" resource=@resource isLoading=@isLoading)
@@ -18,11 +34,25 @@
18
34
  Documents=(component "order/details/documents" resource=@resource onChange=@onDocumentsChanged isLoading=@isLoading)
19
35
  Comments=(component "order/details/comments" resource=@resource onChange=@onCommentsChanged isLoading=@isLoading)
20
36
  Metadata=(component "order/details/metadata" resource=@resource onChange=@onMetadataChanged isLoading=@isLoading)
37
+ RegistryYieldEnd=(component
38
+ "registry-yield"
39
+ registry="fleet-ops:component:order:details:end"
40
+ resource=@resource
41
+ controller=@controller
42
+ permission=(get-write-permission @resource)
43
+ isLoading=@isLoading
44
+ )
21
45
  )
22
46
  }}
23
47
  {{else}}
48
+ <RegistryYield @registry="fleet-ops:component:order:details:start" as |RegistryComponent|>
49
+ <RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} @isLoading={{@isLoading}} />
50
+ </RegistryYield>
24
51
  <Order::Details::Activity @resource={{@resource}} @onChange={{this.handleActivityChanged}} @isLoadingActivity={{@isLoadingActivity}} @isLoading={{@isLoading}} />
25
52
  <Order::Details::Detail @resource={{@resource}} @onChange={{@onDetailsChanged}} @isLoading={{@isLoading}} />
53
+ <RegistryYield @registry="fleet-ops:component:order:details:after-details" as |RegistryComponent|>
54
+ <RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} @isLoading={{@isLoading}} />
55
+ </RegistryYield>
26
56
  <Order::Details::CustomFields @resource={{@resource}} @onChange={{@onCustomFieldsChanged}} @isLoading={{@isLoading}} />
27
57
  <Order::Details::PurchaseRate @resource={{@resource}} @isLoading={{@isLoading}} />
28
58
  <Order::Details::Tracking @resource={{@resource}} @isLoading={{@isLoading}} />
@@ -37,5 +67,8 @@
37
67
  <Order::Details::Documents @resource={{@resource}} @onChange={{@onDocumentsChanged}} @isLoading={{@isLoading}} />
38
68
  <Order::Details::Comments @resource={{@resource}} @onChange={{@onCommentsChanged}} @isLoading={{@isLoading}} />
39
69
  <Order::Details::Metadata @resource={{@resource}} @onChange={{@onMetadataChanged}} @isLoading={{@isLoading}} />
70
+ <RegistryYield @registry="fleet-ops:component:order:details:end" as |RegistryComponent|>
71
+ <RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} @isLoading={{@isLoading}} />
72
+ </RegistryYield>
40
73
  {{/if}}
41
- </div>
74
+ </div>
@@ -234,6 +234,10 @@
234
234
  />
235
235
  </InputGroup>
236
236
  </div>
237
+
238
+ <RegistryYield @registry="fleet-ops:component:order:form:details" as |RegistryComponent|>
239
+ <RegistryComponent @order={{@resource}} @resource={{@resource}} />
240
+ </RegistryYield>
237
241
  </div>
238
242
  </ContentPanel>
239
243
 
@@ -2,6 +2,7 @@
2
2
  {{#if (has-block)}}
3
3
  {{yield
4
4
  (hash
5
+ RegistryYieldStart=(component "registry-yield" registry="fleet-ops:component:order:form:start" order=@resource)
5
6
  Details=(component "order/form/details" resource=@resource)
6
7
  CustomFields=(component "order/form/custom-fields" resource=@resource customFields=@customFields)
7
8
  Route=(component "order/form/route" resource=@resource)
@@ -12,9 +13,13 @@
12
13
  Documents=(component "order/form/documents" resource=@resource)
13
14
  OrchestratorConstraints=(component "order/form/orchestrator-constraints" resource=@resource)
14
15
  Metadata=(component "order/form/metadata" resource=@resource)
16
+ RegistryYieldEnd=(component "registry-yield" registry="fleet-ops:component:order:form:end" order=@resource)
15
17
  )
16
18
  }}
17
19
  {{else}}
20
+ <RegistryYield @registry="fleet-ops:component:order:form:start" as |RegistryComponent|>
21
+ <RegistryComponent @order={{@resource}} @resource={{@resource}} />
22
+ </RegistryYield>
18
23
  <Order::Form::Details @resource={{@resource}} />
19
24
  <Order::Form::Route @resource={{@resource}} />
20
25
  <RegistryYield @registry="fleet-ops:component:order:form" as |RegistryComponent|>
@@ -26,5 +31,8 @@
26
31
  <Order::Form::Documents @resource={{@resource}} />
27
32
  <Order::Form::OrchestratorConstraints @resource={{@resource}} />
28
33
  <Order::Form::Metadata @resource={{@resource}} />
34
+ <RegistryYield @registry="fleet-ops:component:order:form:end" as |RegistryComponent|>
35
+ <RegistryComponent @order={{@resource}} @resource={{@resource}} />
36
+ </RegistryYield>
29
37
  {{/if}}
30
- </div>
38
+ </div>
@@ -92,7 +92,7 @@
92
92
  <div class="rounded-md border border-gray-200 p-3 dark:border-gray-700 md:col-span-2">
93
93
  <div class="text-gray-400 dark:text-gray-500">Update Mode</div>
94
94
  <div class="mt-1 font-semibold text-gray-900 dark:text-gray-100">Provider polling</div>
95
- <div class="mt-1 text-gray-500 dark:text-gray-400">FleetOps polls this provider for device snapshots and telemetry updates.</div>
95
+ <div class="mt-1 text-gray-500 dark:text-gray-400">Fleet-Ops polls this provider for device snapshots and telemetry updates.</div>
96
96
  </div>
97
97
  {{/if}}
98
98
  </div>
@@ -70,7 +70,7 @@
70
70
  <div class="mb-3">
71
71
  <div>
72
72
  <h2 class="text-sm font-bold text-gray-900 dark:text-gray-100">Credentials</h2>
73
- <p class="text-xs text-gray-500 dark:text-gray-300">FleetOps uses these credentials to test the connection and sync devices.</p>
73
+ <p class="text-xs text-gray-500 dark:text-gray-300">Fleet-Ops uses these credentials to test the connection and sync devices.</p>
74
74
  </div>
75
75
  </div>
76
76
 
@@ -266,7 +266,7 @@
266
266
  </InputGroup>
267
267
  {{else}}
268
268
  <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 lg:col-span-2">
269
- This provider does not require FleetOps webhook configuration.
269
+ This provider does not require Fleet-Ops webhook configuration.
270
270
  </div>
271
271
  {{/if}}
272
272
  </div>
@@ -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>
@@ -1,10 +1,16 @@
1
1
  <Layout::Resource::Card ...attributes as |Card|>
2
2
  <Card.header class="{{@headerClass}}">
3
+ <RegistryYield @registry="fleet-ops:component:vehicle:card:header:start" as |RegistryComponent|>
4
+ <RegistryComponent @order={{@resource}} @resource={{@resource}} />
5
+ </RegistryYield>
3
6
  <div class="font-semibold w-full truncate">{{or @resource.name @resource.yearMakeModel}}</div>
4
- <div class="text-gray-300 dark:text-gray-500 text-sm w-full truncate">{{or @resource.plate_number @resource.vin @resource.serial_number @resource.call_sign @resource.public_id}}</div>
7
+ <div class="text-gray-400 dark:text-gray-500 text-sm w-full truncate">{{or @resource.plate_number @resource.vin @resource.serial_number @resource.call_sign @resource.public_id}}</div>
5
8
  {{#if (has-block "header")}}
6
9
  {{yield to="header"}}
7
10
  {{/if}}
11
+ <RegistryYield @registry="fleet-ops:component:vehicle:card:header:end" as |RegistryComponent|>
12
+ <RegistryComponent @order={{@resource}} @resource={{@resource}} />
13
+ </RegistryYield>
8
14
  </Card.header>
9
15
  <Card.body class={{@bodyClass}}>
10
16
  <Image src={{@resource.photo_url}} class="card-img-lg" />
@@ -13,16 +19,22 @@
13
19
  {{/if}}
14
20
  </Card.body>
15
21
  <Card.footer class={{@footerClass}}>
22
+ <RegistryYield @registry="fleet-ops:component:vehicle:card:footer:start" as |RegistryComponent|>
23
+ <RegistryComponent @order={{@resource}} @resource={{@resource}} />
24
+ </RegistryYield>
16
25
  <div class="flex flex-row items-center space-x-1">
17
26
  <Button @icon="eye" @size="xs" @onClick={{fn this.vehicleActions.transition.view @resource}} />
18
27
  <Button @icon="pencil" @size="xs" @onClick={{fn this.vehicleActions.transition.edit @resource}} />
19
28
  <Button @icon="trash" @size="xs" @type="danger" @onClick={{fn this.vehicleActions.delete @resource}} />
20
29
  </div>
30
+ <RegistryYield @registry="fleet-ops:component:vehicle:card:footer:end" as |RegistryComponent|>
31
+ <RegistryComponent @order={{@resource}} @resource={{@resource}} />
32
+ </RegistryYield>
21
33
  {{#if (has-block "footer")}}
22
34
  {{yield to="footer"}}
23
35
  {{/if}}
24
36
  <div class="mt-1.5">
25
- <div class="text-gray-300 dark:text-gray-500 text-xs">Last Modified: {{@resource.updatedAt}}</div>
37
+ <div class="text-gray-400 dark:text-gray-500 text-xs">Last Modified: {{@resource.updatedAt}}</div>
26
38
  </div>
27
39
  </Card.footer>
28
40
  </Layout::Resource::Card>
@@ -1,4 +1,8 @@
1
1
  <div class="form-wrapper" ...attributes>
2
+ <RegistryYield @registry="fleet-ops:component:vehicle:form:start" as |RegistryComponent|>
3
+ <RegistryComponent @resource={{@resource}} @controller={{this.controller}} @permission={{get-write-permission @resource}} />
4
+ </RegistryYield>
5
+
2
6
  {{! DETAILS / IDENTIFICATION }}
3
7
  <ContentPanel @title={{t "common.details"}} @open={{true}} @wrapperClass="bordered-top">
4
8
  <div class="flex flex-row space-x-3 mb-3">
@@ -128,6 +132,10 @@
128
132
  </RegistryYield>
129
133
  </ContentPanel>
130
134
 
135
+ <RegistryYield @registry="fleet-ops:component:vehicle:form:after-details" as |RegistryComponent|>
136
+ <RegistryComponent @resource={{@resource}} @controller={{this.controller}} @permission={{get-write-permission @resource}} />
137
+ </RegistryYield>
138
+
131
139
  {{! MEASUREMENT & UNITS / ODOMETER }}
132
140
  <ContentPanel @title="Measurement & Units" @open={{true}} @wrapperClass="bordered-top">
133
141
  <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
@@ -608,4 +616,8 @@
608
616
  <ContentPanel @title={{t "common.metadata"}} @open={{true}} @wrapperClass="bordered-top">
609
617
  <MetadataEditor @value={{@resource.meta}} @onChange={{fn (mut @resource.meta)}} />
610
618
  </ContentPanel>
611
- </div>
619
+
620
+ <RegistryYield @registry="fleet-ops:component:vehicle:form:end" as |RegistryComponent|>
621
+ <RegistryComponent @resource={{@resource}} @controller={{this.controller}} @permission={{get-write-permission @resource}} />
622
+ </RegistryYield>
623
+ </div>
@@ -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() {
@@ -86,13 +86,13 @@ export default class ConnectivityDevicesIndexDetailsVehicleController extends Co
86
86
 
87
87
  @action openVehicle() {
88
88
  if (this.vehicle?.id) {
89
- 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
90
  }
91
91
  }
92
92
 
93
93
  @action openVehiclePositions() {
94
94
  if (this.vehicle?.id) {
95
- return this.hostRouter.transitionTo('console.fleet-ops.management.vehicles.index.details.positions', this.vehicle);
95
+ return this.hostRouter.transitionTo('console.fleet-ops.management.vehicles.index.details.positions', this.vehicle.public_id);
96
96
  }
97
97
  }
98
98
 
@@ -1,3 +1,70 @@
1
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';
2
5
 
3
- export default class ConnectivityEventsDetailsController extends Controller {}
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
+ }
@@ -178,6 +178,8 @@ export default class ConnectivitySensorsIndexController extends Controller {
178
178
  filterParam: 'type',
179
179
  filterComponent: 'filter/multi-option',
180
180
  filterOptions: fleetOpsOptions('sensorTypes'),
181
+ filterOptionLabel: 'label',
182
+ filterOptionValue: 'value',
181
183
  },
182
184
  {
183
185
  label: 'Last Value',
@@ -200,6 +202,8 @@ export default class ConnectivitySensorsIndexController extends Controller {
200
202
  filterable: true,
201
203
  filterComponent: 'filter/multi-option',
202
204
  filterOptions: fleetOpsOptions('sensorStatuses'),
205
+ filterOptionLabel: 'label',
206
+ filterOptionValue: 'value',
203
207
  },
204
208
  {
205
209
  label: 'Threshold',
@@ -40,7 +40,7 @@ export default class ConnectivityTelematicsDetailsEventsController extends Contr
40
40
  }
41
41
 
42
42
  get unprocessedEventsCount() {
43
- return this.events.filter((event) => !event.processed_at && !event.is_processed).length;
43
+ return this.events.filter((event) => !event.processed_at).length;
44
44
  }
45
45
 
46
46
  get deviceCount() {
@@ -171,6 +171,8 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
171
171
  filterParam: 'type',
172
172
  filterComponent: 'filter/multi-option',
173
173
  filterOptions: fleetOpsOptions('sensorTypes'),
174
+ filterOptionLabel: 'label',
175
+ filterOptionValue: 'value',
174
176
  },
175
177
  {
176
178
  label: 'Device',
@@ -209,6 +211,8 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
209
211
  filterable: true,
210
212
  filterComponent: 'filter/multi-option',
211
213
  filterOptions: fleetOpsOptions('sensorStatuses'),
214
+ filterOptionLabel: 'label',
215
+ filterOptionValue: 'value',
212
216
  },
213
217
  {
214
218
  label: 'Last Reading',
@@ -19,6 +19,12 @@ export default class SettingsMapController extends Controller {
19
19
  * @var {Array}
20
20
  */
21
21
  @tracked mapProviderOptions = MAP_PROVIDER_OPTIONS;
22
+ @tracked googleMapsMapTypeOptions = [
23
+ { label: 'Roadmap', value: 'roadmap' },
24
+ { label: 'Satellite', value: 'satellite' },
25
+ { label: 'Hybrid', value: 'hybrid' },
26
+ { label: 'Terrain', value: 'terrain' },
27
+ ];
22
28
 
23
29
  /**
24
30
  * The currently-selected map provider key (e.g. 'leaflet' or 'google').
@@ -27,6 +33,9 @@ export default class SettingsMapController extends Controller {
27
33
  * @var {String}
28
34
  */
29
35
  @tracked mapProvider = 'leaflet';
36
+ @tracked googleMapsMapType = 'roadmap';
37
+ @tracked showGoogleMapsTrafficLayer = false;
38
+ @tracked showGoogleMapsTransitLayer = false;
30
39
 
31
40
  /**
32
41
  * Whether settings have been loaded from the server.
@@ -68,6 +77,9 @@ export default class SettingsMapController extends Controller {
68
77
  @task *saveSettings() {
69
78
  const settings = {
70
79
  mapProvider: this.mapProvider,
80
+ googleMapsMapType: this.googleMapsMapType,
81
+ showGoogleMapsTrafficLayer: this.showGoogleMapsTrafficLayer,
82
+ showGoogleMapsTransitLayer: this.showGoogleMapsTransitLayer,
71
83
  };
72
84
 
73
85
  try {
@@ -78,6 +90,10 @@ export default class SettingsMapController extends Controller {
78
90
  this.mapManager.setActiveProvider(this.mapProvider);
79
91
  }
80
92
 
93
+ if (this.mapProvider === 'google' && typeof this.mapManager?.applyViewSettingsFromSettings === 'function') {
94
+ yield this.mapManager.applyViewSettingsFromSettings();
95
+ }
96
+
81
97
  this.notifications.success(this.intl.t('settings.map.settings-saved'));
82
98
  } catch (error) {
83
99
  this.notifications.serverError(error);
@@ -89,6 +105,9 @@ export default class SettingsMapController extends Controller {
89
105
  const response = yield this.mapSettings.load({ force: true });
90
106
 
91
107
  this.mapProvider = response?.mapProvider ?? 'leaflet';
108
+ this.googleMapsMapType = response?.googleMapsMapType ?? 'roadmap';
109
+ this.showGoogleMapsTrafficLayer = Boolean(response?.showGoogleMapsTrafficLayer);
110
+ this.showGoogleMapsTransitLayer = Boolean(response?.showGoogleMapsTransitLayer);
92
111
  this.settingsLoaded = true;
93
112
  } catch (error) {
94
113
  this.notifications.serverError(error);
@@ -116,6 +116,7 @@ export default {
116
116
 
117
117
  // Create registries
118
118
  this.createRegistries(registryService);
119
+ registryService.registerRenderableComponent('ai:action-preview:fleet-ops.create_order', new ExtensionComponent('@fleetbase/fleetops-engine', 'ai/create-order-preview'));
119
120
 
120
121
  // // Register console home guidance
121
122
  // this.registerHomeComponents(registryService);
@@ -368,6 +369,13 @@ export default {
368
369
  'fleet-ops:component:place:form:details',
369
370
  'fleet-ops:component:vehicle:form',
370
371
  'fleet-ops:component:vehicle:form:details',
372
+ 'fleet-ops:component:vehicle:form:after-details',
373
+ 'fleet-ops:component:vehicle:form:start',
374
+ 'fleet-ops:component:vehicle:form:end',
375
+ 'fleet-ops:component:vehicle:card:header:start',
376
+ 'fleet-ops:component:vehicle:card:header:end',
377
+ 'fleet-ops:component:vehicle:card:footer:start',
378
+ 'fleet-ops:component:vehicle:card:footer:end',
371
379
  'fleet-ops:component:vendor:form:edit',
372
380
  'fleet-ops:component:vendor:form:edit:details',
373
381
  'fleet-ops:component:vendor:form:create',
@@ -392,6 +400,12 @@ export default {
392
400
  'fleet-ops:contextmenu:driver',
393
401
  'fleet-ops:component:order:details',
394
402
  'fleet-ops:component:order:form',
403
+ 'fleet-ops:component:order:form:start',
404
+ 'fleet-ops:component:order:form:end',
405
+ 'fleet-ops:component:order:form:details',
406
+ 'fleet-ops:component:order:form:details:start',
407
+ 'fleet-ops:component:order:form:details:end',
408
+ 'fleet-ops:component:order:form:details:after-details',
395
409
  'fleet-ops:component:order:form:payload:entity',
396
410
  'fleet-ops:component:order:form:payload:entity:form',
397
411
  'fleet-ops:template:settings:routing',
@@ -10,7 +10,7 @@
10
10
  * Third-party orchestration engines follow the same pattern:
11
11
  * 1. Create a service extending OrchestrationEngineInterfaceService
12
12
  * 2. Create an instance initializer that calls orchestrationEngine.register()
13
- * 3. The engine will appear in the FleetOps orchestrator settings dropdown
13
+ * 3. The engine will appear in the Fleet-Ops orchestrator settings dropdown
14
14
  */
15
15
  export function initialize(appInstance) {
16
16
  const orchestrationEngine = appInstance.lookup('service:orchestration-engine');
@@ -1,3 +1,28 @@
1
1
  import Route from '@ember/routing/route';
2
+ import { action } from '@ember/object';
3
+ import { inject as service } from '@ember/service';
2
4
 
3
- export default class ConnectivityEventsDetailsRoute extends Route {}
5
+ export default class ConnectivityEventsDetailsRoute extends Route {
6
+ @service abilities;
7
+ @service hostRouter;
8
+ @service intl;
9
+ @service notifications;
10
+ @service store;
11
+
12
+ @action error(error) {
13
+ this.notifications.serverError(error);
14
+
15
+ return this.hostRouter.transitionTo('console.fleet-ops.connectivity.events.index');
16
+ }
17
+
18
+ beforeModel() {
19
+ if (this.abilities.cannot('fleet-ops view device-event')) {
20
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
21
+ return this.hostRouter.transitionTo('console.fleet-ops.connectivity.events.index');
22
+ }
23
+ }
24
+
25
+ model({ public_id }) {
26
+ return this.store.findRecord('device-event', public_id);
27
+ }
28
+ }
@@ -24,6 +24,6 @@ export default class ManagementFleetsIndexDetailsRoute extends Route {
24
24
  }
25
25
 
26
26
  model({ public_id }) {
27
- return this.store.queryRecord('fleet', { public_id, single: true, with: ['parent_fleet', 'service_area', 'zone'] });
27
+ return this.store.queryRecord('fleet', { public_id, single: true, with: ['parent_fleet', 'service_area', 'zone', 'subfleets', 'drivers', 'vehicles'] });
28
28
  }
29
29
  }
@@ -28,6 +28,8 @@ export default class ManagementVehiclesIndexDetailsRoute extends Route {
28
28
  }
29
29
 
30
30
  afterModel(model) {
31
- model.loadDriver();
31
+ if (model && typeof model.loadDriver === 'function') {
32
+ model.loadDriver();
33
+ }
32
34
  }
33
35
  }
@@ -62,6 +62,16 @@ const DEFAULT_GOOGLE_MAP_STYLES = [
62
62
  },
63
63
  ];
64
64
 
65
+ function buildGoogleMapStyles({ showTransitLayer = false, googleOptions = {} } = {}) {
66
+ const baseStyles = showTransitLayer ? DEFAULT_GOOGLE_MAP_STYLES.filter((style) => style.featureType !== 'transit') : DEFAULT_GOOGLE_MAP_STYLES;
67
+
68
+ if (googleOptions.styles === undefined) {
69
+ return baseStyles;
70
+ }
71
+
72
+ return [...baseStyles, ...googleOptions.styles];
73
+ }
74
+
65
75
  function buildWaypointMarkerContent(label, color) {
66
76
  const wrapper = document.createElement('div');
67
77
  wrapper.className = 'fleetops-map-marker';
@@ -108,6 +118,12 @@ export default class GoogleMapsAdapter extends MapAdapterInterface {
108
118
  /** @type {google.maps.ImageMapType|null} */
109
119
  _customTileLayer = null;
110
120
 
121
+ /** @type {google.maps.TrafficLayer|null} */
122
+ _trafficLayer = null;
123
+
124
+ /** @type {google.maps.TransitLayer|null} */
125
+ _transitLayer = null;
126
+
111
127
  /** @type {Map<string, Function[]>} Normalized event name → array of [handler, gmListener] pairs */
112
128
  _eventListeners = new Map();
113
129
 
@@ -160,7 +176,10 @@ export default class GoogleMapsAdapter extends MapAdapterInterface {
160
176
  const { AdvancedMarkerElement } = await googleMaps.maps.importLibrary('marker');
161
177
  this._advancedMarkerElementClass = AdvancedMarkerElement;
162
178
  const mergedGoogleOptions = options.googleOptions ?? {};
163
- const mergedMapStyles = mergedGoogleOptions.styles === undefined ? DEFAULT_GOOGLE_MAP_STYLES : [...DEFAULT_GOOGLE_MAP_STYLES, ...mergedGoogleOptions.styles];
179
+ const mergedMapStyles = buildGoogleMapStyles({
180
+ showTransitLayer: options.showTransitLayer,
181
+ googleOptions: mergedGoogleOptions,
182
+ });
164
183
  const mapId = options.mapId ?? mergedGoogleOptions.mapId ?? this.#getConfig()?.googleMaps?.mapId ?? this.#getConfig()?.GOOGLE_MAPS_MAP_ID ?? null;
165
184
  this._supportsAdvancedMarkers = Boolean(mapId && mapId !== 'FLEETOPS_MAP');
166
185
 
@@ -176,6 +195,7 @@ export default class GoogleMapsAdapter extends MapAdapterInterface {
176
195
  styles: mergedMapStyles,
177
196
  });
178
197
 
198
+ await this.applyViewSettings(options);
179
199
  await this.#initDrawingManager();
180
200
 
181
201
  debug('[GoogleMapsAdapter] Map initialized');
@@ -231,6 +251,10 @@ export default class GoogleMapsAdapter extends MapAdapterInterface {
231
251
  this._tooltipOverlayView?.setMap?.(null);
232
252
  this._tooltipOverlayView = null;
233
253
  this._supportsAdvancedMarkers = false;
254
+ this._trafficLayer?.setMap(null);
255
+ this._trafficLayer = null;
256
+ this._transitLayer?.setMap(null);
257
+ this._transitLayer = null;
234
258
 
235
259
  this._map = null;
236
260
  debug('[GoogleMapsAdapter] Map destroyed');
@@ -241,6 +265,47 @@ export default class GoogleMapsAdapter extends MapAdapterInterface {
241
265
  google.maps.event.trigger(this._map, 'resize');
242
266
  }
243
267
 
268
+ async applyViewSettings(options = {}) {
269
+ if (!this._map) return;
270
+
271
+ const googleMaps = window.google;
272
+ const { TrafficLayer, TransitLayer } = await googleMaps.maps.importLibrary('maps');
273
+ const mapTypeId = options.mapTypeId ?? googleMaps.maps.MapTypeId.ROADMAP;
274
+ const showTrafficLayer = Boolean(options.showTrafficLayer);
275
+ const showTransitLayer = Boolean(options.showTransitLayer);
276
+ const googleOptions = options.googleOptions ?? {};
277
+
278
+ this._map.setMapTypeId?.(mapTypeId);
279
+ this._map.setOptions?.({
280
+ styles: buildGoogleMapStyles({
281
+ showTransitLayer,
282
+ googleOptions,
283
+ }),
284
+ });
285
+
286
+ if (showTrafficLayer) {
287
+ if (!this._trafficLayer) {
288
+ this._trafficLayer = new TrafficLayer();
289
+ }
290
+ this._trafficLayer.setMap(this._map);
291
+ } else if (this._trafficLayer) {
292
+ this._trafficLayer.setMap(null);
293
+ this._trafficLayer = null;
294
+ }
295
+
296
+ if (showTransitLayer) {
297
+ if (!this._transitLayer) {
298
+ this._transitLayer = new TransitLayer();
299
+ }
300
+ this._transitLayer.setMap(this._map);
301
+ } else if (this._transitLayer) {
302
+ this._transitLayer.setMap(null);
303
+ this._transitLayer = null;
304
+ }
305
+
306
+ debug(`[GoogleMapsAdapter] Applied view settings: mapTypeId=${mapTypeId}, traffic=${showTrafficLayer}, transit=${showTransitLayer}`);
307
+ }
308
+
244
309
  // ─── Viewport ──────────────────────────────────────────────────────────────
245
310
 
246
311
  setCenter(lat, lng, zoom) {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Implements the MapAdapterInterface using the Leaflet library (via ember-leaflet).
5
5
  * This adapter wraps all existing Leaflet-specific logic, preserving 100% of
6
- * the original FleetOps map functionality while conforming to the provider-agnostic
6
+ * the original Fleet-Ops map functionality while conforming to the provider-agnostic
7
7
  * interface.
8
8
  *
9
9
  * All Leaflet-specific plugins are supported:
@@ -2,7 +2,7 @@
2
2
  * MapAdapterInterface
3
3
  *
4
4
  * Abstract base class that defines the standardized contract all map provider
5
- * adapters must implement. This enables FleetOps to support multiple map
5
+ * adapters must implement. This enables Fleet-Ops to support multiple map
6
6
  * providers (Leaflet, Google Maps, Mapbox, etc.) without changing any
7
7
  * application-level business logic.
8
8
  *