@fleetbase/fleetops-engine 0.6.20 → 0.6.21
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/details.hbs +92 -43
- package/addon/components/device/form.hbs +108 -60
- package/addon/components/device/form.js +36 -8
- package/addon/components/device/panel-header.hbs +32 -0
- package/addon/components/device/panel-header.js +3 -0
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/driver/form.js +49 -47
- 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 +58 -0
- package/addon/components/map/drawer/device-event-listing.js +181 -0
- package/addon/components/map/drawer/position-listing.hbs +84 -0
- package/addon/components/map/drawer/position-listing.js +289 -0
- package/addon/components/map/drawer.js +2 -0
- package/addon/components/map/leaflet-live-map.hbs +7 -2
- package/addon/components/order/details/payload.hbs +6 -4
- package/addon/components/order/details/payload.js +2 -0
- package/addon/components/order-config-manager/custom-fields.js +1 -1
- package/addon/components/positions-replay.hbs +333 -0
- package/addon/components/positions-replay.js +372 -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 +1 -1
- 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 +21 -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/vehicles/index/details/positions.js +3 -0
- package/addon/routes.js +1 -0
- package/addon/services/movement-tracker.js +81 -30
- 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/vehicles/index/details/positions.hbs +1 -0
- package/addon/utils/fleet-ops-options.js +95 -0
- package/app/components/device/panel-header.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/positions-replay.js +1 -0
- package/app/components/sensor/panel-header.js +1 -0
- package/app/helpers/get-fleet-ops-option-label.js +1 -0
- package/app/routes/management/vehicles/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/Internal/v1/TelematicWebhookController.php +170 -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/Device.php +72 -10
- package/server/src/Models/DeviceEvent.php +7 -0
- package/server/src/Models/Driver.php +28 -1
- package/server/src/Models/Payload.php +0 -1
- package/server/src/Models/Place.php +4 -1
- package/server/src/Models/Position.php +17 -17
- package/server/src/Models/Sensor.php +78 -13
- package/server/src/Models/Telematic.php +116 -6
- package/server/src/Models/Vehicle.php +8 -11
- 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 +12 -1
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import Controller from '@ember/controller';
|
|
2
2
|
import { inject as service } from '@ember/service';
|
|
3
3
|
import { tracked } from '@glimmer/tracking';
|
|
4
|
+
import fleetOpsOptions from '../../../utils/fleet-ops-options';
|
|
4
5
|
|
|
5
6
|
export default class ConnectivityTelematicsIndexController extends Controller {
|
|
6
7
|
@service telematicActions;
|
|
7
8
|
@service intl;
|
|
8
9
|
|
|
9
10
|
/** query params */
|
|
10
|
-
@tracked queryParams = ['name', 'page', 'limit', 'sort', 'query', 'public_id', 'created_at', 'updated_at'];
|
|
11
|
+
@tracked queryParams = ['name', 'provider', 'status', 'page', 'limit', 'sort', 'query', 'public_id', 'created_at', 'updated_at'];
|
|
11
12
|
@tracked page = 1;
|
|
12
13
|
@tracked limit;
|
|
13
14
|
@tracked sort = '-created_at';
|
|
14
15
|
@tracked public_id;
|
|
15
16
|
@tracked name;
|
|
17
|
+
@tracked provider;
|
|
18
|
+
@tracked status;
|
|
16
19
|
|
|
17
20
|
/** action buttons */
|
|
18
21
|
@tracked actionButtons = [
|
|
@@ -54,25 +57,32 @@ export default class ConnectivityTelematicsIndexController extends Controller {
|
|
|
54
57
|
/** columns */
|
|
55
58
|
@tracked columns = [
|
|
56
59
|
{
|
|
57
|
-
label:
|
|
58
|
-
valuePath: '
|
|
59
|
-
width: '180px',
|
|
60
|
+
label: 'Provider',
|
|
61
|
+
valuePath: 'provider',
|
|
60
62
|
cellComponent: 'table/cell/anchor',
|
|
61
63
|
cellClassNames: 'uppercase',
|
|
62
64
|
action: this.telematicActions.transition.view,
|
|
63
65
|
permission: 'fleet-ops view telematic',
|
|
64
|
-
hidden: true,
|
|
65
66
|
resizable: true,
|
|
66
67
|
sortable: true,
|
|
67
68
|
filterable: true,
|
|
68
69
|
filterParam: 'name',
|
|
69
70
|
filterComponent: 'filter/string',
|
|
70
71
|
},
|
|
72
|
+
{
|
|
73
|
+
label: this.intl.t('column.status'),
|
|
74
|
+
valuePath: 'status',
|
|
75
|
+
cellComponent: 'table/cell/status',
|
|
76
|
+
resizable: true,
|
|
77
|
+
sortable: true,
|
|
78
|
+
filterable: true,
|
|
79
|
+
filterComponent: 'filter/multi-option',
|
|
80
|
+
filterOptions: fleetOpsOptions('telematicStatuses'),
|
|
81
|
+
},
|
|
71
82
|
{
|
|
72
83
|
label: this.intl.t('column.created-at'),
|
|
73
84
|
valuePath: 'createdAt',
|
|
74
85
|
sortParam: 'created_at',
|
|
75
|
-
width: '10%',
|
|
76
86
|
resizable: true,
|
|
77
87
|
sortable: true,
|
|
78
88
|
filterable: true,
|
|
@@ -82,7 +92,6 @@ export default class ConnectivityTelematicsIndexController extends Controller {
|
|
|
82
92
|
label: this.intl.t('column.updated-at'),
|
|
83
93
|
valuePath: 'updatedAt',
|
|
84
94
|
sortParam: 'updated_at',
|
|
85
|
-
width: '10%',
|
|
86
95
|
resizable: true,
|
|
87
96
|
sortable: true,
|
|
88
97
|
hidden: true,
|
|
@@ -96,18 +105,18 @@ export default class ConnectivityTelematicsIndexController extends Controller {
|
|
|
96
105
|
ddButtonText: false,
|
|
97
106
|
ddButtonIcon: 'ellipsis-h',
|
|
98
107
|
ddButtonIconPrefix: 'fas',
|
|
99
|
-
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.
|
|
108
|
+
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.telematic') }),
|
|
100
109
|
cellClassNames: 'overflow-visible',
|
|
101
110
|
wrapperClass: 'flex items-center justify-end mx-2',
|
|
102
111
|
width: '10%',
|
|
103
112
|
actions: [
|
|
104
113
|
{
|
|
105
|
-
label: this.intl.t('
|
|
114
|
+
label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.telematic') }),
|
|
106
115
|
fn: this.telematicActions.transition.view,
|
|
107
116
|
permission: 'fleet-ops view telematic',
|
|
108
117
|
},
|
|
109
118
|
{
|
|
110
|
-
label: this.intl.t('
|
|
119
|
+
label: this.intl.t('common.edit-resource', { resource: this.intl.t('resource.telematic') }),
|
|
111
120
|
fn: this.telematicActions.transition.edit,
|
|
112
121
|
permission: 'fleet-ops update telematic',
|
|
113
122
|
},
|
|
@@ -115,7 +124,7 @@ export default class ConnectivityTelematicsIndexController extends Controller {
|
|
|
115
124
|
separator: true,
|
|
116
125
|
},
|
|
117
126
|
{
|
|
118
|
-
label: this.intl.t('
|
|
127
|
+
label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.telematic') }),
|
|
119
128
|
fn: this.telematicActions.delete,
|
|
120
129
|
permission: 'fleet-ops delete telematic',
|
|
121
130
|
},
|
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
import Controller from '@ember/controller';
|
|
2
|
-
import { tracked } from '@glimmer/tracking';
|
|
3
2
|
import { inject as service } from '@ember/service';
|
|
4
3
|
|
|
5
4
|
export default class ManagementFleetsIndexDetailsController extends Controller {
|
|
6
5
|
@service hostRouter;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
6
|
+
|
|
7
|
+
get tabs() {
|
|
8
|
+
return [
|
|
9
|
+
{
|
|
10
|
+
route: 'management.fleets.index.details.index',
|
|
11
|
+
label: 'Overview',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
route: 'management.fleets.index.details.vehicles',
|
|
15
|
+
label: 'Vehicles',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
route: 'management.fleets.index.details.drivers',
|
|
19
|
+
label: 'Drivers',
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get actionButtons() {
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
icon: 'pencil',
|
|
28
|
+
fn: () => this.hostRouter.transitionTo('console.fleet-ops.management.fleets.index.edit', this.model),
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
27
32
|
}
|
|
@@ -10,12 +10,15 @@ export default class ManagementFleetsIndexEditController extends Controller {
|
|
|
10
10
|
@service notifications;
|
|
11
11
|
@service modalsManager;
|
|
12
12
|
@tracked overlay;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
|
|
14
|
+
get actionButtons() {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
icon: 'eye',
|
|
18
|
+
fn: this.view,
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
}
|
|
19
22
|
|
|
20
23
|
@task *save(fleet) {
|
|
21
24
|
try {
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import Controller from '@ember/controller';
|
|
2
|
-
import { tracked } from '@glimmer/tracking';
|
|
3
2
|
import { inject as service } from '@ember/service';
|
|
4
3
|
|
|
5
4
|
export default class ManagementVehiclesIndexDetailsController extends Controller {
|
|
6
5
|
@service hostRouter;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
|
|
7
|
+
get tabs() {
|
|
8
|
+
return [
|
|
9
|
+
{
|
|
10
|
+
route: 'management.vehicles.index.details.index',
|
|
11
|
+
label: 'Overview',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
route: 'management.vehicles.index.details.positions',
|
|
15
|
+
label: 'Positions',
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
get actionButtons() {
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
icon: 'pencil',
|
|
23
|
+
fn: () => this.hostRouter.transitionTo('console.fleet-ops.management.vehicles.index.edit', this.model),
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
}
|
|
19
27
|
}
|
|
@@ -33,6 +33,12 @@ export default class SettingsCustomFieldsController extends Controller {
|
|
|
33
33
|
label: 'Place',
|
|
34
34
|
groups: [],
|
|
35
35
|
},
|
|
36
|
+
{
|
|
37
|
+
model: 'entity',
|
|
38
|
+
type: 'fleet-ops:entity',
|
|
39
|
+
label: 'Entity',
|
|
40
|
+
groups: [],
|
|
41
|
+
},
|
|
36
42
|
{
|
|
37
43
|
model: 'fleet',
|
|
38
44
|
type: 'fleet-ops:fleet',
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { helper } from '@ember/component/helper';
|
|
2
|
+
import fleetOpsOptions from '../utils/fleet-ops-options';
|
|
3
|
+
|
|
4
|
+
export function getFleetOpsOptionLabel(optionsKey, value) {
|
|
5
|
+
const allOptions = fleetOpsOptions(optionsKey);
|
|
6
|
+
return allOptions.find((opt) => opt.value === value)?.label ?? null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default helper(function getFleetOpsOptionLabelHelper([optionsKey, value]) {
|
|
10
|
+
return getFleetOpsOptionLabel(optionsKey, value);
|
|
11
|
+
});
|
|
@@ -1,3 +1,29 @@
|
|
|
1
1
|
import Route from '@ember/routing/route';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
import { action } from '@ember/object';
|
|
2
4
|
|
|
3
|
-
export default class ConnectivityDevicesIndexDetailsRoute extends Route {
|
|
5
|
+
export default class ConnectivityDevicesIndexDetailsRoute extends Route {
|
|
6
|
+
@service store;
|
|
7
|
+
@service notifications;
|
|
8
|
+
@service hostRouter;
|
|
9
|
+
@service abilities;
|
|
10
|
+
@service intl;
|
|
11
|
+
|
|
12
|
+
@action error(error) {
|
|
13
|
+
this.notifications.serverError(error);
|
|
14
|
+
if (typeof error.message === 'string' && error.message.endsWith('not found')) {
|
|
15
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.devices.index');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
beforeModel() {
|
|
20
|
+
if (this.abilities.cannot('fleet-ops view device')) {
|
|
21
|
+
this.notifications.warning(this.intl.t('common.unauthorized-access'));
|
|
22
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.devices.index');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
model({ public_id }) {
|
|
27
|
+
return this.store.findRecord('device', public_id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,3 +1,29 @@
|
|
|
1
1
|
import Route from '@ember/routing/route';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
import { action } from '@ember/object';
|
|
2
4
|
|
|
3
|
-
export default class ConnectivityDevicesIndexEditRoute extends Route {
|
|
5
|
+
export default class ConnectivityDevicesIndexEditRoute extends Route {
|
|
6
|
+
@service store;
|
|
7
|
+
@service notifications;
|
|
8
|
+
@service hostRouter;
|
|
9
|
+
@service abilities;
|
|
10
|
+
@service intl;
|
|
11
|
+
|
|
12
|
+
@action error(error) {
|
|
13
|
+
this.notifications.serverError(error);
|
|
14
|
+
if (typeof error.message === 'string' && error.message.endsWith('not found')) {
|
|
15
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.devices.index');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
beforeModel() {
|
|
20
|
+
if (this.abilities.cannot('fleet-ops update device')) {
|
|
21
|
+
this.notifications.warning(this.intl.t('common.unauthorized-access'));
|
|
22
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.devices.index');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
model({ public_id }) {
|
|
27
|
+
return this.store.findRecord('device', public_id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,3 +1,29 @@
|
|
|
1
1
|
import Route from '@ember/routing/route';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
import { action } from '@ember/object';
|
|
2
4
|
|
|
3
|
-
export default class ConnectivitySensorsIndexDetailsRoute extends Route {
|
|
5
|
+
export default class ConnectivitySensorsIndexDetailsRoute extends Route {
|
|
6
|
+
@service store;
|
|
7
|
+
@service notifications;
|
|
8
|
+
@service hostRouter;
|
|
9
|
+
@service abilities;
|
|
10
|
+
@service intl;
|
|
11
|
+
|
|
12
|
+
@action error(error) {
|
|
13
|
+
this.notifications.serverError(error);
|
|
14
|
+
if (typeof error.message === 'string' && error.message.endsWith('not found')) {
|
|
15
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.sensors.index');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
beforeModel() {
|
|
20
|
+
if (this.abilities.cannot('fleet-ops view sensor')) {
|
|
21
|
+
this.notifications.warning(this.intl.t('common.unauthorized-access'));
|
|
22
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.sensors.index');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
model({ public_id }) {
|
|
27
|
+
return this.store.findRecord('sensor', public_id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,3 +1,29 @@
|
|
|
1
1
|
import Route from '@ember/routing/route';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
import { action } from '@ember/object';
|
|
2
4
|
|
|
3
|
-
export default class ConnectivitySensorsIndexEditRoute extends Route {
|
|
5
|
+
export default class ConnectivitySensorsIndexEditRoute extends Route {
|
|
6
|
+
@service store;
|
|
7
|
+
@service notifications;
|
|
8
|
+
@service hostRouter;
|
|
9
|
+
@service abilities;
|
|
10
|
+
@service intl;
|
|
11
|
+
|
|
12
|
+
@action error(error) {
|
|
13
|
+
this.notifications.serverError(error);
|
|
14
|
+
if (typeof error.message === 'string' && error.message.endsWith('not found')) {
|
|
15
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.sensors.index');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
beforeModel() {
|
|
20
|
+
if (this.abilities.cannot('fleet-ops update sensor')) {
|
|
21
|
+
this.notifications.warning(this.intl.t('common.unauthorized-access'));
|
|
22
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.sensors.index');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
model({ public_id }) {
|
|
27
|
+
return this.store.findRecord('sensor', public_id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,3 +1,29 @@
|
|
|
1
1
|
import Route from '@ember/routing/route';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
import { action } from '@ember/object';
|
|
2
4
|
|
|
3
|
-
export default class ConnectivityTelematicsIndexDetailsRoute extends Route {
|
|
5
|
+
export default class ConnectivityTelematicsIndexDetailsRoute extends Route {
|
|
6
|
+
@service store;
|
|
7
|
+
@service notifications;
|
|
8
|
+
@service hostRouter;
|
|
9
|
+
@service abilities;
|
|
10
|
+
@service intl;
|
|
11
|
+
|
|
12
|
+
@action error(error) {
|
|
13
|
+
this.notifications.serverError(error);
|
|
14
|
+
if (typeof error.message === 'string' && error.message.endsWith('not found')) {
|
|
15
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.telematics.index');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
beforeModel() {
|
|
20
|
+
if (this.abilities.cannot('fleet-ops view telematic')) {
|
|
21
|
+
this.notifications.warning(this.intl.t('common.unauthorized-access'));
|
|
22
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.telematics.index');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
model({ public_id }) {
|
|
27
|
+
return this.store.findRecord('telematic', public_id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,3 +1,29 @@
|
|
|
1
1
|
import Route from '@ember/routing/route';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
import { action } from '@ember/object';
|
|
2
4
|
|
|
3
|
-
export default class ConnectivityTelematicsIndexEditRoute extends Route {
|
|
5
|
+
export default class ConnectivityTelematicsIndexEditRoute extends Route {
|
|
6
|
+
@service store;
|
|
7
|
+
@service notifications;
|
|
8
|
+
@service hostRouter;
|
|
9
|
+
@service abilities;
|
|
10
|
+
@service intl;
|
|
11
|
+
|
|
12
|
+
@action error(error) {
|
|
13
|
+
this.notifications.serverError(error);
|
|
14
|
+
if (typeof error.message === 'string' && error.message.endsWith('not found')) {
|
|
15
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.telematics.index');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
beforeModel() {
|
|
20
|
+
if (this.abilities.cannot('fleet-ops update telematic')) {
|
|
21
|
+
this.notifications.warning(this.intl.t('common.unauthorized-access'));
|
|
22
|
+
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.telematics.index');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
model({ public_id }) {
|
|
27
|
+
return this.store.findRecord('telematic', public_id);
|
|
28
|
+
}
|
|
29
|
+
}
|
package/addon/routes.js
CHANGED
|
@@ -10,17 +10,19 @@ import LeafletTrackingMarkerComponent from '../components/leaflet-tracking-marke
|
|
|
10
10
|
export class EventBuffer {
|
|
11
11
|
@tracked events = [];
|
|
12
12
|
@tracked waitTime = 1000 * 3;
|
|
13
|
+
@tracked callback;
|
|
13
14
|
@tracked intervalId;
|
|
14
15
|
@tracked model;
|
|
15
16
|
|
|
16
|
-
constructor(model, waitTime = 1000 * 3) {
|
|
17
|
+
constructor(model, { callback = null, waitTime = 1000 * 3 }) {
|
|
17
18
|
this.model = model;
|
|
19
|
+
this.callback = callback;
|
|
18
20
|
this.waitTime = waitTime;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
start() {
|
|
22
24
|
this.intervalId = setInterval(() => {
|
|
23
|
-
const bufferReady = this.process.isIdle && this.events.length;
|
|
25
|
+
const bufferReady = this.process.isIdle && this.events.length > 0;
|
|
24
26
|
if (bufferReady) {
|
|
25
27
|
this.process.perform();
|
|
26
28
|
}
|
|
@@ -32,11 +34,11 @@ export class EventBuffer {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
clear() {
|
|
35
|
-
this.events
|
|
37
|
+
this.events = [];
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
add(event) {
|
|
39
|
-
this.events.
|
|
41
|
+
this.events = [...this.events, event];
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
removeByIndex(index) {
|
|
@@ -44,65 +46,112 @@ export class EventBuffer {
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
remove(event) {
|
|
47
|
-
this.events = this.events.
|
|
49
|
+
this.events = this.events.filter((e) => e !== event);
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
@task *process() {
|
|
51
53
|
debug('Processing movement tracker event buffer.');
|
|
54
|
+
|
|
55
|
+
// Take a snapshot of events to process and clear buffer immediately
|
|
56
|
+
// This prevents losing events that arrive during processing
|
|
57
|
+
const eventsToProcess = [...this.events];
|
|
58
|
+
this.events = []; // Clear immediately to accept new events
|
|
59
|
+
|
|
52
60
|
// Sort events by created_at
|
|
53
|
-
|
|
61
|
+
eventsToProcess.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
|
|
62
|
+
debug(`[MovementTracker EventBuffer processing ${eventsToProcess.length} events]`);
|
|
54
63
|
|
|
55
64
|
// Process sorted events
|
|
56
|
-
for (const output of
|
|
65
|
+
for (const output of eventsToProcess) {
|
|
57
66
|
const { event, data } = output;
|
|
58
67
|
|
|
68
|
+
// get movingObject marker
|
|
69
|
+
const marker = this.model.leafletLayer || this.model._layer || this.model._marker;
|
|
70
|
+
if (!marker || !marker._map) {
|
|
71
|
+
debug('No marker or marker not on map yet');
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
// log incoming event
|
|
60
76
|
debug(`${event} - ${data.id} ${data.additionalData?.index ? '#' + data.additionalData?.index : ''} (${output.created_at}) [ ${data.location.coordinates.join(' ')} ]`);
|
|
61
77
|
|
|
62
|
-
//
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
78
|
+
// GeoJSON -> Leaflet [lat, lng]
|
|
79
|
+
const [lng, lat] = data.location.coordinates;
|
|
80
|
+
const nextLatLng = [lat, lng];
|
|
81
|
+
|
|
82
|
+
// Calc speed
|
|
83
|
+
const map = marker._map;
|
|
84
|
+
const prev = marker.getLatLng();
|
|
85
|
+
const meters = map ? map.distance(prev, nextLatLng) : prev.distanceTo(nextLatLng);
|
|
86
|
+
|
|
87
|
+
// Assume payload speed is m/s; if it's km/h, convert: mps = kmh / 3.6
|
|
88
|
+
let mps = Number.isFinite(data.speed) && data.speed > 0 ? data.speed : null;
|
|
89
|
+
|
|
90
|
+
// Reduce animation duration and clamp between 100ms and 500ms
|
|
91
|
+
// This makes animations faster and prevents long delays
|
|
92
|
+
const durationMs = mps ? Math.max(100, Math.min((meters / mps) * 1000, 500)) : 500;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// Apply rotation if heading is valid
|
|
96
|
+
if (typeof marker.setRotationAngle === 'function' && Number.isFinite(data.heading) && data.heading !== -1) {
|
|
66
97
|
marker.setRotationAngle(data.heading);
|
|
67
98
|
}
|
|
68
99
|
|
|
100
|
+
// Move marker with animation
|
|
69
101
|
if (typeof marker.slideTo === 'function') {
|
|
70
|
-
marker.slideTo(
|
|
102
|
+
marker.slideTo(nextLatLng, { duration: durationMs });
|
|
71
103
|
} else {
|
|
72
|
-
marker.setLatLng(
|
|
104
|
+
marker.setLatLng(nextLatLng);
|
|
73
105
|
}
|
|
74
106
|
|
|
75
|
-
|
|
107
|
+
if (typeof this.callback === 'function') {
|
|
108
|
+
this.callback(output, { nextLatLng, duration: durationMs, mps });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Wait for animation to complete
|
|
112
|
+
yield timeout(durationMs + 50);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
debug('MovementTracker EventBuffer error: ' + err.message);
|
|
76
115
|
}
|
|
77
116
|
}
|
|
78
117
|
|
|
79
|
-
//
|
|
80
|
-
|
|
118
|
+
// Don't clear here - we already cleared at the start
|
|
119
|
+
debug(`[MovementTracker EventBuffer finished processing ${eventsToProcess.length} events]`);
|
|
81
120
|
}
|
|
82
121
|
}
|
|
83
122
|
|
|
84
123
|
export default class MovementTrackerService extends Service {
|
|
85
124
|
@service socket;
|
|
86
125
|
@tracked channels = [];
|
|
126
|
+
@tracked buffers = new Map();
|
|
87
127
|
|
|
88
128
|
constructor() {
|
|
89
129
|
super(...arguments);
|
|
90
130
|
this.registerTrackingMarker();
|
|
91
131
|
}
|
|
92
132
|
|
|
93
|
-
|
|
133
|
+
#getOwner(owner = null) {
|
|
94
134
|
return owner ?? window.Fleetbase ?? getOwner(this);
|
|
95
135
|
}
|
|
96
136
|
|
|
137
|
+
#getBuffer(key, model, opts = {}) {
|
|
138
|
+
let buf = this.buffers.get(key);
|
|
139
|
+
if (!buf) {
|
|
140
|
+
buf = new EventBuffer(model, opts);
|
|
141
|
+
buf.start();
|
|
142
|
+
this.buffers.set(key, buf);
|
|
143
|
+
}
|
|
144
|
+
return buf;
|
|
145
|
+
}
|
|
146
|
+
|
|
97
147
|
registerTrackingMarker(_owner = null) {
|
|
98
|
-
const owner = this
|
|
148
|
+
const owner = this.#getOwner(_owner);
|
|
99
149
|
const emberLeafletService = owner.lookup('service:ember-leaflet');
|
|
100
150
|
|
|
101
151
|
if (emberLeafletService) {
|
|
102
152
|
const alreadyRegistered = emberLeafletService.components.find((registeredComponent) => registeredComponent.name === 'leaflet-tracking-marker');
|
|
103
|
-
if (alreadyRegistered)
|
|
104
|
-
|
|
105
|
-
}
|
|
153
|
+
if (alreadyRegistered) return;
|
|
154
|
+
|
|
106
155
|
// we then invoke the `registerComponent` method
|
|
107
156
|
emberLeafletService.registerComponent('leaflet-tracking-marker', {
|
|
108
157
|
as: 'tracking-marker',
|
|
@@ -123,37 +172,39 @@ export default class MovementTrackerService extends Service {
|
|
|
123
172
|
});
|
|
124
173
|
}
|
|
125
174
|
|
|
126
|
-
async track(model) {
|
|
175
|
+
async track(model, options = {}) {
|
|
127
176
|
// Create socket instance
|
|
128
177
|
const socket = this.socket.instance();
|
|
129
178
|
|
|
130
179
|
// Get model type and identifier
|
|
131
180
|
const type = getModelName(model);
|
|
132
181
|
const identifier = model.id;
|
|
133
|
-
|
|
182
|
+
|
|
183
|
+
// Location events to listen for
|
|
184
|
+
const locationEvents = [`${type}.location_changed`, `${type}.simulated_location_changed`, 'position.changed', 'position.simulated'];
|
|
134
185
|
|
|
135
186
|
// Listen on the specific channel
|
|
136
|
-
const channelId = `${type}.${identifier}`;
|
|
187
|
+
const channelId = options?.channelId ?? `${type}.${identifier}`;
|
|
137
188
|
const channel = socket.subscribe(channelId);
|
|
138
189
|
|
|
190
|
+
// Debug output
|
|
191
|
+
debug(`Tracking movement started for ${type} with id ${identifier}${options?.channelId ? ' on channel ' + channelId : ''}`, model);
|
|
192
|
+
|
|
139
193
|
// Track the channel
|
|
140
|
-
this.channels.
|
|
194
|
+
this.channels = [...this.channels, channel];
|
|
141
195
|
|
|
142
196
|
// Listen to the channel for events
|
|
143
197
|
await channel.listener('subscribe').once();
|
|
144
198
|
|
|
145
199
|
// Create event buffer for tracking model
|
|
146
|
-
const eventBuffer =
|
|
147
|
-
|
|
148
|
-
// Start tracking with event buffer
|
|
149
|
-
eventBuffer.start();
|
|
200
|
+
const eventBuffer = this.#getBuffer(channelId, model, options);
|
|
150
201
|
|
|
151
202
|
// Get incoming data and console out
|
|
152
203
|
(async () => {
|
|
153
204
|
for await (let output of channel) {
|
|
154
205
|
const { event } = output;
|
|
155
206
|
|
|
156
|
-
if (
|
|
207
|
+
if (locationEvents.includes(event)) {
|
|
157
208
|
eventBuffer.add(output);
|
|
158
209
|
debug(`Socket Event : ${event} : Added to EventBuffer : ${JSON.stringify(output)}`);
|
|
159
210
|
}
|