@fleetbase/fleetops-engine 0.6.51 → 0.6.52

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 (360) 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.js +20 -2
  20. package/addon/components/map/drawer/device-event-listing.hbs +12 -2
  21. package/addon/components/map/drawer/driver-listing.hbs +14 -1
  22. package/addon/components/map/drawer/place-listing.hbs +14 -1
  23. package/addon/components/map/drawer/position-listing.hbs +12 -2
  24. package/addon/components/map/drawer/vehicle-listing.hbs +14 -1
  25. package/addon/components/modals/assigned-orders.hbs +40 -0
  26. package/addon/components/modals/attach-device.hbs +4 -3
  27. package/addon/components/modals/attach-telematic-device.hbs +25 -0
  28. package/addon/components/modals/attach-telematic-device.js +3 -0
  29. package/addon/components/modals/driver-assign-order.hbs +7 -58
  30. package/addon/components/modals/driver-assign-vehicle.hbs +10 -2
  31. package/addon/components/modals/fleet-assign-driver.hbs +18 -0
  32. package/addon/components/modals/fleet-assign-driver.js +3 -0
  33. package/addon/components/modals/fleet-assign-vehicle.hbs +18 -0
  34. package/addon/components/modals/fleet-assign-vehicle.js +3 -0
  35. package/addon/components/modals/telematic-connection-diagnostics.hbs +75 -0
  36. package/addon/components/modals/telematic-connection-diagnostics.js +287 -0
  37. package/addon/components/modals/vehicle-details.hbs +6 -1
  38. package/addon/components/modals/vehicle-form.hbs +8 -1
  39. package/addon/components/order/card.hbs +106 -0
  40. package/addon/components/order/card.js +257 -0
  41. package/addon/components/order/form/service-rate.hbs +2 -0
  42. package/addon/components/service-rate/form.hbs +3 -2
  43. package/addon/components/telematic/details.hbs +98 -87
  44. package/addon/components/telematic/details.js +230 -1
  45. package/addon/components/telematic/form.hbs +303 -72
  46. package/addon/components/telematic/form.js +519 -3
  47. package/addon/components/telematic/hub.hbs +149 -0
  48. package/addon/components/telematic/hub.js +142 -0
  49. package/addon/components/telematic/settings.hbs +152 -0
  50. package/addon/components/telematic/settings.js +45 -0
  51. package/addon/components/telematic/tab-empty-state.hbs +29 -0
  52. package/addon/components/telematic/tab-empty-state.js +33 -0
  53. package/addon/components/vehicle/details.hbs +6 -1
  54. package/addon/components/vehicle/form.hbs +2 -1
  55. package/addon/components/widget/fuel-providers.hbs +43 -0
  56. package/addon/components/widget/fuel-providers.js +27 -0
  57. package/addon/controllers/connectivity/devices/index/details.js +27 -1
  58. package/addon/controllers/connectivity/devices/index.js +14 -0
  59. package/addon/controllers/connectivity/fuel-providers/details/sync.js +18 -0
  60. package/addon/controllers/connectivity/fuel-providers/details/transactions.js +24 -0
  61. package/addon/controllers/connectivity/fuel-providers/details.js +69 -0
  62. package/addon/controllers/connectivity/fuel-providers/edit.js +24 -0
  63. package/addon/controllers/connectivity/fuel-providers/index.js +177 -0
  64. package/addon/controllers/connectivity/fuel-providers/new.js +37 -0
  65. package/addon/controllers/connectivity/telematics/details/attachments.js +247 -0
  66. package/addon/controllers/connectivity/telematics/details/devices.js +368 -0
  67. package/addon/controllers/connectivity/telematics/details/events.js +244 -0
  68. package/addon/controllers/connectivity/telematics/details/index.js +3 -0
  69. package/addon/controllers/connectivity/telematics/details/logs.js +43 -0
  70. package/addon/controllers/connectivity/telematics/details/sensors.js +270 -0
  71. package/addon/controllers/connectivity/telematics/details.js +149 -0
  72. package/addon/controllers/connectivity/telematics/{index/edit.js → edit.js} +15 -4
  73. package/addon/controllers/connectivity/telematics/index.js +25 -5
  74. package/addon/controllers/connectivity/telematics/{index/new.js → new.js} +8 -2
  75. package/addon/controllers/management/drivers/index/details.js +78 -1
  76. package/addon/controllers/management/drivers/index.js +39 -0
  77. package/addon/controllers/management/fleets/index/details.js +23 -1
  78. package/addon/controllers/management/fleets/index.js +6 -1
  79. package/addon/controllers/management/fuel-reports/index.js +37 -1
  80. package/addon/controllers/management/fuel-transactions/index/details.js +70 -0
  81. package/addon/controllers/management/fuel-transactions/index.js +240 -0
  82. package/addon/controllers/management/vehicles/index/details.js +32 -0
  83. package/addon/controllers/management/vehicles/index.js +26 -0
  84. package/addon/controllers/operations/orders/index.js +4 -0
  85. package/addon/controllers/settings/payments/index.js +5 -0
  86. package/addon/extension.js +10 -0
  87. package/addon/routes/connectivity/fuel-providers/details/index.js +7 -0
  88. package/addon/routes/connectivity/fuel-providers/details/matching.js +7 -0
  89. package/addon/routes/connectivity/fuel-providers/details/settings.js +7 -0
  90. package/addon/routes/connectivity/fuel-providers/details/sync.js +18 -0
  91. package/addon/routes/connectivity/fuel-providers/details/transactions.js +14 -0
  92. package/addon/routes/connectivity/fuel-providers/details.js +18 -0
  93. package/addon/routes/connectivity/fuel-providers/edit.js +18 -0
  94. package/addon/routes/connectivity/fuel-providers/index.js +20 -0
  95. package/addon/routes/connectivity/fuel-providers/new.js +3 -0
  96. package/addon/routes/connectivity/fuel-providers.js +3 -0
  97. package/addon/routes/connectivity/telematics/details/attachments.js +28 -0
  98. package/addon/routes/connectivity/telematics/details/devices.js +30 -0
  99. package/addon/routes/connectivity/telematics/details/events.js +33 -0
  100. package/addon/routes/connectivity/telematics/details/index.js +3 -0
  101. package/addon/routes/connectivity/telematics/details/logs.js +17 -0
  102. package/addon/routes/connectivity/telematics/details/sensors.js +29 -0
  103. package/addon/routes/connectivity/telematics/{index/details.js → details.js} +1 -1
  104. package/addon/routes/connectivity/telematics/{index/edit.js → edit.js} +1 -1
  105. package/addon/routes/connectivity/telematics/new.js +3 -0
  106. package/addon/routes/management/drivers/index/details/activity.js +3 -0
  107. package/addon/routes/management/fuel-reports/index.js +2 -0
  108. package/addon/routes/management/fuel-transactions/index/details.js +18 -0
  109. package/addon/routes/management/fuel-transactions/index.js +21 -0
  110. package/addon/routes/management/fuel-transactions.js +3 -0
  111. package/addon/routes.js +28 -9
  112. package/addon/services/device-actions.js +64 -1
  113. package/addon/services/device-event-actions.js +12 -1
  114. package/addon/services/driver-actions.js +147 -11
  115. package/addon/services/fleet-actions.js +66 -1
  116. package/addon/services/fuel-integration-actions.js +31 -0
  117. package/addon/services/issue-actions.js +1 -1
  118. package/addon/services/telematic-actions.js +9 -3
  119. package/addon/services/vehicle-actions.js +125 -0
  120. package/addon/styles/fleetops-engine.css +310 -34
  121. package/addon/templates/analytics/reports/index.hbs +19 -1
  122. package/addon/templates/connectivity/devices/index.hbs +19 -1
  123. package/addon/templates/connectivity/events/index.hbs +16 -1
  124. package/addon/templates/connectivity/fuel-providers/details/index.hbs +38 -0
  125. package/addon/templates/connectivity/fuel-providers/details/matching.hbs +13 -0
  126. package/addon/templates/connectivity/fuel-providers/details/settings.hbs +11 -0
  127. package/addon/templates/connectivity/fuel-providers/details/sync.hbs +48 -0
  128. package/addon/templates/connectivity/fuel-providers/details/transactions.hbs +21 -0
  129. package/addon/templates/connectivity/fuel-providers/details.hbs +51 -0
  130. package/addon/templates/connectivity/fuel-providers/edit.hbs +24 -0
  131. package/addon/templates/connectivity/fuel-providers/index.hbs +12 -0
  132. package/addon/templates/connectivity/fuel-providers/new.hbs +22 -0
  133. package/addon/templates/connectivity/fuel-providers.hbs +1 -0
  134. package/addon/templates/connectivity/sensors/index.hbs +19 -1
  135. package/addon/templates/connectivity/telematics/details/attachments.hbs +163 -0
  136. package/addon/templates/connectivity/telematics/details/devices.hbs +88 -0
  137. package/addon/templates/connectivity/telematics/details/events.hbs +101 -0
  138. package/addon/templates/connectivity/telematics/{index/details → details}/index.hbs +0 -1
  139. package/addon/templates/connectivity/telematics/details/logs.hbs +69 -0
  140. package/addon/templates/connectivity/telematics/details/sensors.hbs +88 -0
  141. package/addon/templates/connectivity/telematics/details.hbs +53 -0
  142. package/addon/templates/connectivity/telematics/edit.hbs +27 -0
  143. package/addon/templates/connectivity/telematics/index.hbs +6 -8
  144. package/addon/templates/connectivity/telematics/new.hbs +24 -0
  145. package/addon/templates/maintenance/equipment/index.hbs +19 -1
  146. package/addon/templates/maintenance/maintenances/index.hbs +15 -1
  147. package/addon/templates/maintenance/parts/index.hbs +19 -1
  148. package/addon/templates/maintenance/schedules/index.hbs +19 -1
  149. package/addon/templates/maintenance/work-orders/index.hbs +19 -1
  150. package/addon/templates/management/contacts/customers.hbs +15 -1
  151. package/addon/templates/management/contacts/index.hbs +19 -1
  152. package/addon/templates/management/drivers/index/details/activity.hbs +1 -0
  153. package/addon/templates/management/drivers/index.hbs +19 -1
  154. package/addon/templates/management/fleets/index.hbs +19 -1
  155. package/addon/templates/management/fuel-reports/index.hbs +19 -1
  156. package/addon/templates/management/fuel-transactions/index/details.hbs +73 -0
  157. package/addon/templates/management/fuel-transactions/index.hbs +58 -0
  158. package/addon/templates/management/fuel-transactions.hbs +1 -0
  159. package/addon/templates/management/issues/index.hbs +19 -1
  160. package/addon/templates/management/places/index.hbs +19 -1
  161. package/addon/templates/management/vehicles/index.hbs +19 -1
  162. package/addon/templates/management/vendors/index.hbs +19 -1
  163. package/addon/templates/management/vendors/integrated.hbs +13 -1
  164. package/addon/templates/operations/orders/index.hbs +19 -1
  165. package/addon/templates/operations/service-rates/index.hbs +19 -1
  166. package/addon/templates/settings/payments/index.hbs +15 -1
  167. package/addon/utils/fleet-ops-options.js +3 -0
  168. package/app/components/cell/telematic-provider.js +1 -0
  169. package/app/components/cell/telematic-status.js +1 -0
  170. package/app/components/fuel-integration/form.js +1 -0
  171. package/app/components/fuel-integration/hub.js +1 -0
  172. package/app/components/fuel-integration/matching-priority.js +1 -0
  173. package/app/components/modals/assigned-orders.js +1 -0
  174. package/app/components/modals/attach-telematic-device.js +1 -0
  175. package/app/components/modals/fleet-assign-driver.js +1 -0
  176. package/app/components/modals/fleet-assign-vehicle.js +1 -0
  177. package/app/components/modals/telematic-connection-diagnostics.js +1 -0
  178. package/app/components/order/card.js +1 -0
  179. package/app/components/telematic/hub.js +1 -0
  180. package/app/components/telematic/settings.js +1 -0
  181. package/app/components/telematic/tab-empty-state.js +1 -0
  182. package/app/components/widget/fuel-providers.js +1 -0
  183. package/app/controllers/connectivity/fuel-providers/details/sync.js +1 -0
  184. package/app/controllers/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  185. package/app/controllers/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  186. package/app/controllers/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  187. package/app/controllers/connectivity/{telematics/index/details → fuel-providers}/index.js +1 -1
  188. package/app/controllers/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  189. package/app/controllers/connectivity/telematics/details/attachments.js +1 -0
  190. package/app/controllers/connectivity/telematics/details/devices.js +1 -0
  191. package/app/controllers/connectivity/telematics/details/events.js +1 -0
  192. package/app/controllers/connectivity/telematics/details/index.js +1 -0
  193. package/app/controllers/connectivity/telematics/details/logs.js +1 -0
  194. package/app/controllers/connectivity/telematics/details/sensors.js +1 -0
  195. package/app/controllers/connectivity/telematics/details.js +1 -0
  196. package/app/controllers/connectivity/telematics/edit.js +1 -0
  197. package/app/controllers/connectivity/telematics/new.js +1 -0
  198. package/app/controllers/management/fuel-transactions/index/details.js +1 -0
  199. package/app/controllers/management/fuel-transactions/index.js +1 -0
  200. package/app/routes/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
  201. package/app/routes/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
  202. package/app/routes/connectivity/fuel-providers/details/settings.js +1 -0
  203. package/app/routes/connectivity/fuel-providers/details/sync.js +1 -0
  204. package/app/routes/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  205. package/app/routes/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  206. package/app/routes/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  207. package/app/routes/connectivity/fuel-providers/index.js +1 -0
  208. package/app/routes/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  209. package/app/routes/connectivity/fuel-providers.js +1 -0
  210. package/app/routes/connectivity/telematics/details/attachments.js +1 -0
  211. package/app/routes/connectivity/telematics/details/devices.js +1 -0
  212. package/app/routes/connectivity/telematics/details/events.js +1 -0
  213. package/app/routes/connectivity/telematics/details/index.js +1 -0
  214. package/app/routes/connectivity/telematics/details/logs.js +1 -0
  215. package/app/routes/connectivity/telematics/details/sensors.js +1 -0
  216. package/app/routes/connectivity/telematics/details.js +1 -0
  217. package/app/routes/connectivity/telematics/edit.js +1 -0
  218. package/app/routes/connectivity/telematics/new.js +1 -0
  219. package/app/routes/management/drivers/index/details/activity.js +1 -0
  220. package/app/routes/management/fuel-transactions/index/details.js +1 -0
  221. package/app/routes/management/fuel-transactions/index.js +1 -0
  222. package/app/routes/management/fuel-transactions.js +1 -0
  223. package/app/services/fuel-integration-actions.js +1 -0
  224. package/app/templates/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
  225. package/app/templates/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
  226. package/app/templates/connectivity/fuel-providers/details/settings.js +1 -0
  227. package/app/templates/connectivity/fuel-providers/details/sync.js +1 -0
  228. package/app/templates/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  229. package/app/templates/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  230. package/app/templates/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  231. package/app/templates/connectivity/fuel-providers/index.js +1 -0
  232. package/app/templates/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  233. package/app/templates/connectivity/fuel-providers.js +1 -0
  234. package/app/templates/connectivity/telematics/details/attachments.js +1 -0
  235. package/app/templates/connectivity/telematics/details/devices.js +1 -0
  236. package/app/templates/connectivity/telematics/details/events.js +1 -0
  237. package/app/templates/connectivity/telematics/details/index.js +1 -0
  238. package/app/templates/connectivity/telematics/details/logs.js +1 -0
  239. package/app/templates/connectivity/telematics/details/sensors.js +1 -0
  240. package/app/templates/connectivity/telematics/details.js +1 -0
  241. package/app/templates/connectivity/telematics/edit.js +1 -0
  242. package/app/templates/connectivity/telematics/new.js +1 -0
  243. package/app/templates/management/drivers/index/details/activity.js +1 -0
  244. package/app/templates/management/fuel-transactions/index/details.js +1 -0
  245. package/app/templates/management/fuel-transactions/index.js +1 -0
  246. package/app/templates/management/fuel-transactions.js +1 -0
  247. package/assets/images/telematics/providers/afaqy.webp +0 -0
  248. package/assets/images/telematics/providers/default.webp +0 -0
  249. package/assets/images/telematics/providers/flespi.webp +0 -0
  250. package/assets/images/telematics/providers/geotab.webp +0 -0
  251. package/assets/images/telematics/providers/safee.webp +0 -0
  252. package/assets/images/telematics/providers/samsara.webp +0 -0
  253. package/composer.json +1 -1
  254. package/extension.json +1 -1
  255. package/package.json +4 -4
  256. package/server/config/fuel-providers.php +42 -0
  257. package/server/config/telematics.php +139 -130
  258. package/server/migrations/2026_06_06_000001_harden_device_events_telematics_contract.php +59 -0
  259. package/server/migrations/2026_06_08_000001_create_fuel_provider_connections_table.php +37 -0
  260. package/server/migrations/2026_06_08_000002_create_fuel_provider_transactions_table.php +53 -0
  261. package/server/migrations/2026_06_08_000003_create_fuel_provider_sync_runs_table.php +40 -0
  262. package/server/migrations/2026_06_10_000001_add_fuel_card_number_to_vehicles_table.php +25 -0
  263. package/server/migrations/2026_06_10_000002_add_vehicle_identity_columns_to_fuel_provider_transactions_table.php +39 -0
  264. package/server/src/Contracts/FuelProvider.php +30 -0
  265. package/server/src/Contracts/TelematicProviderDescriptor.php +3 -1
  266. package/server/src/Events/FuelProviderTransactionImported.php +17 -0
  267. package/server/src/Events/FuelProviderTransactionMatched.php +17 -0
  268. package/server/src/Events/FuelProviderTransactionUnmatched.php +17 -0
  269. package/server/src/Events/FuelReportCreatedFromProvider.php +18 -0
  270. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +6 -0
  271. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +76 -2
  272. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +44 -0
  273. package/server/src/Http/Controllers/Internal/v1/DriverController.php +158 -7
  274. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +124 -0
  275. package/server/src/Http/Controllers/Internal/v1/FuelProviderSyncRunController.php +10 -0
  276. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +76 -0
  277. package/server/src/Http/Controllers/Internal/v1/SensorController.php +1 -1
  278. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +178 -15
  279. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +165 -0
  280. package/server/src/Http/Controllers/TelematicWebhookController.php +103 -19
  281. package/server/src/Http/Filter/FuelProviderConnectionFilter.php +33 -0
  282. package/server/src/Http/Filter/FuelProviderSyncRunFilter.php +28 -0
  283. package/server/src/Http/Filter/FuelProviderTransactionFilter.php +59 -0
  284. package/server/src/Http/Filter/FuelReportFilter.php +10 -0
  285. package/server/src/Http/Resources/v1/Driver.php +15 -0
  286. package/server/src/Http/Resources/v1/FuelProviderConnection.php +30 -0
  287. package/server/src/Http/Resources/v1/FuelProviderSyncRun.php +36 -0
  288. package/server/src/Http/Resources/v1/FuelProviderTransaction.php +54 -0
  289. package/server/src/Http/Resources/v1/FuelReport.php +28 -22
  290. package/server/src/Http/Resources/v1/Index/Driver.php +26 -20
  291. package/server/src/Http/Resources/v1/Index/Vehicle.php +29 -22
  292. package/server/src/Http/Resources/v1/Vehicle.php +17 -0
  293. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +2 -0
  294. package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +57 -0
  295. package/server/src/Jobs/SyncTelematicDevicesJob.php +36 -6
  296. package/server/src/Jobs/TestTelematicConnectionJob.php +9 -22
  297. package/server/src/Models/Asset.php +2 -1
  298. package/server/src/Models/Contact.php +2 -1
  299. package/server/src/Models/Device.php +27 -4
  300. package/server/src/Models/DeviceEvent.php +40 -3
  301. package/server/src/Models/Driver.php +2 -1
  302. package/server/src/Models/Equipment.php +2 -1
  303. package/server/src/Models/Fleet.php +2 -1
  304. package/server/src/Models/FuelProviderConnection.php +60 -0
  305. package/server/src/Models/FuelProviderSyncRun.php +58 -0
  306. package/server/src/Models/FuelProviderTransaction.php +119 -0
  307. package/server/src/Models/FuelReport.php +16 -1
  308. package/server/src/Models/Part.php +2 -1
  309. package/server/src/Models/Sensor.php +21 -4
  310. package/server/src/Models/ServiceRate.php +39 -2
  311. package/server/src/Models/Vehicle.php +14 -2
  312. package/server/src/Models/Vendor.php +2 -1
  313. package/server/src/Providers/FleetOpsServiceProvider.php +10 -0
  314. package/server/src/Support/Analytics/FuelProviderSummary.php +42 -0
  315. package/server/src/Support/FuelProviders/FuelProviderDescriptor.php +55 -0
  316. package/server/src/Support/FuelProviders/FuelProviderRegistry.php +75 -0
  317. package/server/src/Support/FuelProviders/FuelProviderService.php +447 -0
  318. package/server/src/Support/FuelProviders/Providers/AbstractFuelProvider.php +98 -0
  319. package/server/src/Support/FuelProviders/Providers/PetroAppFuelProvider.php +167 -0
  320. package/server/src/Support/Telematics/Providers/AbstractProvider.php +27 -6
  321. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +55 -26
  322. package/server/src/Support/Telematics/Providers/FlespiProvider.php +1 -0
  323. package/server/src/Support/Telematics/Providers/GeotabProvider.php +1 -0
  324. package/server/src/Support/Telematics/Providers/SafeeProvider.php +32 -22
  325. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +3 -1
  326. package/server/src/Support/Telematics/TelematicService.php +340 -21
  327. package/server/src/routes.php +41 -4
  328. package/server/tests/FuelProviderRegistryTest.php +416 -0
  329. package/server/tests/ServiceRateAlgorithmWeightVariablesTest.php +44 -0
  330. package/server/tests/TelematicsHardeningTest.php +268 -0
  331. package/server/tests/VehicleDriverAssignmentTest.php +51 -0
  332. package/translations/ar-ae.yml +22 -0
  333. package/translations/bg-bg.yaml +72 -0
  334. package/translations/en-us.yaml +83 -0
  335. package/translations/es-pa.yaml +72 -0
  336. package/translations/fr-fr.yaml +72 -0
  337. package/translations/mn-mn.yaml +72 -0
  338. package/translations/pt-br.yaml +72 -0
  339. package/translations/ru-ru.yaml +72 -0
  340. package/translations/vi-vn.yaml +97 -0
  341. package/addon/controllers/connectivity/telematics/index/details/devices.js +0 -3
  342. package/addon/controllers/connectivity/telematics/index/details/events.js +0 -3
  343. package/addon/controllers/connectivity/telematics/index/details/index.js +0 -3
  344. package/addon/controllers/connectivity/telematics/index/details/sensors.js +0 -3
  345. package/addon/controllers/connectivity/telematics/index/details.js +0 -24
  346. package/addon/routes/connectivity/telematics/index/details/devices.js +0 -3
  347. package/addon/routes/connectivity/telematics/index/details/events.js +0 -3
  348. package/addon/routes/connectivity/telematics/index/details/index.js +0 -3
  349. package/addon/routes/connectivity/telematics/index/details/sensors.js +0 -3
  350. package/addon/routes/connectivity/telematics/index/new.js +0 -3
  351. package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
  352. package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
  353. package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
  354. package/addon/templates/connectivity/telematics/index/details.hbs +0 -14
  355. package/addon/templates/connectivity/telematics/index/edit.hbs +0 -12
  356. package/addon/templates/connectivity/telematics/index/new.hbs +0 -12
  357. package/app/controllers/connectivity/telematics/index/details/events.js +0 -1
  358. package/app/controllers/connectivity/telematics/index/details/sensors.js +0 -1
  359. package/app/routes/connectivity/telematics/index/details/sensors.js +0 -1
  360. package/app/templates/connectivity/telematics/index/details/sensors.js +0 -1
@@ -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
+ }
@@ -1,87 +1,318 @@
1
- <div class="form-wrapper" ...attributes>
2
- <ContentPanel @title="Telematics Provider" @open={{true}} @wrapperClass="bordered-top">
3
- <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
4
- <InputGroup @name="Provider" @wrapperClass="col-span-2">
5
- <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
6
- <PowerSelect
7
- @options={{this.providers}}
8
- @selected={{this.selectedProvider}}
9
- @onChange={{this.selectProvider}}
10
- @placeholder="Select Provider"
11
- @triggerClass="form-select form-input"
12
- @disabled={{cannot-write @resource}}
13
- as |provider|
14
- >
15
- <div class="space-x-2 text-sm">
16
- <div class="inline-block align-top">
17
- <div class="hide-from-trigger h-1.5 w-full"></div>
18
- <Image src={{provider.icon}} class="w-5 h-5" />
1
+ <div class="flex min-h-0 flex-1 flex-col" ...attributes>
2
+ <div class="min-h-0 flex-1 overflow-y-auto px-4 py-4 md:px-6">
3
+ <div class="mx-auto flex w-full max-w-6xl flex-col gap-4">
4
+ <section class="rounded-md border border-gray-200 bg-white p-3 shadow-sm dark:border-gray-700 dark:bg-gray-800">
5
+ <div class="grid grid-cols-1 gap-2 md:grid-cols-5">
6
+ {{#each this.setupSteps as |step index|}}
7
+ <button
8
+ type="button"
9
+ class="rounded-md border p-2 text-left transition
10
+ {{if step.active 'border-blue-500 bg-blue-50 dark:border-blue-400 dark:bg-blue-500/10' 'border-gray-200 bg-white hover:border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:hover:border-gray-600'}}"
11
+ {{on "click" (fn this.goToStep index)}}
12
+ >
13
+ <div class="flex items-center gap-2">
14
+ <div
15
+ class="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md text-xs
16
+ {{if step.active 'bg-blue-600 text-white dark:bg-blue-500' (if step.complete 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300' 'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-300')}}"
17
+ >
18
+ <FaIcon @icon={{if step.complete "check" step.icon}} />
19
+ </div>
20
+ <div class="min-w-0">
21
+ <div class="text-[10px] font-semibold uppercase text-gray-400 dark:text-gray-500">{{add index 1}}</div>
22
+ <div class="truncate text-xs font-bold text-gray-900 dark:text-gray-100">{{step.label}}</div>
23
+ </div>
19
24
  </div>
20
- <div class="inline-block">
21
- <div class="font-semibold normalize-in-trigger">{{provider.label}}</div>
22
- <div class="hide-from-trigger">{{n-a provider.description}}</div>
25
+ </button>
26
+ {{/each}}
27
+ </div>
28
+ </section>
29
+
30
+ {{#if (eq this.activeStep 0)}}
31
+ <section class="rounded-md border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
32
+ <div class="mb-3">
33
+ <h2 class="text-sm font-bold text-gray-900 dark:text-gray-100">Choose Provider</h2>
34
+ <p class="text-xs text-gray-500 dark:text-gray-300">Select the provider that will supply device connectivity and telemetry.</p>
35
+ </div>
36
+
37
+ {{#if this.loadProviders.isRunning}}
38
+ <div class="flex min-h-[140px] items-center justify-center"><Spinner /></div>
39
+ {{else}}
40
+ <div class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-4">
41
+ {{#each this.providerCards as |provider|}}
42
+ <button
43
+ type="button"
44
+ class="flex min-h-[128px] flex-col rounded-md border p-3 text-left transition
45
+ {{if provider.selected 'border-blue-500 bg-blue-50 dark:border-blue-400 dark:bg-gray-800' 'border-gray-200 bg-white hover:border-blue-300 dark:border-gray-700 dark:bg-gray-800 dark:hover:border-blue-400'}}"
46
+ disabled={{cannot-write @resource}}
47
+ {{on "click" (fn this.selectProvider provider)}}
48
+ >
49
+ <div class="flex items-start justify-between gap-3">
50
+ <div class="flex min-w-0 items-center gap-2">
51
+ <Image src={{provider.icon}} class="h-7 w-7 rounded" />
52
+ <div class="min-w-0">
53
+ <div class="truncate text-sm font-bold text-gray-900 dark:text-gray-100">{{provider.label}}</div>
54
+ <div class="truncate text-[11px] uppercase text-gray-400 dark:text-gray-500">{{provider.type}}</div>
55
+ </div>
56
+ </div>
57
+ {{#if provider.selected}}
58
+ <Badge @status="success" @hideStatusDot={{true}}>Selected</Badge>
59
+ {{/if}}
23
60
  </div>
61
+ <p class="mt-3 line-clamp-2 flex-1 text-xs leading-5 text-gray-500 dark:text-gray-300">{{provider.description}}</p>
62
+ </button>
63
+ {{/each}}
64
+ </div>
65
+ {{/if}}
66
+ </section>
67
+ {{else if (eq this.activeStep 1)}}
68
+ <section class="grid grid-cols-1 gap-4 lg:grid-cols-3">
69
+ <div class="rounded-md border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 lg:col-span-2">
70
+ <div class="mb-3">
71
+ <div>
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>
74
+ </div>
75
+ </div>
76
+
77
+ {{#if this.selectedProvider}}
78
+ <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 no-input-group-padding text-xs">
79
+ {{#each this.credentialFields as |field|}}
80
+ <InputGroup
81
+ @name={{field.label}}
82
+ @value={{get @resource.credentials field.name}}
83
+ @required={{field.required}}
84
+ @type={{field.type}}
85
+ @placeholder={{field.placeholder}}
86
+ @helpText={{field.help_text}}
87
+ {{on "input" (fn this.setCredential field)}}
88
+ />
89
+ {{/each}}
90
+ </div>
91
+ {{#if this.hasAdvancedCredentialFields}}
92
+ <details class="mt-4 rounded-md border border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900/40">
93
+ <summary class="flex cursor-pointer list-none items-center justify-between gap-3 px-3 py-2 text-xs font-semibold text-gray-700 dark:text-gray-200">
94
+ <span class="inline-flex items-center gap-2">
95
+ <FaIcon @icon="sliders" />
96
+ Advanced connection settings
97
+ </span>
98
+ <span class="text-[11px] font-normal text-gray-400 dark:text-gray-500">Endpoint overrides</span>
99
+ </summary>
100
+ <div class="border-t border-gray-200 p-3 dark:border-gray-700">
101
+ <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 no-input-group-padding text-xs">
102
+ {{#each this.advancedCredentialFields as |field|}}
103
+ <InputGroup
104
+ @name={{field.label}}
105
+ @value={{or (get @resource.credentials field.name) field.default_value}}
106
+ @required={{field.required}}
107
+ @type={{field.type}}
108
+ @placeholder={{field.placeholder}}
109
+ @helpText={{field.help_text}}
110
+ {{on "input" (fn this.setCredential field)}}
111
+ />
112
+ {{/each}}
113
+ </div>
114
+ </div>
115
+ </details>
116
+ {{/if}}
117
+ {{else}}
118
+ <div class="rounded-md border border-dashed border-gray-300 p-6 text-center text-xs text-gray-500 dark:border-gray-700 dark:text-gray-300">
119
+ Choose a provider before entering credentials.
120
+ </div>
121
+ {{/if}}
122
+ </div>
123
+
124
+ {{#if this.selectedProvider}}
125
+ <aside class="rounded-md border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
126
+ <div class="flex items-start gap-3">
127
+ <Image src={{this.selectedProvider.icon}} class="h-9 w-9 rounded" />
128
+ <div class="min-w-0">
129
+ <div class="truncate text-sm font-bold text-gray-900 dark:text-gray-100">{{this.selectedProvider.label}}</div>
130
+ <p class="mt-1 text-xs leading-5 text-gray-500 dark:text-gray-300">{{this.selectedProvider.description}}</p>
24
131
  </div>
25
- </PowerSelect>
132
+ </div>
133
+ <div class="mt-3 flex flex-wrap gap-2">
134
+ <Badge @status={{if this.selectedProvider.supports_discovery "success" "default"}} @hideStatusDot={{true}}>Discovery</Badge>
135
+ <Badge @status={{if this.selectedProvider.supports_webhooks "success" "default"}} @hideStatusDot={{true}}>Webhooks</Badge>
136
+ </div>
137
+ </aside>
138
+ {{/if}}
139
+ </section>
140
+ {{else if (eq this.activeStep 2)}}
141
+ <section class="rounded-md border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
142
+ <div class="mb-3">
143
+ <div>
144
+ <h2 class="text-sm font-bold text-gray-900 dark:text-gray-100">Test Connection</h2>
145
+ <p class="text-xs text-gray-500 dark:text-gray-300">Confirm the provider accepts the credentials before saving this connection.</p>
26
146
  </div>
27
- </InputGroup>
28
- </div>
29
- </ContentPanel>
30
-
31
- {{#if this.selectedProvider}}
32
- <ContentPanel @title="Credentials" @open={{true}} @actionButtons={{this.credentialsActionButtons}} @wrapperClass="bordered-top">
33
- <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
34
- {{#each this.selectedProvider.required_fields as |field|}}
35
- <InputGroup
36
- @name={{field.label}}
37
- @value={{get @resource.credentials field.name}}
38
- @required={{field.required}}
39
- @type={{if (eq field.type "password") "text" field.type}}
40
- @helpText={{field.help_text}}
41
- {{on "input" (fn this.setCredential field)}}
42
- />
43
- {{/each}}
44
147
  </div>
45
- </ContentPanel>
46
148
 
47
- <ContentPanel @title="Configure Integration" @open={{true}} @wrapperClass="bordered-top">
48
- <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
149
+ <div
150
+ class="rounded-md border p-4 text-xs transition
151
+ {{if (eq this.connectionState 'success') 'border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-900/10'}}
152
+ {{if (eq this.connectionState 'failed') 'border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-900/10'}}
153
+ {{if (eq this.connectionState 'testing') 'border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900/10'}}
154
+ {{if (eq this.connectionState 'idle') 'border-dashed border-gray-300 bg-white dark:border-gray-700 dark:bg-gray-800'}}"
155
+ >
156
+ <div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
157
+ <div class="flex min-w-0 items-start gap-3">
158
+ <div
159
+ class="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-md
160
+ {{if (eq this.connectionState 'success') 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300'}}
161
+ {{if (eq this.connectionState 'failed') 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300'}}
162
+ {{if (eq this.connectionState 'testing') 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300 animate-pulse'}}
163
+ {{if (eq this.connectionState 'idle') 'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-300'}}"
164
+ >
165
+ {{#if (eq this.connectionState "testing")}}
166
+ <Spinner />
167
+ {{else if (eq this.connectionState "success")}}
168
+ <FaIcon @icon="check" />
169
+ {{else if (eq this.connectionState "failed")}}
170
+ <FaIcon @icon="triangle-exclamation" />
171
+ {{else}}
172
+ <FaIcon @icon="plug" />
173
+ {{/if}}
174
+ </div>
175
+ <div class="min-w-0">
176
+ <div class="text-sm font-bold text-gray-900 dark:text-gray-100">{{this.connectionStateTitle}}</div>
177
+ {{#if this.connectionStateMessage}}
178
+ <p class="mt-1 max-w-3xl text-xs leading-5 text-gray-600 dark:text-gray-300">{{this.connectionStateMessage}}</p>
179
+ {{/if}}
180
+
181
+ {{#if this.hasConnectionMetadata}}
182
+ <div class="mt-3 flex flex-wrap gap-2">
183
+ {{#each this.connectionMetadataEntries as |item|}}
184
+ <span class="inline-flex items-center rounded-md border border-gray-200 bg-white px-2 py-1 text-[11px] text-gray-600 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
185
+ <span class="mr-1 font-semibold text-gray-900 dark:text-gray-100">{{item.label}}</span>
186
+ {{item.value}}
187
+ </span>
188
+ {{/each}}
189
+ </div>
190
+ {{/if}}
191
+ </div>
192
+ </div>
193
+
194
+ <div class="flex flex-shrink-0 items-center justify-end">
195
+ {{#if (eq this.connectionState "failed")}}
196
+ <Button @icon="rotate" @text="Retry Test" @size="xs" @isLoading={{this.testConnection.isRunning}} @disabled={{this.testConnection.isRunning}} @onClick={{perform this.testConnection}} />
197
+ {{else if (eq this.connectionState "success")}}
198
+ <Button @icon="rotate" @text="Test Again" @size="xs" @isLoading={{this.testConnection.isRunning}} @disabled={{this.testConnection.isRunning}} @onClick={{perform this.testConnection}} />
199
+ {{else if (eq this.connectionState "idle")}}
200
+ <Button @icon="plug" @text="Test Connection" @size="xs" @isLoading={{this.testConnection.isRunning}} @disabled={{this.testConnection.isRunning}} @onClick={{perform this.testConnection}} />
201
+ {{/if}}
202
+ </div>
203
+ </div>
204
+
205
+ <div
206
+ class="-mx-4 mt-4 border-t px-4
207
+ {{if (eq this.connectionState 'success') 'border-green-200 dark:border-green-800'}}
208
+ {{if (eq this.connectionState 'failed') 'border-red-200 dark:border-red-800'}}
209
+ {{if (eq this.connectionState 'testing') 'border-blue-200 dark:border-blue-800'}}
210
+ {{if (eq this.connectionState 'idle') 'border-gray-300 dark:border-gray-700'}}"
211
+ >
212
+ <div class="flex flex-col gap-2 py-2 md:flex-row md:items-center md:justify-between">
213
+ <button type="button" class="inline-flex items-center gap-2 text-xs font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-200" {{on "click" this.toggleConnectionDiagnostics}}>
214
+ <FaIcon @icon={{if this.showConnectionDiagnostics "chevron-up" "chevron-down"}} class="text-[10px]" />
215
+ <span>{{if this.showConnectionDiagnostics "Hide diagnostics" "Show diagnostics"}}</span>
216
+ </button>
217
+
218
+ {{#if this.showConnectionDiagnostics}}
219
+ <Button @icon="copy" @text="Copy diagnostics" @size="xs" @disabled={{not this.hasConnectionDiagnostics}} @onClick={{this.copyConnectionDiagnostics}} />
220
+ {{/if}}
221
+ </div>
222
+
223
+ {{#if this.showConnectionDiagnostics}}
224
+ <div class="mt-3 max-h-56 overflow-y-auto rounded-md border border-gray-800 bg-gray-900 p-3 font-mono text-[11px] leading-5 shadow-inner dark:border-gray-700 dark:bg-gray-900">
225
+ {{#each this.connectionDiagnosticEntries as |entry|}}
226
+ <div
227
+ class="flex gap-3
228
+ {{if (eq entry.tone 'success') 'text-green-300'}}
229
+ {{if (eq entry.tone 'danger') 'text-red-300'}}
230
+ {{if (eq entry.tone 'info') 'text-blue-300'}}
231
+ {{if (eq entry.tone 'warning') 'text-yellow-300'}}
232
+ {{if (eq entry.tone 'muted') 'text-gray-300'}}"
233
+ >
234
+ <span class="flex-shrink-0 text-gray-500">[{{entry.time}}]</span>
235
+ <span class="min-w-0 break-words">{{entry.text}}</span>
236
+ </div>
237
+ {{/each}}
238
+ </div>
239
+ {{/if}}
240
+ </div>
241
+ </div>
242
+
243
+ </section>
244
+ {{else if (eq this.activeStep 3)}}
245
+ <section class="rounded-md border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
246
+ <div class="mb-3">
247
+ <h2 class="text-sm font-bold text-gray-900 dark:text-gray-100">Configure Integration</h2>
248
+ <p class="text-xs text-gray-500 dark:text-gray-300">Name the connection and confirm any provider-facing URLs.</p>
249
+ </div>
250
+
251
+ <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 no-input-group-padding text-xs">
49
252
  <InputGroup @value={{@resource.name}} @name="Integration Name" />
50
253
 
51
254
  {{#if this.selectedProvider.supports_webhooks}}
52
- <InputGroup
53
- @name="Webhook URL"
54
- @helpText={{concat "Configure the URL in your " this.selectedProvider.label " dashboard to receive real-time updates."}}
55
- @wrapperClass="col-span-2"
56
- >
57
- <ClickToCopy @value={{this.selectedProvider.webhook_url}} class="w-full">
58
- <Input @value={{this.selectedProvider.webhook_url}} class="form-input w-full" readonly />
59
- </ClickToCopy>
60
- <small class="form-text text-muted mt-1">
61
- Configure this URL in your
62
- {{this.selectedProvider.label}}
63
- dashboard to receive real-time updates.
64
- </small>
255
+ <InputGroup @name="Webhook URL" @wrapperClass="lg:col-span-2">
256
+ {{#if this.webhookUrl}}
257
+ <ClickToCopy @value={{this.webhookUrl}} class="w-full">
258
+ <Input @value={{this.webhookUrl}} class="form-input w-full" readonly />
259
+ </ClickToCopy>
260
+ <small class="form-text text-muted mt-1">Use this public URL in the provider dashboard.</small>
261
+ {{else}}
262
+ <div class="rounded-md border border-yellow-200 bg-yellow-50 p-3 text-xs text-yellow-800 dark:border-yellow-700 dark:bg-gray-800 dark:text-yellow-300">
263
+ The webhook URL will be available after this connection has a public ID.
264
+ </div>
265
+ {{/if}}
65
266
  </InputGroup>
267
+ {{else}}
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.
270
+ </div>
66
271
  {{/if}}
67
272
  </div>
68
- </ContentPanel>
69
- {{/if}}
70
273
 
71
- <ContentPanel @title="Telematic Blackbox" @open={{true}} @wrapperClass="bordered-top">
72
- <div class="grid grid-cols-1 gap-2 lg:grid-cols-3 gap-2 no-input-group-padding text-xs">
73
- <InputGroup @value={{@resource.model}} @name="Model" />
74
- <InputGroup @value={{@resource.serial_number}} @name="Serial Number" />
75
- <InputGroup @value={{@resource.firmware_version}} @name="Firmware Version" />
76
- <InputGroup @value={{@resource.imei}} @name="IMEI" />
77
- <InputGroup @value={{@resource.imsi}} @name="IMSI" />
78
- <InputGroup @value={{@resource.iccid}} @name="ICCID" />
79
- </div>
80
- </ContentPanel>
274
+ </section>
275
+ {{else}}
276
+ <section class="rounded-md border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
277
+ <div class="mb-3">
278
+ <h2 class="text-sm font-bold text-gray-900 dark:text-gray-100">Hardware Identity</h2>
279
+ <p class="text-xs text-gray-500 dark:text-gray-300">Optional blackbox metadata for provider hardware installed in the fleet.</p>
280
+ </div>
281
+ <div class="grid grid-cols-1 gap-2 lg:grid-cols-3 no-input-group-padding text-xs">
282
+ <InputGroup @value={{@resource.model}} @name="Model" />
283
+ <InputGroup @value={{@resource.serial_number}} @name="Serial Number" />
284
+ <InputGroup @value={{@resource.firmware_version}} @name="Firmware Version" />
285
+ <InputGroup @value={{@resource.imei}} @name="IMEI" />
286
+ <InputGroup @value={{@resource.imsi}} @name="IMSI" />
287
+ <InputGroup @value={{@resource.iccid}} @name="ICCID" />
288
+ </div>
289
+ </section>
81
290
 
82
- <CustomField::Yield @subject={{@resource}} @wrapperClass="bordered-top" />
291
+ <CustomField::Yield @subject={{@resource}} @wrapperClass="bordered-top" />
83
292
 
84
- <RegistryYield @registry="fleet-ops:component:telematic:form" as |RegistryComponent|>
85
- <RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} />
86
- </RegistryYield>
87
- </div>
293
+ <RegistryYield @registry="fleet-ops:component:telematic:form" as |RegistryComponent|>
294
+ <RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} />
295
+ </RegistryYield>
296
+ {{/if}}
297
+ </div>
298
+ </div>
299
+
300
+ <footer class="flex-shrink-0 border-t border-gray-200 bg-white px-4 py-3 shadow-sm dark:border-gray-700 dark:bg-gray-900 dark:shadow-sm dark:shadow-black/30 md:px-6">
301
+ <div class="mx-auto flex w-full max-w-6xl flex-col gap-2 md:flex-row md:items-center md:justify-between">
302
+ <div class="text-xs text-gray-500 dark:text-gray-300">{{@footerHelp}}</div>
303
+ <div class="flex items-center justify-end gap-2">
304
+ <Button @icon="times" @text="Cancel" @size="sm" @onClick={{@onCancel}} />
305
+ <Button @icon="arrow-left" @text="Back" @size="sm" @disabled={{not this.canGoBack}} @onClick={{this.previousStep}} />
306
+ <Button
307
+ @icon={{this.primaryFooterIcon}}
308
+ @text={{this.primaryFooterLabel}}
309
+ @type="primary"
310
+ @size="sm"
311
+ @isLoading={{@isSaving}}
312
+ @disabled={{not this.canUsePrimaryAction}}
313
+ @onClick={{this.primaryFooterAction}}
314
+ />
315
+ </div>
316
+ </div>
317
+ </footer>
318
+ </div>