@fleetbase/fleetops-engine 0.6.20 → 0.6.22
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/custom-entity/form.hbs +14 -14
- package/addon/components/device/card.hbs +1 -0
- package/addon/components/device/card.js +3 -0
- package/addon/components/device/details.hbs +92 -43
- package/addon/components/device/form.hbs +108 -60
- package/addon/components/device/form.js +36 -8
- package/addon/components/device/manager.hbs +29 -0
- package/addon/components/device/manager.js +95 -0
- package/addon/components/device/panel-header.hbs +32 -0
- package/addon/components/device/panel-header.js +3 -0
- package/addon/components/device/pill.hbs +16 -0
- package/addon/components/device/pill.js +3 -0
- package/addon/components/driver/details.hbs +4 -0
- package/addon/components/driver/details.js +19 -1
- package/addon/components/driver/form.hbs +14 -3
- package/addon/components/driver/form.js +49 -47
- package/addon/components/driver/pill.hbs +17 -0
- package/addon/components/driver/pill.js +3 -0
- package/addon/components/entity/form.hbs +7 -5
- package/addon/components/layout/fleet-ops-sidebar.js +12 -12
- package/addon/components/map/drawer/device-event-listing.hbs +64 -0
- package/addon/components/map/drawer/device-event-listing.js +181 -0
- package/addon/components/map/drawer/position-listing.hbs +100 -0
- package/addon/components/map/drawer/position-listing.js +455 -0
- package/addon/components/map/drawer.js +2 -0
- package/addon/components/map/leaflet-live-map.hbs +7 -2
- package/addon/components/modals/attach-device.hbs +18 -0
- package/addon/components/modals/attach-device.js +3 -0
- package/addon/components/order/details/detail.hbs +2 -54
- package/addon/components/order/details/detail.js +1 -0
- package/addon/components/order/details/payload.hbs +6 -4
- package/addon/components/order/details/payload.js +2 -0
- package/addon/components/order/pill.hbs +34 -0
- package/addon/components/order/pill.js +3 -0
- package/addon/components/order-config-manager/custom-fields.js +1 -1
- package/addon/components/positions-replay.hbs +339 -0
- package/addon/components/positions-replay.js +409 -0
- package/addon/components/sensor/details.hbs +64 -38
- package/addon/components/sensor/form.hbs +112 -63
- package/addon/components/sensor/form.js +36 -24
- package/addon/components/sensor/panel-header.hbs +32 -0
- package/addon/components/sensor/panel-header.js +3 -0
- package/addon/components/telematic/details.hbs +40 -16
- package/addon/components/telematic/form.hbs +63 -64
- package/addon/components/telematic/form.js +73 -4
- package/addon/components/vehicle/card.hbs +2 -2
- package/addon/components/vehicle/details.hbs +4 -0
- package/addon/components/vehicle/details.js +19 -1
- package/addon/components/vehicle/form.hbs +4 -0
- package/addon/components/vehicle/pill.hbs +34 -0
- package/addon/components/vehicle/pill.js +3 -0
- package/addon/controllers/analytics/reports/index/edit.js +1 -1
- package/addon/controllers/connectivity/devices/index/details.js +22 -1
- package/addon/controllers/connectivity/devices/index/edit.js +66 -1
- package/addon/controllers/connectivity/devices/index.js +51 -9
- package/addon/controllers/connectivity/events/index.js +65 -16
- package/addon/controllers/connectivity/sensors/index/details.js +22 -1
- package/addon/controllers/connectivity/sensors/index/edit.js +66 -1
- package/addon/controllers/connectivity/sensors/index.js +66 -6
- package/addon/controllers/connectivity/telematics/index/details.js +22 -1
- package/addon/controllers/connectivity/telematics/index/edit.js +66 -1
- package/addon/controllers/connectivity/telematics/index.js +20 -11
- package/addon/controllers/management/fleets/index/details.js +26 -21
- package/addon/controllers/management/fleets/index/edit.js +9 -6
- package/addon/controllers/management/vehicles/index/details.js +26 -13
- package/addon/controllers/settings/custom-fields.js +6 -0
- package/addon/helpers/get-fleet-ops-option-label.js +11 -0
- package/addon/routes/connectivity/devices/index/details.js +27 -1
- package/addon/routes/connectivity/devices/index/edit.js +27 -1
- package/addon/routes/connectivity/sensors/index/details.js +27 -1
- package/addon/routes/connectivity/sensors/index/edit.js +27 -1
- package/addon/routes/connectivity/telematics/index/details.js +27 -1
- package/addon/routes/connectivity/telematics/index/edit.js +27 -1
- package/addon/routes/management/drivers/index/details/positions.js +3 -0
- package/addon/routes/management/vehicles/index/details/positions.js +3 -0
- package/addon/routes.js +4 -0
- package/addon/services/movement-tracker.js +81 -30
- package/addon/services/position-playback.js +486 -0
- package/addon/services/resource-metadata.js +46 -0
- package/addon/styles/fleetops-engine.css +157 -0
- package/addon/templates/connectivity/devices/index/details/index.hbs +2 -2
- package/addon/templates/connectivity/devices/index/details.hbs +15 -2
- package/addon/templates/connectivity/devices/index/edit.hbs +1 -1
- package/addon/templates/connectivity/events/index.hbs +1 -1
- package/addon/templates/connectivity/sensors/index/details/index.hbs +2 -2
- package/addon/templates/connectivity/sensors/index/details.hbs +15 -2
- package/addon/templates/connectivity/sensors/index/edit.hbs +1 -1
- package/addon/templates/connectivity/telematics/index/details/index.hbs +2 -2
- package/addon/templates/connectivity/telematics/index/details.hbs +14 -2
- package/addon/templates/connectivity/telematics/index/edit.hbs +1 -1
- package/addon/templates/management/drivers/index/details/positions.hbs +2 -0
- package/addon/templates/management/vehicles/index/details/devices.hbs +1 -2
- package/addon/templates/management/vehicles/index/details/positions.hbs +1 -0
- package/addon/utils/fleet-ops-options.js +95 -0
- package/app/components/device/card.js +1 -0
- package/app/components/device/manager.js +1 -0
- package/app/components/device/panel-header.js +1 -0
- package/app/components/device/pill.js +1 -0
- package/app/components/driver/pill.js +1 -0
- package/app/components/map/drawer/device-event-listing.js +1 -0
- package/app/components/map/drawer/position-listing.js +1 -0
- package/app/components/modals/attach-device.js +1 -0
- package/app/components/order/pill.js +1 -0
- package/app/components/positions-replay.js +1 -0
- package/app/components/sensor/panel-header.js +1 -0
- package/app/components/vehicle/pill.js +1 -0
- package/app/helpers/get-fleet-ops-option-label.js +1 -0
- package/app/routes/management/drivers/index/details/positions.js +1 -0
- package/app/routes/management/vehicles/index/details/positions.js +1 -0
- package/app/services/position-playback.js +1 -0
- package/app/services/resource-metadata.js +1 -0
- package/app/templates/management/drivers/index/details/positions.js +1 -0
- package/app/templates/management/vehicles/index/details/positions.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/config/telematics.php +111 -0
- package/server/migrations/2025_10_27_000001_add_telematics_integration_fields.php +70 -0
- package/server/migrations/2025_10_27_171322_fix_device_column_names.php +107 -0
- package/server/migrations/2025_10_27_203023_add_company_uuid_to_device_events_table.php +28 -0
- package/server/src/Console/Commands/ReplayVehicleLocations.php +225 -0
- package/server/src/Contracts/TelematicProviderDescriptor.php +72 -0
- package/server/src/Contracts/TelematicProviderInterface.php +119 -0
- package/server/src/Exceptions/TelematicProviderException.php +14 -0
- package/server/src/Exceptions/TelematicRateLimitExceededException.php +12 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +24 -14
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +27 -7
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +22 -0
- package/server/src/Http/Controllers/Internal/v1/PositionController.php +240 -0
- package/server/src/Http/Controllers/Internal/v1/SensorController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +141 -0
- package/server/src/Http/Controllers/TelematicWebhookController.php +169 -0
- package/server/src/Http/Filter/DeviceEventFilter.php +68 -0
- package/server/src/Http/Filter/PositionFilter.php +35 -0
- package/server/src/Http/Resources/v1/Position.php +44 -0
- package/server/src/Jobs/ReplayPositions.php +64 -0
- package/server/src/Jobs/SendPositionReplay.php +65 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +106 -0
- package/server/src/Jobs/TestTelematicConnectionJob.php +102 -0
- package/server/src/Models/Asset.php +10 -8
- package/server/src/Models/Device.php +79 -12
- package/server/src/Models/DeviceEvent.php +33 -3
- package/server/src/Models/Driver.php +28 -1
- package/server/src/Models/Maintenance.php +15 -12
- package/server/src/Models/Part.php +2 -0
- package/server/src/Models/Payload.php +0 -1
- package/server/src/Models/Place.php +4 -1
- package/server/src/Models/Position.php +27 -17
- package/server/src/Models/Sensor.php +78 -13
- package/server/src/Models/Telematic.php +116 -6
- package/server/src/Models/TrackingNumber.php +3 -1
- package/server/src/Models/Vehicle.php +8 -11
- package/server/src/Models/WorkOrder.php +8 -5
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
- package/server/src/Support/Telematics/Providers/AbstractProvider.php +151 -0
- package/server/src/Support/Telematics/Providers/FlespiProvider.php +182 -0
- package/server/src/Support/Telematics/Providers/GeotabProvider.php +181 -0
- package/server/src/Support/Telematics/Providers/SamsaraProvider.php +177 -0
- package/server/src/Support/Telematics/TelematicProviderRegistry.php +147 -0
- package/server/src/Support/Telematics/TelematicService.php +223 -0
- package/server/src/Support/Utils.php +1 -1
- package/server/src/routes.php +24 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Models;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Casts\Json;
|
|
6
|
+
use Fleetbase\FleetOps\Support\Telematics\TelematicProviderRegistry;
|
|
6
7
|
use Fleetbase\Models\Model;
|
|
7
8
|
use Fleetbase\Models\User;
|
|
8
9
|
use Fleetbase\Traits\HasApiModelBehavior;
|
|
@@ -85,8 +86,8 @@ class Telematic extends Model
|
|
|
85
86
|
'last_seen_at',
|
|
86
87
|
'last_metrics',
|
|
87
88
|
'config',
|
|
89
|
+
'credentials',
|
|
88
90
|
'meta',
|
|
89
|
-
'slug',
|
|
90
91
|
];
|
|
91
92
|
|
|
92
93
|
/**
|
|
@@ -94,7 +95,7 @@ class Telematic extends Model
|
|
|
94
95
|
*
|
|
95
96
|
* @var array
|
|
96
97
|
*/
|
|
97
|
-
protected $appends = ['warranty_name', 'is_online', 'signal_strength', 'last_location'];
|
|
98
|
+
protected $appends = ['warranty_name', 'is_online', 'signal_strength', 'last_location', 'provider_descriptor'];
|
|
98
99
|
|
|
99
100
|
/**
|
|
100
101
|
* The attributes excluded from the model's JSON form.
|
|
@@ -109,10 +110,97 @@ class Telematic extends Model
|
|
|
109
110
|
* @var array
|
|
110
111
|
*/
|
|
111
112
|
protected $casts = [
|
|
112
|
-
'last_seen_at'
|
|
113
|
-
'last_metrics'
|
|
114
|
-
'config'
|
|
115
|
-
'
|
|
113
|
+
'last_seen_at' => 'datetime',
|
|
114
|
+
'last_metrics' => Json::class,
|
|
115
|
+
'config' => Json::class,
|
|
116
|
+
'credentials' => Json::class,
|
|
117
|
+
'meta' => Json::class,
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Telematic statuses.
|
|
122
|
+
*
|
|
123
|
+
* @var array
|
|
124
|
+
*/
|
|
125
|
+
public static $statuses = [
|
|
126
|
+
[
|
|
127
|
+
'key' => 'initialized',
|
|
128
|
+
'label' => 'Initialized',
|
|
129
|
+
'description' => 'Provider entry has been created but not yet configured.',
|
|
130
|
+
],
|
|
131
|
+
[
|
|
132
|
+
'key' => 'configured',
|
|
133
|
+
'label' => 'Configured',
|
|
134
|
+
'description' => 'Provider credentials and settings are valid but connection not yet tested.',
|
|
135
|
+
],
|
|
136
|
+
[
|
|
137
|
+
'key' => 'connecting',
|
|
138
|
+
'label' => 'Connecting',
|
|
139
|
+
'description' => 'Attempting to establish a connection with provider API.',
|
|
140
|
+
],
|
|
141
|
+
[
|
|
142
|
+
'key' => 'connected',
|
|
143
|
+
'label' => 'Connected',
|
|
144
|
+
'description' => 'Successfully authenticated and connected to provider API.',
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
'key' => 'synchronizing',
|
|
148
|
+
'label' => 'Synchronizing',
|
|
149
|
+
'description' => 'Currently syncing data (devices, vehicles, positions, etc.) from provider.',
|
|
150
|
+
],
|
|
151
|
+
[
|
|
152
|
+
'key' => 'active',
|
|
153
|
+
'label' => 'Active',
|
|
154
|
+
'description' => 'Integration is healthy and data syncs are occurring normally.',
|
|
155
|
+
],
|
|
156
|
+
[
|
|
157
|
+
'key' => 'degraded',
|
|
158
|
+
'label' => 'Degraded',
|
|
159
|
+
'description' => 'Integration partially working; intermittent errors or missing data.',
|
|
160
|
+
],
|
|
161
|
+
[
|
|
162
|
+
'key' => 'disconnected',
|
|
163
|
+
'label' => 'Disconnected',
|
|
164
|
+
'description' => 'Connection lost or failed authentication.',
|
|
165
|
+
],
|
|
166
|
+
[
|
|
167
|
+
'key' => 'error',
|
|
168
|
+
'label' => 'Error',
|
|
169
|
+
'description' => 'Provider integration encountered a fatal issue.',
|
|
170
|
+
],
|
|
171
|
+
[
|
|
172
|
+
'key' => 'disabled',
|
|
173
|
+
'label' => 'Disabled',
|
|
174
|
+
'description' => 'Manually disabled by the user.',
|
|
175
|
+
],
|
|
176
|
+
[
|
|
177
|
+
'key' => 'archived',
|
|
178
|
+
'label' => 'Archived',
|
|
179
|
+
'description' => 'Deprecated or replaced integration, kept for record.',
|
|
180
|
+
],
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Telematic health statuses.
|
|
185
|
+
*
|
|
186
|
+
* @var array
|
|
187
|
+
*/
|
|
188
|
+
public static $healthStates = [
|
|
189
|
+
[
|
|
190
|
+
'key' => 'healthy',
|
|
191
|
+
'label' => 'Healthy',
|
|
192
|
+
'description' => 'Integration tested and stable.',
|
|
193
|
+
],
|
|
194
|
+
[
|
|
195
|
+
'key' => 'warning',
|
|
196
|
+
'label' => 'Warning',
|
|
197
|
+
'description' => 'Minor issues detected (e.g., slow response, nearing quota).',
|
|
198
|
+
],
|
|
199
|
+
[
|
|
200
|
+
'key' => 'critical',
|
|
201
|
+
'label' => 'Critical',
|
|
202
|
+
'description' => 'Persistent failure or no data received in X hours.',
|
|
203
|
+
],
|
|
116
204
|
];
|
|
117
205
|
|
|
118
206
|
/**
|
|
@@ -169,6 +257,20 @@ class Telematic extends Model
|
|
|
169
257
|
return $this->hasMany(Asset::class, 'telematic_uuid', 'uuid');
|
|
170
258
|
}
|
|
171
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Get the provider config.
|
|
262
|
+
*/
|
|
263
|
+
public function getProviderDescriptorAttribute(): array
|
|
264
|
+
{
|
|
265
|
+
$registry = app(TelematicProviderRegistry::class);
|
|
266
|
+
$provider = $registry->findByKey($this->provider);
|
|
267
|
+
if ($provider) {
|
|
268
|
+
return $provider->toArray();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
|
|
172
274
|
/**
|
|
173
275
|
* Get the warranty name.
|
|
174
276
|
*/
|
|
@@ -333,4 +435,12 @@ class Telematic extends Model
|
|
|
333
435
|
|
|
334
436
|
return true;
|
|
335
437
|
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Sets a default status if none input.
|
|
441
|
+
*/
|
|
442
|
+
public function setStatusAttribute(?string $status = null): void
|
|
443
|
+
{
|
|
444
|
+
$this->attributes['status'] = $status ?? 'initialized';
|
|
445
|
+
}
|
|
336
446
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Models;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\Casts\PolymorphicType;
|
|
5
6
|
use Fleetbase\FleetOps\Casts\Point;
|
|
6
7
|
use Fleetbase\FleetOps\Support\Utils;
|
|
7
8
|
use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialTrait;
|
|
@@ -66,7 +67,8 @@ class TrackingNumber extends Model
|
|
|
66
67
|
* @var array
|
|
67
68
|
*/
|
|
68
69
|
protected $casts = [
|
|
69
|
-
'location'
|
|
70
|
+
'location' => Point::class,
|
|
71
|
+
'owner_type' => PolymorphicType::class,
|
|
70
72
|
];
|
|
71
73
|
|
|
72
74
|
/**
|
|
@@ -536,10 +536,7 @@ class Vehicle extends Model
|
|
|
536
536
|
}
|
|
537
537
|
|
|
538
538
|
/**
|
|
539
|
-
* Creates a new position for the vehicle
|
|
540
|
-
*
|
|
541
|
-
* @param array $attributes
|
|
542
|
-
* @return Position|null
|
|
539
|
+
* Creates a new position for the vehicle.
|
|
543
540
|
*/
|
|
544
541
|
public function createPosition(array $attributes = [], Model|string|null $destination = null): ?Position
|
|
545
542
|
{
|
|
@@ -555,11 +552,11 @@ class Vehicle extends Model
|
|
|
555
552
|
$destinationUuid = Str::isUuid($destination) ? $destination : data_get($destination, 'uuid');
|
|
556
553
|
|
|
557
554
|
return Position::create([
|
|
558
|
-
...Arr::only($attributes, ['coordinates', 'heading', 'bearing', 'speed', 'altitude']),
|
|
559
|
-
'subject_uuid'
|
|
560
|
-
'subject_type'
|
|
561
|
-
'company_uuid'
|
|
562
|
-
'destination_uuid' => $destinationUuid
|
|
555
|
+
...Arr::only($attributes, ['coordinates', 'heading', 'bearing', 'speed', 'altitude', 'order_uuid']),
|
|
556
|
+
'subject_uuid' => $this->uuid,
|
|
557
|
+
'subject_type' => $this->getMorphClass(),
|
|
558
|
+
'company_uuid' => $this->company_uuid,
|
|
559
|
+
'destination_uuid' => $destinationUuid,
|
|
563
560
|
]);
|
|
564
561
|
}
|
|
565
562
|
|
|
@@ -760,8 +757,8 @@ class Vehicle extends Model
|
|
|
760
757
|
*/
|
|
761
758
|
public function setVinDatas(array $newVinData = []): array
|
|
762
759
|
{
|
|
763
|
-
$vinData
|
|
764
|
-
$vinData
|
|
760
|
+
$vinData = is_array($this->vin_data) ? $this->vin_data : (array) $this->vin_data;
|
|
761
|
+
$vinData = array_merge($vinData, $newVinData);
|
|
765
762
|
$this->vin_data = $vinData;
|
|
766
763
|
|
|
767
764
|
return $vinData;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Models;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Casts\Json;
|
|
6
|
+
use Fleetbase\Casts\PolymorphicType;
|
|
6
7
|
use Fleetbase\Models\File;
|
|
7
8
|
use Fleetbase\Models\Model;
|
|
8
9
|
use Fleetbase\Models\User;
|
|
@@ -115,11 +116,13 @@ class WorkOrder extends Model
|
|
|
115
116
|
* @var array
|
|
116
117
|
*/
|
|
117
118
|
protected $casts = [
|
|
118
|
-
'opened_at'
|
|
119
|
-
'due_at'
|
|
120
|
-
'closed_at'
|
|
121
|
-
'checklist'
|
|
122
|
-
'meta'
|
|
119
|
+
'opened_at' => 'datetime',
|
|
120
|
+
'due_at' => 'datetime',
|
|
121
|
+
'closed_at' => 'datetime',
|
|
122
|
+
'checklist' => Json::class,
|
|
123
|
+
'meta' => Json::class,
|
|
124
|
+
'target_type' => PolymorphicType::class,
|
|
125
|
+
'assignee_type' => PolymorphicType::class,
|
|
123
126
|
];
|
|
124
127
|
|
|
125
128
|
/**
|
|
@@ -59,6 +59,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
59
59
|
\Fleetbase\FleetOps\Console\Commands\DebugOrderTracker::class,
|
|
60
60
|
\Fleetbase\FleetOps\Console\Commands\PurgeUnpurchasedServiceQuotes::class,
|
|
61
61
|
\Fleetbase\FleetOps\Console\Commands\SendDriverNotification::class,
|
|
62
|
+
\Fleetbase\FleetOps\Console\Commands\ReplayVehicleLocations::class,
|
|
62
63
|
];
|
|
63
64
|
|
|
64
65
|
/**
|
|
@@ -103,6 +104,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
103
104
|
$this->loadMigrationsFrom(__DIR__ . '/../../migrations');
|
|
104
105
|
$this->loadViewsFrom(__DIR__ . '/../../resources/views', 'fleetops');
|
|
105
106
|
$this->mergeConfigFrom(__DIR__ . '/../../config/fleetops.php', 'fleetops');
|
|
107
|
+
$this->mergeConfigFrom(__DIR__ . '/../../config/telematics.php', 'telematics');
|
|
106
108
|
$this->mergeConfigFrom(__DIR__ . '/../../config/api.php', 'api');
|
|
107
109
|
$this->mergeConfigFrom(__DIR__ . '/../../config/cache.stores.php', 'cache.stores');
|
|
108
110
|
$this->mergeConfigFrom(__DIR__ . '/../../config/geocoder.php', 'geocoder');
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Telematics\Providers;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Contracts\TelematicProviderInterface;
|
|
6
|
+
use Fleetbase\FleetOps\Exceptions\TelematicRateLimitExceededException;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Telematic;
|
|
8
|
+
use Illuminate\Support\Facades\Cache;
|
|
9
|
+
use Illuminate\Support\Facades\Crypt;
|
|
10
|
+
use Illuminate\Support\Facades\Http;
|
|
11
|
+
use Illuminate\Support\Facades\Log;
|
|
12
|
+
use Illuminate\Support\Str;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Class AbstractProvider.
|
|
16
|
+
*
|
|
17
|
+
* Base implementation for all telematics providers.
|
|
18
|
+
* Provides common functionality for HTTP requests, rate limiting,
|
|
19
|
+
* and credential management.
|
|
20
|
+
*/
|
|
21
|
+
abstract class AbstractProvider implements TelematicProviderInterface
|
|
22
|
+
{
|
|
23
|
+
protected Telematic $telematic;
|
|
24
|
+
protected array $credentials = [];
|
|
25
|
+
protected array $headers = [];
|
|
26
|
+
protected string $baseUrl = '';
|
|
27
|
+
protected int $requestsPerMinute = 60;
|
|
28
|
+
protected int $burstSize = 10;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Connect to the provider.
|
|
32
|
+
*/
|
|
33
|
+
public function connect(Telematic $telematic): void
|
|
34
|
+
{
|
|
35
|
+
$this->telematic = $telematic;
|
|
36
|
+
$this->credentials = json_decode(Crypt::decryptString($telematic->credentials), true);
|
|
37
|
+
$this->prepareAuthentication();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Prepare authentication headers/tokens.
|
|
42
|
+
* Override this in provider implementations.
|
|
43
|
+
*/
|
|
44
|
+
abstract protected function prepareAuthentication(): void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Make an HTTP request to the provider API.
|
|
48
|
+
*
|
|
49
|
+
* @throws TelematicRateLimitExceededException
|
|
50
|
+
*/
|
|
51
|
+
protected function request(string $method, string $endpoint, array $data = []): array
|
|
52
|
+
{
|
|
53
|
+
$this->checkRateLimit();
|
|
54
|
+
|
|
55
|
+
$url = $this->baseUrl . $endpoint;
|
|
56
|
+
$correlationId = Str::uuid()->toString();
|
|
57
|
+
|
|
58
|
+
Log::info('Provider API request', [
|
|
59
|
+
'correlation_id' => $correlationId,
|
|
60
|
+
'provider' => class_basename($this),
|
|
61
|
+
'method' => $method,
|
|
62
|
+
'url' => $url,
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
$response = Http::withHeaders($this->headers)
|
|
66
|
+
->timeout(30)
|
|
67
|
+
->{strtolower($method)}($url, $data);
|
|
68
|
+
|
|
69
|
+
$this->recordRequest();
|
|
70
|
+
|
|
71
|
+
if ($response->failed()) {
|
|
72
|
+
Log::error('Provider API request failed', [
|
|
73
|
+
'correlation_id' => $correlationId,
|
|
74
|
+
'status' => $response->status(),
|
|
75
|
+
'body' => $response->body(),
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
throw new \Exception('API request failed: ' . $response->body());
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return $response->json();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check rate limit using token bucket algorithm.
|
|
86
|
+
*
|
|
87
|
+
* @throws TelematicRateLimitExceededException
|
|
88
|
+
*/
|
|
89
|
+
protected function checkRateLimit(): void
|
|
90
|
+
{
|
|
91
|
+
$key = 'rate_limit:' . class_basename($this) . ':' . $this->telematic->uuid;
|
|
92
|
+
$tokens = Cache::get($key, $this->burstSize);
|
|
93
|
+
|
|
94
|
+
if ($tokens <= 0) {
|
|
95
|
+
throw new TelematicRateLimitExceededException('Rate limit exceeded for provider');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Cache::put($key, $tokens - 1, 60);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Record a request for metrics.
|
|
103
|
+
*/
|
|
104
|
+
protected function recordRequest(): void
|
|
105
|
+
{
|
|
106
|
+
$key = 'rate_limit:' . class_basename($this) . ':' . $this->telematic->uuid;
|
|
107
|
+
$tokens = Cache::get($key, 0);
|
|
108
|
+
|
|
109
|
+
// Refill tokens gradually
|
|
110
|
+
if ($tokens < $this->burstSize) {
|
|
111
|
+
Cache::put($key, min($tokens + 1, $this->burstSize), 60);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public function supportsWebhooks(): bool
|
|
116
|
+
{
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public function supportsDiscovery(): bool
|
|
121
|
+
{
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public function getRateLimits(): array
|
|
126
|
+
{
|
|
127
|
+
return [
|
|
128
|
+
'requests_per_minute' => $this->requestsPerMinute,
|
|
129
|
+
'burst_size' => $this->burstSize,
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public function validateWebhookSignature(string $payload, string $signature, array $credentials): bool
|
|
134
|
+
{
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public function processWebhook(array $payload, array $headers = []): array
|
|
139
|
+
{
|
|
140
|
+
return [
|
|
141
|
+
'devices' => [],
|
|
142
|
+
'events' => [],
|
|
143
|
+
'sensors' => [],
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public function getCredentialSchema(): array
|
|
148
|
+
{
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Telematics\Providers;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Class FlespiProvider.
|
|
7
|
+
*
|
|
8
|
+
* Flespi telematics provider implementation.
|
|
9
|
+
* https://flespi.com/
|
|
10
|
+
*/
|
|
11
|
+
class FlespiProvider extends AbstractProvider
|
|
12
|
+
{
|
|
13
|
+
protected string $baseUrl = 'https://flespi.io/gw';
|
|
14
|
+
protected int $requestsPerMinute = 100;
|
|
15
|
+
|
|
16
|
+
protected function prepareAuthentication(): void
|
|
17
|
+
{
|
|
18
|
+
$this->headers = [
|
|
19
|
+
'Authorization' => 'FlespiToken ' . $this->credentials['token'],
|
|
20
|
+
'Accept' => 'application/json',
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public function testConnection(array $credentials): array
|
|
25
|
+
{
|
|
26
|
+
try {
|
|
27
|
+
$this->credentials = $credentials;
|
|
28
|
+
$this->prepareAuthentication();
|
|
29
|
+
|
|
30
|
+
// Test by fetching channels
|
|
31
|
+
$response = $this->request('GET', '/channels/all');
|
|
32
|
+
|
|
33
|
+
return [
|
|
34
|
+
'success' => true,
|
|
35
|
+
'message' => 'Connection successful',
|
|
36
|
+
'metadata' => [
|
|
37
|
+
'channels_count' => count($response['result'] ?? []),
|
|
38
|
+
],
|
|
39
|
+
];
|
|
40
|
+
} catch (\Exception $e) {
|
|
41
|
+
return [
|
|
42
|
+
'success' => false,
|
|
43
|
+
'message' => $e->getMessage(),
|
|
44
|
+
'metadata' => [],
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public function fetchDevices(array $options = []): array
|
|
50
|
+
{
|
|
51
|
+
$limit = $options['limit'] ?? 100;
|
|
52
|
+
$cursor = $options['cursor'] ?? null;
|
|
53
|
+
|
|
54
|
+
$params = ['count' => $limit];
|
|
55
|
+
if ($cursor) {
|
|
56
|
+
$params['offset'] = $cursor;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
$response = $this->request('GET', '/devices/all', $params);
|
|
60
|
+
|
|
61
|
+
$devices = $response['result'] ?? [];
|
|
62
|
+
$nextCursor = count($devices) >= $limit ? ((int) ($cursor ?? 0) + $limit) : null;
|
|
63
|
+
|
|
64
|
+
return [
|
|
65
|
+
'devices' => $devices,
|
|
66
|
+
'next_cursor' => $nextCursor,
|
|
67
|
+
'has_more' => $nextCursor !== null,
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public function fetchDeviceDetails(string $externalId): array
|
|
72
|
+
{
|
|
73
|
+
$response = $this->request('GET', "/devices/{$externalId}");
|
|
74
|
+
|
|
75
|
+
return $response['result'][0] ?? [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public function normalizeDevice(array $payload): array
|
|
79
|
+
{
|
|
80
|
+
return [
|
|
81
|
+
'external_id' => $payload['id'],
|
|
82
|
+
'device_name' => $payload['name'] ?? 'Unknown Device',
|
|
83
|
+
'device_provider' => 'flespi',
|
|
84
|
+
'device_model' => $payload['device_type_id'] ?? null,
|
|
85
|
+
'imei' => $payload['configuration']['ident'] ?? null,
|
|
86
|
+
'phone' => $payload['configuration']['phone'] ?? null,
|
|
87
|
+
'status' => isset($payload['telemetry']) ? 'active' : 'inactive',
|
|
88
|
+
'location' => [
|
|
89
|
+
'lat' => $payload['telemetry']['position.latitude'] ?? null,
|
|
90
|
+
'lng' => $payload['telemetry']['position.longitude'] ?? null,
|
|
91
|
+
],
|
|
92
|
+
'meta' => $payload,
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public function normalizeEvent(array $payload): array
|
|
97
|
+
{
|
|
98
|
+
return [
|
|
99
|
+
'external_id' => $payload['id'] ?? null,
|
|
100
|
+
'event_type' => $payload['event.enum'] ?? 'telemetry_update',
|
|
101
|
+
'occurred_at' => isset($payload['timestamp']) ? date('Y-m-d H:i:s', $payload['timestamp']) : now(),
|
|
102
|
+
'location' => [
|
|
103
|
+
'lat' => $payload['position.latitude'] ?? null,
|
|
104
|
+
'lng' => $payload['position.longitude'] ?? null,
|
|
105
|
+
],
|
|
106
|
+
'meta' => $payload,
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public function normalizeSensor(array $payload): array
|
|
111
|
+
{
|
|
112
|
+
return [
|
|
113
|
+
'sensor_type' => $payload['sensor_type'] ?? 'generic',
|
|
114
|
+
'value' => $payload['value'] ?? null,
|
|
115
|
+
'unit' => $payload['unit'] ?? null,
|
|
116
|
+
'recorded_at' => isset($payload['timestamp']) ? date('Y-m-d H:i:s', $payload['timestamp']) : now(),
|
|
117
|
+
'meta' => $payload,
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public function validateWebhookSignature(string $payload, string $signature, array $credentials): bool
|
|
122
|
+
{
|
|
123
|
+
if (!isset($credentials['webhook_secret'])) {
|
|
124
|
+
return true; // No secret configured, skip validation
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
$expectedSignature = hash_hmac('sha256', $payload, $credentials['webhook_secret']);
|
|
128
|
+
|
|
129
|
+
return hash_equals($expectedSignature, $signature);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public function processWebhook(array $payload, array $headers = []): array
|
|
133
|
+
{
|
|
134
|
+
$devices = [];
|
|
135
|
+
$events = [];
|
|
136
|
+
|
|
137
|
+
// Flespi sends array of messages
|
|
138
|
+
foreach ($payload as $message) {
|
|
139
|
+
if (isset($message['device.id'])) {
|
|
140
|
+
$devices[] = $this->normalizeDevice([
|
|
141
|
+
'id' => $message['device.id'],
|
|
142
|
+
'name' => $message['device.name'] ?? null,
|
|
143
|
+
'telemetry' => $message,
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
$events[] = $this->normalizeEvent($message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return [
|
|
151
|
+
'devices' => $devices,
|
|
152
|
+
'events' => $events,
|
|
153
|
+
'sensors' => [],
|
|
154
|
+
];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public function getCredentialSchema(): array
|
|
158
|
+
{
|
|
159
|
+
return [
|
|
160
|
+
[
|
|
161
|
+
'name' => 'token',
|
|
162
|
+
'label' => 'Flespi Token',
|
|
163
|
+
'type' => 'password',
|
|
164
|
+
'placeholder' => 'Enter your Flespi API token',
|
|
165
|
+
'required' => true,
|
|
166
|
+
'validation' => 'required|string|min:20',
|
|
167
|
+
],
|
|
168
|
+
[
|
|
169
|
+
'name' => 'webhook_secret',
|
|
170
|
+
'label' => 'Webhook Secret (Optional)',
|
|
171
|
+
'type' => 'password',
|
|
172
|
+
'placeholder' => 'Enter webhook secret for signature validation',
|
|
173
|
+
'required' => false,
|
|
174
|
+
],
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public function supportsWebhooks(): bool
|
|
179
|
+
{
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
}
|