@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,111 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
return [
|
|
4
|
+
'providers' => [
|
|
5
|
+
[
|
|
6
|
+
'key' => 'flespi',
|
|
7
|
+
'label' => 'Flespi',
|
|
8
|
+
'type' => 'native',
|
|
9
|
+
'driver_class' => \Fleetbase\FleetOps\Support\Telematics\Providers\FlespiProvider::class,
|
|
10
|
+
'icon' => 'https://flespi.com/favicon.ico',
|
|
11
|
+
'description' => 'Flespi is a robust telematics platform offering device management, data processing, and API integration.',
|
|
12
|
+
'docs_url' => 'https://flespi.com/docs',
|
|
13
|
+
'required_fields' => [
|
|
14
|
+
[
|
|
15
|
+
'name' => 'token',
|
|
16
|
+
'label' => 'Flespi Token',
|
|
17
|
+
'type' => 'password',
|
|
18
|
+
'placeholder' => 'Enter your Flespi API token',
|
|
19
|
+
'required' => true,
|
|
20
|
+
'validation' => 'required|string|min:20',
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
'name' => 'webhook_secret',
|
|
24
|
+
'label' => 'Webhook Secret (Optional)',
|
|
25
|
+
'type' => 'password',
|
|
26
|
+
'placeholder' => 'Enter webhook secret for signature validation',
|
|
27
|
+
'required' => false,
|
|
28
|
+
],
|
|
29
|
+
],
|
|
30
|
+
'supports_webhooks' => true,
|
|
31
|
+
'supports_discovery' => true,
|
|
32
|
+
'metadata' => [
|
|
33
|
+
'rate_limit' => 100,
|
|
34
|
+
'pagination' => 'offset',
|
|
35
|
+
],
|
|
36
|
+
],
|
|
37
|
+
|
|
38
|
+
[
|
|
39
|
+
'key' => 'geotab',
|
|
40
|
+
'label' => 'Geotab',
|
|
41
|
+
'type' => 'native',
|
|
42
|
+
'driver_class' => \Fleetbase\FleetOps\Support\Telematics\Providers\GeotabProvider::class,
|
|
43
|
+
'icon' => 'https://www.geotab.com/favicon.ico',
|
|
44
|
+
'description' => 'Geotab provides fleet management solutions with GPS tracking, driver safety, and compliance features.',
|
|
45
|
+
'docs_url' => 'https://developers.geotab.com/',
|
|
46
|
+
'required_fields' => [
|
|
47
|
+
[
|
|
48
|
+
'name' => 'database',
|
|
49
|
+
'label' => 'Database Name',
|
|
50
|
+
'type' => 'text',
|
|
51
|
+
'placeholder' => 'Enter your Geotab database name',
|
|
52
|
+
'required' => true,
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
'name' => 'username',
|
|
56
|
+
'label' => 'Username',
|
|
57
|
+
'type' => 'text',
|
|
58
|
+
'placeholder' => 'Enter your Geotab username',
|
|
59
|
+
'required' => true,
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
'name' => 'password',
|
|
63
|
+
'label' => 'Password',
|
|
64
|
+
'type' => 'password',
|
|
65
|
+
'placeholder' => 'Enter your Geotab password',
|
|
66
|
+
'required' => true,
|
|
67
|
+
],
|
|
68
|
+
],
|
|
69
|
+
'supports_webhooks' => false,
|
|
70
|
+
'supports_discovery' => true,
|
|
71
|
+
'metadata' => [
|
|
72
|
+
'rate_limit' => 50,
|
|
73
|
+
'auth_type' => 'session',
|
|
74
|
+
],
|
|
75
|
+
],
|
|
76
|
+
|
|
77
|
+
[
|
|
78
|
+
'key' => 'samsara',
|
|
79
|
+
'label' => 'Samsara',
|
|
80
|
+
'type' => 'native',
|
|
81
|
+
'driver_class' => \Fleetbase\FleetOps\Support\Telematics\Providers\SamsaraProvider::class,
|
|
82
|
+
'icon' => 'https://www.samsara.com/favicon.ico',
|
|
83
|
+
'description' => 'Samsara offers IoT solutions for fleet operations, including GPS tracking, dashcams, and asset monitoring.',
|
|
84
|
+
'docs_url' => 'https://developers.samsara.com/',
|
|
85
|
+
'required_fields' => [
|
|
86
|
+
[
|
|
87
|
+
'name' => 'api_token',
|
|
88
|
+
'label' => 'API Token',
|
|
89
|
+
'type' => 'password',
|
|
90
|
+
'placeholder' => 'Enter your Samsara API token',
|
|
91
|
+
'required' => true,
|
|
92
|
+
'validation' => 'required|string|min:20',
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
'name' => 'webhook_secret',
|
|
96
|
+
'label' => 'Webhook Secret (Optional)',
|
|
97
|
+
'type' => 'password',
|
|
98
|
+
'placeholder' => 'Enter webhook secret for signature validation',
|
|
99
|
+
'required' => false,
|
|
100
|
+
],
|
|
101
|
+
],
|
|
102
|
+
'supports_webhooks' => true,
|
|
103
|
+
'supports_discovery' => true,
|
|
104
|
+
'metadata' => [
|
|
105
|
+
'rate_limit' => 60,
|
|
106
|
+
'pagination' => 'cursor',
|
|
107
|
+
],
|
|
108
|
+
],
|
|
109
|
+
]
|
|
110
|
+
];
|
|
111
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Illuminate\Database\Migrations\Migration;
|
|
4
|
+
use Illuminate\Database\Schema\Blueprint;
|
|
5
|
+
use Illuminate\Support\Facades\Schema;
|
|
6
|
+
|
|
7
|
+
return new class extends Migration {
|
|
8
|
+
/**
|
|
9
|
+
* Run the migrations.
|
|
10
|
+
*
|
|
11
|
+
* @return void
|
|
12
|
+
*/
|
|
13
|
+
public function up()
|
|
14
|
+
{
|
|
15
|
+
// Update telematics table
|
|
16
|
+
Schema::table('telematics', function (Blueprint $table) {
|
|
17
|
+
$table->json('credentials')->after('config');
|
|
18
|
+
$table->string('public_id')->after('uuid')->nullable()->index();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Update devices table
|
|
22
|
+
Schema::table('devices', function (Blueprint $table) {
|
|
23
|
+
$table->string('public_id')->after('uuid')->nullable()->index();
|
|
24
|
+
$table->foreignUuid('company_uuid')->nullable()->after('_key')->constrained('companies', 'uuid')->nullOnDelete();
|
|
25
|
+
$table->foreignUuid('photo_uuid')->nullable()->after('telematic_uuid')->constrained('files', 'uuid')->nullOnDelete();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Update sensors table
|
|
29
|
+
Schema::table('sensors', function (Blueprint $table) {
|
|
30
|
+
$table->string('public_id')->after('uuid')->nullable()->index();
|
|
31
|
+
$table->foreignUuid('photo_uuid')->nullable()->after('company_uuid')->constrained('files', 'uuid')->nullOnDelete();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Update device events table
|
|
35
|
+
Schema::table('device_events', function (Blueprint $table) {
|
|
36
|
+
$table->string('public_id')->after('uuid')->nullable()->index();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reverse the migrations.
|
|
42
|
+
*
|
|
43
|
+
* @return void
|
|
44
|
+
*/
|
|
45
|
+
public function down()
|
|
46
|
+
{
|
|
47
|
+
// Remove columns from telematics
|
|
48
|
+
Schema::table('telematics', function (Blueprint $table) {
|
|
49
|
+
$table->dropColumn(['credentials', 'public_id']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Remove columns from devices
|
|
53
|
+
Schema::table('devices', function (Blueprint $table) {
|
|
54
|
+
$table->dropForeign(['company_uuid']);
|
|
55
|
+
$table->dropForeign(['photo_uuid']);
|
|
56
|
+
$table->dropColumn(['public_id', 'photo_uuid', 'company_uuid']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Remove columns from sensors
|
|
60
|
+
Schema::table('sensors', function (Blueprint $table) {
|
|
61
|
+
$table->dropForeign(['photo_uuid']);
|
|
62
|
+
$table->dropColumn(['public_id', 'photo_uuid']);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Remove columns from device events
|
|
66
|
+
Schema::table('device_events', function (Blueprint $table) {
|
|
67
|
+
$table->dropColumn(['public_id']);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Illuminate\Database\Migrations\Migration;
|
|
4
|
+
use Illuminate\Database\Schema\Blueprint;
|
|
5
|
+
use Illuminate\Support\Facades\DB;
|
|
6
|
+
use Illuminate\Support\Facades\Schema;
|
|
7
|
+
|
|
8
|
+
return new class extends Migration {
|
|
9
|
+
public function up(): void
|
|
10
|
+
{
|
|
11
|
+
/**
|
|
12
|
+
* Devices: rename columns WITHOUT doctrine/dbal (MySQL 8+).
|
|
13
|
+
*/
|
|
14
|
+
DB::statement('ALTER TABLE devices RENAME COLUMN device_name TO name');
|
|
15
|
+
DB::statement('ALTER TABLE devices RENAME COLUMN device_type TO type');
|
|
16
|
+
DB::statement('ALTER TABLE devices RENAME COLUMN device_location TO location');
|
|
17
|
+
DB::statement('ALTER TABLE devices RENAME COLUMN device_model TO model');
|
|
18
|
+
DB::statement('ALTER TABLE devices RENAME COLUMN device_provider TO provider');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Devices: add new scalar cols + spatial POINT (nullable first!).
|
|
22
|
+
*/
|
|
23
|
+
Schema::table('devices', function (Blueprint $table) {
|
|
24
|
+
$table->string('internal_id')->nullable()->after('device_id');
|
|
25
|
+
$table->string('imei')->nullable()->after('device_id');
|
|
26
|
+
$table->string('imsi')->nullable()->after('device_id');
|
|
27
|
+
$table->string('firmware_version')->nullable()->after('device_id');
|
|
28
|
+
|
|
29
|
+
// must be nullable now; we'll backfill and then make NOT NULL
|
|
30
|
+
$table->point('last_position')->nullable()->after('serial_number');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Backfill existing rows so NOT NULL will succeed
|
|
34
|
+
DB::statement('UPDATE devices SET last_position = ST_SRID(POINT(0, 0), 4326) WHERE last_position IS NULL');
|
|
35
|
+
|
|
36
|
+
// Make column NOT NULL (and optionally enforce SRID at column level)
|
|
37
|
+
// If you want to enforce SRID on the column itself, uncomment the SRID variant:
|
|
38
|
+
// DB::statement('ALTER TABLE devices MODIFY last_position POINT NOT NULL SRID 4326');
|
|
39
|
+
DB::statement('ALTER TABLE devices MODIFY last_position POINT NOT NULL');
|
|
40
|
+
|
|
41
|
+
// NOW add the spatial index (requires NOT NULL)
|
|
42
|
+
Schema::table('devices', function (Blueprint $table) {
|
|
43
|
+
$table->spatialIndex('last_position', 'devices_last_position_spx');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Sensors: drop legacy column, add fields + POINT (nullable), backfill, NOT NULL, index.
|
|
48
|
+
*/
|
|
49
|
+
Schema::table('sensors', function (Blueprint $table) {
|
|
50
|
+
if (Schema::hasColumn('sensors', 'sensor_type')) {
|
|
51
|
+
$table->dropColumn('sensor_type');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
$table->foreignUuid('telematic_uuid')->nullable()->after('company_uuid')->constrained('telematics', 'uuid')->nullOnDelete();
|
|
55
|
+
$table->string('firmware_version')->nullable()->after('name');
|
|
56
|
+
$table->string('imei')->nullable()->after('name');
|
|
57
|
+
$table->string('imsi')->nullable()->after('name');
|
|
58
|
+
$table->string('serial_number')->nullable()->after('name');
|
|
59
|
+
$table->string('internal_id')->nullable()->after('name');
|
|
60
|
+
$table->string('status')->nullable()->after('last_value');
|
|
61
|
+
|
|
62
|
+
$table->point('last_position')->nullable()->after('serial_number');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
DB::statement('UPDATE sensors SET last_position = ST_SRID(POINT(0, 0), 4326) WHERE last_position IS NULL');
|
|
66
|
+
DB::statement('ALTER TABLE sensors MODIFY last_position POINT NOT NULL');
|
|
67
|
+
|
|
68
|
+
Schema::table('sensors', function (Blueprint $table) {
|
|
69
|
+
$table->spatialIndex('last_position', 'sensors_last_position_spx');
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public function down(): void
|
|
74
|
+
{
|
|
75
|
+
/**
|
|
76
|
+
* Devices: drop spatial index then column, drop added scalars, rename back.
|
|
77
|
+
*/
|
|
78
|
+
Schema::table('devices', function (Blueprint $table) {
|
|
79
|
+
// drop index BEFORE dropping column
|
|
80
|
+
$table->dropSpatialIndex('devices_last_position_spx');
|
|
81
|
+
$table->dropColumn('last_position');
|
|
82
|
+
|
|
83
|
+
$table->dropColumn(['internal_id', 'imei', 'imsi', 'firmware_version']);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
DB::statement('ALTER TABLE devices RENAME COLUMN name TO device_name');
|
|
87
|
+
DB::statement('ALTER TABLE devices RENAME COLUMN type TO device_type');
|
|
88
|
+
DB::statement('ALTER TABLE devices RENAME COLUMN location TO device_location');
|
|
89
|
+
DB::statement('ALTER TABLE devices RENAME COLUMN model TO device_model');
|
|
90
|
+
DB::statement('ALTER TABLE devices RENAME COLUMN provider TO device_provider');
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Sensors: drop spatial index/column, drop new fields, restore sensor_type.
|
|
94
|
+
*/
|
|
95
|
+
Schema::table('sensors', function (Blueprint $table) {
|
|
96
|
+
$table->dropSpatialIndex('sensors_last_position_spx');
|
|
97
|
+
$table->dropColumn('last_position');
|
|
98
|
+
|
|
99
|
+
$table->dropForeign(['telematic_uuid']);
|
|
100
|
+
$table->dropColumn(['telematic_uuid']);
|
|
101
|
+
|
|
102
|
+
$table->dropColumn(['serial_number', 'internal_id', 'imei', 'imsi', 'firmware_version', 'status']);
|
|
103
|
+
|
|
104
|
+
$table->string('sensor_type')->nullable()->after('slug');
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Illuminate\Database\Migrations\Migration;
|
|
4
|
+
use Illuminate\Database\Schema\Blueprint;
|
|
5
|
+
use Illuminate\Support\Facades\Schema;
|
|
6
|
+
|
|
7
|
+
return new class extends Migration {
|
|
8
|
+
/**
|
|
9
|
+
* Run the migrations.
|
|
10
|
+
*/
|
|
11
|
+
public function up(): void
|
|
12
|
+
{
|
|
13
|
+
Schema::table('device_events', function (Blueprint $table) {
|
|
14
|
+
$table->foreignUuid('company_uuid')->nullable()->after('_key')->constrained('companies', 'uuid')->nullOnDelete();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Reverse the migrations.
|
|
20
|
+
*/
|
|
21
|
+
public function down(): void
|
|
22
|
+
{
|
|
23
|
+
Schema::table('device_events', function (Blueprint $table) {
|
|
24
|
+
$table->dropForeign(['company_uuid']);
|
|
25
|
+
$table->dropColumn(['company_uuid']);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Console\Commands;
|
|
4
|
+
|
|
5
|
+
use Carbon\Carbon;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
7
|
+
use Fleetbase\Support\SocketCluster\SocketClusterService;
|
|
8
|
+
use Illuminate\Console\Command;
|
|
9
|
+
|
|
10
|
+
class ReplayVehicleLocations extends Command
|
|
11
|
+
{
|
|
12
|
+
/**
|
|
13
|
+
* The name and signature of the console command.
|
|
14
|
+
*
|
|
15
|
+
* @var string
|
|
16
|
+
*/
|
|
17
|
+
protected $signature = 'vehicle:replay-locations
|
|
18
|
+
{file : Path to the JSON file containing vehicle location data}
|
|
19
|
+
{--speed=1 : Speed multiplier for replay (1 = real-time, 2 = 2x speed, 0.5 = half speed)}
|
|
20
|
+
{--vehicle= : Filter by specific vehicle ID (optional)}
|
|
21
|
+
{--limit= : Limit the number of events to process (optional)}
|
|
22
|
+
{--sleep= : Set a manual sleep for replay (in seconds)}
|
|
23
|
+
{--skip-sleep : Skip sleep delays and send all events immediately}';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The console command description.
|
|
27
|
+
*
|
|
28
|
+
* @var string
|
|
29
|
+
*/
|
|
30
|
+
protected $description = 'Replay vehicle location events from JSON file with timing simulation via SocketCluster';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Execute the console command.
|
|
34
|
+
*
|
|
35
|
+
* @return int
|
|
36
|
+
*/
|
|
37
|
+
public function handle()
|
|
38
|
+
{
|
|
39
|
+
$filePath = $this->argument('file');
|
|
40
|
+
$speedMultiplier = (float) $this->option('speed');
|
|
41
|
+
$vehicleFilter = $this->option('vehicle');
|
|
42
|
+
$limit = $this->option('limit') ? (int) $this->option('limit') : null;
|
|
43
|
+
$skipSleep = $this->option('skip-sleep');
|
|
44
|
+
$sleep = $this->option('sleep') ? (int) $this->option('sleep') : null;
|
|
45
|
+
|
|
46
|
+
// Validate file exists
|
|
47
|
+
if (!file_exists($filePath)) {
|
|
48
|
+
$this->error("File not found: {$filePath}");
|
|
49
|
+
|
|
50
|
+
return Command::FAILURE;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validate speed multiplier
|
|
54
|
+
if ($speedMultiplier <= 0) {
|
|
55
|
+
$this->error('Speed multiplier must be greater than 0');
|
|
56
|
+
|
|
57
|
+
return Command::FAILURE;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
$this->info('Starting vehicle location replay...');
|
|
61
|
+
$this->info("File: {$filePath}");
|
|
62
|
+
$this->info("Speed: {$speedMultiplier}x");
|
|
63
|
+
|
|
64
|
+
if ($vehicleFilter) {
|
|
65
|
+
$this->info("Filtering for vehicle: {$vehicleFilter}");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if ($skipSleep) {
|
|
69
|
+
$this->warn('Sleep delays disabled - sending all events immediately');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Load and parse JSON data
|
|
73
|
+
$this->info('Loading location data...');
|
|
74
|
+
$jsonContent = file_get_contents($filePath);
|
|
75
|
+
$locationEvents = json_decode($jsonContent, true);
|
|
76
|
+
|
|
77
|
+
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
78
|
+
$this->error('Failed to parse JSON: ' . json_last_error_msg());
|
|
79
|
+
|
|
80
|
+
return Command::FAILURE;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!is_array($locationEvents) || empty($locationEvents)) {
|
|
84
|
+
$this->error('Invalid or empty location data');
|
|
85
|
+
|
|
86
|
+
return Command::FAILURE;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Filter by vehicle if specified
|
|
90
|
+
if ($vehicleFilter) {
|
|
91
|
+
$locationEvents = array_filter($locationEvents, function ($event) use ($vehicleFilter) {
|
|
92
|
+
return isset($event['data']['id']) && $event['data']['id'] === $vehicleFilter;
|
|
93
|
+
});
|
|
94
|
+
$locationEvents = array_values($locationEvents); // Re-index array
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
$totalEvents = count($locationEvents);
|
|
98
|
+
|
|
99
|
+
if ($totalEvents === 0) {
|
|
100
|
+
$this->warn('No events found matching the criteria');
|
|
101
|
+
|
|
102
|
+
return Command::SUCCESS;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Apply limit if specified
|
|
106
|
+
if ($limit && $limit < $totalEvents) {
|
|
107
|
+
$locationEvents = array_slice($locationEvents, 0, $limit);
|
|
108
|
+
$totalEvents = $limit;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
$this->info("Total events to process: {$totalEvents}");
|
|
112
|
+
$this->newLine();
|
|
113
|
+
|
|
114
|
+
// Initialize SocketCluster client
|
|
115
|
+
$socketClusterClient = new SocketClusterService();
|
|
116
|
+
|
|
117
|
+
// Statistics tracking
|
|
118
|
+
$successCount = 0;
|
|
119
|
+
$errorCount = 0;
|
|
120
|
+
$startTime = microtime(true);
|
|
121
|
+
$previousTimestamp = null;
|
|
122
|
+
|
|
123
|
+
// Process each location event
|
|
124
|
+
foreach ($locationEvents as $index => $event) {
|
|
125
|
+
$eventNumber = $index + 1;
|
|
126
|
+
$vehicleId = $event['data']['id'] ?? 'unknown';
|
|
127
|
+
$eventId = $event['id'] ?? 'unknown';
|
|
128
|
+
$createdAt = $event['created_at'] ?? null;
|
|
129
|
+
|
|
130
|
+
// Get vehicle record
|
|
131
|
+
$vehicle = Vehicle::where('public_id', $vehicleId)->first();
|
|
132
|
+
if (!$vehicle) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Calculate sleep duration based on timestamp difference
|
|
137
|
+
if (!$skipSleep && $previousTimestamp !== null && $createdAt !== null) {
|
|
138
|
+
try {
|
|
139
|
+
$currentTime = Carbon::parse($createdAt);
|
|
140
|
+
$previousTime = Carbon::parse($previousTimestamp);
|
|
141
|
+
$diffInSeconds = $currentTime->diffInSeconds($previousTime);
|
|
142
|
+
|
|
143
|
+
// Apply speed multiplier
|
|
144
|
+
$sleepDuration = $diffInSeconds / $speedMultiplier;
|
|
145
|
+
|
|
146
|
+
if ($sleep) {
|
|
147
|
+
$this->info("[{$eventNumber}/{$totalEvents}] Waiting {$sleep}s (real: {$diffInSeconds}s)...");
|
|
148
|
+
sleep((int) $sleep);
|
|
149
|
+
} elseif ($sleepDuration > 0) {
|
|
150
|
+
$this->info("[{$eventNumber}/{$totalEvents}] Waiting {$sleepDuration}s (real: {$diffInSeconds}s)...");
|
|
151
|
+
sleep((int) $sleepDuration);
|
|
152
|
+
|
|
153
|
+
// Handle fractional seconds
|
|
154
|
+
$fractional = $sleepDuration - floor($sleepDuration);
|
|
155
|
+
if ($fractional > 0) {
|
|
156
|
+
usleep((int) ($fractional * 1000000));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (\Exception $e) {
|
|
160
|
+
$this->warn("Failed to calculate time difference: {$e->getMessage()}");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Update previous timestamp
|
|
165
|
+
$previousTimestamp = $createdAt;
|
|
166
|
+
|
|
167
|
+
// Prepare channel names
|
|
168
|
+
$channels = ["vehicle.{$vehicleId}", "vehicle.{$vehicle->uuid}"];
|
|
169
|
+
|
|
170
|
+
foreach ($channels as $channel) {
|
|
171
|
+
// Send event via SocketCluster
|
|
172
|
+
try {
|
|
173
|
+
$sent = $socketClusterClient->send($channel, $event);
|
|
174
|
+
|
|
175
|
+
$location = $event['data']['location']['coordinates'] ?? ['N/A', 'N/A'];
|
|
176
|
+
$speed = $event['data']['speed'] ?? 'N/A';
|
|
177
|
+
$heading = $event['data']['heading'] ?? 'N/A';
|
|
178
|
+
|
|
179
|
+
$this->line(sprintf(
|
|
180
|
+
"[{$eventNumber}/{$totalEvents}] ✓ Sent event %s for vehicle %s | Channel: %s | Coords: [%.6f, %.6f] | Speed: %s | Heading: %s | Time: %s",
|
|
181
|
+
$eventId,
|
|
182
|
+
$vehicleId,
|
|
183
|
+
$channel,
|
|
184
|
+
$location[0],
|
|
185
|
+
$location[1],
|
|
186
|
+
$speed,
|
|
187
|
+
$heading,
|
|
188
|
+
$createdAt ?? 'N/A'
|
|
189
|
+
));
|
|
190
|
+
|
|
191
|
+
$successCount++;
|
|
192
|
+
} catch (\WebSocket\ConnectionException $e) {
|
|
193
|
+
$this->error("[{$eventNumber}/{$totalEvents}] ✗ Connection error for event {$eventId}: {$e->getMessage()}");
|
|
194
|
+
$errorCount++;
|
|
195
|
+
} catch (\WebSocket\TimeoutException $e) {
|
|
196
|
+
$this->error("[{$eventNumber}/{$totalEvents}] ✗ Timeout error for event {$eventId}: {$e->getMessage()}");
|
|
197
|
+
$errorCount++;
|
|
198
|
+
} catch (\Throwable $e) {
|
|
199
|
+
$this->error("[{$eventNumber}/{$totalEvents}] ✗ Error for event {$eventId}: {$e->getMessage()}");
|
|
200
|
+
$errorCount++;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Summary
|
|
206
|
+
$endTime = microtime(true);
|
|
207
|
+
$duration = round($endTime - $startTime, 2);
|
|
208
|
+
|
|
209
|
+
$this->newLine();
|
|
210
|
+
$this->info('=== Replay Complete ===');
|
|
211
|
+
$this->info("Total events processed: {$totalEvents}");
|
|
212
|
+
$this->info("Successful: {$successCount}");
|
|
213
|
+
|
|
214
|
+
if ($errorCount > 0) {
|
|
215
|
+
$this->error("Failed: {$errorCount}");
|
|
216
|
+
} else {
|
|
217
|
+
$this->info("Failed: {$errorCount}");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
$this->info("Duration: {$duration}s");
|
|
221
|
+
$this->info('Average rate: ' . round($totalEvents / max($duration, 0.001), 2) . ' events/second');
|
|
222
|
+
|
|
223
|
+
return $errorCount > 0 ? Command::FAILURE : Command::SUCCESS;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Contracts;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Support\Utils;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Class TelematicProviderDescriptor.
|
|
9
|
+
*
|
|
10
|
+
* Data Transfer Object for provider metadata.
|
|
11
|
+
* Used by the ProviderRegistry to describe available providers.
|
|
12
|
+
*/
|
|
13
|
+
class TelematicProviderDescriptor
|
|
14
|
+
{
|
|
15
|
+
public string $key;
|
|
16
|
+
public string $label;
|
|
17
|
+
public string $type; // 'native' or 'custom'
|
|
18
|
+
public ?string $driverClass;
|
|
19
|
+
public ?string $icon;
|
|
20
|
+
public ?string $description;
|
|
21
|
+
public ?string $docsUrl;
|
|
22
|
+
public array $requiredFields;
|
|
23
|
+
public bool $supportsWebhooks;
|
|
24
|
+
public bool $supportsDiscovery;
|
|
25
|
+
public array $metadata;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a new ProviderDescriptor instance.
|
|
29
|
+
*/
|
|
30
|
+
public function __construct(array $data)
|
|
31
|
+
{
|
|
32
|
+
$this->key = $data['key'];
|
|
33
|
+
$this->label = $data['label'];
|
|
34
|
+
$this->type = $data['type'] ?? 'native';
|
|
35
|
+
$this->driverClass = $data['driver_class'] ?? null;
|
|
36
|
+
$this->icon = $data['icon'] ?? null;
|
|
37
|
+
$this->description = $data['description'] ?? null;
|
|
38
|
+
$this->docsUrl = $data['docs_url'] ?? null;
|
|
39
|
+
$this->requiredFields = $data['required_fields'] ?? [];
|
|
40
|
+
$this->supportsWebhooks = $data['supports_webhooks'] ?? false;
|
|
41
|
+
$this->supportsDiscovery = $data['supports_discovery'] ?? false;
|
|
42
|
+
$this->metadata = $data['metadata'] ?? [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Convert to array for JSON serialization.
|
|
47
|
+
*/
|
|
48
|
+
public function toArray(): array
|
|
49
|
+
{
|
|
50
|
+
return [
|
|
51
|
+
'key' => $this->key,
|
|
52
|
+
'label' => $this->label,
|
|
53
|
+
'type' => $this->type,
|
|
54
|
+
'icon' => $this->icon,
|
|
55
|
+
'description' => $this->description,
|
|
56
|
+
'docs_url' => $this->docsUrl,
|
|
57
|
+
'required_fields' => $this->requiredFields,
|
|
58
|
+
'supports_webhooks' => $this->supportsWebhooks,
|
|
59
|
+
'supports_discovery' => $this->supportsDiscovery,
|
|
60
|
+
'metadata' => $this->metadata,
|
|
61
|
+
'webhook_url' => Utils::apiUrl('webhooks/telematics/' . $this->key),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get JSON representation.
|
|
67
|
+
*/
|
|
68
|
+
public function toJson(): string
|
|
69
|
+
{
|
|
70
|
+
return json_encode($this->toArray());
|
|
71
|
+
}
|
|
72
|
+
}
|