@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.
- package/addon/components/admin/routing-settings.hbs +2 -1
- package/addon/components/cell/attached-vehicle.hbs +10 -0
- package/addon/components/cell/attached-vehicle.js +58 -0
- package/addon/components/cell/device-identity.hbs +24 -0
- package/addon/components/cell/device-identity.js +129 -0
- package/addon/components/cell/driver-identity.hbs +33 -0
- package/addon/components/cell/driver-identity.js +143 -0
- package/addon/components/cell/equipment-identity.hbs +5 -0
- package/addon/components/cell/equipment-identity.js +48 -0
- package/addon/components/cell/part-identity.hbs +5 -0
- package/addon/components/cell/part-identity.js +63 -0
- package/addon/components/cell/telematic-device.hbs +38 -0
- package/addon/components/cell/telematic-device.js +44 -0
- package/addon/components/cell/telematic-provider.hbs +33 -16
- package/addon/components/cell/telematic-provider.js +50 -8
- package/addon/components/cell/vehicle-identity.hbs +33 -0
- package/addon/components/cell/vehicle-identity.js +138 -0
- package/addon/components/device/details.hbs +106 -135
- package/addon/components/device/details.js +174 -1
- package/addon/components/device/form.hbs +2 -2
- package/addon/components/device/form.js +7 -0
- package/addon/components/device/panel-header.hbs +46 -21
- package/addon/components/device/panel-header.js +49 -1
- package/addon/components/device/panel-tabs/events.hbs +30 -0
- package/addon/components/device/panel-tabs/events.js +128 -0
- package/addon/components/device/panel-tabs/sensors.hbs +30 -0
- package/addon/components/device/panel-tabs/sensors.js +93 -0
- package/addon/components/device/panel-tabs/vehicle.hbs +68 -0
- package/addon/components/device/panel-tabs/vehicle.js +90 -0
- package/addon/components/equipment/form.js +8 -1
- package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +76 -11
- package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +101 -17
- package/addon/components/map/drawer/device-event-listing.js +2 -0
- package/addon/components/map/drawer/driver-listing.js +2 -0
- package/addon/components/map/drawer/place-listing.js +2 -0
- package/addon/components/map/drawer/position-listing.hbs +11 -6
- package/addon/components/map/drawer/position-listing.js +17 -4
- package/addon/components/map/drawer/vehicle-listing.js +2 -0
- package/addon/components/order/form/details.hbs +1 -43
- package/addon/components/order/form/details.js +18 -53
- package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
- package/addon/components/order/form/orchestrator-constraints.js +61 -0
- package/addon/components/order/form/payload.js +48 -2
- package/addon/components/order/form/route.js +15 -0
- package/addon/components/order/form/service-rate.hbs +62 -11
- package/addon/components/order/form/service-rate.js +124 -2
- package/addon/components/order/form.hbs +2 -0
- package/addon/components/sensor/panel-header.hbs +47 -15
- package/addon/components/sensor/panel-header.js +59 -1
- package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
- package/addon/components/table/cell/dropdown/action-item.js +62 -0
- package/addon/components/table/cell/dropdown.hbs +28 -0
- package/addon/components/table/cell/dropdown.js +91 -0
- package/addon/components/telematic/details.hbs +7 -1
- package/addon/components/telematic/form.js +16 -8
- package/addon/components/vendor/panel-header.hbs +82 -0
- package/addon/components/vendor/panel-header.js +48 -0
- package/addon/components/work-order/details.hbs +5 -1
- package/addon/components/work-order/form.hbs +26 -4
- package/addon/components/work-order/form.js +5 -1
- package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
- package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
- package/addon/controllers/connectivity/devices/index/details/vehicle.js +122 -0
- package/addon/controllers/connectivity/devices/index/details.js +12 -0
- package/addon/controllers/connectivity/devices/index.js +144 -129
- package/addon/controllers/connectivity/events/details.js +3 -0
- package/addon/controllers/connectivity/events/index.js +187 -11
- package/addon/controllers/connectivity/sensors/index.js +156 -14
- package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
- package/addon/controllers/connectivity/telematics/details/devices.js +170 -129
- package/addon/controllers/connectivity/telematics/details/events.js +39 -11
- package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
- package/addon/controllers/maintenance/equipment/index.js +1 -2
- package/addon/controllers/maintenance/parts/index.js +1 -2
- package/addon/controllers/maintenance/work-orders/index.js +18 -1
- package/addon/controllers/management/drivers/index.js +82 -42
- package/addon/controllers/management/fuel-reports/index.js +63 -4
- package/addon/controllers/management/issues/index.js +63 -4
- package/addon/controllers/management/vehicles/index.js +33 -6
- package/addon/routes/connectivity/devices/index/details/events.js +37 -1
- package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
- package/addon/routes/connectivity/devices/index/details/vehicle.js +29 -0
- package/addon/routes/connectivity/devices/index.js +7 -0
- package/addon/routes/connectivity/events/details.js +3 -0
- package/addon/routes/connectivity/events/index.js +6 -2
- package/addon/routes/connectivity/sensors/index.js +7 -2
- package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
- package/addon/routes/connectivity/telematics/details/devices.js +6 -0
- package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
- package/addon/routes/connectivity/telematics/details.js +1 -1
- package/addon/routes/connectivity/telematics/edit.js +1 -1
- package/addon/routes/maintenance/work-orders/index.js +1 -0
- package/addon/routes.js +2 -0
- package/addon/services/device-actions.js +30 -0
- package/addon/services/device-event-actions.js +1 -1
- package/addon/services/equipment-actions.js +1 -0
- package/addon/services/order-creation.js +54 -1
- package/addon/services/order-validation.js +3 -2
- package/addon/services/sensor-actions.js +3 -0
- package/addon/services/service-rate-actions.js +40 -1
- package/addon/services/vehicle-actions.js +24 -12
- package/addon/services/vendor-actions.js +3 -0
- package/addon/styles/fleetops-engine.css +28 -4
- package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
- package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
- package/addon/templates/connectivity/devices/index/details/vehicle.hbs +147 -0
- package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
- package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
- package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
- package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
- package/addon/templates/management/vehicles/index/edit.hbs +1 -1
- package/addon/templates/management/vendors/index/details.hbs +2 -2
- package/addon/templates/settings/notifications.hbs +2 -1
- package/addon/templates/settings/routing.hbs +1 -0
- package/addon/utils/device-table-columns.js +288 -0
- package/addon/utils/fleet-ops-options.js +38 -0
- package/addon/utils/identity-cell-resource.js +20 -0
- package/addon/utils/map-drawer-dropdown-position.js +47 -0
- package/addon/utils/trackable-option.js +93 -0
- package/app/components/cell/attached-vehicle.js +1 -0
- package/app/components/cell/device-identity.js +1 -0
- package/app/components/cell/driver-identity.js +1 -0
- package/app/components/cell/equipment-identity.js +1 -0
- package/app/components/cell/part-identity.js +1 -0
- package/app/components/cell/telematic-device.js +1 -0
- package/app/components/cell/vehicle-identity.js +1 -0
- package/app/components/device/panel-tabs/events.hbs +30 -0
- package/app/components/device/panel-tabs/events.js +128 -0
- package/app/components/device/panel-tabs/sensors.hbs +30 -0
- package/app/components/device/panel-tabs/sensors.js +93 -0
- package/app/components/device/panel-tabs/vehicle.hbs +68 -0
- package/app/components/device/panel-tabs/vehicle.js +90 -0
- package/app/components/order/form/orchestrator-constraints.js +1 -0
- package/app/components/table/cell/dropdown/action-item.js +1 -0
- package/app/components/table/cell/dropdown.js +1 -0
- package/app/components/vendor/panel-header.js +1 -0
- package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
- package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
- package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
- package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/utils/map-drawer-dropdown-position.js +1 -0
- package/app/utils/trackable-option.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
- package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
- package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
- package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
- package/server/src/Console/Commands/SyncTelematics.php +89 -0
- package/server/src/Exceptions/TelematicProviderException.php +13 -2
- package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +55 -1
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +17 -6
- package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +178 -1
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
- package/server/src/Http/Controllers/Internal/v1/SearchController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
- package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
- package/server/src/Http/Filter/DeviceEventFilter.php +65 -0
- package/server/src/Http/Filter/DeviceFilter.php +80 -0
- package/server/src/Http/Filter/SensorFilter.php +125 -0
- package/server/src/Http/Filter/VehicleFilter.php +13 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
- package/server/src/Http/Resources/v1/Driver.php +11 -2
- package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
- package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
- package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
- package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
- package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +180 -123
- package/server/src/Models/Device.php +17 -1
- package/server/src/Models/DeviceEvent.php +82 -1
- package/server/src/Models/Driver.php +16 -0
- package/server/src/Models/Equipment.php +55 -0
- package/server/src/Models/Sensor.php +15 -2
- package/server/src/Models/Telematic.php +1 -1
- package/server/src/Models/Vehicle.php +5 -1
- package/server/src/Models/WorkOrder.php +5 -2
- package/server/src/Observers/DriverObserver.php +3 -3
- package/server/src/Observers/FleetObserver.php +23 -0
- package/server/src/Observers/VehicleObserver.php +3 -3
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
- package/server/src/Support/LiveCacheService.php +1 -1
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +143 -20
- package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
- package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +70 -5
- package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
- package/server/src/Support/Telematics/TelematicService.php +330 -30
- package/server/src/routes.php +1 -1
- package/server/tests/DeviceFilterTest.php +45 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
- package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
- package/server/tests/OperationsMonitorEndpointTest.php +38 -0
- package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
- package/server/tests/ServiceRateRouteValidationTest.php +101 -0
- package/server/tests/TelematicsHardeningTest.php +598 -6
- package/server/tests/VehicleDriverAssignmentTest.php +27 -0
- package/translations/en-us.yaml +1 -0
- package/addon/controllers/connectivity/events/index/details.js +0 -3
- package/addon/routes/connectivity/events/index/details.js +0 -3
- /package/addon/templates/connectivity/events/{index/details.hbs → details.hbs} +0 -0
|
@@ -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
|
-
|
|
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
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
@infiniteScroll={{false}}
|
|
11
11
|
@renderInPlace={{true}}
|
|
12
12
|
@onChange={{this.selectTelematic}}
|
|
13
|
-
@disabled={{cannot-write @resource}}
|
|
13
|
+
@disabled={{or (cannot-write @resource) this.isTelematicLocked}}
|
|
14
14
|
as |model|
|
|
15
15
|
>
|
|
16
16
|
<div class="space-x-2 text-sm">
|
|
@@ -227,4 +227,4 @@
|
|
|
227
227
|
<RegistryYield @registry="fleet-ops:component:device:form" as |RegistryComponent|>
|
|
228
228
|
<RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} />
|
|
229
229
|
</RegistryYield>
|
|
230
|
-
</div>
|
|
230
|
+
</div>
|
|
@@ -8,6 +8,13 @@ export default class DeviceFormComponent extends Component {
|
|
|
8
8
|
@service currentUser;
|
|
9
9
|
@service notifications;
|
|
10
10
|
|
|
11
|
+
get isTelematicLocked() {
|
|
12
|
+
const resource = this.args.resource;
|
|
13
|
+
const hasTelematic = Boolean(resource?.telematic_uuid || resource?.telematic?.id);
|
|
14
|
+
|
|
15
|
+
return Boolean(hasTelematic && resource?.isNew === false);
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
@action selectTelematic(telematic) {
|
|
12
19
|
this.args.resource.setProperties({
|
|
13
20
|
telematic,
|
|
@@ -1,35 +1,60 @@
|
|
|
1
|
-
<div class="px-4 py-
|
|
2
|
-
<div class="flex flex-1 flex-row items-start justify-between">
|
|
3
|
-
<div class="flex flex-row
|
|
4
|
-
<div class="flex items-start justify-start
|
|
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
5
|
<Image
|
|
6
|
-
src={{
|
|
6
|
+
src={{this.resource.photo_url}}
|
|
7
7
|
@fallbackSrc={{config "defaultValues.placeholderImage"}}
|
|
8
|
-
alt={{
|
|
8
|
+
alt={{this.name}}
|
|
9
9
|
height="48"
|
|
10
10
|
width="48"
|
|
11
|
-
class="h-14 w-14 rounded-
|
|
11
|
+
class="h-14 w-14 rounded-md border border-gray-200 object-cover shadow-sm dark:border-gray-700"
|
|
12
12
|
/>
|
|
13
13
|
<Attach::Tooltip @class="clean" @animation="scale" @placement="top">
|
|
14
|
-
<InputInfo @text={{
|
|
14
|
+
<InputInfo @text={{this.name}} />
|
|
15
15
|
</Attach::Tooltip>
|
|
16
16
|
</div>
|
|
17
|
-
<div class="flex flex-col">
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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.connectionStatus}} @size="xs">{{smart-humanize this.connectionStatus}}</Badge>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="mt-1 flex max-w-2xl flex-wrap items-center gap-1.5">
|
|
24
|
+
{{#if this.provider}}
|
|
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="satellite-dish" class="text-gray-400 dark:text-gray-300" />
|
|
27
|
+
<span class="truncate">{{titleize this.provider}}</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="microchip" class="text-gray-400 dark:text-gray-300" />
|
|
33
|
+
<span class="truncate">{{n-a (get-fleet-ops-option-label "deviceTypes" this.type)}}</span>
|
|
34
|
+
</span>
|
|
35
|
+
{{/if}}
|
|
36
|
+
{{#if this.identifier}}
|
|
37
|
+
<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">
|
|
38
|
+
<FaIcon @icon="id-card" class="text-gray-400 dark:text-gray-300" />
|
|
39
|
+
<span class="truncate">{{this.identifier}}</span>
|
|
40
|
+
</span>
|
|
41
|
+
{{/if}}
|
|
42
|
+
{{#if this.attachedVehicle}}
|
|
43
|
+
<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">
|
|
44
|
+
<FaIcon @icon="car" class="text-gray-400 dark:text-gray-300" />
|
|
45
|
+
<span class="truncate">{{this.attachedVehicle}}</span>
|
|
46
|
+
</span>
|
|
47
|
+
{{/if}}
|
|
48
|
+
<span class="inline-flex max-w-[13rem] 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">
|
|
49
|
+
<FaIcon @icon="clock" class="text-gray-400 dark:text-gray-300" />
|
|
50
|
+
<span class="truncate">{{#if this.lastOnlineAt}}{{format-date-fns this.lastOnlineAt "dd MMM yyyy, HH:mm"}}{{else}}No last online{{/if}}</span>
|
|
51
|
+
</span>
|
|
26
52
|
</div>
|
|
27
|
-
<Badge @status={{if @resource.online "online" "offline"}}>{{if @resource.online (t "common.online") (t "common.offline")}}</Badge>
|
|
28
53
|
</div>
|
|
29
54
|
</div>
|
|
30
|
-
<div class="next-view-header-right">
|
|
55
|
+
<div class="next-view-header-right flex-shrink-0">
|
|
31
56
|
<Layout::Resource::Panel::HeaderActions
|
|
32
|
-
@resource={{
|
|
57
|
+
@resource={{this.resource}}
|
|
33
58
|
@saveTask={{@saveTask}}
|
|
34
59
|
@saveOptions={{@saveOptions}}
|
|
35
60
|
@saveDisabled={{@saveDisabled}}
|
|
@@ -40,4 +65,4 @@
|
|
|
40
65
|
/>
|
|
41
66
|
</div>
|
|
42
67
|
</div>
|
|
43
|
-
</div>
|
|
68
|
+
</div>
|
|
@@ -1,3 +1,51 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
|
+
import { get } from '@ember/object';
|
|
2
3
|
|
|
3
|
-
export default class DevicePanelHeaderComponent extends Component {
|
|
4
|
+
export default class DevicePanelHeaderComponent extends Component {
|
|
5
|
+
get resource() {
|
|
6
|
+
return this.args.resource ?? {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get name() {
|
|
10
|
+
return (
|
|
11
|
+
get(this.resource, 'displayName') ??
|
|
12
|
+
get(this.resource, 'display_name') ??
|
|
13
|
+
get(this.resource, 'name') ??
|
|
14
|
+
get(this.resource, 'device_id') ??
|
|
15
|
+
get(this.resource, 'imei') ??
|
|
16
|
+
get(this.resource, 'serial_number') ??
|
|
17
|
+
get(this.resource, 'public_id') ??
|
|
18
|
+
'-'
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get connectionStatus() {
|
|
23
|
+
return get(this.resource, 'connection_status') ?? get(this.resource, 'status') ?? (get(this.resource, 'is_online') || get(this.resource, 'online') ? 'online' : 'offline');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get provider() {
|
|
27
|
+
return get(this.resource, 'telematic.name') ?? get(this.resource, 'telematic_name') ?? get(this.resource, 'provider');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get type() {
|
|
31
|
+
return get(this.resource, 'type');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get identifier() {
|
|
35
|
+
return get(this.resource, 'imei') ?? get(this.resource, 'serial_number') ?? get(this.resource, 'device_id') ?? get(this.resource, 'public_id');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get attachedVehicle() {
|
|
39
|
+
return (
|
|
40
|
+
get(this.resource, 'attached_to_name') ??
|
|
41
|
+
get(this.resource, 'attachable.displayName') ??
|
|
42
|
+
get(this.resource, 'attachable.display_name') ??
|
|
43
|
+
get(this.resource, 'attachable.name') ??
|
|
44
|
+
get(this.resource, 'attachable_uuid')
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get lastOnlineAt() {
|
|
49
|
+
return get(this.resource, 'last_online_at') ?? get(this.resource, 'lastOnlineAt');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<div class="flex flex-col gap-3 p-3" ...attributes>
|
|
2
|
+
<div class="flex items-start justify-between gap-3">
|
|
3
|
+
<div class="min-w-0">
|
|
4
|
+
<h2 class="text-base font-bold text-gray-900 dark:text-gray-100">Telemetry Events</h2>
|
|
5
|
+
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-300">Recent device events, warning signals, and processing state.</p>
|
|
6
|
+
</div>
|
|
7
|
+
<Button @icon="refresh" @size="xs" @onClick={{this.refreshEvents}} @isLoading={{this.loadEvents.isRunning}} />
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<Layout::Resource::Tabular
|
|
11
|
+
@resource="device-event"
|
|
12
|
+
@title="Telemetry Events"
|
|
13
|
+
@data={{this.events}}
|
|
14
|
+
@columns={{this.columns}}
|
|
15
|
+
@controller={{this}}
|
|
16
|
+
@pagination={{false}}
|
|
17
|
+
@withoutHeader={{true}}
|
|
18
|
+
@tableWrapperClass="no-table-extra-spacing"
|
|
19
|
+
@emptyStateComponent={{component
|
|
20
|
+
"table/empty-state"
|
|
21
|
+
variant="compact"
|
|
22
|
+
icon="bolt"
|
|
23
|
+
title="No telemetry events"
|
|
24
|
+
description="Events appear after this device reports provider activity."
|
|
25
|
+
primaryText="Refresh"
|
|
26
|
+
primaryIcon="refresh"
|
|
27
|
+
primaryAction=this.refreshEvents
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { tracked } from '@glimmer/tracking';
|
|
5
|
+
import { task } from 'ember-concurrency';
|
|
6
|
+
|
|
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
|
+
function toRecentList(records) {
|
|
16
|
+
const list = Array.from(records ?? []);
|
|
17
|
+
|
|
18
|
+
list.meta = {
|
|
19
|
+
current_page: 1,
|
|
20
|
+
last_page: 1,
|
|
21
|
+
per_page: list.length,
|
|
22
|
+
total: list.length,
|
|
23
|
+
from: list.length > 0 ? 1 : 0,
|
|
24
|
+
to: list.length,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return list;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default class DevicePanelTabsEventsComponent extends Component {
|
|
31
|
+
@service deviceEventActions;
|
|
32
|
+
@service store;
|
|
33
|
+
|
|
34
|
+
@tracked events = toRecentList();
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
super(...arguments);
|
|
38
|
+
this.loadEvents.perform();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get device() {
|
|
42
|
+
return this.args.resource ?? this.args.model;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get columns() {
|
|
46
|
+
return [
|
|
47
|
+
{
|
|
48
|
+
sticky: true,
|
|
49
|
+
label: 'Event',
|
|
50
|
+
valuePath: 'event_type',
|
|
51
|
+
cellComponent: 'table/cell/anchor',
|
|
52
|
+
action: this.deviceEventActions.panel?.view ?? this.deviceEventActions.transition.view,
|
|
53
|
+
permission: 'fleet-ops view device-event',
|
|
54
|
+
resizable: true,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: 'Severity',
|
|
58
|
+
valuePath: 'severity',
|
|
59
|
+
cellComponent: 'table/cell/status',
|
|
60
|
+
filterOptions: severityOptions,
|
|
61
|
+
resizable: true,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
label: 'Message',
|
|
65
|
+
valuePath: 'message',
|
|
66
|
+
resizable: true,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
label: 'Code',
|
|
70
|
+
valuePath: 'code',
|
|
71
|
+
resizable: true,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: 'Processed',
|
|
75
|
+
valuePath: 'processedAt',
|
|
76
|
+
sortParam: 'processed_at',
|
|
77
|
+
resizable: true,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: 'Occurred',
|
|
81
|
+
valuePath: 'occurredAt',
|
|
82
|
+
sortParam: 'occurred_at',
|
|
83
|
+
resizable: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
label: '',
|
|
87
|
+
cellComponent: 'table/cell/dropdown',
|
|
88
|
+
ddButtonText: false,
|
|
89
|
+
ddButtonIcon: 'ellipsis-h',
|
|
90
|
+
ddButtonIconPrefix: 'fas',
|
|
91
|
+
wrapperClass: 'flex items-center justify-end mx-2',
|
|
92
|
+
sticky: 'right',
|
|
93
|
+
width: 60,
|
|
94
|
+
actions: [
|
|
95
|
+
{
|
|
96
|
+
label: 'View event',
|
|
97
|
+
fn: this.deviceEventActions.panel?.view ?? this.deviceEventActions.transition.view,
|
|
98
|
+
permission: 'fleet-ops view device-event',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
label: 'Mark processed',
|
|
102
|
+
fn: this.markProcessed,
|
|
103
|
+
permission: 'fleet-ops update device-event',
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@action async markProcessed(deviceEvent) {
|
|
111
|
+
await this.deviceEventActions.markProcessed(deviceEvent);
|
|
112
|
+
await this.loadEvents.perform();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@action refreshEvents() {
|
|
116
|
+
return this.loadEvents.perform();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@task *loadEvents() {
|
|
120
|
+
if (!this.device?.id) {
|
|
121
|
+
this.events = toRecentList();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const events = yield this.store.query('device-event', { device_uuid: this.device.id, limit: 10, sort: '-created_at' });
|
|
126
|
+
this.events = toRecentList(events);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<div class="flex flex-col gap-3 p-3" ...attributes>
|
|
2
|
+
<div class="flex items-start justify-between gap-3">
|
|
3
|
+
<div class="min-w-0">
|
|
4
|
+
<h2 class="text-base font-bold text-gray-900 dark:text-gray-100">Sensor Inventory</h2>
|
|
5
|
+
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-300">Recent sensor readings and health for this device.</p>
|
|
6
|
+
</div>
|
|
7
|
+
<Button @icon="refresh" @size="xs" @onClick={{this.refreshSensors}} @isLoading={{this.loadSensors.isRunning}} />
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<Layout::Resource::Tabular
|
|
11
|
+
@resource="sensor"
|
|
12
|
+
@title="Device Sensors"
|
|
13
|
+
@data={{this.sensors}}
|
|
14
|
+
@columns={{this.columns}}
|
|
15
|
+
@controller={{this}}
|
|
16
|
+
@pagination={{false}}
|
|
17
|
+
@withoutHeader={{true}}
|
|
18
|
+
@tableWrapperClass="no-table-extra-spacing"
|
|
19
|
+
@emptyStateComponent={{component
|
|
20
|
+
"table/empty-state"
|
|
21
|
+
variant="compact"
|
|
22
|
+
icon="gauge-high"
|
|
23
|
+
title="No sensors connected"
|
|
24
|
+
description="Sensors appear here after provider telemetry links them to this device."
|
|
25
|
+
primaryText="Refresh"
|
|
26
|
+
primaryIcon="refresh"
|
|
27
|
+
primaryAction=this.refreshSensors
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { tracked } from '@glimmer/tracking';
|
|
5
|
+
import { task } from 'ember-concurrency';
|
|
6
|
+
|
|
7
|
+
function toRecentList(records) {
|
|
8
|
+
const list = Array.from(records ?? []);
|
|
9
|
+
|
|
10
|
+
list.meta = {
|
|
11
|
+
current_page: 1,
|
|
12
|
+
last_page: 1,
|
|
13
|
+
per_page: list.length,
|
|
14
|
+
total: list.length,
|
|
15
|
+
from: list.length > 0 ? 1 : 0,
|
|
16
|
+
to: list.length,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return list;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default class DevicePanelTabsSensorsComponent extends Component {
|
|
23
|
+
@service sensorActions;
|
|
24
|
+
@service store;
|
|
25
|
+
|
|
26
|
+
@tracked sensors = toRecentList();
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
super(...arguments);
|
|
30
|
+
this.loadSensors.perform();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get device() {
|
|
34
|
+
return this.args.resource ?? this.args.model;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get columns() {
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
sticky: true,
|
|
41
|
+
label: 'Sensor',
|
|
42
|
+
valuePath: 'name',
|
|
43
|
+
cellComponent: 'table/cell/anchor',
|
|
44
|
+
action: this.sensorActions.panel?.view ?? this.sensorActions.transition.view,
|
|
45
|
+
permission: 'fleet-ops view sensor',
|
|
46
|
+
resizable: true,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label: 'Type',
|
|
50
|
+
valuePath: 'type',
|
|
51
|
+
cellComponent: 'table/cell/base',
|
|
52
|
+
humanize: true,
|
|
53
|
+
resizable: true,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: 'Value',
|
|
57
|
+
valuePath: 'last_value',
|
|
58
|
+
resizable: true,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: 'Unit',
|
|
62
|
+
valuePath: 'unit',
|
|
63
|
+
resizable: true,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: 'Status',
|
|
67
|
+
valuePath: 'status',
|
|
68
|
+
cellComponent: 'table/cell/status',
|
|
69
|
+
resizable: true,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
label: 'Last Reading',
|
|
73
|
+
valuePath: 'lastReadingAt',
|
|
74
|
+
sortParam: 'last_reading_at',
|
|
75
|
+
resizable: true,
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@action refreshSensors() {
|
|
81
|
+
return this.loadSensors.perform();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@task *loadSensors() {
|
|
85
|
+
if (!this.device?.id) {
|
|
86
|
+
this.sensors = toRecentList();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const sensors = yield this.store.query('sensor', { device_uuid: this.device.id, limit: 10, sort: '-updated_at' });
|
|
91
|
+
this.sensors = toRecentList(sensors);
|
|
92
|
+
}
|
|
93
|
+
}
|