@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,18 +1,72 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
|
-
import { task } from 'ember-concurrency';
|
|
4
|
+
import { task, timeout } from 'ember-concurrency';
|
|
5
|
+
import { SERVICE_QUOTE_REFRESH_REQUESTED } from '../../../services/order-creation';
|
|
6
|
+
|
|
7
|
+
const SERVICE_QUOTE_REFRESH_DEBOUNCE_MS = 500;
|
|
5
8
|
|
|
6
9
|
export default class OrderFormServiceRateComponent extends Component {
|
|
7
10
|
@service serviceRateActions;
|
|
11
|
+
@service orderCreation;
|
|
8
12
|
@tracked selectedRate;
|
|
9
13
|
@tracked serviceRates = [];
|
|
10
14
|
@tracked serviceQuotes = [];
|
|
15
|
+
@tracked isAutoRefreshingServiceQuotes = false;
|
|
16
|
+
serviceQuoteRefreshRequestId = 0;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
super(...arguments);
|
|
20
|
+
this._serviceQuoteRefreshRequested = (event) => this.handleServiceQuoteRefreshRequest(event);
|
|
21
|
+
this.orderCreation.on(SERVICE_QUOTE_REFRESH_REQUESTED, this._serviceQuoteRefreshRequested);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
willDestroy() {
|
|
25
|
+
this.orderCreation.off(SERVICE_QUOTE_REFRESH_REQUESTED, this._serviceQuoteRefreshRequested);
|
|
26
|
+
this.refreshServiceQuotes.cancelAll();
|
|
27
|
+
super.willDestroy(...arguments);
|
|
28
|
+
}
|
|
11
29
|
|
|
12
30
|
get isServicable() {
|
|
13
31
|
return this.args.resource?.order_config && this.args.resource?.payloadCoordinates?.length >= 2;
|
|
14
32
|
}
|
|
15
33
|
|
|
34
|
+
get canRefreshServiceQuotes() {
|
|
35
|
+
return this.args.resource?.servicable && this.selectedRate && this.args.resource?.payloadCoordinates?.length >= 2;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get hasServiceQuotes() {
|
|
39
|
+
return this.serviceQuotes?.length > 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get isLoadingServiceQuotes() {
|
|
43
|
+
return this.getServiceQuotes.isRunning || this.isAutoRefreshingServiceQuotes;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get shouldShowServiceQuotesLoader() {
|
|
47
|
+
return this.isLoadingServiceQuotes && !this.hasServiceQuotes;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get serviceQuoteOverride() {
|
|
51
|
+
return this.orderCreation.getServiceQuoteOverride(this.args.resource);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get hasServiceQuoteOverride() {
|
|
55
|
+
return this.serviceQuoteOverride?.mode === 'locked';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get overrideQuote() {
|
|
59
|
+
return this.serviceQuoteOverride?.quote;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get overrideQuoteItems() {
|
|
63
|
+
return this.overrideQuote?.items ?? [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get overrideCurrency() {
|
|
67
|
+
return this.overrideQuote?.currency ?? this.serviceQuoteOverride?.currency ?? 'USD';
|
|
68
|
+
}
|
|
69
|
+
|
|
16
70
|
@task *queryServiceRates(toggled) {
|
|
17
71
|
this.args.resource.servicable = toggled;
|
|
18
72
|
if (!toggled) return;
|
|
@@ -21,6 +75,74 @@ export default class OrderFormServiceRateComponent extends Component {
|
|
|
21
75
|
|
|
22
76
|
@task *getServiceQuotes(serviceRate) {
|
|
23
77
|
this.selectedRate = serviceRate;
|
|
24
|
-
|
|
78
|
+
yield this.loadServiceQuotes(serviceRate);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@task *refreshServiceQuotes(refreshRequestId) {
|
|
82
|
+
this.isAutoRefreshingServiceQuotes = true;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
yield timeout(SERVICE_QUOTE_REFRESH_DEBOUNCE_MS);
|
|
86
|
+
|
|
87
|
+
if (!this.canRefreshServiceQuotes) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
yield this.loadServiceQuotes(this.selectedRate);
|
|
92
|
+
} finally {
|
|
93
|
+
if (refreshRequestId === this.serviceQuoteRefreshRequestId) {
|
|
94
|
+
this.isAutoRefreshingServiceQuotes = false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
handleServiceQuoteRefreshRequest({ order } = {}) {
|
|
100
|
+
if (order && order !== this.args.resource) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (this.hasServiceQuoteOverride) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (this.args.resource?.servicable && !this.serviceRates?.length && !this.queryServiceRates.isRunning) {
|
|
109
|
+
this.queryServiceRates.perform(true);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!this.canRefreshServiceQuotes) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.refreshServiceQuotes.cancelAll();
|
|
117
|
+
this.serviceQuoteRefreshRequestId++;
|
|
118
|
+
this.isAutoRefreshingServiceQuotes = true;
|
|
119
|
+
this.refreshServiceQuotes.perform(this.serviceQuoteRefreshRequestId);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async loadServiceQuotes(serviceRate) {
|
|
123
|
+
const serviceQuotes = await this.serviceRateActions.getServiceQuotes.perform(serviceRate, this.args.resource);
|
|
124
|
+
this.serviceQuotes = serviceQuotes ?? [];
|
|
125
|
+
this.clearStaleSelectedServiceQuote();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
clearStaleSelectedServiceQuote() {
|
|
129
|
+
const selectedServiceQuote = this.args.resource?.service_quote_uuid;
|
|
130
|
+
|
|
131
|
+
if (!selectedServiceQuote) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!this.serviceQuotes?.length) {
|
|
136
|
+
this.args.resource.service_quote_uuid = null;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const selectedQuoteExists = this.serviceQuotes.some((serviceQuote) => {
|
|
141
|
+
return serviceQuote?.uuid === selectedServiceQuote || serviceQuote?.id === selectedServiceQuote;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!selectedQuoteExists) {
|
|
145
|
+
this.args.resource.service_quote_uuid = null;
|
|
146
|
+
}
|
|
25
147
|
}
|
|
26
148
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
ServiceRate=(component "order/form/service-rate" resource=@resource)
|
|
11
11
|
Notes=(component "order/form/notes" resource=@resource)
|
|
12
12
|
Documents=(component "order/form/documents" resource=@resource)
|
|
13
|
+
OrchestratorConstraints=(component "order/form/orchestrator-constraints" resource=@resource)
|
|
13
14
|
Metadata=(component "order/form/metadata" resource=@resource)
|
|
14
15
|
)
|
|
15
16
|
}}
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
<Order::Form::ServiceRate @resource={{@resource}} />
|
|
24
25
|
<Order::Form::Notes @resource={{@resource}} />
|
|
25
26
|
<Order::Form::Documents @resource={{@resource}} />
|
|
27
|
+
<Order::Form::OrchestratorConstraints @resource={{@resource}} />
|
|
26
28
|
<Order::Form::Metadata @resource={{@resource}} />
|
|
27
29
|
{{/if}}
|
|
28
30
|
</div>
|
|
@@ -1,29 +1,61 @@
|
|
|
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
6
|
src={{@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
|
-
|
|
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
|
+
{{#if this.thresholdStatus}}
|
|
22
|
+
<Badge @status={{if (eq this.thresholdStatus "normal") "success" "warning"}} @size="xs">{{smart-humanize this.thresholdStatus}}</Badge>
|
|
23
|
+
{{/if}}
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="mt-1 flex max-w-2xl flex-wrap items-center gap-1.5">
|
|
27
|
+
{{#if this.type}}
|
|
28
|
+
<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">
|
|
29
|
+
<FaIcon @icon="gauge-high" class="text-gray-400 dark:text-gray-300" />
|
|
30
|
+
<span class="truncate">{{n-a (get-fleet-ops-option-label "sensorTypes" this.type)}}</span>
|
|
31
|
+
</span>
|
|
32
|
+
{{/if}}
|
|
33
|
+
{{#if this.identifier}}
|
|
34
|
+
<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">
|
|
35
|
+
<FaIcon @icon="id-card" class="text-gray-400 dark:text-gray-300" />
|
|
36
|
+
<span class="truncate">{{this.identifier}}</span>
|
|
37
|
+
</span>
|
|
38
|
+
{{/if}}
|
|
39
|
+
{{#if this.reading}}
|
|
40
|
+
<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">
|
|
41
|
+
<FaIcon @icon="chart-line" class="text-gray-400 dark:text-gray-300" />
|
|
42
|
+
<span class="truncate">{{this.reading}}</span>
|
|
43
|
+
</span>
|
|
44
|
+
{{/if}}
|
|
45
|
+
{{#if this.deviceName}}
|
|
46
|
+
<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">
|
|
47
|
+
<FaIcon @icon="microchip" class="text-gray-400 dark:text-gray-300" />
|
|
48
|
+
<span class="truncate">{{this.deviceName}}</span>
|
|
49
|
+
</span>
|
|
50
|
+
{{/if}}
|
|
51
|
+
<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">
|
|
52
|
+
<FaIcon @icon="clock" class="text-gray-400 dark:text-gray-300" />
|
|
53
|
+
<span class="truncate">{{#if this.lastReadingAt}}{{format-date-fns this.lastReadingAt "dd MMM yyyy, HH:mm"}}{{else}}No reading yet{{/if}}</span>
|
|
54
|
+
</span>
|
|
22
55
|
</div>
|
|
23
|
-
<Badge @status={{if @resource.online "online" "offline"}}>{{if @resource.online (t "common.online") (t "common.offline")}}</Badge>
|
|
24
56
|
</div>
|
|
25
57
|
</div>
|
|
26
|
-
<div class="next-view-header-right">
|
|
58
|
+
<div class="next-view-header-right flex-shrink-0">
|
|
27
59
|
<Layout::Resource::Panel::HeaderActions
|
|
28
60
|
@resource={{@resource}}
|
|
29
61
|
@saveTask={{@saveTask}}
|
|
@@ -36,4 +68,4 @@
|
|
|
36
68
|
/>
|
|
37
69
|
</div>
|
|
38
70
|
</div>
|
|
39
|
-
</div>
|
|
71
|
+
</div>
|
|
@@ -1,3 +1,61 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
|
+
import { get } from '@ember/object';
|
|
2
3
|
|
|
3
|
-
export default class SensorPanelHeaderComponent extends Component {
|
|
4
|
+
export default class SensorPanelHeaderComponent 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, 'serial_number') ??
|
|
15
|
+
get(this.resource, 'imei') ??
|
|
16
|
+
get(this.resource, 'public_id') ??
|
|
17
|
+
'-'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get status() {
|
|
22
|
+
return get(this.resource, 'status') ?? (get(this.resource, 'is_active') ? 'active' : 'inactive');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get thresholdStatus() {
|
|
26
|
+
return get(this.resource, 'threshold_status');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get type() {
|
|
30
|
+
return get(this.resource, 'type');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get identifier() {
|
|
34
|
+
return get(this.resource, 'serial_number') ?? get(this.resource, 'imei') ?? get(this.resource, 'internal_id') ?? get(this.resource, 'public_id');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get reading() {
|
|
38
|
+
const value = get(this.resource, 'last_value');
|
|
39
|
+
const unit = get(this.resource, 'unit');
|
|
40
|
+
|
|
41
|
+
if (value === null || value === undefined || value === '') {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return unit ? `${value} ${unit}` : String(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get deviceName() {
|
|
49
|
+
return (
|
|
50
|
+
get(this.resource, 'device.displayName') ??
|
|
51
|
+
get(this.resource, 'device.display_name') ??
|
|
52
|
+
get(this.resource, 'device.name') ??
|
|
53
|
+
get(this.resource, 'device_name') ??
|
|
54
|
+
get(this.resource, 'device_uuid')
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get lastReadingAt() {
|
|
59
|
+
return get(this.resource, 'last_reading_at') ?? get(this.resource, 'lastReadingAt');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{{#if this.visible}}
|
|
2
|
+
{{#if @columnAction.separator}}
|
|
3
|
+
<div class="next-dd-menu-seperator"></div>
|
|
4
|
+
{{else}}
|
|
5
|
+
<div role="group" class="px-1">
|
|
6
|
+
<a
|
|
7
|
+
href="javascript:;"
|
|
8
|
+
role="menuitem"
|
|
9
|
+
class="next-dd-item {{@columnAction.class}} {{if this.disabled 'disabled'}}"
|
|
10
|
+
disabled={{this.disabled}}
|
|
11
|
+
{{on "click" (fn this.onClick @columnAction @row @dd)}}
|
|
12
|
+
>
|
|
13
|
+
{{#if @columnAction.icon}}
|
|
14
|
+
<div class="w-6">
|
|
15
|
+
<FaIcon class={{@columnAction.iconClass}} @icon={{@columnAction.icon}} @prefix={{@columnAction.iconPrefix}} />
|
|
16
|
+
</div>
|
|
17
|
+
{{/if}}
|
|
18
|
+
{{@columnAction.label}}
|
|
19
|
+
{{#if this.doesntHavePermissions}}
|
|
20
|
+
<Attach::Tooltip @class="clean" @animation="scale" @placement={{or @columnAction.tooltipPlacement "right"}}>
|
|
21
|
+
<InputInfo @text={{t "common.unauthorized"}} />
|
|
22
|
+
</Attach::Tooltip>
|
|
23
|
+
{{else if @columnAction.helpText}}
|
|
24
|
+
<Attach::Tooltip @class="clean" @animation="scale" @placement={{or @columnAction.tooltipPlacement "right"}}>
|
|
25
|
+
<InputInfo @text={{@columnAction.helpText}} @exampleText={{@columnAction.exampleText}} />
|
|
26
|
+
</Attach::Tooltip>
|
|
27
|
+
{{/if}}
|
|
28
|
+
</a>
|
|
29
|
+
</div>
|
|
30
|
+
{{/if}}
|
|
31
|
+
{{/if}}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { isNone } from '@ember/utils';
|
|
6
|
+
|
|
7
|
+
export default class TableCellDropdownActionItemComponent extends Component {
|
|
8
|
+
@service abilities;
|
|
9
|
+
@tracked permissionRequired;
|
|
10
|
+
@tracked doesntHavePermissions = false;
|
|
11
|
+
@tracked disabled = false;
|
|
12
|
+
@tracked visible = true;
|
|
13
|
+
|
|
14
|
+
constructor(owner, { columnAction = {}, row = {}, disabled = false, permission = null }) {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
this.permissionRequired = columnAction.permission ?? permission;
|
|
17
|
+
this.disabled = this.disabledCheck(columnAction, this.permissionRequired, disabled);
|
|
18
|
+
this.visible = (columnAction.visible ?? true) && this.visibilityCheck(columnAction, row);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@action onClick(columnAction, row, dd) {
|
|
22
|
+
if (this.disabled) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeof dd?.actions?.close === 'function') {
|
|
27
|
+
dd.actions.close();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof columnAction?.fn === 'function') {
|
|
31
|
+
columnAction.fn(row);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disabledCheck(columnAction, permission, defaultValue = false) {
|
|
36
|
+
let disabled = columnAction.disabled ?? defaultValue;
|
|
37
|
+
if (!disabled) {
|
|
38
|
+
disabled = permission && this.abilities.cannot(permission);
|
|
39
|
+
this.doesntHavePermissions = disabled;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return disabled;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
visibilityCheck(columnAction, context) {
|
|
46
|
+
const isVisible = columnAction.isVisible;
|
|
47
|
+
|
|
48
|
+
if (isNone(context) || !isVisible) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof isVisible === 'boolean') {
|
|
53
|
+
return isVisible;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof isVisible === 'function') {
|
|
57
|
+
return isVisible(context);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div class="cell-dropdown-button overflow-visible {{@column.wrapperClass}}" {{did-insert this.setupComponent}} ...attributes>
|
|
2
|
+
<DropdownButton
|
|
3
|
+
@icon={{@column.ddButtonIcon}}
|
|
4
|
+
@iconPrefix={{@column.ddButtonIconPrefix}}
|
|
5
|
+
@text={{this.buttonText}}
|
|
6
|
+
@size="xs"
|
|
7
|
+
@horizontalPosition="left"
|
|
8
|
+
@calculatePosition={{this.calculatePosition}}
|
|
9
|
+
@renderInPlace={{true}}
|
|
10
|
+
@onOpen={{this.onOpen}}
|
|
11
|
+
@onClose={{this.onClose}}
|
|
12
|
+
as |dd|
|
|
13
|
+
>
|
|
14
|
+
<div class="next-dd-menu next-dd-menu-table-dd mt-0i {{@column.ddMenuWrapperClass}}" role="menu" aria-orientation="vertical" aria-labelledby="user-menu">
|
|
15
|
+
{{#if @column.ddMenuLabel}}
|
|
16
|
+
<div class="next-dd-menu-label-container px-1">
|
|
17
|
+
<div class="next-dd-menu-label {{@column.ddMenuLabelClass}}">
|
|
18
|
+
{{@column.ddMenuLabel}}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="next-dd-menu-seperator"></div>
|
|
22
|
+
{{/if}}
|
|
23
|
+
{{#each @column.actions as |columnAction|}}
|
|
24
|
+
<Table::Cell::Dropdown::ActionItem @columnAction={{columnAction}} @row={{@row}} @column={{@column}} @dd={{dd}} />
|
|
25
|
+
{{/each}}
|
|
26
|
+
</div>
|
|
27
|
+
</DropdownButton>
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action, computed } from '@ember/object';
|
|
4
|
+
|
|
5
|
+
export default class TableCellDropdownComponent extends Component {
|
|
6
|
+
@tracked tableCellNode;
|
|
7
|
+
defaultButtonText = 'Actions';
|
|
8
|
+
|
|
9
|
+
@computed('args.column.ddButtonText', 'defaultButtonText') get buttonText() {
|
|
10
|
+
const { ddButtonText } = this.args.column;
|
|
11
|
+
|
|
12
|
+
if (ddButtonText === undefined) {
|
|
13
|
+
return this.defaultButtonText;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (ddButtonText === false) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return ddButtonText;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@action setupComponent(dropdownWrapperNode) {
|
|
24
|
+
const tableCellNode = this.getOwnerTableCell(dropdownWrapperNode);
|
|
25
|
+
|
|
26
|
+
if (tableCellNode) {
|
|
27
|
+
tableCellNode.style.overflow = 'visible';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.tableCellNode = tableCellNode;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@action onOpen() {
|
|
34
|
+
if (!this.tableCellNode) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const currentZIndex = Number.parseInt(this.tableCellNode.style.zIndex, 10) || 0;
|
|
39
|
+
this.tableCellNode.style.zIndex = currentZIndex + 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@action onClose() {
|
|
43
|
+
if (!this.tableCellNode) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const currentZIndex = Number.parseInt(this.tableCellNode.style.zIndex, 10) || 1;
|
|
48
|
+
this.tableCellNode.style.zIndex = currentZIndex - 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@action getOwnerTableCell(dropdownWrapperNode) {
|
|
52
|
+
while (dropdownWrapperNode) {
|
|
53
|
+
dropdownWrapperNode = dropdownWrapperNode.parentNode;
|
|
54
|
+
|
|
55
|
+
if (dropdownWrapperNode?.tagName?.toLowerCase() === 'td') {
|
|
56
|
+
return dropdownWrapperNode;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@action onDropdownItemClick(columnAction, row, dd) {
|
|
64
|
+
if (typeof dd?.actions?.close === 'function') {
|
|
65
|
+
dd.actions.close();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof columnAction?.fn === 'function') {
|
|
69
|
+
columnAction.fn(row);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@action calculatePosition(trigger, content) {
|
|
74
|
+
if (typeof this.args.column?.calculatePosition === 'function') {
|
|
75
|
+
return this.args.column.calculatePosition(trigger, content);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
79
|
+
const contentRect = content?.getBoundingClientRect?.();
|
|
80
|
+
const contentWidth = contentRect?.width || 224;
|
|
81
|
+
|
|
82
|
+
const style = {
|
|
83
|
+
position: 'fixed',
|
|
84
|
+
marginTop: '0px',
|
|
85
|
+
left: `${triggerRect.left - contentWidth - 3}px`,
|
|
86
|
+
top: `${triggerRect.top}px`,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return { style };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
{{#if this.attentionItems.length}}
|
|
33
33
|
<section class="flex flex-col gap-3">
|
|
34
34
|
{{#each this.attentionItems as |item|}}
|
|
35
|
-
<div class="w-full rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-700 dark:bg-
|
|
35
|
+
<div class="w-full rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-700 dark:bg-yellow-900/20">
|
|
36
36
|
<div class="flex items-start gap-3">
|
|
37
37
|
<div class="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-md bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300">
|
|
38
38
|
<FaIcon @icon={{item.icon}} />
|
|
@@ -88,6 +88,12 @@
|
|
|
88
88
|
<div class="text-yellow-700 dark:text-yellow-300">Webhook URL unavailable until this integration has a public ID.</div>
|
|
89
89
|
{{/if}}
|
|
90
90
|
</div>
|
|
91
|
+
{{else}}
|
|
92
|
+
<div class="rounded-md border border-gray-200 p-3 dark:border-gray-700 md:col-span-2">
|
|
93
|
+
<div class="text-gray-400 dark:text-gray-500">Update Mode</div>
|
|
94
|
+
<div class="mt-1 font-semibold text-gray-900 dark:text-gray-100">Provider polling</div>
|
|
95
|
+
<div class="mt-1 text-gray-500 dark:text-gray-400">FleetOps polls this provider for device snapshots and telemetry updates.</div>
|
|
96
|
+
</div>
|
|
91
97
|
{{/if}}
|
|
92
98
|
</div>
|
|
93
99
|
</div>
|
|
@@ -415,13 +415,17 @@ export default class TelematicFormComponent extends Component {
|
|
|
415
415
|
this.args.resource.setProperties({
|
|
416
416
|
name: this.args.resource.name ?? provider.label,
|
|
417
417
|
provider: provider.key,
|
|
418
|
-
credentials: (provider
|
|
419
|
-
acc[item.name] = item.advanced || item.is_endpoint ? null : (item.default_value ?? null);
|
|
420
|
-
return acc;
|
|
421
|
-
}, {}),
|
|
418
|
+
credentials: this.buildProviderCredentials(provider),
|
|
422
419
|
});
|
|
423
420
|
}
|
|
424
421
|
|
|
422
|
+
buildProviderCredentials(provider) {
|
|
423
|
+
return (provider.required_fields ?? []).reduce((acc, item) => {
|
|
424
|
+
acc[item.name] = item.default_value ?? null;
|
|
425
|
+
return acc;
|
|
426
|
+
}, {});
|
|
427
|
+
}
|
|
428
|
+
|
|
425
429
|
@action goToStep(index) {
|
|
426
430
|
if (!this.canReachStep(index)) {
|
|
427
431
|
return;
|
|
@@ -553,10 +557,7 @@ export default class TelematicFormComponent extends Component {
|
|
|
553
557
|
this.lastConnectionTestCompletedAt = null;
|
|
554
558
|
|
|
555
559
|
try {
|
|
556
|
-
const result = yield this.fetch.post(`telematics/${this.selectedProvider.key}/test-credentials`,
|
|
557
|
-
credentials: this.args.resource.credentials,
|
|
558
|
-
telematic_id: this.args.resource?.id,
|
|
559
|
-
});
|
|
560
|
+
const result = yield this.fetch.post(`telematics/${this.selectedProvider.key}/test-credentials`, this.getConnectionTestPayload());
|
|
560
561
|
this.connectionTestResult = result;
|
|
561
562
|
this.lastConnectionTestCompletedAt = new Date();
|
|
562
563
|
this.updateResourceConnectionTestMeta(result);
|
|
@@ -577,6 +578,13 @@ export default class TelematicFormComponent extends Component {
|
|
|
577
578
|
}
|
|
578
579
|
}
|
|
579
580
|
|
|
581
|
+
getConnectionTestPayload() {
|
|
582
|
+
return {
|
|
583
|
+
credentials: this.args.resource.credentials,
|
|
584
|
+
telematic_id: this.args.resource?.id,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
580
588
|
updateResourceConnectionTestMeta(result) {
|
|
581
589
|
const meta = this.args.resource?.meta ?? {};
|
|
582
590
|
const testedAt = this.lastConnectionTestCompletedAt ?? new Date();
|