@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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Http\Controllers;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Telematic;
|
|
6
|
+
use Fleetbase\FleetOps\Support\Telematics\TelematicProviderRegistry;
|
|
7
|
+
use Fleetbase\FleetOps\Support\Telematics\TelematicService;
|
|
8
|
+
use Fleetbase\Support\IdempotencyManager;
|
|
9
|
+
use Illuminate\Http\JsonResponse;
|
|
10
|
+
use Illuminate\Http\Request;
|
|
11
|
+
use Illuminate\Routing\Controller;
|
|
12
|
+
use Illuminate\Support\Facades\Log;
|
|
13
|
+
use Illuminate\Support\Str;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Class TelematicWebhookController.
|
|
17
|
+
*
|
|
18
|
+
* Handles webhook ingestion from telematics providers.
|
|
19
|
+
*/
|
|
20
|
+
class TelematicWebhookController extends Controller
|
|
21
|
+
{
|
|
22
|
+
protected TelematicProviderRegistry $registry;
|
|
23
|
+
protected TelematicService $service;
|
|
24
|
+
protected IdempotencyManager $idempotency;
|
|
25
|
+
|
|
26
|
+
public function __construct(
|
|
27
|
+
TelematicProviderRegistry $registry,
|
|
28
|
+
TelematicService $service,
|
|
29
|
+
IdempotencyManager $idempotency,
|
|
30
|
+
) {
|
|
31
|
+
$this->registry = $registry;
|
|
32
|
+
$this->service = $service;
|
|
33
|
+
$this->idempotency = $idempotency;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handle provider webhook.
|
|
38
|
+
*/
|
|
39
|
+
public function handle(Request $request, string $providerKey): JsonResponse
|
|
40
|
+
{
|
|
41
|
+
$correlationId = Str::uuid()->toString();
|
|
42
|
+
|
|
43
|
+
Log::info('Webhook received', [
|
|
44
|
+
'correlation_id' => $correlationId,
|
|
45
|
+
'provider' => $providerKey,
|
|
46
|
+
'headers' => $request->headers->all(),
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// Check idempotency
|
|
50
|
+
$idempotencyKey = $request->header('X-Idempotency-Key');
|
|
51
|
+
if ($idempotencyKey && $this->idempotency->isDuplicate($idempotencyKey)) {
|
|
52
|
+
Log::info('Duplicate webhook detected', [
|
|
53
|
+
'correlation_id' => $correlationId,
|
|
54
|
+
'idempotency_key' => $idempotencyKey,
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
return response()->json(['status' => 'duplicate'], 200);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Get provider
|
|
61
|
+
$provider = $this->registry->resolve($providerKey);
|
|
62
|
+
|
|
63
|
+
// Find telematic for this provider
|
|
64
|
+
$telematic = Telematic::where('provider', $providerKey)->first();
|
|
65
|
+
|
|
66
|
+
if (!$telematic) {
|
|
67
|
+
Log::warning('No telematic found for provider', [
|
|
68
|
+
'correlation_id' => $correlationId,
|
|
69
|
+
'provider' => $providerKey,
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
return response()->json(['error' => 'No telematic configured'], 404);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Validate signature
|
|
76
|
+
$signature = $request->header('X-Webhook-Signature');
|
|
77
|
+
$credentials = $this->service->getCredentials($telematic);
|
|
78
|
+
|
|
79
|
+
if ($signature && !$provider->validateWebhookSignature($request->getContent(), $signature, $credentials)) {
|
|
80
|
+
Log::warning('Invalid webhook signature', [
|
|
81
|
+
'correlation_id' => $correlationId,
|
|
82
|
+
'provider' => $providerKey,
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
return response()->json(['error' => 'Invalid signature'], 403);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Process webhook
|
|
89
|
+
try {
|
|
90
|
+
$result = $provider->processWebhook($request->all(), $request->headers->all());
|
|
91
|
+
|
|
92
|
+
// Link devices
|
|
93
|
+
foreach ($result['devices'] as $deviceData) {
|
|
94
|
+
$this->service->linkDevice($telematic, $deviceData);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Store events (TODO: implement event storage)
|
|
98
|
+
// Store sensors (TODO: implement sensor storage)
|
|
99
|
+
|
|
100
|
+
// Mark as processed
|
|
101
|
+
if ($idempotencyKey) {
|
|
102
|
+
$this->idempotency->markProcessed($idempotencyKey);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
Log::info('Webhook processed successfully', [
|
|
106
|
+
'correlation_id' => $correlationId,
|
|
107
|
+
'devices_count' => count($result['devices']),
|
|
108
|
+
'events_count' => count($result['events']),
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
return response()->json(['status' => 'processed'], 200);
|
|
112
|
+
} catch (\Exception $e) {
|
|
113
|
+
Log::error('Webhook processing failed', [
|
|
114
|
+
'correlation_id' => $correlationId,
|
|
115
|
+
'error' => $e->getMessage(),
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
return response()->json(['error' => 'Processing failed'], 500);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Handle custom provider ingest.
|
|
124
|
+
*/
|
|
125
|
+
public function ingest(Request $request, string $id): JsonResponse
|
|
126
|
+
{
|
|
127
|
+
$telematic = Telematic::where('uuid', $id)->orWhere('public_id', $id)->firstOrFail();
|
|
128
|
+
$correlationId = Str::uuid()->toString();
|
|
129
|
+
|
|
130
|
+
Log::info('Custom ingest received', [
|
|
131
|
+
'correlation_id' => $correlationId,
|
|
132
|
+
'telematic_uuid' => $id,
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
// Check idempotency
|
|
136
|
+
$idempotencyKey = $request->header('X-Idempotency-Key');
|
|
137
|
+
if ($idempotencyKey && $this->idempotency->isDuplicate($idempotencyKey)) {
|
|
138
|
+
return response()->json(['status' => 'duplicate'], 200);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
// Process devices
|
|
143
|
+
if ($request->has('devices')) {
|
|
144
|
+
foreach ($request->input('devices') as $deviceData) {
|
|
145
|
+
$this->service->linkDevice($telematic, $deviceData);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Mark as processed
|
|
150
|
+
if ($idempotencyKey) {
|
|
151
|
+
$this->idempotency->markProcessed($idempotencyKey);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Log::info('Custom ingest processed', [
|
|
155
|
+
'correlation_id' => $correlationId,
|
|
156
|
+
'devices_count' => count($request->input('devices', [])),
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
return response()->json(['status' => 'ingested'], 200);
|
|
160
|
+
} catch (\Exception $e) {
|
|
161
|
+
Log::error('Custom ingest failed', [
|
|
162
|
+
'correlation_id' => $correlationId,
|
|
163
|
+
'error' => $e->getMessage(),
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
return response()->json(['error' => 'Ingest failed'], 500);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Http\Filter;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Support\Utils;
|
|
6
|
+
use Fleetbase\Http\Filter\Filter;
|
|
7
|
+
|
|
8
|
+
class DeviceEventFilter extends Filter
|
|
9
|
+
{
|
|
10
|
+
public function queryForInternal()
|
|
11
|
+
{
|
|
12
|
+
$this->builder->where(
|
|
13
|
+
function ($query) {
|
|
14
|
+
$query->where('company_uuid', $this->session->get('company'));
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public function queryForPublic()
|
|
20
|
+
{
|
|
21
|
+
$this->builder->where('company_uuid', $this->session->get('company'));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public function query(?string $searchQuery)
|
|
25
|
+
{
|
|
26
|
+
$this->builder->search($searchQuery);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public function telematic(?string $telematic)
|
|
30
|
+
{
|
|
31
|
+
$this->builder->whereHas('device', function ($query) use ($telematic) {
|
|
32
|
+
$query->whereHas('telematic', function ($query) use ($telematic) {
|
|
33
|
+
$query->where('uuid', $telematic);
|
|
34
|
+
$query->orWhere('public_id', $telematic);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public function device(?string $device)
|
|
40
|
+
{
|
|
41
|
+
$this->builder->whereHas('device', function ($query) use ($device) {
|
|
42
|
+
$query->where('uuid', $device);
|
|
43
|
+
$query->orWhere('public_id', $device);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public function createdAt($createdAt)
|
|
48
|
+
{
|
|
49
|
+
$createdAt = Utils::dateRange($createdAt);
|
|
50
|
+
|
|
51
|
+
if (is_array($createdAt)) {
|
|
52
|
+
$this->builder->whereBetween('created_at', $createdAt);
|
|
53
|
+
} else {
|
|
54
|
+
$this->builder->whereDate('created_at', $createdAt);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public function updatedAt($updatedAt)
|
|
59
|
+
{
|
|
60
|
+
$updatedAt = Utils::dateRange($updatedAt);
|
|
61
|
+
|
|
62
|
+
if (is_array($updatedAt)) {
|
|
63
|
+
$this->builder->whereBetween('updated_at', $updatedAt);
|
|
64
|
+
} else {
|
|
65
|
+
$this->builder->whereDate('updated_at', $updatedAt);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Http\Filter;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Support\Utils;
|
|
6
|
+
use Fleetbase\Http\Filter\Filter;
|
|
7
|
+
|
|
8
|
+
class PositionFilter extends Filter
|
|
9
|
+
{
|
|
10
|
+
public function queryForInternal()
|
|
11
|
+
{
|
|
12
|
+
$this->builder->where('company_uuid', $this->session->get('company'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public function queryForPublic()
|
|
16
|
+
{
|
|
17
|
+
$this->builder->where('company_uuid', $this->session->get('company'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public function query(?string $searchQuery)
|
|
21
|
+
{
|
|
22
|
+
$this->builder->search($searchQuery);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public function createdAt($createdAt)
|
|
26
|
+
{
|
|
27
|
+
$createdAt = Utils::dateRange($createdAt);
|
|
28
|
+
|
|
29
|
+
if (is_array($createdAt)) {
|
|
30
|
+
$this->builder->whereBetween('created_at', $createdAt);
|
|
31
|
+
} else {
|
|
32
|
+
$this->builder->whereDate('created_at', $createdAt);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Http\Resources\v1;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Http\Resources\FleetbaseResource;
|
|
6
|
+
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
7
|
+
use Fleetbase\Support\Http;
|
|
8
|
+
use Fleetbase\Support\Resolve;
|
|
9
|
+
|
|
10
|
+
class Position extends FleetbaseResource
|
|
11
|
+
{
|
|
12
|
+
/**
|
|
13
|
+
* Transform the resource into an array.
|
|
14
|
+
*
|
|
15
|
+
* @param \Illuminate\Http\Request $request
|
|
16
|
+
*
|
|
17
|
+
* @return array
|
|
18
|
+
*/
|
|
19
|
+
public function toArray($request)
|
|
20
|
+
{
|
|
21
|
+
return [
|
|
22
|
+
'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
|
|
23
|
+
'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
|
|
24
|
+
'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
|
|
25
|
+
'order_uuid' => $this->when(Http::isInternalRequest(), $this->order_uuid),
|
|
26
|
+
'company_uuid' => $this->when(Http::isInternalRequest(), $this->company_uuid),
|
|
27
|
+
'destination_uuid' => $this->when(Http::isInternalRequest(), $this->destination_uuid),
|
|
28
|
+
'subject_uuid' => $this->when(Http::isInternalRequest(), $this->subject_uuid),
|
|
29
|
+
'subject_type' => $this->subject_type,
|
|
30
|
+
'subject' => $this->whenLoaded('subject', fn () => Resolve::httpResourceForModel($this->subject)),
|
|
31
|
+
'order' => $this->whenLoaded('order', fn () => new Order($this->order)),
|
|
32
|
+
'destination' => $this->whenLoaded('destination', fn () => new Place($this->destination)),
|
|
33
|
+
'heading' => $this->heading ?? 0,
|
|
34
|
+
'bearing' => $this->bearing ?? 0,
|
|
35
|
+
'speed' => $this->speed ?? 0,
|
|
36
|
+
'altitude' => $this->altitude ?? 0,
|
|
37
|
+
'latitude' => $this->latitude ?? 0,
|
|
38
|
+
'longitude' => $this->longitude ?? 0,
|
|
39
|
+
'coordinates' => $this->coordinates ?? new Point(0, 0),
|
|
40
|
+
'updated_at' => $this->updated_at,
|
|
41
|
+
'created_at' => $this->created_at,
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Jobs;
|
|
4
|
+
|
|
5
|
+
use Carbon\Carbon;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Position;
|
|
7
|
+
use Fleetbase\Support\SocketCluster\SocketClusterService;
|
|
8
|
+
use Illuminate\Bus\Queueable;
|
|
9
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
10
|
+
use Illuminate\Foundation\Bus\Dispatchable;
|
|
11
|
+
use Illuminate\Queue\InteractsWithQueue;
|
|
12
|
+
use Illuminate\Queue\SerializesModels;
|
|
13
|
+
use Illuminate\Support\Facades\Log;
|
|
14
|
+
|
|
15
|
+
class ReplayPositions implements ShouldQueue
|
|
16
|
+
{
|
|
17
|
+
use Dispatchable;
|
|
18
|
+
use InteractsWithQueue;
|
|
19
|
+
use Queueable;
|
|
20
|
+
use SerializesModels;
|
|
21
|
+
|
|
22
|
+
/** @var \Illuminate\Support\Collection<int, Position> */
|
|
23
|
+
protected $positions;
|
|
24
|
+
protected string $channelId;
|
|
25
|
+
protected float $speed;
|
|
26
|
+
protected ?string $subjectUuid;
|
|
27
|
+
|
|
28
|
+
/** Max runtime per worker (seconds) */
|
|
29
|
+
public $timeout = 3600;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param \Illuminate\Support\Collection<int, Position> $positions
|
|
33
|
+
*/
|
|
34
|
+
public function __construct($positions, string $channelId, float $speed = 1, ?string $subjectUuid = null)
|
|
35
|
+
{
|
|
36
|
+
$this->positions = $positions;
|
|
37
|
+
$this->channelId = $channelId;
|
|
38
|
+
$this->speed = max($speed, 0.1);
|
|
39
|
+
$this->subjectUuid = $subjectUuid;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public function handle(): void
|
|
43
|
+
{
|
|
44
|
+
$socket = new SocketClusterService();
|
|
45
|
+
|
|
46
|
+
// Base timestamp to compute relative offsets
|
|
47
|
+
$baseTime = Carbon::parse($this->positions->first()->created_at);
|
|
48
|
+
|
|
49
|
+
foreach ($this->positions as $index => $position) {
|
|
50
|
+
$currentTime = Carbon::parse($position->created_at);
|
|
51
|
+
$offset = $baseTime->diffInSeconds($currentTime, false) / $this->speed;
|
|
52
|
+
|
|
53
|
+
// Schedule a small job for each event with its own delay
|
|
54
|
+
SendPositionReplay::dispatch(
|
|
55
|
+
$this->channelId,
|
|
56
|
+
$position,
|
|
57
|
+
$index,
|
|
58
|
+
$this->subjectUuid
|
|
59
|
+
)->delay(now()->addSeconds(max(0, $offset)));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Log::info("Replay scheduled for {$this->positions->count()} positions on channel {$this->channelId}");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Jobs;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Position;
|
|
6
|
+
use Fleetbase\Support\SocketCluster\SocketClusterService;
|
|
7
|
+
use Illuminate\Bus\Queueable;
|
|
8
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
9
|
+
use Illuminate\Foundation\Bus\Dispatchable;
|
|
10
|
+
use Illuminate\Queue\InteractsWithQueue;
|
|
11
|
+
use Illuminate\Queue\SerializesModels;
|
|
12
|
+
use Illuminate\Support\Facades\Log;
|
|
13
|
+
|
|
14
|
+
class SendPositionReplay implements ShouldQueue
|
|
15
|
+
{
|
|
16
|
+
use Dispatchable;
|
|
17
|
+
use InteractsWithQueue;
|
|
18
|
+
use Queueable;
|
|
19
|
+
use SerializesModels;
|
|
20
|
+
|
|
21
|
+
protected string $channelId;
|
|
22
|
+
protected Position $position;
|
|
23
|
+
protected int $index;
|
|
24
|
+
protected ?string $subjectUuid;
|
|
25
|
+
|
|
26
|
+
public $tries = 3;
|
|
27
|
+
public $timeout = 60;
|
|
28
|
+
|
|
29
|
+
public function __construct(string $channelId, Position $position, int $index, ?string $subjectUuid = null)
|
|
30
|
+
{
|
|
31
|
+
$this->channelId = $channelId;
|
|
32
|
+
$this->position = $position;
|
|
33
|
+
$this->index = $index;
|
|
34
|
+
$this->subjectUuid = $subjectUuid;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public function handle(): void
|
|
38
|
+
{
|
|
39
|
+
$socket = new SocketClusterService();
|
|
40
|
+
|
|
41
|
+
$eventData = [
|
|
42
|
+
'id' => uniqid('event_'),
|
|
43
|
+
'api_version' => config('api.version'),
|
|
44
|
+
'event' => 'position.simulated',
|
|
45
|
+
'created_at' => $this->position->created_at->toDateTimeString(),
|
|
46
|
+
'data' => [
|
|
47
|
+
'id' => $this->subjectUuid ?? $this->position->subject_uuid,
|
|
48
|
+
'location' => $this->position->coordinates,
|
|
49
|
+
'heading' => $this->position->heading ?? 0,
|
|
50
|
+
'speed' => $this->position->speed ?? 0,
|
|
51
|
+
'altitude' => $this->position->altitude ?? 0,
|
|
52
|
+
'additionalData' => [
|
|
53
|
+
'index' => $this->index,
|
|
54
|
+
'position_uuid' => $this->position->uuid,
|
|
55
|
+
],
|
|
56
|
+
],
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
$socket->send($this->channelId, $eventData);
|
|
61
|
+
} catch (\Throwable $e) {
|
|
62
|
+
Log::error("Failed to send replay event [{$this->position->uuid}]: {$e->getMessage()}");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Jobs;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Telematic;
|
|
6
|
+
use Fleetbase\FleetOps\Support\Telematics\TelematicService;
|
|
7
|
+
use Illuminate\Bus\Queueable;
|
|
8
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
9
|
+
use Illuminate\Foundation\Bus\Dispatchable;
|
|
10
|
+
use Illuminate\Queue\InteractsWithQueue;
|
|
11
|
+
use Illuminate\Queue\SerializesModels;
|
|
12
|
+
use Illuminate\Support\Facades\Log;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Class SyncTelematicDevicesJob.
|
|
16
|
+
*
|
|
17
|
+
* Job for discovering and syncing devices from a provider.
|
|
18
|
+
*/
|
|
19
|
+
class SyncTelematicDevicesJob implements ShouldQueue
|
|
20
|
+
{
|
|
21
|
+
use Dispatchable;
|
|
22
|
+
use InteractsWithQueue;
|
|
23
|
+
use Queueable;
|
|
24
|
+
use SerializesModels;
|
|
25
|
+
|
|
26
|
+
public Telematic $telematic;
|
|
27
|
+
public array $options;
|
|
28
|
+
public int $tries = 3;
|
|
29
|
+
public int $timeout = 300;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a new job instance.
|
|
33
|
+
*/
|
|
34
|
+
public function __construct(Telematic $telematic, array $options = [])
|
|
35
|
+
{
|
|
36
|
+
$this->telematic = $telematic;
|
|
37
|
+
$this->options = $options;
|
|
38
|
+
$this->queue = 'telematics-sync';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Execute the job.
|
|
43
|
+
*/
|
|
44
|
+
public function handle(ProviderRegistry $registry, TelematicService $service): void
|
|
45
|
+
{
|
|
46
|
+
$correlationId = \Illuminate\Support\Str::uuid()->toString();
|
|
47
|
+
|
|
48
|
+
Log::info('Device discovery started', [
|
|
49
|
+
'correlation_id' => $correlationId,
|
|
50
|
+
'telematic_uuid' => $this->telematic->uuid,
|
|
51
|
+
'provider' => $this->telematic->provider,
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
$provider = $registry->resolve($this->telematic->provider);
|
|
56
|
+
$provider->connect($this->telematic);
|
|
57
|
+
|
|
58
|
+
$cursor = null;
|
|
59
|
+
$totalSynced = 0;
|
|
60
|
+
|
|
61
|
+
do {
|
|
62
|
+
$response = $provider->fetchDevices([
|
|
63
|
+
'limit' => $this->options['limit'] ?? 100,
|
|
64
|
+
'cursor' => $cursor,
|
|
65
|
+
'filters' => $this->options['filters'] ?? [],
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
foreach ($response['devices'] as $devicePayload) {
|
|
69
|
+
$normalizedDevice = $provider->normalizeDevice($devicePayload);
|
|
70
|
+
$service->linkDevice($this->telematic, $normalizedDevice);
|
|
71
|
+
$totalSynced++;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
$cursor = $response['next_cursor'];
|
|
75
|
+
|
|
76
|
+
Log::info('Device discovery progress', [
|
|
77
|
+
'correlation_id' => $correlationId,
|
|
78
|
+
'synced' => $totalSynced,
|
|
79
|
+
'has_more' => $response['has_more'],
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
// Broadcast progress (TODO: implement WebSocket broadcasting)
|
|
83
|
+
} while ($response['has_more'] && $cursor);
|
|
84
|
+
|
|
85
|
+
Log::info('Device discovery completed', [
|
|
86
|
+
'correlation_id' => $correlationId,
|
|
87
|
+
'total_synced' => $totalSynced,
|
|
88
|
+
]);
|
|
89
|
+
} catch (\Exception $e) {
|
|
90
|
+
Log::error('Device discovery failed', [
|
|
91
|
+
'correlation_id' => $correlationId,
|
|
92
|
+
'error' => $e->getMessage(),
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
throw $e;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get the job ID.
|
|
101
|
+
*/
|
|
102
|
+
public function getJobId(): string
|
|
103
|
+
{
|
|
104
|
+
return $this->job->getJobId() ?? \Illuminate\Support\Str::uuid()->toString();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Jobs;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Telematic;
|
|
6
|
+
use Illuminate\Bus\Queueable;
|
|
7
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
8
|
+
use Illuminate\Foundation\Bus\Dispatchable;
|
|
9
|
+
use Illuminate\Queue\InteractsWithQueue;
|
|
10
|
+
use Illuminate\Queue\SerializesModels;
|
|
11
|
+
use Illuminate\Support\Facades\Crypt;
|
|
12
|
+
use Illuminate\Support\Facades\Log;
|
|
13
|
+
use Illuminate\Support\Str;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Class TestTelematicConnectionJob.
|
|
17
|
+
*
|
|
18
|
+
* Job for testing connection to a provider asynchronously.
|
|
19
|
+
*/
|
|
20
|
+
class TestTelematicConnectionJob implements ShouldQueue
|
|
21
|
+
{
|
|
22
|
+
use Dispatchable;
|
|
23
|
+
use InteractsWithQueue;
|
|
24
|
+
use Queueable;
|
|
25
|
+
use SerializesModels;
|
|
26
|
+
|
|
27
|
+
public Telematic $telematic;
|
|
28
|
+
public int $tries = 1;
|
|
29
|
+
public int $timeout = 30;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a new job instance.
|
|
33
|
+
*/
|
|
34
|
+
public function __construct(Telematic $telematic)
|
|
35
|
+
{
|
|
36
|
+
$this->telematic = $telematic;
|
|
37
|
+
$this->queue = 'telematics-priority';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Execute the job.
|
|
42
|
+
*/
|
|
43
|
+
public function handle(ProviderRegistry $registry): void
|
|
44
|
+
{
|
|
45
|
+
$correlationId = Str::uuid()->toString();
|
|
46
|
+
|
|
47
|
+
Log::info('Connection test started', [
|
|
48
|
+
'correlation_id' => $correlationId,
|
|
49
|
+
'telematic_uuid' => $this->telematic->uuid,
|
|
50
|
+
'provider' => $this->telematic->provider,
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
$provider = $registry->resolve($this->telematic->provider);
|
|
55
|
+
$credentials = json_decode(Crypt::decryptString($this->telematic->credentials), true);
|
|
56
|
+
|
|
57
|
+
$result = $provider->testConnection($credentials);
|
|
58
|
+
|
|
59
|
+
if ($result['success']) {
|
|
60
|
+
$this->telematic->status = 'active';
|
|
61
|
+
$this->telematic->meta = array_merge($this->telematic->meta ?? [], [
|
|
62
|
+
'last_connection_test' => now()->toDateTimeString(),
|
|
63
|
+
'last_test_result' => 'success',
|
|
64
|
+
]);
|
|
65
|
+
} else {
|
|
66
|
+
$this->telematic->status = 'error';
|
|
67
|
+
$this->telematic->meta = array_merge($this->telematic->meta ?? [], [
|
|
68
|
+
'last_connection_test' => now()->toDateTimeString(),
|
|
69
|
+
'last_test_result' => 'failed',
|
|
70
|
+
'last_error' => $result['message'],
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
$this->telematic->save();
|
|
75
|
+
|
|
76
|
+
Log::info('Connection test completed', [
|
|
77
|
+
'correlation_id' => $correlationId,
|
|
78
|
+
'success' => $result['success'],
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
// Broadcast result (TODO: implement WebSocket broadcasting)
|
|
82
|
+
} catch (\Exception $e) {
|
|
83
|
+
Log::error('Connection test failed', [
|
|
84
|
+
'correlation_id' => $correlationId,
|
|
85
|
+
'error' => $e->getMessage(),
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
$this->telematic->status = 'error';
|
|
89
|
+
$this->telematic->save();
|
|
90
|
+
|
|
91
|
+
throw $e;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get the job ID.
|
|
97
|
+
*/
|
|
98
|
+
public function getJobId(): string
|
|
99
|
+
{
|
|
100
|
+
return $this->job->getJobId() ?? Str::uuid()->toString();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -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\FleetOps\Casts\Point;
|
|
7
8
|
use Fleetbase\Models\Category;
|
|
8
9
|
use Fleetbase\Models\File;
|
|
@@ -157,14 +158,15 @@ class Asset extends Model
|
|
|
157
158
|
* @var array
|
|
158
159
|
*/
|
|
159
160
|
protected $casts = [
|
|
160
|
-
'year'
|
|
161
|
-
'odometer'
|
|
162
|
-
'engine_hours'
|
|
163
|
-
'gvw'
|
|
164
|
-
'capacity'
|
|
165
|
-
'specs'
|
|
166
|
-
'attributes'
|
|
167
|
-
'location'
|
|
161
|
+
'year' => 'integer',
|
|
162
|
+
'odometer' => 'integer',
|
|
163
|
+
'engine_hours' => 'integer',
|
|
164
|
+
'gvw' => 'decimal:2',
|
|
165
|
+
'capacity' => Json::class,
|
|
166
|
+
'specs' => Json::class,
|
|
167
|
+
'attributes' => Json::class,
|
|
168
|
+
'location' => Point::class,
|
|
169
|
+
'assigned_to_type' => PolymorphicType::class,
|
|
168
170
|
];
|
|
169
171
|
|
|
170
172
|
/**
|