@fleetbase/fleetops-engine 0.6.53 → 0.6.55

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 (212) hide show
  1. package/addon/components/admin/routing-settings.hbs +2 -1
  2. package/addon/components/cell/attached-vehicle.hbs +10 -0
  3. package/addon/components/cell/attached-vehicle.js +58 -0
  4. package/addon/components/cell/device-identity.hbs +24 -0
  5. package/addon/components/cell/device-identity.js +129 -0
  6. package/addon/components/cell/driver-identity.hbs +33 -0
  7. package/addon/components/cell/driver-identity.js +143 -0
  8. package/addon/components/cell/equipment-identity.hbs +5 -0
  9. package/addon/components/cell/equipment-identity.js +48 -0
  10. package/addon/components/cell/part-identity.hbs +5 -0
  11. package/addon/components/cell/part-identity.js +63 -0
  12. package/addon/components/cell/telematic-device.hbs +38 -0
  13. package/addon/components/cell/telematic-device.js +44 -0
  14. package/addon/components/cell/telematic-provider.hbs +33 -16
  15. package/addon/components/cell/telematic-provider.js +50 -8
  16. package/addon/components/cell/vehicle-identity.hbs +33 -0
  17. package/addon/components/cell/vehicle-identity.js +138 -0
  18. package/addon/components/device/details.hbs +106 -135
  19. package/addon/components/device/details.js +174 -1
  20. package/addon/components/device/form.hbs +2 -2
  21. package/addon/components/device/form.js +7 -0
  22. package/addon/components/device/panel-header.hbs +46 -21
  23. package/addon/components/device/panel-header.js +49 -1
  24. package/addon/components/device/panel-tabs/events.hbs +30 -0
  25. package/addon/components/device/panel-tabs/events.js +128 -0
  26. package/addon/components/device/panel-tabs/sensors.hbs +30 -0
  27. package/addon/components/device/panel-tabs/sensors.js +93 -0
  28. package/addon/components/device/panel-tabs/vehicle.hbs +68 -0
  29. package/addon/components/device/panel-tabs/vehicle.js +90 -0
  30. package/addon/components/equipment/form.js +8 -1
  31. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +76 -11
  32. package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
  33. package/addon/components/layout/fleet-ops-sidebar.js +101 -17
  34. package/addon/components/map/drawer/device-event-listing.js +2 -0
  35. package/addon/components/map/drawer/driver-listing.js +2 -0
  36. package/addon/components/map/drawer/place-listing.js +2 -0
  37. package/addon/components/map/drawer/position-listing.hbs +11 -6
  38. package/addon/components/map/drawer/position-listing.js +17 -4
  39. package/addon/components/map/drawer/vehicle-listing.js +2 -0
  40. package/addon/components/order/form/details.hbs +1 -43
  41. package/addon/components/order/form/details.js +18 -53
  42. package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
  43. package/addon/components/order/form/orchestrator-constraints.js +61 -0
  44. package/addon/components/order/form/payload.js +48 -2
  45. package/addon/components/order/form/route.js +15 -0
  46. package/addon/components/order/form/service-rate.hbs +62 -11
  47. package/addon/components/order/form/service-rate.js +124 -2
  48. package/addon/components/order/form.hbs +2 -0
  49. package/addon/components/sensor/panel-header.hbs +47 -15
  50. package/addon/components/sensor/panel-header.js +59 -1
  51. package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
  52. package/addon/components/table/cell/dropdown/action-item.js +62 -0
  53. package/addon/components/table/cell/dropdown.hbs +28 -0
  54. package/addon/components/table/cell/dropdown.js +91 -0
  55. package/addon/components/telematic/details.hbs +7 -1
  56. package/addon/components/telematic/form.js +16 -8
  57. package/addon/components/vendor/panel-header.hbs +82 -0
  58. package/addon/components/vendor/panel-header.js +48 -0
  59. package/addon/components/work-order/details.hbs +5 -1
  60. package/addon/components/work-order/form.hbs +26 -4
  61. package/addon/components/work-order/form.js +5 -1
  62. package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
  63. package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
  64. package/addon/controllers/connectivity/devices/index/details/vehicle.js +122 -0
  65. package/addon/controllers/connectivity/devices/index/details.js +12 -0
  66. package/addon/controllers/connectivity/devices/index.js +144 -129
  67. package/addon/controllers/connectivity/events/details.js +3 -0
  68. package/addon/controllers/connectivity/events/index.js +187 -11
  69. package/addon/controllers/connectivity/sensors/index.js +156 -14
  70. package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
  71. package/addon/controllers/connectivity/telematics/details/devices.js +170 -129
  72. package/addon/controllers/connectivity/telematics/details/events.js +39 -11
  73. package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
  74. package/addon/controllers/maintenance/equipment/index.js +1 -2
  75. package/addon/controllers/maintenance/parts/index.js +1 -2
  76. package/addon/controllers/maintenance/work-orders/index.js +18 -1
  77. package/addon/controllers/management/drivers/index.js +82 -42
  78. package/addon/controllers/management/fuel-reports/index.js +63 -4
  79. package/addon/controllers/management/issues/index.js +63 -4
  80. package/addon/controllers/management/vehicles/index.js +33 -6
  81. package/addon/routes/connectivity/devices/index/details/events.js +37 -1
  82. package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
  83. package/addon/routes/connectivity/devices/index/details/vehicle.js +29 -0
  84. package/addon/routes/connectivity/devices/index.js +7 -0
  85. package/addon/routes/connectivity/events/details.js +3 -0
  86. package/addon/routes/connectivity/events/index.js +6 -2
  87. package/addon/routes/connectivity/sensors/index.js +7 -2
  88. package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
  89. package/addon/routes/connectivity/telematics/details/devices.js +6 -0
  90. package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
  91. package/addon/routes/connectivity/telematics/details.js +1 -1
  92. package/addon/routes/connectivity/telematics/edit.js +1 -1
  93. package/addon/routes/maintenance/work-orders/index.js +1 -0
  94. package/addon/routes.js +2 -0
  95. package/addon/services/device-actions.js +30 -0
  96. package/addon/services/device-event-actions.js +1 -1
  97. package/addon/services/equipment-actions.js +1 -0
  98. package/addon/services/order-creation.js +54 -1
  99. package/addon/services/order-validation.js +3 -2
  100. package/addon/services/sensor-actions.js +3 -0
  101. package/addon/services/service-rate-actions.js +40 -1
  102. package/addon/services/vehicle-actions.js +24 -12
  103. package/addon/services/vendor-actions.js +3 -0
  104. package/addon/styles/fleetops-engine.css +28 -4
  105. package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
  106. package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
  107. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +147 -0
  108. package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
  109. package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
  110. package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
  111. package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
  112. package/addon/templates/management/vehicles/index/edit.hbs +1 -1
  113. package/addon/templates/management/vendors/index/details.hbs +2 -2
  114. package/addon/templates/settings/notifications.hbs +2 -1
  115. package/addon/templates/settings/routing.hbs +1 -0
  116. package/addon/utils/device-table-columns.js +288 -0
  117. package/addon/utils/fleet-ops-options.js +38 -0
  118. package/addon/utils/identity-cell-resource.js +20 -0
  119. package/addon/utils/map-drawer-dropdown-position.js +47 -0
  120. package/addon/utils/trackable-option.js +93 -0
  121. package/app/components/cell/attached-vehicle.js +1 -0
  122. package/app/components/cell/device-identity.js +1 -0
  123. package/app/components/cell/driver-identity.js +1 -0
  124. package/app/components/cell/equipment-identity.js +1 -0
  125. package/app/components/cell/part-identity.js +1 -0
  126. package/app/components/cell/telematic-device.js +1 -0
  127. package/app/components/cell/vehicle-identity.js +1 -0
  128. package/app/components/device/panel-tabs/events.hbs +30 -0
  129. package/app/components/device/panel-tabs/events.js +128 -0
  130. package/app/components/device/panel-tabs/sensors.hbs +30 -0
  131. package/app/components/device/panel-tabs/sensors.js +93 -0
  132. package/app/components/device/panel-tabs/vehicle.hbs +68 -0
  133. package/app/components/device/panel-tabs/vehicle.js +90 -0
  134. package/app/components/order/form/orchestrator-constraints.js +1 -0
  135. package/app/components/table/cell/dropdown/action-item.js +1 -0
  136. package/app/components/table/cell/dropdown.js +1 -0
  137. package/app/components/vendor/panel-header.js +1 -0
  138. package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
  139. package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
  140. package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
  141. package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
  142. package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
  143. package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
  144. package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
  145. package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
  146. package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
  147. package/app/utils/map-drawer-dropdown-position.js +1 -0
  148. package/app/utils/trackable-option.js +1 -0
  149. package/composer.json +1 -1
  150. package/extension.json +1 -1
  151. package/package.json +4 -4
  152. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
  153. package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
  154. package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
  155. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
  156. package/server/src/Console/Commands/SyncTelematics.php +89 -0
  157. package/server/src/Exceptions/TelematicProviderException.php +13 -2
  158. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
  159. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +55 -1
  160. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +17 -6
  161. package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
  162. package/server/src/Http/Controllers/Internal/v1/LiveController.php +178 -1
  163. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
  164. package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
  165. package/server/src/Http/Controllers/Internal/v1/SearchController.php +2 -2
  166. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
  167. package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
  168. package/server/src/Http/Filter/DeviceEventFilter.php +65 -0
  169. package/server/src/Http/Filter/DeviceFilter.php +80 -0
  170. package/server/src/Http/Filter/SensorFilter.php +125 -0
  171. package/server/src/Http/Filter/VehicleFilter.php +13 -0
  172. package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
  173. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
  174. package/server/src/Http/Resources/v1/Driver.php +11 -2
  175. package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
  176. package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
  177. package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
  178. package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
  179. package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
  180. package/server/src/Jobs/SyncTelematicDevicesJob.php +180 -123
  181. package/server/src/Models/Device.php +17 -1
  182. package/server/src/Models/DeviceEvent.php +82 -1
  183. package/server/src/Models/Driver.php +16 -0
  184. package/server/src/Models/Equipment.php +55 -0
  185. package/server/src/Models/Sensor.php +15 -2
  186. package/server/src/Models/Telematic.php +1 -1
  187. package/server/src/Models/Vehicle.php +5 -1
  188. package/server/src/Models/WorkOrder.php +5 -2
  189. package/server/src/Observers/DriverObserver.php +3 -3
  190. package/server/src/Observers/FleetObserver.php +23 -0
  191. package/server/src/Observers/VehicleObserver.php +3 -3
  192. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  193. package/server/src/Support/LiveCacheService.php +1 -1
  194. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +143 -20
  195. package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
  196. package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
  197. package/server/src/Support/Telematics/Providers/SafeeProvider.php +70 -5
  198. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
  199. package/server/src/Support/Telematics/TelematicService.php +330 -30
  200. package/server/src/routes.php +1 -1
  201. package/server/tests/DeviceFilterTest.php +45 -0
  202. package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
  203. package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
  204. package/server/tests/OperationsMonitorEndpointTest.php +38 -0
  205. package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
  206. package/server/tests/ServiceRateRouteValidationTest.php +101 -0
  207. package/server/tests/TelematicsHardeningTest.php +598 -6
  208. package/server/tests/VehicleDriverAssignmentTest.php +27 -0
  209. package/translations/en-us.yaml +1 -0
  210. package/addon/controllers/connectivity/events/index/details.js +0 -3
  211. package/addon/routes/connectivity/events/index/details.js +0 -3
  212. /package/addon/templates/connectivity/events/{index/details.hbs → details.hbs} +0 -0
@@ -0,0 +1,82 @@
1
+ <div class="px-4 py-3" ...attributes>
2
+ <div class="flex flex-1 flex-row items-start justify-between gap-4">
3
+ <div class="flex min-w-0 flex-row items-start gap-3">
4
+ <div class="relative flex flex-shrink-0 items-start justify-start">
5
+ <Image
6
+ src={{this.imageUrl}}
7
+ @fallbackSrc={{config "defaultValues.vendorImage"}}
8
+ alt={{this.name}}
9
+ height="48"
10
+ width="48"
11
+ class="h-14 w-14 rounded-md border border-gray-200 object-cover shadow-sm dark:border-gray-700"
12
+ />
13
+ <Attach::Tooltip @class="clean" @animation="scale" @placement="top">
14
+ <InputInfo @text={{this.name}} />
15
+ </Attach::Tooltip>
16
+ </div>
17
+ <div class="flex min-w-0 flex-col">
18
+ <div class="flex min-w-0 flex-wrap items-center gap-2">
19
+ <h1 class="truncate text-base font-semibold leading-5 text-gray-900 dark:text-gray-100">{{this.name}}</h1>
20
+ <Badge @status={{this.status}} @size="xs">{{smart-humanize this.status}}</Badge>
21
+ </div>
22
+
23
+ <div class="mt-1 flex max-w-2xl flex-wrap items-center gap-1.5">
24
+ {{#if this.businessId}}
25
+ <span class="inline-flex max-w-[12rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
26
+ <FaIcon @icon="id-card" class="text-gray-400 dark:text-gray-300" />
27
+ <span class="truncate">{{this.businessId}}</span>
28
+ </span>
29
+ {{/if}}
30
+ {{#if this.type}}
31
+ <span class="inline-flex max-w-[10rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
32
+ <FaIcon @icon="building" class="text-gray-400 dark:text-gray-300" />
33
+ <span class="truncate">{{smart-humanize this.type}}</span>
34
+ </span>
35
+ {{/if}}
36
+ {{#if this.email}}
37
+ <span class="inline-flex max-w-[14rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
38
+ <FaIcon @icon="envelope" class="text-gray-400 dark:text-gray-300" />
39
+ <span class="truncate">{{this.email}}</span>
40
+ </span>
41
+ {{/if}}
42
+ {{#if this.phone}}
43
+ <span class="inline-flex max-w-[10rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
44
+ <FaIcon @icon="phone" class="text-gray-400 dark:text-gray-300" />
45
+ <span class="truncate">{{this.phone}}</span>
46
+ </span>
47
+ {{/if}}
48
+ {{#if this.country}}
49
+ <span class="inline-flex max-w-[10rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
50
+ <FaIcon @icon="globe" class="text-gray-400 dark:text-gray-300" />
51
+ <span class="truncate"><CountryName @country={{this.country}} @showFlag={{true}} /></span>
52
+ </span>
53
+ {{/if}}
54
+ {{#if this.address}}
55
+ <span class="inline-flex max-w-[16rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
56
+ <FaIcon @icon="location-dot" class="text-gray-400 dark:text-gray-300" />
57
+ <span class="truncate">{{this.address}}</span>
58
+ </span>
59
+ {{/if}}
60
+ {{#if this.website}}
61
+ <span class="inline-flex max-w-[14rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
62
+ <FaIcon @icon="link" class="text-gray-400 dark:text-gray-300" />
63
+ <span class="truncate">{{this.website}}</span>
64
+ </span>
65
+ {{/if}}
66
+ </div>
67
+ </div>
68
+ </div>
69
+ <div class="next-view-header-right flex-shrink-0">
70
+ <Layout::Resource::Panel::HeaderActions
71
+ @resource={{@resource}}
72
+ @saveTask={{@saveTask}}
73
+ @saveOptions={{@saveOptions}}
74
+ @saveDisabled={{@saveDisabled}}
75
+ @pojoResource={{@pojoResource}}
76
+ @authSchema={{@authSchema}}
77
+ @actionButtons={{@actionButtons}}
78
+ @onPressCancel={{@onPressCancel}}
79
+ />
80
+ </div>
81
+ </div>
82
+ </div>
@@ -0,0 +1,48 @@
1
+ import Component from '@glimmer/component';
2
+ import { get } from '@ember/object';
3
+
4
+ export default class VendorPanelHeaderComponent extends Component {
5
+ get resource() {
6
+ return this.args.resource;
7
+ }
8
+
9
+ get imageUrl() {
10
+ return get(this.resource, 'photo_url') ?? get(this.resource, 'logo_url');
11
+ }
12
+
13
+ get name() {
14
+ return get(this.resource, 'name') ?? get(this.resource, 'displayName') ?? get(this.resource, 'business_id') ?? get(this.resource, 'public_id') ?? '-';
15
+ }
16
+
17
+ get status() {
18
+ return get(this.resource, 'status') ?? 'active';
19
+ }
20
+
21
+ get businessId() {
22
+ return get(this.resource, 'business_id') ?? get(this.resource, 'internal_id') ?? get(this.resource, 'public_id');
23
+ }
24
+
25
+ get type() {
26
+ return get(this.resource, 'type');
27
+ }
28
+
29
+ get email() {
30
+ return get(this.resource, 'email');
31
+ }
32
+
33
+ get phone() {
34
+ return get(this.resource, 'phone');
35
+ }
36
+
37
+ get country() {
38
+ return get(this.resource, 'country');
39
+ }
40
+
41
+ get address() {
42
+ return get(this.resource, 'address_street') ?? get(this.resource, 'address');
43
+ }
44
+
45
+ get website() {
46
+ return get(this.resource, 'website_url');
47
+ }
48
+ }
@@ -20,6 +20,10 @@
20
20
  <div class="field-name">Subject</div>
21
21
  <div class="field-value">{{n-a @resource.subject}}</div>
22
22
  </div>
23
+ <div class="field-info-container col-span-2">
24
+ <div class="field-name">Category</div>
25
+ <div class="field-value">{{n-a (smart-humanize @resource.category)}}</div>
26
+ </div>
23
27
 
24
28
  {{! — Status & Priority — }}
25
29
  <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
@@ -185,4 +189,4 @@
185
189
  <CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
186
190
 
187
191
  <Spacer @height="200px" />
188
- </div>
192
+ </div>
@@ -13,6 +13,27 @@
13
13
  <Input @value={{@resource.subject}} @type="text" class="w-full form-input" placeholder="Brief description of the work order" disabled={{cannot-write @resource}} />
14
14
  </InputGroup>
15
15
 
16
+ {{! Work Classification }}
17
+ <div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
18
+ Work Order Classification
19
+ </div>
20
+ <InputGroup @name="Category" @wrapperClass="col-span-1 lg:col-span-2">
21
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
22
+ <PowerSelect
23
+ @options={{this.categoryOptions}}
24
+ @selected={{find-by "value" @resource.category this.categoryOptions}}
25
+ @onChange={{set-model-attr @resource "category"}}
26
+ @placeholder="Select Category"
27
+ @triggerClass="form-select form-input"
28
+ @disabled={{cannot-write @resource}}
29
+ as |category|
30
+ >
31
+ <div class="font-semibold normalize-in-trigger">{{category.label}}</div>
32
+ <div class="hide-from-trigger">{{category.description}}</div>
33
+ </PowerSelect>
34
+ </div>
35
+ </InputGroup>
36
+
16
37
  {{! Status & Priority }}
17
38
  <div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
18
39
  Status & Priority
@@ -21,14 +42,15 @@
21
42
  <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
22
43
  <PowerSelect
23
44
  @options={{this.statusOptions}}
24
- @selected={{@resource.status}}
25
- @onChange={{fn (mut @resource.status)}}
45
+ @selected={{find-by "value" @resource.status this.statusOptions}}
46
+ @onChange={{set-model-attr @resource "status"}}
26
47
  @placeholder="Select Status"
27
48
  @triggerClass="form-select form-input"
28
49
  @disabled={{cannot-write @resource}}
29
50
  as |status|
30
51
  >
31
- {{smart-humanize status}}
52
+ <div class="font-semibold normalize-in-trigger">{{status.label}}</div>
53
+ <div class="hide-from-trigger">{{status.description}}</div>
32
54
  </PowerSelect>
33
55
  </div>
34
56
  </InputGroup>
@@ -243,4 +265,4 @@
243
265
  <ContentPanel @title="Metadata" @open={{false}} @wrapperClass="bordered-top">
244
266
  <MetadataEditor @value={{@resource.meta}} @onChange={{fn (mut @resource.meta)}} />
245
267
  </ContentPanel>
246
- </div>
268
+ </div>
@@ -1,6 +1,7 @@
1
1
  import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { action } from '@ember/object';
4
+ import { workOrderCategories, workOrderStatuses } from '../../utils/fleet-ops-options';
4
5
 
5
6
  /**
6
7
  * Maps a user-facing polymorphic type string to the Ember Data model name
@@ -42,7 +43,10 @@ const ASSIGNEE_MODEL_TO_TYPE = {
42
43
 
43
44
  export default class WorkOrderFormComponent extends Component {
44
45
  /** Status options for work orders. */
45
- statusOptions = ['open', 'in_progress', 'on_hold', 'completed', 'cancelled'];
46
+ statusOptions = workOrderStatuses;
47
+
48
+ /** Operational category options for work orders. */
49
+ categoryOptions = workOrderCategories;
46
50
 
47
51
  /** Priority options for work orders. */
48
52
  priorityOptions = ['low', 'medium', 'high', 'critical'];
@@ -1,3 +1,246 @@
1
1
  import Controller from '@ember/controller';
2
+ import { action } from '@ember/object';
3
+ import { inject as service } from '@ember/service';
4
+ import { tracked } from '@glimmer/tracking';
5
+ import { task, timeout } from 'ember-concurrency';
2
6
 
3
- export default class ConnectivityDevicesIndexDetailsEventsController extends Controller {}
7
+ const severityOptions = [
8
+ { label: 'Info', value: 'info' },
9
+ { label: 'Warning', value: 'warning' },
10
+ { label: 'Error', value: 'error' },
11
+ { label: 'Critical', value: 'critical' },
12
+ { label: 'High', value: 'high' },
13
+ ];
14
+
15
+ const warningSeverities = ['warning', 'error', 'critical', 'high'];
16
+
17
+ export default class ConnectivityDevicesIndexDetailsEventsController extends Controller {
18
+ @service deviceEventActions;
19
+ @service hostRouter;
20
+ @service intl;
21
+
22
+ @tracked queryParams = [
23
+ 'events_page',
24
+ 'events_limit',
25
+ 'events_sort',
26
+ 'events_query',
27
+ 'events_event_type',
28
+ 'events_severity',
29
+ 'events_processed',
30
+ 'events_occurred_at',
31
+ 'events_created_at',
32
+ ];
33
+ @tracked device;
34
+ @tracked events_page = 1;
35
+ @tracked events_limit;
36
+ @tracked events_sort = '-created_at';
37
+ @tracked events_query;
38
+ @tracked events_event_type;
39
+ @tracked events_severity;
40
+ @tracked events_processed;
41
+ @tracked events_occurred_at;
42
+ @tracked events_created_at;
43
+
44
+ @tracked bulkActions = [];
45
+
46
+ get events() {
47
+ return Array.from(this.model ?? []);
48
+ }
49
+
50
+ get totalEvents() {
51
+ return this.model?.meta?.total ?? this.events.length;
52
+ }
53
+
54
+ get warningEventsCount() {
55
+ return this.events.filter((event) => warningSeverities.includes(String(event.severity ?? '').toLowerCase())).length;
56
+ }
57
+
58
+ get unprocessedEventsCount() {
59
+ return this.events.filter((event) => !event.processed_at && !event.is_processed).length;
60
+ }
61
+
62
+ get processedEventsCount() {
63
+ return Math.max(this.events.length - this.unprocessedEventsCount, 0);
64
+ }
65
+
66
+ get hasHealthyEventState() {
67
+ return this.events.length > 0 && this.warningEventsCount === 0 && this.unprocessedEventsCount === 0 && !this.hasActiveFilters;
68
+ }
69
+
70
+ get hasActiveFilters() {
71
+ return Boolean(this.events_query || this.events_event_type || this.events_severity || this.events_processed || this.events_occurred_at || this.events_created_at);
72
+ }
73
+
74
+ get metrics() {
75
+ return [
76
+ { label: 'Recent events', value: this.totalEvents, icon: 'list', accentClass: 'fleetops-connectivity-kpi-accent-blue' },
77
+ {
78
+ label: 'Warnings',
79
+ value: this.warningEventsCount,
80
+ icon: 'triangle-exclamation',
81
+ accentClass: this.warningEventsCount ? 'fleetops-connectivity-kpi-accent-amber' : 'fleetops-connectivity-kpi-accent-green',
82
+ },
83
+ {
84
+ label: 'Unprocessed',
85
+ value: this.unprocessedEventsCount,
86
+ icon: 'clock',
87
+ accentClass: this.unprocessedEventsCount ? 'fleetops-connectivity-kpi-accent-rose' : 'fleetops-connectivity-kpi-accent-green',
88
+ },
89
+ { label: 'Processed', value: this.processedEventsCount, icon: 'check', accentClass: 'fleetops-connectivity-kpi-accent-green' },
90
+ ];
91
+ }
92
+
93
+ get actionButtons() {
94
+ return [
95
+ {
96
+ icon: 'refresh',
97
+ size: 'sm',
98
+ onClick: this.refresh,
99
+ helpText: this.intl.t('common.refresh'),
100
+ wrapperClass: 'fleetops-telematics-action-button',
101
+ isLoading: this.refreshTask.isRunning,
102
+ disabled: this.refreshTask.isRunning,
103
+ },
104
+ ];
105
+ }
106
+
107
+ get columns() {
108
+ return [
109
+ {
110
+ sticky: true,
111
+ label: 'Event',
112
+ valuePath: 'event_type',
113
+ cellComponent: 'table/cell/anchor',
114
+ action: this.deviceEventActions.transition.view,
115
+ permission: 'fleet-ops view device-event',
116
+ resizable: true,
117
+ sortable: true,
118
+ filterable: true,
119
+ filterParam: 'events_event_type',
120
+ filterComponent: 'filter/string',
121
+ },
122
+ {
123
+ label: 'Severity',
124
+ valuePath: 'severity',
125
+ cellComponent: 'table/cell/status',
126
+ resizable: true,
127
+ sortable: true,
128
+ filterable: true,
129
+ filterParam: 'events_severity',
130
+ filterComponent: 'filter/multi-option',
131
+ filterOptions: severityOptions,
132
+ filterOptionLabel: 'label',
133
+ filterOptionValue: 'value',
134
+ },
135
+ {
136
+ label: 'Message',
137
+ valuePath: 'message',
138
+ resizable: true,
139
+ sortable: false,
140
+ },
141
+ {
142
+ label: 'Code',
143
+ valuePath: 'code',
144
+ resizable: true,
145
+ sortable: true,
146
+ },
147
+ {
148
+ label: 'Processed',
149
+ valuePath: 'processedAt',
150
+ sortParam: 'processed_at',
151
+ resizable: true,
152
+ sortable: true,
153
+ filterable: true,
154
+ filterParam: 'events_processed',
155
+ filterComponent: 'filter/multi-option',
156
+ filterOptions: [
157
+ { label: 'Processed', value: 'processed' },
158
+ { label: 'Unprocessed', value: 'unprocessed' },
159
+ ],
160
+ filterOptionLabel: 'label',
161
+ filterOptionValue: 'value',
162
+ },
163
+ {
164
+ label: 'Occurred',
165
+ valuePath: 'occurredAt',
166
+ sortParam: 'occurred_at',
167
+ resizable: true,
168
+ sortable: true,
169
+ filterable: true,
170
+ filterParam: 'events_occurred_at',
171
+ filterComponent: 'filter/date',
172
+ },
173
+ {
174
+ label: this.intl.t('column.created-at'),
175
+ valuePath: 'createdAt',
176
+ sortParam: 'created_at',
177
+ resizable: true,
178
+ sortable: true,
179
+ filterable: true,
180
+ filterParam: 'events_created_at',
181
+ filterComponent: 'filter/date',
182
+ hidden: true,
183
+ },
184
+ {
185
+ label: '',
186
+ cellComponent: 'table/cell/dropdown',
187
+ ddButtonText: false,
188
+ ddButtonIcon: 'ellipsis-h',
189
+ ddButtonIconPrefix: 'fas',
190
+ ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.device-event') }),
191
+ cellClassNames: 'overflow-visible align-middle',
192
+ wrapperClass: 'flex items-center justify-end mx-2',
193
+ sticky: 'right',
194
+ width: 60,
195
+ actions: [
196
+ {
197
+ label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.device-event') }),
198
+ fn: this.deviceEventActions.transition.view,
199
+ permission: 'fleet-ops view device-event',
200
+ },
201
+ {
202
+ label: 'Mark processed',
203
+ fn: this.markProcessed,
204
+ permission: 'fleet-ops update device-event',
205
+ },
206
+ ],
207
+ sortable: false,
208
+ filterable: false,
209
+ resizable: false,
210
+ searchable: false,
211
+ },
212
+ ];
213
+ }
214
+
215
+ @action refresh() {
216
+ if (this.refreshTask.isRunning) {
217
+ return;
218
+ }
219
+
220
+ return this.refreshTask.perform();
221
+ }
222
+
223
+ @action async markProcessed(deviceEvent) {
224
+ await this.deviceEventActions.markProcessed(deviceEvent);
225
+ await this.hostRouter.refresh();
226
+ }
227
+
228
+ @task *refreshTask() {
229
+ yield this.hostRouter.refresh();
230
+ }
231
+
232
+ @task({ restartable: true }) *searchTask(event) {
233
+ const {
234
+ target: { value },
235
+ } = event;
236
+
237
+ if (!value) {
238
+ this.events_query = null;
239
+ return;
240
+ }
241
+
242
+ yield timeout(250);
243
+ if (this.events_page > 1) this.events_page = 1;
244
+ this.events_query = value;
245
+ }
246
+ }
@@ -0,0 +1,199 @@
1
+ import Controller from '@ember/controller';
2
+ import { action } from '@ember/object';
3
+ import { inject as service } from '@ember/service';
4
+ import { tracked } from '@glimmer/tracking';
5
+ import { task, timeout } from 'ember-concurrency';
6
+ import fleetOpsOptions from '../../../../../utils/fleet-ops-options';
7
+
8
+ export default class ConnectivityDevicesIndexDetailsSensorsController extends Controller {
9
+ @service sensorActions;
10
+ @service hostRouter;
11
+ @service intl;
12
+
13
+ @tracked queryParams = ['sensors_page', 'sensors_limit', 'sensors_sort', 'sensors_query', 'sensors_status', 'sensors_type', 'sensors_last_reading_at'];
14
+ @tracked device;
15
+ @tracked sensors_page = 1;
16
+ @tracked sensors_limit;
17
+ @tracked sensors_sort = '-updated_at';
18
+ @tracked sensors_query;
19
+ @tracked sensors_status;
20
+ @tracked sensors_type;
21
+ @tracked sensors_last_reading_at;
22
+
23
+ @tracked bulkActions = [];
24
+
25
+ get sensors() {
26
+ return Array.from(this.model ?? []);
27
+ }
28
+
29
+ get totalSensors() {
30
+ return this.model?.meta?.total ?? this.sensors.length;
31
+ }
32
+
33
+ get activeSensorsCount() {
34
+ return this.sensors.filter((sensor) => sensor.is_active || sensor.status === 'active' || sensor.status === 'online').length;
35
+ }
36
+
37
+ get reportingSensorsCount() {
38
+ return this.sensors.filter((sensor) => sensor.last_value || sensor.last_reading_at).length;
39
+ }
40
+
41
+ get outOfRangeSensorsCount() {
42
+ return this.sensors.filter((sensor) => ['out_of_range', 'above_maximum', 'below_minimum'].includes(sensor.threshold_status)).length;
43
+ }
44
+
45
+ get hasActiveFilters() {
46
+ return Boolean(this.sensors_query || this.sensors_status || this.sensors_type || this.sensors_last_reading_at);
47
+ }
48
+
49
+ get metrics() {
50
+ return [
51
+ { label: 'Sensors', value: this.totalSensors, icon: 'gauge-high', accentClass: 'fleetops-connectivity-kpi-accent-blue' },
52
+ { label: 'Active', value: this.activeSensorsCount, icon: 'signal', accentClass: 'fleetops-connectivity-kpi-accent-green' },
53
+ { label: 'Reporting', value: this.reportingSensorsCount, icon: 'chart-line', accentClass: 'fleetops-connectivity-kpi-accent-amber' },
54
+ {
55
+ label: 'Out of range',
56
+ value: this.outOfRangeSensorsCount,
57
+ icon: 'triangle-exclamation',
58
+ accentClass: this.outOfRangeSensorsCount ? 'fleetops-connectivity-kpi-accent-rose' : 'fleetops-connectivity-kpi-accent-green',
59
+ },
60
+ ];
61
+ }
62
+
63
+ get actionButtons() {
64
+ return [
65
+ {
66
+ icon: 'refresh',
67
+ size: 'sm',
68
+ onClick: this.refresh,
69
+ helpText: this.intl.t('common.refresh'),
70
+ wrapperClass: 'fleetops-telematics-action-button',
71
+ isLoading: this.refreshTask.isRunning,
72
+ disabled: this.refreshTask.isRunning,
73
+ },
74
+ ];
75
+ }
76
+
77
+ get columns() {
78
+ return [
79
+ {
80
+ sticky: true,
81
+ label: this.intl.t('column.name'),
82
+ valuePath: 'name',
83
+ cellComponent: 'table/cell/anchor',
84
+ action: this.sensorActions.transition.view,
85
+ permission: 'fleet-ops view sensor',
86
+ resizable: true,
87
+ sortable: true,
88
+ filterable: true,
89
+ filterParam: 'sensors_query',
90
+ filterComponent: 'filter/string',
91
+ },
92
+ {
93
+ label: 'Type',
94
+ valuePath: 'type',
95
+ resizable: true,
96
+ sortable: true,
97
+ filterable: true,
98
+ filterParam: 'sensors_type',
99
+ filterComponent: 'filter/multi-option',
100
+ filterOptions: fleetOpsOptions('sensorTypes'),
101
+ },
102
+ {
103
+ label: 'Value',
104
+ valuePath: 'last_value',
105
+ resizable: true,
106
+ sortable: true,
107
+ },
108
+ {
109
+ label: 'Unit',
110
+ valuePath: 'unit',
111
+ resizable: true,
112
+ sortable: true,
113
+ },
114
+ {
115
+ label: 'Threshold',
116
+ valuePath: 'threshold_status',
117
+ cellComponent: 'table/cell/status',
118
+ resizable: true,
119
+ sortable: true,
120
+ },
121
+ {
122
+ label: this.intl.t('column.status'),
123
+ valuePath: 'status',
124
+ cellComponent: 'table/cell/status',
125
+ resizable: true,
126
+ sortable: true,
127
+ filterable: true,
128
+ filterParam: 'sensors_status',
129
+ filterComponent: 'filter/multi-option',
130
+ filterOptions: fleetOpsOptions('sensorStatuses'),
131
+ },
132
+ {
133
+ label: 'Last Reading',
134
+ valuePath: 'lastReadingAt',
135
+ sortParam: 'last_reading_at',
136
+ resizable: true,
137
+ sortable: true,
138
+ filterable: true,
139
+ filterParam: 'sensors_last_reading_at',
140
+ filterComponent: 'filter/date',
141
+ },
142
+ {
143
+ label: '',
144
+ cellComponent: 'table/cell/dropdown',
145
+ ddButtonText: false,
146
+ ddButtonIcon: 'ellipsis-h',
147
+ ddButtonIconPrefix: 'fas',
148
+ ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.sensor') }),
149
+ cellClassNames: 'overflow-visible align-middle',
150
+ wrapperClass: 'flex items-center justify-end mx-2',
151
+ sticky: 'right',
152
+ width: 60,
153
+ actions: [
154
+ {
155
+ label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.sensor') }),
156
+ fn: this.sensorActions.transition.view,
157
+ permission: 'fleet-ops view sensor',
158
+ },
159
+ {
160
+ label: this.intl.t('common.edit-resource', { resource: this.intl.t('resource.sensor') }),
161
+ fn: this.sensorActions.transition.edit,
162
+ permission: 'fleet-ops update sensor',
163
+ },
164
+ ],
165
+ sortable: false,
166
+ filterable: false,
167
+ resizable: false,
168
+ searchable: false,
169
+ },
170
+ ];
171
+ }
172
+
173
+ @action refresh() {
174
+ if (this.refreshTask.isRunning) {
175
+ return;
176
+ }
177
+
178
+ return this.refreshTask.perform();
179
+ }
180
+
181
+ @task *refreshTask() {
182
+ yield this.hostRouter.refresh();
183
+ }
184
+
185
+ @task({ restartable: true }) *searchTask(event) {
186
+ const {
187
+ target: { value },
188
+ } = event;
189
+
190
+ if (!value) {
191
+ this.sensors_query = null;
192
+ return;
193
+ }
194
+
195
+ yield timeout(250);
196
+ if (this.sensors_page > 1) this.sensors_page = 1;
197
+ this.sensors_query = value;
198
+ }
199
+ }