@fleetbase/fleetops-engine 0.6.69 → 0.6.70

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 (209) hide show
  1. package/RELEASE.md +41 -8
  2. package/addon/components/admin/telematics-settings.hbs +143 -0
  3. package/addon/components/admin/telematics-settings.js +145 -0
  4. package/addon/components/fuel-integration/hub.hbs +1 -1
  5. package/addon/components/layout/fleet-ops-sidebar.js +37 -17
  6. package/addon/components/map/leaflet-live-map.hbs +4 -242
  7. package/addon/components/map/leaflet-live-map.js +2 -1
  8. package/addon/components/map/marker-card/driver.hbs +57 -0
  9. package/addon/components/map/marker-card/driver.js +3 -0
  10. package/addon/components/map/marker-card/vehicle.hbs +78 -0
  11. package/addon/components/map/marker-card/vehicle.js +3 -0
  12. package/addon/components/telematic/hub.hbs +1 -1
  13. package/addon/components/widget/live-fleet.hbs +8 -40
  14. package/addon/controllers/analytics/reports/index.js +5 -0
  15. package/addon/controllers/connectivity/devices/index.js +4 -0
  16. package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
  17. package/addon/controllers/connectivity/sensors/index.js +4 -0
  18. package/addon/controllers/connectivity/telematics/index.js +4 -0
  19. package/addon/controllers/maintenance/equipment/index.js +11 -4
  20. package/addon/controllers/maintenance/inspection-forms/index.js +2 -2
  21. package/addon/controllers/maintenance/inspection-submissions/index.js +8 -2
  22. package/addon/controllers/maintenance/maintenances/index.js +4 -0
  23. package/addon/controllers/maintenance/parts/index.js +11 -4
  24. package/addon/controllers/maintenance/schedules/index.js +17 -4
  25. package/addon/controllers/maintenance/work-orders/index.js +11 -4
  26. package/addon/controllers/management/contacts/index.js +4 -0
  27. package/addon/controllers/management/drivers/index.js +4 -0
  28. package/addon/controllers/management/fleets/index.js +4 -0
  29. package/addon/controllers/management/fuel-reports/index.js +4 -0
  30. package/addon/controllers/management/fuel-transactions/index.js +9 -7
  31. package/addon/controllers/management/issues/index.js +4 -0
  32. package/addon/controllers/management/places/index.js +4 -0
  33. package/addon/controllers/management/vehicles/index.js +4 -0
  34. package/addon/controllers/management/vendors/index.js +4 -0
  35. package/addon/controllers/operations/orders/index/details.js +30 -1
  36. package/addon/controllers/operations/orders/index.js +7 -0
  37. package/addon/controllers/operations/service-rates/index.js +3 -0
  38. package/addon/controllers/settings/index.js +13 -0
  39. package/addon/controllers/settings/telematics.js +113 -0
  40. package/addon/extension.js +38 -6
  41. package/addon/routes/analytics/index.js +14 -1
  42. package/addon/routes/analytics/reports/index/details.js +1 -1
  43. package/addon/routes/analytics/reports/index/edit.js +1 -1
  44. package/addon/routes/analytics/reports/index/new.js +14 -1
  45. package/addon/routes/analytics/reports.js +14 -1
  46. package/addon/routes/connectivity/devices/index/new.js +14 -1
  47. package/addon/routes/connectivity/devices.js +14 -1
  48. package/addon/routes/connectivity/events.js +14 -1
  49. package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
  50. package/addon/routes/connectivity/fuel-providers/details.js +9 -0
  51. package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
  52. package/addon/routes/connectivity/fuel-providers/new.js +14 -1
  53. package/addon/routes/connectivity/fuel-providers.js +14 -1
  54. package/addon/routes/connectivity/sensors/index/new.js +14 -1
  55. package/addon/routes/connectivity/sensors.js +14 -1
  56. package/addon/routes/connectivity/telematics/new.js +14 -1
  57. package/addon/routes/connectivity/telematics.js +14 -1
  58. package/addon/routes/connectivity/tracking.js +14 -1
  59. package/addon/routes/maintenance/equipment/index/details.js +2 -2
  60. package/addon/routes/maintenance/equipment/index/edit.js +2 -2
  61. package/addon/routes/maintenance/equipment/index/new.js +14 -1
  62. package/addon/routes/maintenance/equipment.js +14 -1
  63. package/addon/routes/maintenance/index.js +14 -1
  64. package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
  65. package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
  66. package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
  67. package/addon/routes/maintenance/inspection-forms.js +14 -1
  68. package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
  69. package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
  70. package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
  71. package/addon/routes/maintenance/inspection-submissions.js +14 -1
  72. package/addon/routes/maintenance/maintenances/index/details.js +2 -2
  73. package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
  74. package/addon/routes/maintenance/maintenances/index/new.js +14 -1
  75. package/addon/routes/maintenance/maintenances.js +14 -1
  76. package/addon/routes/maintenance/parts/index/details.js +2 -2
  77. package/addon/routes/maintenance/parts/index/edit.js +2 -2
  78. package/addon/routes/maintenance/parts/index/new.js +14 -1
  79. package/addon/routes/maintenance/parts.js +14 -1
  80. package/addon/routes/maintenance/schedules/index/details.js +2 -2
  81. package/addon/routes/maintenance/schedules/index/edit.js +2 -2
  82. package/addon/routes/maintenance/schedules/index/new.js +15 -1
  83. package/addon/routes/maintenance/schedules.js +15 -1
  84. package/addon/routes/maintenance/work-orders/index/details.js +2 -2
  85. package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
  86. package/addon/routes/maintenance/work-orders/index/new.js +14 -1
  87. package/addon/routes/maintenance/work-orders.js +14 -1
  88. package/addon/routes/management/fuel-transactions/index/details.js +9 -0
  89. package/addon/routes/management/fuel-transactions.js +14 -1
  90. package/addon/routes/management/index.js +13 -0
  91. package/addon/routes/management/trailers.js +16 -0
  92. package/addon/routes/management/vendors/integrated/details.js +14 -1
  93. package/addon/routes/management/vendors/integrated/edit.js +14 -1
  94. package/addon/routes/management/vendors/integrated/new.js +14 -1
  95. package/addon/routes/management/vendors/integrated.js +14 -1
  96. package/addon/routes/operations/orders.js +40 -1
  97. package/addon/routes/operations/routes/index/details.js +14 -1
  98. package/addon/routes/operations/routes/index/new.js +13 -0
  99. package/addon/routes/operations/routes.js +14 -1
  100. package/addon/routes/operations/service-rates/index/new.js +11 -0
  101. package/addon/routes/operations/service-rates.js +14 -1
  102. package/addon/routes/settings/avatars.js +14 -1
  103. package/addon/routes/settings/custom-fields.js +14 -1
  104. package/addon/routes/settings/map.js +14 -1
  105. package/addon/routes/settings/notifications.js +11 -0
  106. package/addon/routes/settings/orchestrator.js +1 -1
  107. package/addon/routes/settings/payments/index.js +1 -1
  108. package/addon/routes/settings/routing.js +14 -1
  109. package/addon/routes/settings/scheduling.js +14 -1
  110. package/addon/routes/settings/telematics.js +21 -0
  111. package/addon/routes/virtual.js +11 -0
  112. package/addon/routes.js +1 -0
  113. package/addon/templates/settings/telematics.hbs +70 -0
  114. package/app/components/admin/telematics-settings.js +1 -0
  115. package/app/components/map/marker-card/driver.js +1 -0
  116. package/app/components/map/marker-card/vehicle.js +1 -0
  117. package/app/controllers/settings/telematics.js +1 -0
  118. package/app/routes/settings/telematics.js +1 -0
  119. package/app/templates/settings/telematics.js +1 -0
  120. package/composer.json +2 -2
  121. package/docs/AFAQY.md +1 -1
  122. package/docs/TELEMATICS_QUEUES.md +1 -1
  123. package/docs/TELEMETRY_ARCHITECTURE.md +72 -1
  124. package/extension.json +1 -1
  125. package/package.json +1 -1
  126. package/server/config/telemetry.php +12 -0
  127. package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
  128. package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
  129. package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
  130. package/server/src/Auth/Schemas/FleetOps.php +104 -5
  131. package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
  132. package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
  133. package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
  134. package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +9 -0
  135. package/server/src/Http/Controllers/FleetOpsController.php +21 -0
  136. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
  137. package/server/src/Http/Controllers/Internal/v1/ContactController.php +11 -0
  138. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +14 -0
  139. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
  140. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
  141. package/server/src/Http/Controllers/Internal/v1/DriverController.php +22 -0
  142. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
  143. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
  144. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
  145. package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
  146. package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
  147. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
  148. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
  149. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
  150. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
  151. package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
  152. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
  153. package/server/src/Http/Controllers/Internal/v1/OrderController.php +22 -0
  154. package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
  155. package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
  156. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
  157. package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
  158. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
  159. package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
  160. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
  161. package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
  162. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
  163. package/server/src/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
  164. package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
  165. package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
  166. package/server/src/Mail/CustomerCredentialsMail.php +1 -1
  167. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
  168. package/server/src/Providers/FleetOpsServiceProvider.php +3 -0
  169. package/server/src/Support/Analytics/LiveFleet.php +18 -2
  170. package/server/src/Support/Authorization.php +103 -0
  171. package/server/src/Support/Database/TableIndexes.php +66 -0
  172. package/server/src/Support/OrderTracker.php +2 -2
  173. package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
  174. package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
  175. package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
  176. package/server/src/Support/Telematics/TelematicService.php +12 -2
  177. package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
  178. package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
  179. package/server/src/Tracking/TrackingOptions.php +2 -2
  180. package/server/src/Traits/AuthorizesMethods.php +37 -0
  181. package/server/src/routes.php +9 -1
  182. package/server/tests/AnalyticsRoutesTest.php +29 -2
  183. package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +16 -1
  184. package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
  185. package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
  186. package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
  187. package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
  188. package/server/tests/NavigatorControllerContractsTest.php +36 -4
  189. package/server/tests/NotificationAndMailContractsTest.php +31 -0
  190. package/server/tests/ObserverContractsTest.php +1 -1
  191. package/server/tests/OrchestratorConsumableApiTest.php +42 -0
  192. package/server/tests/ProviderContractsTest.php +10 -0
  193. package/server/tests/ReportSchemaContractsTest.php +120 -6
  194. package/server/tests/SettingControllerContractsTest.php +429 -18
  195. package/server/tests/SmallControllerContractsTest.php +8 -5
  196. package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
  197. package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
  198. package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
  199. package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
  200. package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
  201. package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
  202. package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +6 -0
  203. package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
  204. package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
  205. package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
  206. package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
  207. package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
  208. package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
  209. package/translations/en-us.yaml +48 -0
@@ -92,12 +92,14 @@ export default class ConnectivitySensorsIndexController extends Controller {
92
92
  type: 'primary',
93
93
  icon: 'plus',
94
94
  onClick: this.sensorActions.transition.create,
95
+ permission: 'fleet-ops create sensor',
95
96
  },
96
97
  {
97
98
  text: this.intl.t('common.import'),
98
99
  type: 'magic',
99
100
  icon: 'upload',
100
101
  onClick: this.sensorActions.import,
102
+ permission: 'fleet-ops import sensor',
101
103
  },
102
104
  {
103
105
  text: this.intl.t('common.export'),
@@ -105,6 +107,7 @@ export default class ConnectivitySensorsIndexController extends Controller {
105
107
  iconClass: 'rotate-icon-45',
106
108
  wrapperClass: 'hidden md:flex',
107
109
  onClick: this.sensorActions.export,
110
+ permission: 'fleet-ops export sensor',
108
111
  },
109
112
  ];
110
113
 
@@ -114,6 +117,7 @@ export default class ConnectivitySensorsIndexController extends Controller {
114
117
  label: 'Delete selected...',
115
118
  class: 'text-red-500',
116
119
  fn: this.sensorActions.bulkDelete,
120
+ permission: 'fleet-ops delete sensor',
117
121
  },
118
122
  ];
119
123
 
@@ -38,12 +38,14 @@ export default class ConnectivityTelematicsIndexController extends Controller {
38
38
  type: 'primary',
39
39
  icon: 'plus',
40
40
  onClick: this.telematicActions.transition.create,
41
+ permission: 'fleet-ops create telematic',
41
42
  },
42
43
  {
43
44
  text: this.intl.t('common.import'),
44
45
  type: 'magic',
45
46
  icon: 'upload',
46
47
  onClick: this.telematicActions.import,
48
+ permission: 'fleet-ops import telematic',
47
49
  },
48
50
  {
49
51
  text: this.intl.t('common.export'),
@@ -51,6 +53,7 @@ export default class ConnectivityTelematicsIndexController extends Controller {
51
53
  iconClass: 'rotate-icon-45',
52
54
  wrapperClass: 'hidden md:flex',
53
55
  onClick: this.telematicActions.export,
56
+ permission: 'fleet-ops export telematic',
54
57
  },
55
58
  ];
56
59
 
@@ -60,6 +63,7 @@ export default class ConnectivityTelematicsIndexController extends Controller {
60
63
  label: 'Delete selected...',
61
64
  class: 'text-red-500',
62
65
  fn: this.telematicActions.bulkDelete,
66
+ permission: 'fleet-ops delete telematic',
63
67
  },
64
68
  ];
65
69
 
@@ -46,14 +46,21 @@ export default class MaintenanceEquipmentIndexController extends Controller {
46
46
  helpText: 'Change the layout',
47
47
  },
48
48
  { icon: 'refresh', onClick: this.equipmentActions.refresh, helpText: this.intl.t('common.refresh') },
49
- { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.equipmentActions.transition.create },
50
- { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.equipmentActions.import },
51
- { text: this.intl.t('common.export'), icon: 'long-arrow-up', iconClass: 'rotate-icon-45', wrapperClass: 'hidden md:flex', onClick: this.equipmentActions.export },
49
+ { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.equipmentActions.transition.create, permission: 'fleet-ops create equipment' },
50
+ { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.equipmentActions.import, permission: 'fleet-ops import equipment' },
51
+ {
52
+ text: this.intl.t('common.export'),
53
+ icon: 'long-arrow-up',
54
+ iconClass: 'rotate-icon-45',
55
+ wrapperClass: 'hidden md:flex',
56
+ onClick: this.equipmentActions.export,
57
+ permission: 'fleet-ops export equipment',
58
+ },
52
59
  ];
53
60
  }
54
61
 
55
62
  get bulkActions() {
56
- return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.equipmentActions.bulkDelete }];
63
+ return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.equipmentActions.bulkDelete, permission: 'fleet-ops delete equipment' }];
57
64
  }
58
65
 
59
66
  get columns() {
@@ -17,12 +17,12 @@ export default class MaintenanceInspectionFormsIndexController extends Controlle
17
17
  get actionButtons() {
18
18
  return [
19
19
  { icon: 'refresh', onClick: this.inspectionFormActions.refresh, helpText: this.intl.t('common.refresh') },
20
- { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.inspectionFormActions.transition.create },
20
+ { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.inspectionFormActions.transition.create, permission: 'fleet-ops create inspection-form' },
21
21
  ];
22
22
  }
23
23
 
24
24
  get bulkActions() {
25
- return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.inspectionFormActions.bulkDelete }];
25
+ return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.inspectionFormActions.bulkDelete, permission: 'fleet-ops delete inspection-form' }];
26
26
  }
27
27
 
28
28
  get columns() {
@@ -23,12 +23,18 @@ export default class MaintenanceInspectionSubmissionsIndexController extends Con
23
23
  get actionButtons() {
24
24
  return [
25
25
  { icon: 'refresh', onClick: this.inspectionSubmissionActions.refresh, helpText: this.intl.t('common.refresh') },
26
- { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.inspectionSubmissionActions.transition.create },
26
+ {
27
+ text: this.intl.t('common.new'),
28
+ type: 'primary',
29
+ icon: 'plus',
30
+ onClick: this.inspectionSubmissionActions.transition.create,
31
+ permission: 'fleet-ops create inspection-submission',
32
+ },
27
33
  ];
28
34
  }
29
35
 
30
36
  get bulkActions() {
31
- return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.inspectionSubmissionActions.bulkDelete }];
37
+ return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.inspectionSubmissionActions.bulkDelete, permission: 'fleet-ops delete inspection-submission' }];
32
38
  }
33
39
 
34
40
  get columns() {
@@ -31,12 +31,14 @@ export default class MaintenanceMaintenancesIndexController extends Controller {
31
31
  type: 'primary',
32
32
  icon: 'plus',
33
33
  onClick: this.maintenanceActions.transition.create,
34
+ permission: 'fleet-ops create maintenance',
34
35
  },
35
36
  {
36
37
  text: this.intl.t('common.import'),
37
38
  type: 'magic',
38
39
  icon: 'upload',
39
40
  onClick: this.maintenanceActions.import,
41
+ permission: 'fleet-ops import maintenance',
40
42
  },
41
43
  {
42
44
  text: this.intl.t('common.export'),
@@ -44,6 +46,7 @@ export default class MaintenanceMaintenancesIndexController extends Controller {
44
46
  iconClass: 'rotate-icon-45',
45
47
  wrapperClass: 'hidden md:flex',
46
48
  onClick: this.maintenanceActions.export,
49
+ permission: 'fleet-ops export maintenance',
47
50
  },
48
51
  ];
49
52
  }
@@ -55,6 +58,7 @@ export default class MaintenanceMaintenancesIndexController extends Controller {
55
58
  label: 'Delete selected...',
56
59
  class: 'text-red-500',
57
60
  fn: this.maintenanceActions.bulkDelete,
61
+ permission: 'fleet-ops delete maintenance',
58
62
  },
59
63
  ];
60
64
  }
@@ -46,14 +46,21 @@ export default class MaintenancePartsIndexController extends Controller {
46
46
  helpText: 'Change the layout',
47
47
  },
48
48
  { icon: 'refresh', onClick: this.partActions.refresh, helpText: this.intl.t('common.refresh') },
49
- { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.partActions.transition.create },
50
- { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.partActions.import },
51
- { text: this.intl.t('common.export'), icon: 'long-arrow-up', iconClass: 'rotate-icon-45', wrapperClass: 'hidden md:flex', onClick: this.partActions.export },
49
+ { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.partActions.transition.create, permission: 'fleet-ops create part' },
50
+ { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.partActions.import, permission: 'fleet-ops import part' },
51
+ {
52
+ text: this.intl.t('common.export'),
53
+ icon: 'long-arrow-up',
54
+ iconClass: 'rotate-icon-45',
55
+ wrapperClass: 'hidden md:flex',
56
+ onClick: this.partActions.export,
57
+ permission: 'fleet-ops export part',
58
+ },
52
59
  ];
53
60
  }
54
61
 
55
62
  get bulkActions() {
56
- return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.partActions.bulkDelete }];
63
+ return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.partActions.bulkDelete, permission: 'fleet-ops delete part' }];
57
64
  }
58
65
 
59
66
  get columns() {
@@ -151,14 +151,27 @@ export default class MaintenanceSchedulesIndexController extends Controller {
151
151
  helpText: this.intl.t('common.change-layout'),
152
152
  },
153
153
  { icon: 'refresh', onClick: this.maintenanceScheduleActions.refresh, helpText: this.intl.t('common.refresh') },
154
- { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.maintenanceScheduleActions.transition.create },
155
- { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.maintenanceScheduleActions.import },
156
- { text: this.intl.t('common.export'), icon: 'long-arrow-up', iconClass: 'rotate-icon-45', wrapperClass: 'hidden md:flex', onClick: this.maintenanceScheduleActions.export },
154
+ {
155
+ text: this.intl.t('common.new'),
156
+ type: 'primary',
157
+ icon: 'plus',
158
+ onClick: this.maintenanceScheduleActions.transition.create,
159
+ permission: 'fleet-ops create maintenance-schedule',
160
+ },
161
+ { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.maintenanceScheduleActions.import, permission: 'fleet-ops import maintenance-schedule' },
162
+ {
163
+ text: this.intl.t('common.export'),
164
+ icon: 'long-arrow-up',
165
+ iconClass: 'rotate-icon-45',
166
+ wrapperClass: 'hidden md:flex',
167
+ onClick: this.maintenanceScheduleActions.export,
168
+ permission: 'fleet-ops export maintenance-schedule',
169
+ },
157
170
  ];
158
171
  }
159
172
 
160
173
  get bulkActions() {
161
- return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.maintenanceScheduleActions.bulkDelete }];
174
+ return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.maintenanceScheduleActions.bulkDelete, permission: 'fleet-ops delete maintenance-schedule' }];
162
175
  }
163
176
 
164
177
  get columns() {
@@ -21,14 +21,21 @@ export default class MaintenanceWorkOrdersIndexController extends Controller {
21
21
  get actionButtons() {
22
22
  return [
23
23
  { icon: 'refresh', onClick: this.workOrderActions.refresh, helpText: this.intl.t('common.refresh') },
24
- { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.workOrderActions.transition.create },
25
- { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.workOrderActions.import },
26
- { text: this.intl.t('common.export'), icon: 'long-arrow-up', iconClass: 'rotate-icon-45', wrapperClass: 'hidden md:flex', onClick: this.workOrderActions.export },
24
+ { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.workOrderActions.transition.create, permission: 'fleet-ops create work-order' },
25
+ { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.workOrderActions.import, permission: 'fleet-ops import work-order' },
26
+ {
27
+ text: this.intl.t('common.export'),
28
+ icon: 'long-arrow-up',
29
+ iconClass: 'rotate-icon-45',
30
+ wrapperClass: 'hidden md:flex',
31
+ onClick: this.workOrderActions.export,
32
+ permission: 'fleet-ops export work-order',
33
+ },
27
34
  ];
28
35
  }
29
36
 
30
37
  get bulkActions() {
31
- return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.workOrderActions.bulkDelete }];
38
+ return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.workOrderActions.bulkDelete, permission: 'fleet-ops delete work-order' }];
32
39
  }
33
40
 
34
41
  get columns() {
@@ -62,12 +62,14 @@ export default class ManagementContactsIndexController extends Controller {
62
62
  type: 'primary',
63
63
  icon: 'plus',
64
64
  onClick: this.contactActions.transition.create,
65
+ permission: 'fleet-ops create contact',
65
66
  },
66
67
  {
67
68
  text: this.intl.t('common.import'),
68
69
  type: 'magic',
69
70
  icon: 'upload',
70
71
  onClick: this.contactActions.import,
72
+ permission: 'fleet-ops import contact',
71
73
  },
72
74
  {
73
75
  text: this.intl.t('common.export'),
@@ -75,6 +77,7 @@ export default class ManagementContactsIndexController extends Controller {
75
77
  iconClass: 'rotate-icon-45',
76
78
  wrapperClass: 'hidden md:flex',
77
79
  onClick: this.contactActions.export,
80
+ permission: 'fleet-ops export contact',
78
81
  },
79
82
  ];
80
83
  }
@@ -88,6 +91,7 @@ export default class ManagementContactsIndexController extends Controller {
88
91
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
89
92
  class: 'text-red-500',
90
93
  fn: this.contactActions.bulkDelete,
94
+ permission: 'fleet-ops delete contact',
91
95
  },
92
96
  ];
93
97
  }
@@ -103,12 +103,14 @@ export default class ManagementDriversIndexController extends Controller {
103
103
  type: 'primary',
104
104
  icon: 'plus',
105
105
  onClick: this.driverActions.transition.create,
106
+ permission: 'fleet-ops create driver',
106
107
  },
107
108
  {
108
109
  text: this.intl.t('common.import'),
109
110
  type: 'magic',
110
111
  icon: 'upload',
111
112
  onClick: this.driverActions.import,
113
+ permission: 'fleet-ops import driver',
112
114
  },
113
115
  {
114
116
  text: this.intl.t('common.export'),
@@ -116,6 +118,7 @@ export default class ManagementDriversIndexController extends Controller {
116
118
  iconClass: 'rotate-icon-45',
117
119
  wrapperClass: 'hidden md:flex',
118
120
  onClick: this.driverActions.export,
121
+ permission: 'fleet-ops export driver',
119
122
  },
120
123
  ];
121
124
  }
@@ -129,6 +132,7 @@ export default class ManagementDriversIndexController extends Controller {
129
132
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
130
133
  class: 'text-red-500',
131
134
  fn: this.driverActions.bulkDelete,
135
+ permission: 'fleet-ops delete driver',
132
136
  },
133
137
  ];
134
138
  }
@@ -42,12 +42,14 @@ export default class ManagementFleetsIndexController extends Controller {
42
42
  type: 'primary',
43
43
  icon: 'plus',
44
44
  onClick: this.fleetActions.transition.create,
45
+ permission: 'fleet-ops create fleet',
45
46
  },
46
47
  {
47
48
  text: this.intl.t('common.import'),
48
49
  type: 'magic',
49
50
  icon: 'upload',
50
51
  onClick: this.fleetActions.import,
52
+ permission: 'fleet-ops import fleet',
51
53
  },
52
54
  {
53
55
  text: this.intl.t('common.export'),
@@ -55,6 +57,7 @@ export default class ManagementFleetsIndexController extends Controller {
55
57
  iconClass: 'rotate-icon-45',
56
58
  wrapperClass: 'hidden md:flex',
57
59
  onClick: this.fleetActions.export,
60
+ permission: 'fleet-ops export fleet',
58
61
  },
59
62
  ];
60
63
  }
@@ -68,6 +71,7 @@ export default class ManagementFleetsIndexController extends Controller {
68
71
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
69
72
  class: 'text-red-500',
70
73
  fn: this.fleetActions.bulkDelete,
74
+ permission: 'fleet-ops delete fleet',
71
75
  },
72
76
  ];
73
77
  }
@@ -56,12 +56,14 @@ export default class ManagementFuelReportsIndexController extends Controller {
56
56
  type: 'primary',
57
57
  icon: 'plus',
58
58
  onClick: this.fuelReportActions.transition.create,
59
+ permission: 'fleet-ops create fuel-report',
59
60
  },
60
61
  {
61
62
  text: this.intl.t('common.import'),
62
63
  type: 'magic',
63
64
  icon: 'upload',
64
65
  onClick: this.fuelReportActions.import,
66
+ permission: 'fleet-ops import fuel-report',
65
67
  },
66
68
  {
67
69
  text: this.intl.t('common.export'),
@@ -69,6 +71,7 @@ export default class ManagementFuelReportsIndexController extends Controller {
69
71
  iconClass: 'rotate-icon-45',
70
72
  wrapperClass: 'hidden md:flex',
71
73
  onClick: this.fuelReportActions.export,
74
+ permission: 'fleet-ops export fuel-report',
72
75
  },
73
76
  ];
74
77
  }
@@ -82,6 +85,7 @@ export default class ManagementFuelReportsIndexController extends Controller {
82
85
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
83
86
  class: 'text-red-500',
84
87
  fn: this.fuelReportActions.bulkDelete,
88
+ permission: 'fleet-ops delete fuel-report',
85
89
  },
86
90
  ];
87
91
  }
@@ -71,6 +71,7 @@ export default class ManagementFuelTransactionsIndexController extends Controlle
71
71
  icon: 'gas-pump',
72
72
  text: 'Fuel Integrations',
73
73
  onClick: () => this.hostRouter.transitionTo('console.fleet-ops.connectivity.fuel-providers.index'),
74
+ permission: 'fleet-ops list fuel-provider-connection',
74
75
  },
75
76
  ];
76
77
  }
@@ -86,6 +87,7 @@ export default class ManagementFuelTransactionsIndexController extends Controlle
86
87
  {
87
88
  label: `Reprocess ${selected.length} selected`,
88
89
  fn: () => this.confirmAction('reprocess', selected),
90
+ permission: 'fleet-ops update fuel-provider-transaction',
89
91
  },
90
92
  ];
91
93
  }
@@ -196,14 +198,14 @@ export default class ManagementFuelTransactionsIndexController extends Controlle
196
198
  sticky: 'right',
197
199
  width: 60,
198
200
  actions: [
199
- { label: 'Review Details', fn: this.openDetails },
200
- { label: 'Open Fuel Report', fn: this.openFuelReport, isVisible: (transaction) => Boolean(transaction.fuel_report_id) },
201
+ { label: 'Review Details', fn: this.openDetails, permission: 'fleet-ops view fuel-provider-transaction' },
202
+ { label: 'Open Fuel Report', fn: this.openFuelReport, isVisible: (transaction) => Boolean(transaction.fuel_report_id), permission: 'fleet-ops view fuel-report' },
201
203
  { separator: true },
202
- { label: 'Match to Vehicle', fn: this.matchVehicle },
203
- { label: 'Match to Order', fn: this.matchOrder },
204
- { label: 'Reprocess / Rematch', fn: (transaction) => this.confirmAction('reprocess', transaction) },
205
- { label: 'Ignore Transaction', fn: (transaction) => this.confirmAction('ignored', transaction) },
206
- { label: 'Mark Reviewed', fn: (transaction) => this.confirmAction('reviewed', transaction) },
204
+ { label: 'Match to Vehicle', fn: this.matchVehicle, permission: 'fleet-ops update fuel-provider-transaction' },
205
+ { label: 'Match to Order', fn: this.matchOrder, permission: 'fleet-ops update fuel-provider-transaction' },
206
+ { label: 'Reprocess / Rematch', fn: (transaction) => this.confirmAction('reprocess', transaction), permission: 'fleet-ops update fuel-provider-transaction' },
207
+ { label: 'Ignore Transaction', fn: (transaction) => this.confirmAction('ignored', transaction), permission: 'fleet-ops review fuel-provider-transaction' },
208
+ { label: 'Mark Reviewed', fn: (transaction) => this.confirmAction('reviewed', transaction), permission: 'fleet-ops review fuel-provider-transaction' },
207
209
  ],
208
210
  sortable: false,
209
211
  filterable: false,
@@ -56,12 +56,14 @@ export default class ManagementIssuesIndexController extends Controller {
56
56
  type: 'primary',
57
57
  icon: 'plus',
58
58
  onClick: this.issueActions.transition.create,
59
+ permission: 'fleet-ops create issue',
59
60
  },
60
61
  {
61
62
  text: this.intl.t('common.import'),
62
63
  type: 'magic',
63
64
  icon: 'upload',
64
65
  onClick: this.issueActions.import,
66
+ permission: 'fleet-ops import issue',
65
67
  },
66
68
  {
67
69
  text: this.intl.t('common.export'),
@@ -69,6 +71,7 @@ export default class ManagementIssuesIndexController extends Controller {
69
71
  iconClass: 'rotate-icon-45',
70
72
  wrapperClass: 'hidden md:flex',
71
73
  onClick: this.issueActions.export,
74
+ permission: 'fleet-ops export issue',
72
75
  },
73
76
  ];
74
77
  }
@@ -82,6 +85,7 @@ export default class ManagementIssuesIndexController extends Controller {
82
85
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
83
86
  class: 'text-red-500',
84
87
  fn: this.issueActions.bulkDelete,
88
+ permission: 'fleet-ops delete issue',
85
89
  },
86
90
  ];
87
91
  }
@@ -34,12 +34,14 @@ export default class ManagementPlacesIndexController extends Controller {
34
34
  type: 'primary',
35
35
  icon: 'plus',
36
36
  onClick: this.placeActions.transition.create,
37
+ permission: 'fleet-ops create place',
37
38
  },
38
39
  {
39
40
  text: this.intl.t('common.import'),
40
41
  type: 'magic',
41
42
  icon: 'upload',
42
43
  onClick: this.placeActions.import,
44
+ permission: 'fleet-ops import place',
43
45
  },
44
46
  {
45
47
  text: this.intl.t('common.export'),
@@ -47,6 +49,7 @@ export default class ManagementPlacesIndexController extends Controller {
47
49
  iconClass: 'rotate-icon-45',
48
50
  wrapperClass: 'hidden md:flex',
49
51
  onClick: this.placeActions.export,
52
+ permission: 'fleet-ops export place',
50
53
  },
51
54
  ];
52
55
  }
@@ -60,6 +63,7 @@ export default class ManagementPlacesIndexController extends Controller {
60
63
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
61
64
  class: 'text-red-500',
62
65
  fn: this.placeActions.bulkDelete,
66
+ permission: 'fleet-ops delete place',
63
67
  },
64
68
  ];
65
69
  }
@@ -102,12 +102,14 @@ export default class ManagementVehiclesIndexController extends Controller {
102
102
  type: 'primary',
103
103
  icon: 'plus',
104
104
  onClick: this.vehicleActions.transition.create,
105
+ permission: 'fleet-ops create vehicle',
105
106
  },
106
107
  {
107
108
  text: this.intl.t('common.import'),
108
109
  type: 'magic',
109
110
  icon: 'upload',
110
111
  onClick: this.vehicleActions.import,
112
+ permission: 'fleet-ops import vehicle',
111
113
  },
112
114
  {
113
115
  text: this.intl.t('common.export'),
@@ -115,6 +117,7 @@ export default class ManagementVehiclesIndexController extends Controller {
115
117
  iconClass: 'rotate-icon-45',
116
118
  wrapperClass: 'hidden md:flex',
117
119
  onClick: this.vehicleActions.export,
120
+ permission: 'fleet-ops export vehicle',
118
121
  },
119
122
  ];
120
123
  }
@@ -128,6 +131,7 @@ export default class ManagementVehiclesIndexController extends Controller {
128
131
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
129
132
  class: 'text-red-500',
130
133
  fn: this.vehicleActions.bulkDelete,
134
+ permission: 'fleet-ops delete vehicle',
131
135
  },
132
136
  ];
133
137
  }
@@ -83,12 +83,14 @@ export default class ManagementVendorsIndexController extends Controller {
83
83
  type: 'primary',
84
84
  icon: 'plus',
85
85
  onClick: this.vendorActions.transition.create,
86
+ permission: 'fleet-ops create vendor',
86
87
  },
87
88
  {
88
89
  text: this.intl.t('common.import'),
89
90
  type: 'magic',
90
91
  icon: 'upload',
91
92
  onClick: this.vendorActions.import,
93
+ permission: 'fleet-ops import vendor',
92
94
  },
93
95
  {
94
96
  text: this.intl.t('common.export'),
@@ -96,6 +98,7 @@ export default class ManagementVendorsIndexController extends Controller {
96
98
  iconClass: 'rotate-icon-45',
97
99
  wrapperClass: 'hidden md:flex',
98
100
  onClick: this.vendorActions.export,
101
+ permission: 'fleet-ops export vendor',
99
102
  },
100
103
  ];
101
104
  }
@@ -109,6 +112,7 @@ export default class ManagementVendorsIndexController extends Controller {
109
112
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
110
113
  class: 'text-red-500',
111
114
  fn: this.vendorActions.bulkDelete,
115
+ permission: 'fleet-ops delete vendor',
112
116
  },
113
117
  ];
114
118
  }
@@ -17,6 +17,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
17
17
  @service hostRouter;
18
18
  @service universe;
19
19
  @service sidebar;
20
+ @service abilities;
20
21
  @tracked routingControl;
21
22
  @tracked routingCompleted = false;
22
23
  @tracked realtimeOrderPublicId = null;
@@ -90,12 +91,14 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
90
91
  {
91
92
  text: 'Edit details',
92
93
  icon: 'pencil',
94
+ permission: 'fleet-ops update order',
93
95
  disabled: this.model.status === 'canceled',
94
96
  fn: () => this.orderActions.editOrderDetails(this.model),
95
97
  },
96
98
  {
97
99
  text: 'Update activity',
98
100
  icon: 'signal',
101
+ permission: 'fleet-ops update order',
99
102
  disabled: this.model.status === 'canceled',
100
103
  fn: () =>
101
104
  this.orderActions.updateActivity(this.model, {
@@ -105,12 +108,14 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
105
108
  {
106
109
  text: this.model.has_driver_assigned ? 'Unassign driver' : 'Assign driver',
107
110
  icon: this.model.has_driver_assigned ? 'user-xmark' : 'edit',
111
+ permission: 'fleet-ops assign-driver-for order',
108
112
  disabled: this.model.has_driver_assigned ? !this.model.hasActiveStatus || !this.model.driver_assigned : !this.model.hasActiveStatus,
109
113
  fn: () => (this.model.has_driver_assigned ? this.orderActions.unassignDriver(this.model) : this.orderActions.assignDriver(this.model)),
110
114
  },
111
115
  {
112
116
  text: 'View order label',
113
117
  icon: 'file-invoice',
118
+ permission: 'fleet-ops view order',
114
119
  fn: () => this.orderActions.viewLabel(this.model),
115
120
  },
116
121
  {
@@ -133,6 +138,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
133
138
  text: 'Cancel order',
134
139
  icon: 'ban',
135
140
  class: 'text-danger',
141
+ permission: 'fleet-ops cancel order',
136
142
  disabled: this.model.status === 'canceled',
137
143
  fn: () => this.orderActions.cancel(this.model),
138
144
  },
@@ -140,6 +146,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
140
146
  text: 'Delete order',
141
147
  icon: 'trash',
142
148
  class: 'text-danger',
149
+ permission: 'fleet-ops delete order',
143
150
  fn: () =>
144
151
  this.orderActions.delete(this.model, {
145
152
  taskOptions: {
@@ -151,7 +158,29 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
151
158
  },
152
159
  ].filter(Boolean),
153
160
  },
154
- ];
161
+ ].map((actionButton) => ({ ...actionButton, items: this.permittedMenuItems(actionButton.items) }));
162
+ }
163
+
164
+ /**
165
+ * The panel header dropdown renders `items` as-is and ignores `permission`, so items the
166
+ * user is not permitted to use are removed here, along with any separators left dangling.
167
+ */
168
+ permittedMenuItems(items = []) {
169
+ const permitted = items.filter((item) => !item.permission || this.abilities.can(item.permission));
170
+ const result = permitted.reduce((list, item) => {
171
+ if (item.separator && (list.length === 0 || list[list.length - 1].separator)) {
172
+ return list;
173
+ }
174
+
175
+ list.push(item);
176
+ return list;
177
+ }, []);
178
+
179
+ if (result.length && result[result.length - 1].separator) {
180
+ result.pop();
181
+ }
182
+
183
+ return result;
155
184
  }
156
185
 
157
186
  @action handleActivityModalFinish(options) {