@fleetbase/fleetops-engine 0.6.52 → 0.6.54

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 (151) hide show
  1. package/addon/components/device/details.hbs +106 -135
  2. package/addon/components/device/details.js +174 -1
  3. package/addon/components/device/panel-header.hbs +2 -2
  4. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
  5. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +654 -0
  6. package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
  7. package/addon/components/layout/fleet-ops-sidebar.js +364 -395
  8. package/addon/components/maintenance-schedule/form.js +1 -0
  9. package/addon/components/order/form/details.hbs +1 -43
  10. package/addon/components/order/form/details.js +18 -53
  11. package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
  12. package/addon/components/order/form/orchestrator-constraints.js +61 -0
  13. package/addon/components/order/form/payload.js +50 -2
  14. package/addon/components/order/form/route.js +15 -0
  15. package/addon/components/order/form/service-rate.hbs +62 -11
  16. package/addon/components/order/form/service-rate.js +124 -2
  17. package/addon/components/order/form.hbs +2 -0
  18. package/addon/components/telematic/details.hbs +7 -1
  19. package/addon/components/telematic/form.js +16 -8
  20. package/addon/components/work-order/details.hbs +5 -1
  21. package/addon/components/work-order/form.hbs +26 -4
  22. package/addon/components/work-order/form.js +5 -1
  23. package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
  24. package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
  25. package/addon/controllers/connectivity/devices/index/details/vehicle.js +74 -0
  26. package/addon/controllers/connectivity/devices/index/details.js +12 -0
  27. package/addon/controllers/connectivity/devices/index.js +4 -1
  28. package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
  29. package/addon/controllers/connectivity/telematics/details/devices.js +90 -41
  30. package/addon/controllers/connectivity/telematics/details/events.js +39 -11
  31. package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
  32. package/addon/controllers/maintenance/index.js +100 -0
  33. package/addon/controllers/maintenance/work-orders/index.js +18 -1
  34. package/addon/controllers/management/fuel-transactions/index.js +1 -1
  35. package/addon/controllers/management/index.js +88 -0
  36. package/addon/controllers/settings/index.js +142 -0
  37. package/addon/extension.js +21 -0
  38. package/addon/routes/analytics/index.js +3 -0
  39. package/addon/routes/connectivity/devices/index/details/events.js +37 -1
  40. package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
  41. package/addon/routes/connectivity/devices/index/details/vehicle.js +7 -0
  42. package/addon/routes/connectivity/devices/index.js +3 -0
  43. package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
  44. package/addon/routes/connectivity/telematics/details/devices.js +4 -0
  45. package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
  46. package/addon/routes/connectivity/telematics/details.js +1 -1
  47. package/addon/routes/connectivity/telematics/edit.js +1 -1
  48. package/addon/routes/maintenance/index.js +3 -0
  49. package/addon/routes/maintenance/work-orders/index.js +3 -0
  50. package/addon/routes/settings/index.js +3 -0
  51. package/addon/routes.js +2 -0
  52. package/addon/services/order-creation.js +54 -1
  53. package/addon/services/order-validation.js +3 -2
  54. package/addon/services/service-rate-actions.js +1 -0
  55. package/addon/styles/fleetops-engine.css +1821 -6
  56. package/addon/templates/analytics/index.hbs +14 -0
  57. package/addon/templates/analytics/reports/index.hbs +4 -4
  58. package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
  59. package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
  60. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +84 -0
  61. package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
  62. package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
  63. package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
  64. package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
  65. package/addon/templates/maintenance/index.hbs +113 -0
  66. package/addon/templates/management/fuel-transactions/index/details.hbs +1 -1
  67. package/addon/templates/management/index.hbs +109 -73
  68. package/addon/templates/management/vehicles/index/edit.hbs +1 -1
  69. package/addon/templates/operations/orders/index.hbs +1 -1
  70. package/addon/templates/operations/service-rates/index.hbs +1 -1
  71. package/addon/templates/settings/index.hbs +83 -0
  72. package/addon/templates/settings/routing.hbs +2 -2
  73. package/addon/utils/fleet-ops-options.js +38 -0
  74. package/app/components/order/form/orchestrator-constraints.js +1 -0
  75. package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
  76. package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
  77. package/app/controllers/maintenance/index.js +1 -0
  78. package/app/controllers/management/index.js +1 -0
  79. package/app/controllers/settings/index.js +1 -0
  80. package/app/routes/analytics/index.js +1 -0
  81. package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
  82. package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
  83. package/app/routes/maintenance/index.js +1 -0
  84. package/app/routes/settings/index.js +1 -0
  85. package/app/templates/analytics/index.js +1 -0
  86. package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
  87. package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
  88. package/app/templates/maintenance/index.js +1 -0
  89. package/app/templates/management/index.js +1 -0
  90. package/app/templates/settings/index.js +1 -0
  91. package/composer.json +1 -1
  92. package/extension.json +1 -1
  93. package/package.json +3 -3
  94. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
  95. package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
  96. package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
  97. package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
  98. package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
  99. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
  100. package/server/src/Console/Commands/SyncTelematics.php +89 -0
  101. package/server/src/Exceptions/TelematicProviderException.php +13 -2
  102. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
  103. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +112 -11
  104. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +16 -5
  105. package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
  106. package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
  107. package/server/src/Http/Controllers/Internal/v1/LiveController.php +177 -0
  108. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
  109. package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
  110. package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
  111. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
  112. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -1
  113. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +94 -10
  114. package/server/src/Http/Filter/DeviceEventFilter.php +44 -0
  115. package/server/src/Http/Filter/DeviceFilter.php +131 -0
  116. package/server/src/Http/Filter/DriverFilter.php +6 -0
  117. package/server/src/Http/Filter/SensorFilter.php +125 -0
  118. package/server/src/Http/Filter/VehicleFilter.php +19 -0
  119. package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
  120. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
  121. package/server/src/Http/Resources/v1/Driver.php +11 -2
  122. package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
  123. package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
  124. package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
  125. package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
  126. package/server/src/Jobs/SyncTelematicDevicesJob.php +181 -66
  127. package/server/src/Models/Device.php +15 -1
  128. package/server/src/Models/DeviceEvent.php +1 -1
  129. package/server/src/Models/Driver.php +17 -1
  130. package/server/src/Models/Sensor.php +15 -2
  131. package/server/src/Models/Telematic.php +1 -1
  132. package/server/src/Models/Vehicle.php +5 -1
  133. package/server/src/Models/WorkOrder.php +5 -2
  134. package/server/src/Observers/DriverObserver.php +3 -3
  135. package/server/src/Observers/FleetObserver.php +23 -0
  136. package/server/src/Observers/VehicleObserver.php +3 -3
  137. package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
  138. package/server/src/Support/LiveCacheService.php +1 -1
  139. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +188 -28
  140. package/server/src/Support/Telematics/Providers/SafeeProvider.php +44 -3
  141. package/server/src/Support/Telematics/TelematicService.php +330 -30
  142. package/server/src/routes.php +10 -0
  143. package/server/tests/DeviceFilterTest.php +36 -0
  144. package/server/tests/HubRoutesTest.php +62 -0
  145. package/server/tests/OperationsMonitorEndpointTest.php +38 -0
  146. package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
  147. package/server/tests/TelematicsHardeningTest.php +455 -3
  148. package/server/tests/VehicleDriverAssignmentTest.php +27 -0
  149. package/translations/ar-ae.yml +1 -1
  150. package/translations/en-us.yaml +1 -0
  151. package/translations/vi-vn.yaml +1 -1
@@ -1,154 +1,125 @@
1
- <div class="details-wrapper" ...attributes>
2
- <ContentPanel @title="Integration" @open={{true}} @wrapperClass="bordered-top">
3
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
4
- <div class="field-info-container lg:col-span-3">
5
- <div class="field-name">{{t "device.fields.telematic"}}</div>
6
- <div class="field-value">
7
- {{#if @resource.telematic}}
8
- <div class="flex flex-row space-x-2 text-sm">
9
- <div class="pt-1.5">
10
- <Image src={{@resource.telematic.provider_descriptor.icon}} class="w-5 h-5" />
11
- </div>
12
- <div>
13
- <div class="font-semibold">{{@resource.telematic.provider_descriptor.label}}</div>
14
- <div class="text-xs text-gray-400 dark:text-gray-500">
15
- {{n-a @resource.telematic.provider_descriptor.description}}
16
- </div>
17
- </div>
18
- </div>
19
- {{else}}
20
- {{n-a null}}
21
- {{/if}}
1
+ <div class="flex flex-col gap-3 p-3" ...attributes>
2
+ <section class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
3
+ <div class="flex flex-col gap-3 p-3 lg:flex-row lg:items-start lg:justify-between">
4
+ <div class="min-w-0">
5
+ <div class="flex flex-wrap items-center gap-2">
6
+ <h2 class="truncate text-base font-bold text-gray-900 dark:text-gray-100">{{this.displayName}}</h2>
7
+ <Badge @status={{this.connectionTone}}>{{smart-humanize this.connectionLabel}}</Badge>
8
+ <Badge @status={{this.attachmentTone}}>{{this.attachmentLabel}}</Badge>
9
+ </div>
10
+ <div class="mt-2 flex flex-wrap gap-2 text-xs text-gray-500 dark:text-gray-300">
11
+ <span>Last seen: {{n-a (format-date-fns this.lastSeenLabel "dd MMM yyyy, HH:mm")}}</span>
12
+ <span>Provider: {{n-a (titleize this.providerLabel)}}</span>
13
+ <span>Signal: {{n-a this.signalLabel}}</span>
14
+ <span>Device ID: {{n-a @resource.device_id}}</span>
22
15
  </div>
23
16
  </div>
24
17
 
25
- <div class="field-info-container">
26
- <div class="field-name">{{t "device.fields.data-frequency"}}</div>
27
- <div class="field-value">{{n-a @resource.data_frequency}}</div>
28
- </div>
29
- </div>
30
- </ContentPanel>
31
-
32
- <ContentPanel @title="Identity" @open={{true}} @wrapperClass="bordered-top">
33
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
34
- <div class="field-info-container">
35
- <div class="field-name">{{t "device.fields.device-name"}}</div>
36
- <div class="field-value">{{n-a @resource.name}}</div>
37
- </div>
38
-
39
- <div class="field-info-container">
40
- <div class="field-name">{{t "device.fields.device-type"}}</div>
41
- <div class="field-value">{{n-a (get-fleet-ops-option-label "deviceTypes" @resource.type)}}</div>
42
- </div>
43
-
44
- <div class="field-info-container">
45
- <div class="field-name">{{t "device.fields.device-id"}}</div>
46
- <div class="field-value">{{n-a @resource.device_id}}</div>
47
- </div>
48
-
49
- <div class="field-info-container">
50
- <div class="field-name">Internal ID</div>
51
- <div class="field-value">{{n-a @resource.internal_id}}</div>
52
- </div>
18
+ {{#if this.providerIcon}}
19
+ <div class="flex flex-shrink-0 items-center gap-2 rounded-md border border-gray-100 bg-gray-50 px-3 py-2 dark:border-gray-700 dark:bg-gray-800/70">
20
+ <Image src={{this.providerIcon}} class="h-6 w-6 rounded" />
21
+ <span class="text-xs font-semibold text-gray-700 dark:text-gray-200">{{n-a this.providerLabel}}</span>
22
+ </div>
23
+ {{/if}}
53
24
  </div>
54
- </ContentPanel>
55
-
56
- <ContentPanel @title="Hardware" @open={{true}} @wrapperClass="bordered-top">
57
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
58
- <div class="field-info-container">
59
- <div class="field-name">{{t "device.fields.device-provider"}}</div>
60
- <div class="field-value">{{n-a (titleize @resource.provider)}}</div>
61
- </div>
62
-
63
- <div class="field-info-container">
64
- <div class="field-name">{{t "device.fields.device-model"}}</div>
65
- <div class="field-value">{{n-a @resource.model}}</div>
25
+ </section>
26
+
27
+ <section class="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-4">
28
+ {{#each this.metrics as |metric|}}
29
+ <div class="fleet-ops-widget fleet-ops-kpi-tile fleetops-connectivity-kpi-tile {{metric.accentClass}} relative min-h-[82px] overflow-hidden rounded-md border bg-white shadow-sm dark:bg-gray-800">
30
+ <div class="flex h-full items-start justify-between gap-2 p-3">
31
+ <div class="min-w-0">
32
+ <div class="truncate text-[10px] font-bold uppercase text-gray-500 dark:text-gray-400">{{metric.label}}</div>
33
+ <div class="mt-1 truncate text-lg font-extrabold leading-none text-gray-900 dark:text-gray-50">{{metric.value}}</div>
34
+ {{#if metric.meta}}
35
+ <div class="mt-1 truncate text-xs text-gray-500 dark:text-gray-300">{{metric.meta}}</div>
36
+ {{/if}}
37
+ </div>
38
+ <div class="fleetops-connectivity-kpi-icon flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md text-[13px]">
39
+ <FaIcon @icon={{metric.icon}} />
40
+ </div>
41
+ </div>
66
42
  </div>
43
+ {{/each}}
44
+ </section>
67
45
 
68
- <div class="field-info-container">
69
- <div class="field-name">{{t "device.fields.manufacturer"}}</div>
70
- <div class="field-value">{{n-a @resource.manufacturer}}</div>
46
+ <section class="grid grid-cols-1 gap-3 xl:grid-cols-2">
47
+ <div class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
48
+ <div class="border-b border-gray-200 px-3 py-2 dark:border-gray-700">
49
+ <h3 class="text-sm font-bold text-gray-900 dark:text-gray-100">Operational Snapshot</h3>
71
50
  </div>
72
-
73
- <div class="field-info-container lg:col-span-3">
74
- <div class="field-name">{{t "device.fields.serial-number"}}</div>
75
- <div class="field-value">{{n-a @resource.serial_number}}</div>
51
+ <div class="grid grid-cols-1 gap-2 p-3 sm:grid-cols-2">
52
+ <div class="field-info-container">
53
+ <div class="field-name">Location</div>
54
+ <div class="field-value">{{n-a (or @resource.location this.lastPositionLabel)}}</div>
55
+ </div>
56
+ <div class="field-info-container">
57
+ <div class="field-name">{{t "device.fields.data-frequency"}}</div>
58
+ <div class="field-value">{{n-a @resource.data_frequency}}</div>
59
+ </div>
60
+ <div class="field-info-container">
61
+ <div class="field-name">Firmware</div>
62
+ <div class="field-value">{{n-a this.firmwareLabel}}</div>
63
+ </div>
64
+ <div class="field-info-container">
65
+ <div class="field-name">{{t "device.fields.installation-date"}}</div>
66
+ <div class="field-value">{{n-a (format-date @resource.installation_date)}}</div>
67
+ </div>
68
+ <div class="field-info-container">
69
+ <div class="field-name">{{t "device.fields.last-maintenance-date"}}</div>
70
+ <div class="field-value">{{n-a (format-date @resource.last_maintenance_date)}}</div>
71
+ </div>
72
+ <div class="field-info-container">
73
+ <div class="field-name">{{t "device.fields.status"}}</div>
74
+ <div class="field-value"><Badge @status={{@resource.status}}>{{smart-humanize @resource.status}}</Badge></div>
75
+ </div>
76
76
  </div>
77
77
  </div>
78
- </ContentPanel>
79
78
 
80
- <ContentPanel @title="Installation & Maintenance" @open={{true}} @wrapperClass="bordered-top">
81
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
82
- <div class="field-info-container lg:col-span-2">
83
- <div class="field-name">{{t "device.fields.device-location"}}</div>
84
- <div class="field-value">{{n-a @resource.location}}</div>
79
+ <div class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
80
+ <div class="border-b border-gray-200 px-3 py-2 dark:border-gray-700">
81
+ <h3 class="text-sm font-bold text-gray-900 dark:text-gray-100">Critical Details</h3>
85
82
  </div>
86
-
87
- <div class="field-info-container">
88
- <div class="field-name">{{t "device.fields.installation-date"}}</div>
89
- <div class="field-value">{{or (format-date @resource.installation_date) "-"}}</div>
90
- </div>
91
-
92
- <div class="field-info-container">
93
- <div class="field-name">{{t "device.fields.last-maintenance-date"}}</div>
94
- <div class="field-value">{{or (format-date @resource.last_maintenance_date) "-"}}</div>
95
- </div>
96
- </div>
97
- </ContentPanel>
98
-
99
- <ContentPanel @title="Connectivity" @open={{true}} @wrapperClass="bordered-top">
100
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
101
- <div class="field-info-container">
102
- <div class="field-name">{{t "common.online"}}</div>
103
- <div class="field-value">
104
- {{#if @resource.is_online}}
105
- <Badge @status="online">{{t "common.online"}}</Badge>
106
- {{else}}
107
- <Badge @status="offline">{{t "common.offline"}}</Badge>
108
- {{/if}}
83
+ <div class="grid grid-cols-1 gap-2 p-3 sm:grid-cols-2">
84
+ <div class="field-info-container">
85
+ <div class="field-name">{{t "device.fields.device-type"}}</div>
86
+ <div class="field-value">{{n-a (get-fleet-ops-option-label "deviceTypes" @resource.type)}}</div>
109
87
  </div>
110
- </div>
111
-
112
- <div class="field-info-container">
113
- <div class="field-name">{{t "common.last-seen-at"}}</div>
114
- <div class="field-value">{{or (format-date @resource.last_online_at) "-"}}</div>
115
- </div>
116
-
117
- {{#if @resource.signal_strength}}
118
88
  <div class="field-info-container">
119
- <div class="field-name">{{t "device.fields.signal-strength"}}</div>
120
- <div class="field-value">{{n-a @resource.signal_strength}}</div>
89
+ <div class="field-name">{{t "device.fields.device-model"}}</div>
90
+ <div class="field-value">{{n-a @resource.model}}</div>
121
91
  </div>
122
- {{/if}}
123
- </div>
124
- </ContentPanel>
125
-
126
- <ContentPanel @title="Warranty & Status" @open={{true}} @wrapperClass="bordered-top">
127
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
128
- <div class="field-info-container">
129
- <div class="field-name">{{t "device.fields.status"}}</div>
130
- <div class="field-value">
131
- <Badge @status={{@resource.status}}>
132
- {{smart-humanize @resource.status}}
133
- </Badge>
92
+ <div class="field-info-container">
93
+ <div class="field-name">{{t "device.fields.manufacturer"}}</div>
94
+ <div class="field-value">{{n-a @resource.manufacturer}}</div>
95
+ </div>
96
+ <div class="field-info-container">
97
+ <div class="field-name">{{t "device.fields.serial-number"}}</div>
98
+ <div class="field-value">{{n-a @resource.serial_number}}</div>
99
+ </div>
100
+ <div class="field-info-container">
101
+ <div class="field-name">IMEI</div>
102
+ <div class="field-value">{{n-a @resource.imei}}</div>
103
+ </div>
104
+ <div class="field-info-container">
105
+ <div class="field-name">IMSI</div>
106
+ <div class="field-value">{{n-a @resource.imsi}}</div>
107
+ </div>
108
+ <div class="field-info-container">
109
+ <div class="field-name">Internal ID</div>
110
+ <div class="field-value">{{n-a @resource.internal_id}}</div>
111
+ </div>
112
+ <div class="field-info-container">
113
+ <div class="field-name">{{t "device.fields.warranty"}}</div>
114
+ <div class="field-value">{{n-a (or @resource.warranty.name @resource.warranty_name)}}</div>
115
+ </div>
116
+ <div class="field-info-container sm:col-span-2">
117
+ <div class="field-name">{{t "device.fields.notes"}}</div>
118
+ <div class="field-value whitespace-pre-line">{{n-a @resource.notes}}</div>
134
119
  </div>
135
- </div>
136
-
137
- <div class="field-info-container lg:col-span-2">
138
- <div class="field-name">{{t "device.fields.warranty"}}</div>
139
- <div class="field-value">{{n-a @resource.warranty.name}}</div>
140
- </div>
141
- </div>
142
- </ContentPanel>
143
-
144
- <ContentPanel @title="Notes" @open={{true}} @wrapperClass="bordered-top">
145
- <div class="grid grid-cols-1 lg:grid-cols-1 gap-2">
146
- <div class="field-info-container">
147
- <div class="field-name">{{t "device.fields.notes"}}</div>
148
- <div class="field-value">{{n-a @resource.notes}}</div>
149
120
  </div>
150
121
  </div>
151
- </ContentPanel>
122
+ </section>
152
123
 
153
124
  <CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
154
- </div>
125
+ </div>
@@ -1,3 +1,176 @@
1
1
  import Component from '@glimmer/component';
2
+ import { inject as service } from '@ember/service';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { task } from 'ember-concurrency';
2
5
 
3
- export default class DeviceDetailsComponent extends Component {}
6
+ const warningSeverities = ['warning', 'error', 'critical', 'high'];
7
+
8
+ export default class DeviceDetailsComponent extends Component {
9
+ @service store;
10
+ @service intl;
11
+
12
+ @tracked sensors = [];
13
+ @tracked events = [];
14
+ @tracked sensorTotal;
15
+ @tracked eventTotal;
16
+
17
+ constructor() {
18
+ super(...arguments);
19
+ this.loadOperationalSummary.perform();
20
+ }
21
+
22
+ get resource() {
23
+ return this.args.resource;
24
+ }
25
+
26
+ get displayName() {
27
+ return this.resource?.displayName ?? this.resource?.name ?? this.resource?.device_id ?? this.resource?.serial_number ?? this.resource?.public_id ?? this.intl.t('resource.device');
28
+ }
29
+
30
+ get providerDescriptor() {
31
+ return this.resource?.telematic?.provider_descriptor;
32
+ }
33
+
34
+ get providerLabel() {
35
+ return this.providerDescriptor?.label ?? this.resource?.telematic?.name ?? this.resource?.provider ?? this.resource?.telematic_name;
36
+ }
37
+
38
+ get providerIcon() {
39
+ return this.providerDescriptor?.icon;
40
+ }
41
+
42
+ get connectionStatus() {
43
+ return this.resource?.connection_status ?? (this.resource?.is_online ? 'online' : 'offline');
44
+ }
45
+
46
+ get connectionTone() {
47
+ switch (this.connectionStatus) {
48
+ case 'online':
49
+ return 'online';
50
+ case 'recently_offline':
51
+ return 'warning';
52
+ case 'never_connected':
53
+ case 'long_offline':
54
+ return 'danger';
55
+ default:
56
+ return 'offline';
57
+ }
58
+ }
59
+
60
+ get connectionLabel() {
61
+ return this.connectionStatus ? this.connectionStatus.replaceAll('_', ' ') : 'Unknown';
62
+ }
63
+
64
+ get attachedVehicleName() {
65
+ return this.resource?.attached_to_name ?? this.resource?.attachable?.displayName ?? this.resource?.attachable?.display_name ?? this.resource?.attachable?.name;
66
+ }
67
+
68
+ get attachmentLabel() {
69
+ return this.attachedVehicleName ?? 'Unattached';
70
+ }
71
+
72
+ get attachmentTone() {
73
+ return this.attachedVehicleName ? 'success' : 'warning';
74
+ }
75
+
76
+ get lastSeenLabel() {
77
+ return this.resource?.last_online_at ?? this.resource?.lastOnlineAt;
78
+ }
79
+
80
+ get signalLabel() {
81
+ return this.resource?.signal_strength ?? this.resource?.data?.signal_strength ?? this.resource?.meta?.signal_strength;
82
+ }
83
+
84
+ get firmwareLabel() {
85
+ return this.resource?.firmware_version ?? this.resource?.data?.firmware_version ?? this.resource?.meta?.firmware_version;
86
+ }
87
+
88
+ get lastPositionLabel() {
89
+ const position = this.resource?.last_position;
90
+
91
+ if (!position) {
92
+ return null;
93
+ }
94
+
95
+ if (typeof position === 'string') {
96
+ return position;
97
+ }
98
+
99
+ const latitude = position.latitude ?? position.lat ?? position.coordinates?.[1];
100
+ const longitude = position.longitude ?? position.lng ?? position.lon ?? position.coordinates?.[0];
101
+
102
+ if (latitude && longitude) {
103
+ return `${latitude}, ${longitude}`;
104
+ }
105
+
106
+ return null;
107
+ }
108
+
109
+ get warningEventsCount() {
110
+ return this.events.filter((event) => warningSeverities.includes(String(event.severity ?? '').toLowerCase())).length;
111
+ }
112
+
113
+ get unprocessedEventsCount() {
114
+ return this.events.filter((event) => !event.processed_at && !event.is_processed).length;
115
+ }
116
+
117
+ get activeSensorsCount() {
118
+ return this.sensors.filter((sensor) => sensor.is_active || sensor.status === 'active' || sensor.status === 'online').length;
119
+ }
120
+
121
+ get reportingSensorsCount() {
122
+ return this.sensors.filter((sensor) => sensor.last_value || sensor.last_reading_at).length;
123
+ }
124
+
125
+ get metrics() {
126
+ return [
127
+ {
128
+ label: 'Connection',
129
+ value: this.connectionLabel,
130
+ icon: this.connectionStatus === 'online' ? 'signal' : 'power-off',
131
+ accentClass: this.connectionStatus === 'online' ? 'fleetops-connectivity-kpi-accent-green' : 'fleetops-connectivity-kpi-accent-rose',
132
+ },
133
+ {
134
+ label: 'Sensors',
135
+ value: this.sensorTotal ?? this.sensors.length,
136
+ meta: `${this.activeSensorsCount} active`,
137
+ icon: 'gauge-high',
138
+ accentClass: 'fleetops-connectivity-kpi-accent-blue',
139
+ },
140
+ {
141
+ label: 'Events',
142
+ value: this.eventTotal ?? this.events.length,
143
+ meta: `${this.warningEventsCount + this.unprocessedEventsCount} need review`,
144
+ icon: 'bolt',
145
+ accentClass: this.warningEventsCount || this.unprocessedEventsCount ? 'fleetops-connectivity-kpi-accent-amber' : 'fleetops-connectivity-kpi-accent-green',
146
+ },
147
+ {
148
+ label: 'Vehicle',
149
+ value: this.attachmentLabel,
150
+ icon: this.attachedVehicleName ? 'truck' : 'link-slash',
151
+ accentClass: this.attachedVehicleName ? 'fleetops-connectivity-kpi-accent-green' : 'fleetops-connectivity-kpi-accent-amber',
152
+ },
153
+ ];
154
+ }
155
+
156
+ @task *loadOperationalSummary() {
157
+ if (!this.resource?.id) {
158
+ return;
159
+ }
160
+
161
+ try {
162
+ const [sensors, events] = yield Promise.all([
163
+ this.store.query('sensor', { device_uuid: this.resource.id, limit: 10 }),
164
+ this.store.query('device-event', { device_uuid: this.resource.id, limit: 10, sort: '-created_at' }),
165
+ ]);
166
+
167
+ this.sensors = Array.from(sensors ?? []);
168
+ this.events = Array.from(events ?? []);
169
+ this.sensorTotal = sensors?.meta?.total;
170
+ this.eventTotal = events?.meta?.total;
171
+ } catch {
172
+ this.sensors = [];
173
+ this.events = [];
174
+ }
175
+ }
176
+ }
@@ -24,7 +24,7 @@
24
24
  }}</div>
25
25
  <div class="text-xs text-gray-400 dark:text-gray-500">{{n-a @resource.serial_number}}</div>
26
26
  </div>
27
- <Badge @status={{if @resource.online "online" "offline"}}>{{if @resource.online (t "common.online") (t "common.offline")}}</Badge>
27
+ <Badge @status={{if @resource.is_online "online" "offline"}}>{{if @resource.is_online (t "common.online") (t "common.offline")}}</Badge>
28
28
  </div>
29
29
  </div>
30
30
  <div class="next-view-header-right">
@@ -40,4 +40,4 @@
40
40
  />
41
41
  </div>
42
42
  </div>
43
- </div>
43
+ </div>