@fleetbase/fleetops-engine 0.6.51 → 0.6.53

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 (396) hide show
  1. package/README.md +0 -2
  2. package/addon/components/cell/telematic-provider.hbs +16 -0
  3. package/addon/components/cell/telematic-provider.js +36 -0
  4. package/addon/components/cell/telematic-status.hbs +1 -0
  5. package/addon/components/cell/telematic-status.js +39 -0
  6. package/addon/components/device/manager.hbs +5 -6
  7. package/addon/components/device/manager.js +12 -15
  8. package/addon/components/driver/details.hbs +1 -1
  9. package/addon/components/fleet/vehicle-listing.js +1 -0
  10. package/addon/components/fuel-integration/form.hbs +200 -0
  11. package/addon/components/fuel-integration/form.js +242 -0
  12. package/addon/components/fuel-integration/hub.hbs +154 -0
  13. package/addon/components/fuel-integration/hub.js +165 -0
  14. package/addon/components/fuel-integration/matching-priority.hbs +76 -0
  15. package/addon/components/fuel-integration/matching-priority.js +145 -0
  16. package/addon/components/fuel-report/details.hbs +13 -1
  17. package/addon/components/issue/details.hbs +3 -57
  18. package/addon/components/issue/details.js +0 -96
  19. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
  20. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +589 -0
  21. package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
  22. package/addon/components/layout/fleet-ops-sidebar.js +287 -376
  23. package/addon/components/maintenance-schedule/form.js +1 -0
  24. package/addon/components/map/drawer/device-event-listing.hbs +12 -2
  25. package/addon/components/map/drawer/driver-listing.hbs +14 -1
  26. package/addon/components/map/drawer/place-listing.hbs +14 -1
  27. package/addon/components/map/drawer/position-listing.hbs +12 -2
  28. package/addon/components/map/drawer/vehicle-listing.hbs +14 -1
  29. package/addon/components/modals/assigned-orders.hbs +40 -0
  30. package/addon/components/modals/attach-device.hbs +4 -3
  31. package/addon/components/modals/attach-telematic-device.hbs +25 -0
  32. package/addon/components/modals/attach-telematic-device.js +3 -0
  33. package/addon/components/modals/driver-assign-order.hbs +7 -58
  34. package/addon/components/modals/driver-assign-vehicle.hbs +10 -2
  35. package/addon/components/modals/fleet-assign-driver.hbs +18 -0
  36. package/addon/components/modals/fleet-assign-driver.js +3 -0
  37. package/addon/components/modals/fleet-assign-vehicle.hbs +18 -0
  38. package/addon/components/modals/fleet-assign-vehicle.js +3 -0
  39. package/addon/components/modals/telematic-connection-diagnostics.hbs +75 -0
  40. package/addon/components/modals/telematic-connection-diagnostics.js +287 -0
  41. package/addon/components/modals/vehicle-details.hbs +6 -1
  42. package/addon/components/modals/vehicle-form.hbs +8 -1
  43. package/addon/components/order/card.hbs +106 -0
  44. package/addon/components/order/card.js +257 -0
  45. package/addon/components/order/form/payload.js +3 -1
  46. package/addon/components/order/form/service-rate.hbs +2 -0
  47. package/addon/components/service-rate/form.hbs +3 -2
  48. package/addon/components/telematic/details.hbs +98 -87
  49. package/addon/components/telematic/details.js +230 -1
  50. package/addon/components/telematic/form.hbs +303 -72
  51. package/addon/components/telematic/form.js +519 -3
  52. package/addon/components/telematic/hub.hbs +149 -0
  53. package/addon/components/telematic/hub.js +142 -0
  54. package/addon/components/telematic/settings.hbs +152 -0
  55. package/addon/components/telematic/settings.js +45 -0
  56. package/addon/components/telematic/tab-empty-state.hbs +29 -0
  57. package/addon/components/telematic/tab-empty-state.js +33 -0
  58. package/addon/components/vehicle/details.hbs +6 -1
  59. package/addon/components/vehicle/form.hbs +2 -1
  60. package/addon/components/widget/fuel-providers.hbs +43 -0
  61. package/addon/components/widget/fuel-providers.js +27 -0
  62. package/addon/controllers/connectivity/devices/index/details.js +27 -1
  63. package/addon/controllers/connectivity/devices/index.js +18 -1
  64. package/addon/controllers/connectivity/fuel-providers/details/sync.js +18 -0
  65. package/addon/controllers/connectivity/fuel-providers/details/transactions.js +24 -0
  66. package/addon/controllers/connectivity/fuel-providers/details.js +69 -0
  67. package/addon/controllers/connectivity/fuel-providers/edit.js +24 -0
  68. package/addon/controllers/connectivity/fuel-providers/index.js +177 -0
  69. package/addon/controllers/connectivity/fuel-providers/new.js +37 -0
  70. package/addon/controllers/connectivity/telematics/details/attachments.js +247 -0
  71. package/addon/controllers/connectivity/telematics/details/devices.js +368 -0
  72. package/addon/controllers/connectivity/telematics/details/events.js +244 -0
  73. package/addon/controllers/connectivity/telematics/details/index.js +3 -0
  74. package/addon/controllers/connectivity/telematics/details/logs.js +43 -0
  75. package/addon/controllers/connectivity/telematics/details/sensors.js +270 -0
  76. package/addon/controllers/connectivity/telematics/details.js +149 -0
  77. package/addon/controllers/connectivity/telematics/{index/edit.js → edit.js} +15 -4
  78. package/addon/controllers/connectivity/telematics/index.js +25 -5
  79. package/addon/controllers/connectivity/telematics/{index/new.js → new.js} +8 -2
  80. package/addon/controllers/maintenance/index.js +100 -0
  81. package/addon/controllers/management/drivers/index/details.js +78 -1
  82. package/addon/controllers/management/drivers/index.js +39 -0
  83. package/addon/controllers/management/fleets/index/details.js +23 -1
  84. package/addon/controllers/management/fleets/index.js +6 -1
  85. package/addon/controllers/management/fuel-reports/index.js +37 -1
  86. package/addon/controllers/management/fuel-transactions/index/details.js +70 -0
  87. package/addon/controllers/management/fuel-transactions/index.js +240 -0
  88. package/addon/controllers/management/index.js +88 -0
  89. package/addon/controllers/management/vehicles/index/details.js +32 -0
  90. package/addon/controllers/management/vehicles/index.js +26 -0
  91. package/addon/controllers/operations/orders/index.js +4 -0
  92. package/addon/controllers/settings/index.js +142 -0
  93. package/addon/controllers/settings/payments/index.js +5 -0
  94. package/addon/extension.js +31 -0
  95. package/addon/routes/analytics/index.js +3 -0
  96. package/addon/routes/connectivity/devices/index.js +3 -0
  97. package/addon/routes/connectivity/fuel-providers/details/index.js +7 -0
  98. package/addon/routes/connectivity/fuel-providers/details/matching.js +7 -0
  99. package/addon/routes/connectivity/fuel-providers/details/settings.js +7 -0
  100. package/addon/routes/connectivity/fuel-providers/details/sync.js +18 -0
  101. package/addon/routes/connectivity/fuel-providers/details/transactions.js +14 -0
  102. package/addon/routes/connectivity/fuel-providers/details.js +18 -0
  103. package/addon/routes/connectivity/fuel-providers/edit.js +18 -0
  104. package/addon/routes/connectivity/fuel-providers/index.js +20 -0
  105. package/addon/routes/connectivity/fuel-providers/new.js +3 -0
  106. package/addon/routes/connectivity/fuel-providers.js +3 -0
  107. package/addon/routes/connectivity/telematics/details/attachments.js +28 -0
  108. package/addon/routes/connectivity/telematics/details/devices.js +30 -0
  109. package/addon/routes/connectivity/telematics/details/events.js +33 -0
  110. package/addon/routes/connectivity/telematics/details/index.js +3 -0
  111. package/addon/routes/connectivity/telematics/details/logs.js +17 -0
  112. package/addon/routes/connectivity/telematics/details/sensors.js +29 -0
  113. package/addon/routes/connectivity/telematics/{index/details.js → details.js} +1 -1
  114. package/addon/routes/connectivity/telematics/{index/edit.js → edit.js} +1 -1
  115. package/addon/routes/connectivity/telematics/new.js +3 -0
  116. package/addon/routes/maintenance/index.js +3 -0
  117. package/addon/routes/maintenance/work-orders/index.js +2 -0
  118. package/addon/routes/management/drivers/index/details/activity.js +3 -0
  119. package/addon/routes/management/fuel-reports/index.js +2 -0
  120. package/addon/routes/management/fuel-transactions/index/details.js +18 -0
  121. package/addon/routes/management/fuel-transactions/index.js +21 -0
  122. package/addon/routes/management/fuel-transactions.js +3 -0
  123. package/addon/routes/settings/index.js +3 -0
  124. package/addon/routes.js +28 -9
  125. package/addon/services/device-actions.js +64 -1
  126. package/addon/services/device-event-actions.js +12 -1
  127. package/addon/services/driver-actions.js +147 -11
  128. package/addon/services/fleet-actions.js +66 -1
  129. package/addon/services/fuel-integration-actions.js +31 -0
  130. package/addon/services/issue-actions.js +1 -1
  131. package/addon/services/telematic-actions.js +9 -3
  132. package/addon/services/vehicle-actions.js +125 -0
  133. package/addon/styles/fleetops-engine.css +2237 -162
  134. package/addon/templates/analytics/index.hbs +14 -0
  135. package/addon/templates/analytics/reports/index.hbs +19 -1
  136. package/addon/templates/connectivity/devices/index.hbs +19 -1
  137. package/addon/templates/connectivity/events/index.hbs +16 -1
  138. package/addon/templates/connectivity/fuel-providers/details/index.hbs +38 -0
  139. package/addon/templates/connectivity/fuel-providers/details/matching.hbs +13 -0
  140. package/addon/templates/connectivity/fuel-providers/details/settings.hbs +11 -0
  141. package/addon/templates/connectivity/fuel-providers/details/sync.hbs +48 -0
  142. package/addon/templates/connectivity/fuel-providers/details/transactions.hbs +21 -0
  143. package/addon/templates/connectivity/fuel-providers/details.hbs +51 -0
  144. package/addon/templates/connectivity/fuel-providers/edit.hbs +24 -0
  145. package/addon/templates/connectivity/fuel-providers/index.hbs +12 -0
  146. package/addon/templates/connectivity/fuel-providers/new.hbs +22 -0
  147. package/addon/templates/connectivity/fuel-providers.hbs +1 -0
  148. package/addon/templates/connectivity/sensors/index.hbs +19 -1
  149. package/addon/templates/connectivity/telematics/details/attachments.hbs +163 -0
  150. package/addon/templates/connectivity/telematics/details/devices.hbs +88 -0
  151. package/addon/templates/connectivity/telematics/details/events.hbs +101 -0
  152. package/addon/templates/connectivity/telematics/{index/details → details}/index.hbs +0 -1
  153. package/addon/templates/connectivity/telematics/details/logs.hbs +69 -0
  154. package/addon/templates/connectivity/telematics/details/sensors.hbs +88 -0
  155. package/addon/templates/connectivity/telematics/details.hbs +53 -0
  156. package/addon/templates/connectivity/telematics/edit.hbs +27 -0
  157. package/addon/templates/connectivity/telematics/index.hbs +6 -8
  158. package/addon/templates/connectivity/telematics/new.hbs +24 -0
  159. package/addon/templates/maintenance/equipment/index.hbs +19 -1
  160. package/addon/templates/maintenance/index.hbs +113 -0
  161. package/addon/templates/maintenance/maintenances/index.hbs +15 -1
  162. package/addon/templates/maintenance/parts/index.hbs +19 -1
  163. package/addon/templates/maintenance/schedules/index.hbs +19 -1
  164. package/addon/templates/maintenance/work-orders/index.hbs +19 -1
  165. package/addon/templates/management/contacts/customers.hbs +15 -1
  166. package/addon/templates/management/contacts/index.hbs +19 -1
  167. package/addon/templates/management/drivers/index/details/activity.hbs +1 -0
  168. package/addon/templates/management/drivers/index.hbs +19 -1
  169. package/addon/templates/management/fleets/index.hbs +19 -1
  170. package/addon/templates/management/fuel-reports/index.hbs +19 -1
  171. package/addon/templates/management/fuel-transactions/index/details.hbs +73 -0
  172. package/addon/templates/management/fuel-transactions/index.hbs +58 -0
  173. package/addon/templates/management/fuel-transactions.hbs +1 -0
  174. package/addon/templates/management/index.hbs +109 -73
  175. package/addon/templates/management/issues/index.hbs +19 -1
  176. package/addon/templates/management/places/index.hbs +19 -1
  177. package/addon/templates/management/vehicles/index.hbs +19 -1
  178. package/addon/templates/management/vendors/index.hbs +19 -1
  179. package/addon/templates/management/vendors/integrated.hbs +13 -1
  180. package/addon/templates/operations/orders/index.hbs +19 -1
  181. package/addon/templates/operations/service-rates/index.hbs +19 -1
  182. package/addon/templates/settings/index.hbs +83 -0
  183. package/addon/templates/settings/payments/index.hbs +15 -1
  184. package/addon/templates/settings/routing.hbs +2 -2
  185. package/addon/utils/fleet-ops-options.js +3 -0
  186. package/app/components/cell/telematic-provider.js +1 -0
  187. package/app/components/cell/telematic-status.js +1 -0
  188. package/app/components/fuel-integration/form.js +1 -0
  189. package/app/components/fuel-integration/hub.js +1 -0
  190. package/app/components/fuel-integration/matching-priority.js +1 -0
  191. package/app/components/modals/assigned-orders.js +1 -0
  192. package/app/components/modals/attach-telematic-device.js +1 -0
  193. package/app/components/modals/fleet-assign-driver.js +1 -0
  194. package/app/components/modals/fleet-assign-vehicle.js +1 -0
  195. package/app/components/modals/telematic-connection-diagnostics.js +1 -0
  196. package/app/components/order/card.js +1 -0
  197. package/app/components/telematic/hub.js +1 -0
  198. package/app/components/telematic/settings.js +1 -0
  199. package/app/components/telematic/tab-empty-state.js +1 -0
  200. package/app/components/widget/fuel-providers.js +1 -0
  201. package/app/controllers/connectivity/fuel-providers/details/sync.js +1 -0
  202. package/app/controllers/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  203. package/app/controllers/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  204. package/app/controllers/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  205. package/app/controllers/connectivity/{telematics/index/details → fuel-providers}/index.js +1 -1
  206. package/app/controllers/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  207. package/app/controllers/connectivity/telematics/details/attachments.js +1 -0
  208. package/app/controllers/connectivity/telematics/details/devices.js +1 -0
  209. package/app/controllers/connectivity/telematics/details/events.js +1 -0
  210. package/app/controllers/connectivity/telematics/details/index.js +1 -0
  211. package/app/controllers/connectivity/telematics/details/logs.js +1 -0
  212. package/app/controllers/connectivity/telematics/details/sensors.js +1 -0
  213. package/app/controllers/connectivity/telematics/details.js +1 -0
  214. package/app/controllers/connectivity/telematics/edit.js +1 -0
  215. package/app/controllers/connectivity/telematics/new.js +1 -0
  216. package/app/controllers/maintenance/index.js +1 -0
  217. package/app/controllers/management/fuel-transactions/index/details.js +1 -0
  218. package/app/controllers/management/fuel-transactions/index.js +1 -0
  219. package/app/controllers/management/index.js +1 -0
  220. package/app/controllers/settings/index.js +1 -0
  221. package/app/routes/analytics/index.js +1 -0
  222. package/app/routes/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
  223. package/app/routes/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
  224. package/app/routes/connectivity/fuel-providers/details/settings.js +1 -0
  225. package/app/routes/connectivity/fuel-providers/details/sync.js +1 -0
  226. package/app/routes/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  227. package/app/routes/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  228. package/app/routes/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  229. package/app/routes/connectivity/fuel-providers/index.js +1 -0
  230. package/app/routes/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  231. package/app/routes/connectivity/fuel-providers.js +1 -0
  232. package/app/routes/connectivity/telematics/details/attachments.js +1 -0
  233. package/app/routes/connectivity/telematics/details/devices.js +1 -0
  234. package/app/routes/connectivity/telematics/details/events.js +1 -0
  235. package/app/routes/connectivity/telematics/details/index.js +1 -0
  236. package/app/routes/connectivity/telematics/details/logs.js +1 -0
  237. package/app/routes/connectivity/telematics/details/sensors.js +1 -0
  238. package/app/routes/connectivity/telematics/details.js +1 -0
  239. package/app/routes/connectivity/telematics/edit.js +1 -0
  240. package/app/routes/connectivity/telematics/new.js +1 -0
  241. package/app/routes/maintenance/index.js +1 -0
  242. package/app/routes/management/drivers/index/details/activity.js +1 -0
  243. package/app/routes/management/fuel-transactions/index/details.js +1 -0
  244. package/app/routes/management/fuel-transactions/index.js +1 -0
  245. package/app/routes/management/fuel-transactions.js +1 -0
  246. package/app/routes/settings/index.js +1 -0
  247. package/app/services/fuel-integration-actions.js +1 -0
  248. package/app/templates/analytics/index.js +1 -0
  249. package/app/templates/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
  250. package/app/templates/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
  251. package/app/templates/connectivity/fuel-providers/details/settings.js +1 -0
  252. package/app/templates/connectivity/fuel-providers/details/sync.js +1 -0
  253. package/app/templates/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  254. package/app/templates/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  255. package/app/templates/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  256. package/app/templates/connectivity/fuel-providers/index.js +1 -0
  257. package/app/templates/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  258. package/app/templates/connectivity/fuel-providers.js +1 -0
  259. package/app/templates/connectivity/telematics/details/attachments.js +1 -0
  260. package/app/templates/connectivity/telematics/details/devices.js +1 -0
  261. package/app/templates/connectivity/telematics/details/events.js +1 -0
  262. package/app/templates/connectivity/telematics/details/index.js +1 -0
  263. package/app/templates/connectivity/telematics/details/logs.js +1 -0
  264. package/app/templates/connectivity/telematics/details/sensors.js +1 -0
  265. package/app/templates/connectivity/telematics/details.js +1 -0
  266. package/app/templates/connectivity/telematics/edit.js +1 -0
  267. package/app/templates/connectivity/telematics/new.js +1 -0
  268. package/app/templates/maintenance/index.js +1 -0
  269. package/app/templates/management/drivers/index/details/activity.js +1 -0
  270. package/app/templates/management/fuel-transactions/index/details.js +1 -0
  271. package/app/templates/management/fuel-transactions/index.js +1 -0
  272. package/app/templates/management/fuel-transactions.js +1 -0
  273. package/app/templates/management/index.js +1 -0
  274. package/app/templates/settings/index.js +1 -0
  275. package/assets/images/telematics/providers/afaqy.webp +0 -0
  276. package/assets/images/telematics/providers/default.webp +0 -0
  277. package/assets/images/telematics/providers/flespi.webp +0 -0
  278. package/assets/images/telematics/providers/geotab.webp +0 -0
  279. package/assets/images/telematics/providers/safee.webp +0 -0
  280. package/assets/images/telematics/providers/samsara.webp +0 -0
  281. package/composer.json +1 -1
  282. package/extension.json +1 -1
  283. package/package.json +4 -4
  284. package/server/config/fuel-providers.php +42 -0
  285. package/server/config/telematics.php +139 -130
  286. package/server/migrations/2026_06_06_000001_harden_device_events_telematics_contract.php +59 -0
  287. package/server/migrations/2026_06_08_000001_create_fuel_provider_connections_table.php +37 -0
  288. package/server/migrations/2026_06_08_000002_create_fuel_provider_transactions_table.php +53 -0
  289. package/server/migrations/2026_06_08_000003_create_fuel_provider_sync_runs_table.php +40 -0
  290. package/server/migrations/2026_06_10_000001_add_fuel_card_number_to_vehicles_table.php +25 -0
  291. package/server/migrations/2026_06_10_000002_add_vehicle_identity_columns_to_fuel_provider_transactions_table.php +39 -0
  292. package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
  293. package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
  294. package/server/src/Contracts/FuelProvider.php +30 -0
  295. package/server/src/Contracts/TelematicProviderDescriptor.php +3 -1
  296. package/server/src/Events/FuelProviderTransactionImported.php +17 -0
  297. package/server/src/Events/FuelProviderTransactionMatched.php +17 -0
  298. package/server/src/Events/FuelProviderTransactionUnmatched.php +17 -0
  299. package/server/src/Events/FuelReportCreatedFromProvider.php +18 -0
  300. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +6 -0
  301. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +140 -2
  302. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +44 -0
  303. package/server/src/Http/Controllers/Internal/v1/DriverController.php +158 -7
  304. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +124 -0
  305. package/server/src/Http/Controllers/Internal/v1/FuelProviderSyncRunController.php +10 -0
  306. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +76 -0
  307. package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
  308. package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
  309. package/server/src/Http/Controllers/Internal/v1/SensorController.php +1 -1
  310. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +179 -16
  311. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +249 -0
  312. package/server/src/Http/Controllers/TelematicWebhookController.php +103 -19
  313. package/server/src/Http/Filter/DeviceFilter.php +74 -0
  314. package/server/src/Http/Filter/DriverFilter.php +6 -0
  315. package/server/src/Http/Filter/FuelProviderConnectionFilter.php +33 -0
  316. package/server/src/Http/Filter/FuelProviderSyncRunFilter.php +28 -0
  317. package/server/src/Http/Filter/FuelProviderTransactionFilter.php +59 -0
  318. package/server/src/Http/Filter/FuelReportFilter.php +10 -0
  319. package/server/src/Http/Filter/VehicleFilter.php +6 -0
  320. package/server/src/Http/Resources/v1/Driver.php +15 -0
  321. package/server/src/Http/Resources/v1/FuelProviderConnection.php +30 -0
  322. package/server/src/Http/Resources/v1/FuelProviderSyncRun.php +36 -0
  323. package/server/src/Http/Resources/v1/FuelProviderTransaction.php +54 -0
  324. package/server/src/Http/Resources/v1/FuelReport.php +28 -22
  325. package/server/src/Http/Resources/v1/Index/Driver.php +26 -20
  326. package/server/src/Http/Resources/v1/Index/Vehicle.php +29 -22
  327. package/server/src/Http/Resources/v1/Vehicle.php +17 -0
  328. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +2 -0
  329. package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +57 -0
  330. package/server/src/Jobs/SyncTelematicDevicesJob.php +105 -17
  331. package/server/src/Jobs/TestTelematicConnectionJob.php +9 -22
  332. package/server/src/Models/Asset.php +2 -1
  333. package/server/src/Models/Contact.php +2 -1
  334. package/server/src/Models/Device.php +27 -4
  335. package/server/src/Models/DeviceEvent.php +40 -3
  336. package/server/src/Models/Driver.php +3 -2
  337. package/server/src/Models/Equipment.php +2 -1
  338. package/server/src/Models/Fleet.php +2 -1
  339. package/server/src/Models/FuelProviderConnection.php +60 -0
  340. package/server/src/Models/FuelProviderSyncRun.php +58 -0
  341. package/server/src/Models/FuelProviderTransaction.php +119 -0
  342. package/server/src/Models/FuelReport.php +16 -1
  343. package/server/src/Models/Part.php +2 -1
  344. package/server/src/Models/Sensor.php +21 -4
  345. package/server/src/Models/ServiceRate.php +39 -2
  346. package/server/src/Models/Vehicle.php +14 -2
  347. package/server/src/Models/Vendor.php +2 -1
  348. package/server/src/Providers/FleetOpsServiceProvider.php +20 -0
  349. package/server/src/Support/Analytics/FuelProviderSummary.php +42 -0
  350. package/server/src/Support/FuelProviders/FuelProviderDescriptor.php +55 -0
  351. package/server/src/Support/FuelProviders/FuelProviderRegistry.php +75 -0
  352. package/server/src/Support/FuelProviders/FuelProviderService.php +447 -0
  353. package/server/src/Support/FuelProviders/Providers/AbstractFuelProvider.php +98 -0
  354. package/server/src/Support/FuelProviders/Providers/PetroAppFuelProvider.php +167 -0
  355. package/server/src/Support/Telematics/Providers/AbstractProvider.php +27 -6
  356. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +105 -38
  357. package/server/src/Support/Telematics/Providers/FlespiProvider.php +1 -0
  358. package/server/src/Support/Telematics/Providers/GeotabProvider.php +1 -0
  359. package/server/src/Support/Telematics/Providers/SafeeProvider.php +32 -22
  360. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +3 -1
  361. package/server/src/Support/Telematics/TelematicService.php +340 -21
  362. package/server/src/routes.php +50 -4
  363. package/server/tests/FuelProviderRegistryTest.php +416 -0
  364. package/server/tests/HubRoutesTest.php +62 -0
  365. package/server/tests/ServiceRateAlgorithmWeightVariablesTest.php +44 -0
  366. package/server/tests/TelematicsHardeningTest.php +359 -0
  367. package/server/tests/VehicleDriverAssignmentTest.php +51 -0
  368. package/translations/ar-ae.yml +23 -1
  369. package/translations/bg-bg.yaml +72 -0
  370. package/translations/en-us.yaml +83 -0
  371. package/translations/es-pa.yaml +72 -0
  372. package/translations/fr-fr.yaml +72 -0
  373. package/translations/mn-mn.yaml +72 -0
  374. package/translations/pt-br.yaml +72 -0
  375. package/translations/ru-ru.yaml +72 -0
  376. package/translations/vi-vn.yaml +98 -1
  377. package/addon/controllers/connectivity/telematics/index/details/devices.js +0 -3
  378. package/addon/controllers/connectivity/telematics/index/details/events.js +0 -3
  379. package/addon/controllers/connectivity/telematics/index/details/index.js +0 -3
  380. package/addon/controllers/connectivity/telematics/index/details/sensors.js +0 -3
  381. package/addon/controllers/connectivity/telematics/index/details.js +0 -24
  382. package/addon/routes/connectivity/telematics/index/details/devices.js +0 -3
  383. package/addon/routes/connectivity/telematics/index/details/events.js +0 -3
  384. package/addon/routes/connectivity/telematics/index/details/index.js +0 -3
  385. package/addon/routes/connectivity/telematics/index/details/sensors.js +0 -3
  386. package/addon/routes/connectivity/telematics/index/new.js +0 -3
  387. package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
  388. package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
  389. package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
  390. package/addon/templates/connectivity/telematics/index/details.hbs +0 -14
  391. package/addon/templates/connectivity/telematics/index/edit.hbs +0 -12
  392. package/addon/templates/connectivity/telematics/index/new.hbs +0 -12
  393. package/app/controllers/connectivity/telematics/index/details/events.js +0 -1
  394. package/app/controllers/connectivity/telematics/index/details/sensors.js +0 -1
  395. package/app/routes/connectivity/telematics/index/details/sensors.js +0 -1
  396. package/app/templates/connectivity/telematics/index/details/sensors.js +0 -1
@@ -18,6 +18,8 @@
18
18
  @options={{this.serviceRates}}
19
19
  @selected={{this.selectedRate}}
20
20
  @onChange={{perform this.getServiceQuotes}}
21
+ @searchEnabled={{true}}
22
+ @searchField="service_name"
21
23
  @placeholder={{t "order.fields.select-service-rate"}}
22
24
  @triggerClass="form-select form-input"
23
25
  @disabled={{cannot-write @resource}}
@@ -379,6 +379,7 @@
379
379
  <li class="leading-5"><code>&#123;stops&#125;</code>: total service stops including pickup, dropoff, and intermediate waypoints.</li>
380
380
  <li class="leading-5"><code>&#123;waypoints&#125;</code>: intermediate waypoint count only.</li>
381
381
  <li class="leading-5"><code>&#123;parcels&#125;</code>, <code>&#123;entities&#125;</code>: parcel count and total payload entity count.</li>
382
+ <li class="leading-5"><code>&#123;weight&#125;</code>, <code>&#123;weight_kg&#125;</code>, <code>&#123;weight_g&#125;</code>, <code>&#123;weight_lb&#125;</code>, <code>&#123;weight_oz&#125;</code>, <code>&#123;weight_tonne&#125;</code>, <code>&#123;weight_t&#125;</code>: total entity weight converted across supported units.</li>
382
383
  <li class="leading-5"><code>&#123;base_fee&#125;</code>: the configured base fee for this service rate.</li>
383
384
  <li class="leading-5">Supported functions: <code>max(a,b)</code>, <code>min(a,b)</code>, <code>ceil(x)</code>, <code>floor(x)</code>, <code>round(x)</code>.</li>
384
385
  </ul>
@@ -399,8 +400,8 @@
399
400
 
400
401
  <div>
401
402
  <h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}} 3</h4>
402
- <div class="mb-2 text-inherit">Combine waypoint, parcel, and distance surcharges in one formula.</div>
403
- <code class="text-xs font-mono">max(&#123;waypoints&#125;, 0) * 15 + max(&#123;parcels&#125; - 3, 0) * 2 + max(&#123;distance_km&#125; - 25, 0) * 0.5 + &#123;base_fee&#125;</code>
403
+ <div class="mb-2 text-inherit">Combine waypoint, payload weight, and distance surcharges in one formula.</div>
404
+ <code class="text-xs font-mono">max(&#123;waypoints&#125;, 0) * 15 + max(&#123;weight_kg&#125; - 100, 0) * 0.25 + max(&#123;distance_km&#125; - 25, 0) * 0.5 + &#123;base_fee&#125;</code>
404
405
  </div>
405
406
  </div>
406
407
  </InfoBlock>
@@ -1,95 +1,111 @@
1
- <div class="details-wrapper" ...attributes>
2
- <ContentPanel @title="Telematics Provider" @open={{true}} @wrapperClass="bordered-top">
3
- <div class="field-info-container">
4
- <div class="field-name">Provider</div>
5
- <div class="field-value">
6
- <div class="flex flew-row space-x-2 text-sm">
7
- <div class="pt-1.5">
8
- <Image src={{@resource.provider_descriptor.icon}} class="w-5 h-5" />
1
+ <div class="flex flex-col gap-4 p-4 md:p-6" ...attributes>
2
+ <section class="grid grid-cols-1 gap-3 lg:grid-cols-3">
3
+ {{#each this.healthCards as |card|}}
4
+ <div class="fleet-ops-widget fleet-ops-kpi-tile fleetops-connectivity-kpi-tile {{card.accentClass}} relative min-h-[118px] overflow-hidden rounded-md border bg-white shadow-sm transition hover:-translate-y-px hover:shadow-lg dark:bg-gray-800">
5
+ <div class="flex h-full flex-col justify-between p-3">
6
+ <div class="flex items-start justify-between gap-3">
7
+ <div class="min-w-0">
8
+ <div class="truncate text-[10px] font-bold uppercase text-gray-500 dark:text-gray-400">{{card.label}}</div>
9
+ <div class="mt-2 truncate text-xl font-extrabold leading-none text-gray-900 dark:text-gray-50">{{n-a card.value}}</div>
10
+ <div class="mt-2 truncate text-[11px] text-gray-500 dark:text-gray-400">{{card.help}}</div>
11
+ </div>
12
+ <div class="fleetops-connectivity-kpi-icon flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md text-[13px]">
13
+ <FaIcon @icon={{card.icon}} />
14
+ </div>
9
15
  </div>
10
- <div>
11
- <div class="font-semibold">{{@resource.provider_descriptor.label}}</div>
12
- <div class="text-xs text-gray-400 dark:text-gray-500">{{n-a @resource.provider_descriptor.description}}</div>
16
+
17
+ <div class="mt-3 flex items-center justify-between gap-2 text-[11px]">
18
+ <span class="flex-shrink-0 font-semibold text-gray-500 dark:text-gray-400">{{card.detailLabel}}</span>
19
+ <span class="min-w-0 truncate text-right text-gray-500 dark:text-gray-400">
20
+ {{#if card.detailIsDate}}
21
+ {{n-a (format-date-fns card.detail "dd MMM yyyy, HH:mm")}}
22
+ {{else}}
23
+ {{n-a card.detail}}
24
+ {{/if}}
25
+ </span>
13
26
  </div>
14
27
  </div>
15
28
  </div>
16
- </div>
17
- </ContentPanel>
18
-
19
- <ContentPanel @title="Integration Details" @open={{true}} @wrapperClass="bordered-top">
20
- <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
21
- <div class="field-info-container">
22
- <div class="field-name">Integration Name</div>
23
- <div class="field-value">{{n-a @resource.name}}</div>
24
- </div>
25
-
26
- {{#if @resource.provider_descriptor.supportsWebhooks}}
27
- <InputGroup @name="Webhook URL" @helpText={{concat "Configure the URL in your " @resource.provider_descriptor.label " dashboard to receive real-time updates."}}>
28
- <ClickToCopy @value={{@resource.provider_descriptor.webhook_url}}>
29
- <Input @value={{@resource.provider_descriptor.webhook_url}} class="form-input" readonly />
30
- </ClickToCopy>
31
- <small class="form-text text-muted">
32
- Configure this URL in your
33
- {{@resource.provider_descriptor.label}}
34
- dashboard to receive real-time updates.
35
- </small>
36
- </InputGroup>
37
- {{/if}}
38
- </div>
39
- </ContentPanel>
40
-
41
- <ContentPanel @title="Telematic Blackbox" @open={{true}} @wrapperClass="bordered-top">
42
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
43
- <div class="field-info-container">
44
- <div class="field-name">Model</div>
45
- <div class="field-value">{{n-a @resource.model}}</div>
46
- </div>
47
-
48
- <div class="field-info-container">
49
- <div class="field-name">Serial Number</div>
50
- <div class="field-value">{{n-a @resource.serial_number}}</div>
51
- </div>
52
-
53
- <div class="field-info-container">
54
- <div class="field-name">Firmware Version</div>
55
- <div class="field-value">{{n-a @resource.firmware_version}}</div>
56
- </div>
29
+ {{/each}}
30
+ </section>
57
31
 
58
- <div class="field-info-container">
59
- <div class="field-name">Status</div>
60
- <div class="field-value">
61
- <Badge @status={{@resource.status}}>{{smart-humanize @resource.status}}</Badge>
32
+ {{#if this.attentionItems.length}}
33
+ <section class="flex flex-col gap-3">
34
+ {{#each this.attentionItems as |item|}}
35
+ <div class="w-full rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-700 dark:bg-gray-800">
36
+ <div class="flex items-start gap-3">
37
+ <div class="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-md bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300">
38
+ <FaIcon @icon={{item.icon}} />
39
+ </div>
40
+ <div class="min-w-0">
41
+ <div class="text-sm font-bold text-gray-900 dark:text-gray-100">{{item.title}}</div>
42
+ <p class="mt-1 text-xs leading-5 text-yellow-800 dark:text-yellow-300">{{item.description}}</p>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ {{/each}}
47
+ </section>
48
+ {{else}}
49
+ <section class="rounded-md border border-green-200 bg-green-50 p-3 dark:border-green-700 dark:bg-green-900/20">
50
+ <div class="flex items-center gap-3">
51
+ <div class="flex h-9 w-9 items-center justify-center rounded-md bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300">
52
+ <FaIcon @icon="check" />
53
+ </div>
54
+ <div>
55
+ <div class="text-sm font-bold text-gray-900 dark:text-gray-100">No immediate attention needed</div>
56
+ <div class="text-xs text-green-800 dark:text-green-300">Connection, sync, and telemetry status are ready for review.</div>
62
57
  </div>
63
58
  </div>
59
+ </section>
60
+ {{/if}}
64
61
 
65
- <div class="field-info-container">
66
- <div class="field-name">IMEI</div>
67
- <div class="field-value">{{n-a @resource.imei}}</div>
68
- </div>
69
-
70
- <div class="field-info-container">
71
- <div class="field-name">ICCID</div>
72
- <div class="field-value">{{n-a @resource.iccid}}</div>
73
- </div>
74
-
75
- <div class="field-info-container">
76
- <div class="field-name">IMSI</div>
77
- <div class="field-value">{{n-a @resource.imsi}}</div>
62
+ <section class="grid grid-cols-1 gap-4 lg:grid-cols-3">
63
+ <div class="rounded-md border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800 lg:col-span-2">
64
+ <div class="mb-3">
65
+ <h2 class="text-sm font-bold text-gray-900 dark:text-gray-100">Provider Connection</h2>
66
+ <p class="text-xs text-gray-500 dark:text-gray-300">External provider identity and public webhook configuration.</p>
78
67
  </div>
79
-
80
- <div class="field-info-container">
81
- <div class="field-name">MSISDN</div>
82
- <div class="field-value">{{n-a @resource.msisdn}}</div>
68
+ <div class="grid grid-cols-1 gap-3 text-xs md:grid-cols-2">
69
+ <div class="rounded-md border border-gray-200 p-3 dark:border-gray-700">
70
+ <div class="text-gray-400 dark:text-gray-500">Provider</div>
71
+ <div class="mt-1 flex items-center gap-2 font-bold text-gray-900 dark:text-gray-100">
72
+ <Image src={{@resource.provider_descriptor.icon}} class="h-5 w-5 rounded" />
73
+ {{@resource.provider_descriptor.label}}
74
+ </div>
75
+ </div>
76
+ <div class="rounded-md border border-gray-200 p-3 dark:border-gray-700">
77
+ <div class="text-gray-400 dark:text-gray-500">Integration ID</div>
78
+ <div class="mt-1 font-mono font-bold text-gray-900 dark:text-gray-100">{{n-a @resource.public_id}}</div>
79
+ </div>
80
+ {{#if @resource.provider_descriptor.supports_webhooks}}
81
+ <div class="rounded-md border border-gray-200 p-3 dark:border-gray-700 md:col-span-2">
82
+ <div class="mb-2 text-gray-400 dark:text-gray-500">Webhook URL</div>
83
+ {{#if this.hasWebhookUrl}}
84
+ <ClickToCopy @value={{this.webhookUrl}}>
85
+ <Input @value={{this.webhookUrl}} class="form-input w-full" readonly />
86
+ </ClickToCopy>
87
+ {{else}}
88
+ <div class="text-yellow-700 dark:text-yellow-300">Webhook URL unavailable until this integration has a public ID.</div>
89
+ {{/if}}
90
+ </div>
91
+ {{/if}}
83
92
  </div>
93
+ </div>
84
94
 
85
- <div class="field-info-container">
86
- <div class="field-name">Last Seen</div>
87
- <div class="field-value">{{n-a (format-date @resource.last_seen_at)}}</div>
95
+ <div class="rounded-md border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
96
+ <div class="mb-3">
97
+ <h2 class="text-sm font-bold text-gray-900 dark:text-gray-100">Hardware Identity</h2>
98
+ <p class="text-xs text-gray-500 dark:text-gray-300">Provider blackbox metadata stored on the integration.</p>
88
99
  </div>
89
-
90
- <div class="field-info-container">
91
- <div class="field-name">Online Status</div>
92
- <div class="field-value">
100
+ <div class="grid gap-2 text-xs">
101
+ {{#each this.hardwareFields as |field|}}
102
+ <div class="flex items-center justify-between gap-3 border-b border-gray-100 py-1.5 last:border-b-0 dark:border-gray-700">
103
+ <span class="text-gray-400 dark:text-gray-500">{{field.label}}</span>
104
+ <span class="truncate font-semibold text-gray-900 dark:text-gray-100">{{n-a field.value}}</span>
105
+ </div>
106
+ {{/each}}
107
+ <div class="flex items-center justify-between gap-3 border-b border-gray-100 py-1.5 last:border-b-0 dark:border-gray-700">
108
+ <span class="text-gray-400 dark:text-gray-500">Online</span>
93
109
  {{#if @resource.is_online}}
94
110
  <Badge @status="online">Online</Badge>
95
111
  {{else}}
@@ -97,13 +113,8 @@
97
113
  {{/if}}
98
114
  </div>
99
115
  </div>
100
-
101
- <div class="field-info-container">
102
- <div class="field-name">Signal Strength</div>
103
- <div class="field-value">{{n-a @resource.signal_strength}}</div>
104
- </div>
105
116
  </div>
106
- </ContentPanel>
117
+ </section>
107
118
 
108
119
  <CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
109
- </div>
120
+ </div>
@@ -1,3 +1,232 @@
1
1
  import Component from '@glimmer/component';
2
2
 
3
- export default class TelematicDetailsComponent extends Component {}
3
+ export default class TelematicDetailsComponent extends Component {
4
+ get webhookUrl() {
5
+ const url = this.args.resource?.provider_descriptor?.webhook_url;
6
+ const id = this.args.resource?.public_id;
7
+
8
+ if (!url || !id) {
9
+ return null;
10
+ }
11
+
12
+ const separator = url.includes('?') ? '&' : '?';
13
+ return `${url}${separator}telematic=${id}`;
14
+ }
15
+
16
+ get hasWebhookUrl() {
17
+ return Boolean(this.webhookUrl);
18
+ }
19
+
20
+ get lastTestStatus() {
21
+ const result = this.args.resource?.meta?.last_test_result;
22
+
23
+ if (result === 'success') {
24
+ return {
25
+ status: 'success',
26
+ label: 'Verified',
27
+ };
28
+ }
29
+
30
+ if (result === 'failed') {
31
+ return {
32
+ status: 'danger',
33
+ label: 'Failed',
34
+ };
35
+ }
36
+
37
+ return null;
38
+ }
39
+
40
+ get lastSyncStatus() {
41
+ if (this.args.resource?.status === 'synchronizing') {
42
+ return {
43
+ status: 'info',
44
+ label: 'Syncing',
45
+ };
46
+ }
47
+
48
+ const result = this.args.resource?.meta?.last_sync_result;
49
+
50
+ if (result === 'success') {
51
+ return {
52
+ status: 'success',
53
+ label: 'Synced',
54
+ };
55
+ }
56
+
57
+ if (result === 'failed') {
58
+ return {
59
+ status: 'danger',
60
+ label: 'Failed',
61
+ };
62
+ }
63
+
64
+ return null;
65
+ }
66
+
67
+ get connectionTestValue() {
68
+ return this.lastTestStatus?.label ?? 'Not tested';
69
+ }
70
+
71
+ get deviceSyncValue() {
72
+ if (this.args.resource?.status === 'synchronizing') {
73
+ return 'Syncing provider devices';
74
+ }
75
+
76
+ if (this.args.resource?.meta?.last_sync_result === 'success') {
77
+ return 'Synced';
78
+ }
79
+
80
+ if (this.args.resource?.meta?.last_sync_result === 'failed') {
81
+ return 'Failed';
82
+ }
83
+
84
+ return 'Not synced';
85
+ }
86
+
87
+ get deviceSyncDetail() {
88
+ if (this.args.resource?.status === 'synchronizing') {
89
+ return this.args.resource?.meta?.last_sync_started_at;
90
+ }
91
+
92
+ return this.args.resource?.meta?.last_sync_completed_at;
93
+ }
94
+
95
+ get connectionTestAccentClass() {
96
+ if (this.lastTestStatus?.status === 'danger') {
97
+ return 'fleetops-connectivity-kpi-accent-rose';
98
+ }
99
+
100
+ if (this.lastTestStatus?.status === 'success') {
101
+ return 'fleetops-connectivity-kpi-accent-green';
102
+ }
103
+
104
+ return 'fleetops-connectivity-kpi-accent-blue';
105
+ }
106
+
107
+ get deviceSyncAccentClass() {
108
+ if (this.lastSyncStatus?.status === 'danger') {
109
+ return 'fleetops-connectivity-kpi-accent-rose';
110
+ }
111
+
112
+ if (this.lastSyncStatus?.status === 'success') {
113
+ return 'fleetops-connectivity-kpi-accent-green';
114
+ }
115
+
116
+ if (this.args.resource?.status === 'synchronizing') {
117
+ return 'fleetops-connectivity-kpi-accent-blue';
118
+ }
119
+
120
+ return 'fleetops-connectivity-kpi-accent-amber';
121
+ }
122
+
123
+ get devicesSyncedAccentClass() {
124
+ return this.args.resource?.meta?.last_sync_total ? 'fleetops-connectivity-kpi-accent-green' : 'fleetops-connectivity-kpi-accent-blue';
125
+ }
126
+
127
+ get healthCards() {
128
+ const resource = this.args.resource;
129
+
130
+ return [
131
+ {
132
+ icon: 'plug',
133
+ label: 'Connection test',
134
+ value: this.connectionTestValue,
135
+ help: 'Provider credentials',
136
+ detailLabel: 'Last test',
137
+ detail: resource?.meta?.last_connection_test,
138
+ detailIsDate: true,
139
+ status: this.lastTestStatus?.status,
140
+ statusLabel: this.lastTestStatus?.label,
141
+ accentClass: this.connectionTestAccentClass,
142
+ },
143
+ {
144
+ icon: 'satellite-dish',
145
+ label: 'Device sync',
146
+ value: this.deviceSyncValue,
147
+ help: 'Provider device discovery',
148
+ detailLabel: this.args.resource?.status === 'synchronizing' ? 'Started' : 'Last sync',
149
+ detail: this.deviceSyncDetail,
150
+ detailIsDate: true,
151
+ status: this.lastSyncStatus?.status,
152
+ statusLabel: this.lastSyncStatus?.label,
153
+ accentClass: this.deviceSyncAccentClass,
154
+ },
155
+ {
156
+ icon: 'microchip',
157
+ label: 'Devices synced',
158
+ value: resource?.meta?.last_sync_total ?? 0,
159
+ help: 'Devices from provider',
160
+ detailLabel: 'Sync job',
161
+ detail: resource?.meta?.last_sync_job_id,
162
+ detailIsDate: false,
163
+ status: resource?.meta?.last_sync_total ? 'success' : null,
164
+ statusLabel: resource?.meta?.last_sync_total ? 'Available' : null,
165
+ accentClass: this.devicesSyncedAccentClass,
166
+ },
167
+ ];
168
+ }
169
+
170
+ get attentionItems() {
171
+ const resource = this.args.resource;
172
+ const items = [];
173
+
174
+ if (resource?.meta?.last_error) {
175
+ items.push({
176
+ icon: 'triangle-exclamation',
177
+ title: 'Connection issue',
178
+ description: this.userFacingIssueMessage(resource.meta.last_error, 'Connection test failed. Review the provider credentials and try again.'),
179
+ status: 'warning',
180
+ });
181
+ }
182
+
183
+ if (resource?.meta?.last_sync_error) {
184
+ items.push({
185
+ icon: 'circle-exclamation',
186
+ title: 'Sync issue',
187
+ description: this.userFacingIssueMessage(resource.meta.last_sync_error, 'Device sync failed. Review the provider connection and server logs, then try again.'),
188
+ status: 'warning',
189
+ });
190
+ }
191
+
192
+ if (resource?.meta?.unattached_devices_count > 0) {
193
+ items.push({
194
+ icon: 'truck',
195
+ title: 'Devices need vehicles',
196
+ description: `${resource.meta.unattached_devices_count} synced devices are waiting to be attached.`,
197
+ status: 'warning',
198
+ });
199
+ }
200
+
201
+ return items;
202
+ }
203
+
204
+ userFacingIssueMessage(message, fallback) {
205
+ if (!message || this.isSensitiveIssueMessage(message)) {
206
+ return fallback;
207
+ }
208
+
209
+ return String(message);
210
+ }
211
+
212
+ isSensitiveIssueMessage(message) {
213
+ const value = String(message).toLowerCase();
214
+
215
+ return ['sqlstate', 'insert into', 'update `', 'select ', 'schema', 'stack trace', 'connection:', 'pdoexception'].some((fragment) => value.includes(fragment));
216
+ }
217
+
218
+ get hardwareFields() {
219
+ const resource = this.args.resource;
220
+
221
+ return [
222
+ { label: 'Model', value: resource?.model },
223
+ { label: 'Serial Number', value: resource?.serial_number },
224
+ { label: 'Firmware Version', value: resource?.firmware_version },
225
+ { label: 'IMEI', value: resource?.imei },
226
+ { label: 'ICCID', value: resource?.iccid },
227
+ { label: 'IMSI', value: resource?.imsi },
228
+ { label: 'MSISDN', value: resource?.msisdn },
229
+ { label: 'Signal Strength', value: resource?.signal_strength },
230
+ ];
231
+ }
232
+ }