@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,75 +1,141 @@
|
|
|
1
1
|
<div class="form-wrapper" ...attributes>
|
|
2
|
-
<ContentPanel @title="
|
|
2
|
+
<ContentPanel @title="Integration" @open={{true}} @wrapperClass="bordered-top">
|
|
3
3
|
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
|
|
4
|
-
<InputGroup @name=
|
|
5
|
-
<
|
|
4
|
+
<InputGroup @name={{t "device.fields.telematic"}}>
|
|
5
|
+
<ModelSelect
|
|
6
|
+
@modelName="telematic"
|
|
7
|
+
@selectedModel={{@resource.telematic}}
|
|
8
|
+
@placeholder="Select Telematic"
|
|
9
|
+
@triggerClass="form-select form-input"
|
|
10
|
+
@infiniteScroll={{false}}
|
|
11
|
+
@renderInPlace={{true}}
|
|
12
|
+
@onChange={{this.selectTelematic}}
|
|
13
|
+
@disabled={{cannot-write @resource}}
|
|
14
|
+
as |model|
|
|
15
|
+
>
|
|
16
|
+
<div class="space-x-2 text-sm">
|
|
17
|
+
<div class="inline-block align-top">
|
|
18
|
+
<div class="hide-from-trigger h-1.5 w-full" />
|
|
19
|
+
<Image src={{model.provider_descriptor.icon}} class="w-5 h-5" />
|
|
20
|
+
</div>
|
|
21
|
+
<div class="inline-block">
|
|
22
|
+
<div class="font-semibold normalize-in-trigger">{{or model.name model.public_id}}</div>
|
|
23
|
+
<div class="hide-from-trigger">{{n-a model.provider_descriptor.label (titleize model.provider)}}</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</ModelSelect>
|
|
6
27
|
</InputGroup>
|
|
7
28
|
|
|
8
|
-
<InputGroup @name="
|
|
29
|
+
<InputGroup @name="Device">
|
|
30
|
+
<ModelSelect
|
|
31
|
+
@modelName="device"
|
|
32
|
+
@selectedModel={{@resource.device}}
|
|
33
|
+
@placeholder="Select Device"
|
|
34
|
+
@triggerClass="form-select form-input"
|
|
35
|
+
@infiniteScroll={{false}}
|
|
36
|
+
@renderInPlace={{true}}
|
|
37
|
+
@onChange={{fn (mut @resource.device)}}
|
|
38
|
+
@onChangeId={{fn (mut @resource.device_uuid)}}
|
|
39
|
+
@disabled={{cannot-write @resource}}
|
|
40
|
+
as |model|
|
|
41
|
+
>
|
|
42
|
+
<div class="text-sm">
|
|
43
|
+
<div class="font-semibold normalize-in-trigger">{{model.name}}</div>
|
|
44
|
+
<div class="hide-from-trigger">{{n-a model.serial_number}}</div>
|
|
45
|
+
</div>
|
|
46
|
+
</ModelSelect>
|
|
47
|
+
</InputGroup>
|
|
48
|
+
|
|
49
|
+
<InputGroup @name="Report Frequency (Seconds)" @type="number">
|
|
50
|
+
<Input
|
|
51
|
+
@value={{@resource.report_frequency_sec}}
|
|
52
|
+
@type="number"
|
|
53
|
+
class="w-full form-input"
|
|
54
|
+
placeholder="Frequency of sensor reporting in seconds"
|
|
55
|
+
disabled={{cannot-write @resource}}
|
|
56
|
+
/>
|
|
57
|
+
</InputGroup>
|
|
58
|
+
</div>
|
|
59
|
+
</ContentPanel>
|
|
60
|
+
|
|
61
|
+
<ContentPanel @title="Identity" @open={{true}} @wrapperClass="bordered-top">
|
|
62
|
+
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
|
|
63
|
+
<InputGroup @name="Name" @value={{@resource.name}} @wrapperClass="col-span-2" disabled={{cannot-write @resource}} />
|
|
64
|
+
<InputGroup @name="Serial Number" @value={{@resource.serial_number}} @wrapperClass="col-span-2" disabled={{cannot-write @resource}} />
|
|
65
|
+
<InputGroup @name="Internal ID" @value={{@resource.internal_id}} disabled={{cannot-write @resource}} />
|
|
66
|
+
<InputGroup @name="Unit" @value={{@resource.unit}} disabled={{cannot-write @resource}} />
|
|
67
|
+
<InputGroup @name="Type" @wrapperClass="col-span-2">
|
|
9
68
|
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
10
69
|
<PowerSelect
|
|
11
|
-
@options={{
|
|
12
|
-
@selected={{@resource.
|
|
13
|
-
@onChange={{
|
|
14
|
-
@placeholder="
|
|
70
|
+
@options={{get-fleet-ops-options "sensorTypes"}}
|
|
71
|
+
@selected={{find-by "value" @resource.type (get-fleet-ops-options "sensorTypes")}}
|
|
72
|
+
@onChange={{set-model-attr @resource "type"}}
|
|
73
|
+
@placeholder={{t "common.select-field" field=(t "common.type")}}
|
|
15
74
|
@triggerClass="form-select form-input"
|
|
16
75
|
@disabled={{cannot-write @resource}}
|
|
17
|
-
as |
|
|
76
|
+
as |option|
|
|
18
77
|
>
|
|
19
|
-
|
|
78
|
+
<div class="text-sm">
|
|
79
|
+
<div class="font-semibold normalize-in-trigger">{{option.label}}</div>
|
|
80
|
+
<div class="hide-from-trigger">{{option.description}}</div>
|
|
81
|
+
</div>
|
|
20
82
|
</PowerSelect>
|
|
21
83
|
</div>
|
|
22
84
|
</InputGroup>
|
|
85
|
+
</div>
|
|
86
|
+
</ContentPanel>
|
|
23
87
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
88
|
+
<ContentPanel @title="Threshold" @open={{true}} @wrapperClass="bordered-top">
|
|
89
|
+
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
|
|
90
|
+
<InputGroup @name="Min Threshold" @type="number">
|
|
91
|
+
<Input
|
|
92
|
+
@value={{@resource.min_threshold}}
|
|
93
|
+
@type="number"
|
|
94
|
+
class="w-full form-input"
|
|
95
|
+
placeholder={{t "sensor.fields.min-threshold-placeholder"}}
|
|
96
|
+
disabled={{cannot-write @resource}}
|
|
97
|
+
/>
|
|
30
98
|
</InputGroup>
|
|
31
99
|
|
|
32
|
-
<InputGroup @name="
|
|
33
|
-
<Input
|
|
100
|
+
<InputGroup @name="Max Threshold" @type="number">
|
|
101
|
+
<Input
|
|
102
|
+
@value={{@resource.max_threshold}}
|
|
103
|
+
@type="number"
|
|
104
|
+
class="w-full form-input"
|
|
105
|
+
placeholder={{t "sensor.fields.max-threshold-placeholder"}}
|
|
106
|
+
disabled={{cannot-write @resource}}
|
|
107
|
+
/>
|
|
34
108
|
</InputGroup>
|
|
35
109
|
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
|
|
110
|
+
<div class="col-span-2">
|
|
111
|
+
<div class="mt-2">
|
|
112
|
+
<Checkbox @value={{@resource.threshold_inclusive}} @onChange={{fn (mut @resource.threshold_inclusive)}}>Threshold Inclusive</Checkbox>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</ContentPanel>
|
|
39
117
|
|
|
40
|
-
|
|
118
|
+
<ContentPanel @title="Warranty & Status" @open={{true}} @wrapperClass="bordered-top">
|
|
119
|
+
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
|
|
120
|
+
<InputGroup @name={{t "common.status"}}>
|
|
41
121
|
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
42
122
|
<PowerSelect
|
|
43
|
-
@options={{
|
|
44
|
-
@selected={{@resource.status}}
|
|
45
|
-
@onChange={{
|
|
46
|
-
@placeholder="
|
|
123
|
+
@options={{get-fleet-ops-options "sensorStatuses"}}
|
|
124
|
+
@selected={{find-by "value" @resource.status (get-fleet-ops-options "sensorStatuses")}}
|
|
125
|
+
@onChange={{set-model-attr @resource "status"}}
|
|
126
|
+
@placeholder={{t "common.select-field" field=(t "common.status")}}
|
|
47
127
|
@triggerClass="form-select form-input"
|
|
48
128
|
@disabled={{cannot-write @resource}}
|
|
49
|
-
as |
|
|
129
|
+
as |option|
|
|
50
130
|
>
|
|
51
|
-
|
|
131
|
+
<div class="text-sm">
|
|
132
|
+
<div class="font-semibold normalize-in-trigger">{{option.label}}</div>
|
|
133
|
+
<div class="hide-from-trigger">{{option.description}}</div>
|
|
134
|
+
</div>
|
|
52
135
|
</PowerSelect>
|
|
53
136
|
</div>
|
|
54
137
|
</InputGroup>
|
|
55
138
|
|
|
56
|
-
<InputGroup @name="Device">
|
|
57
|
-
<ModelSelect
|
|
58
|
-
@modelName="device"
|
|
59
|
-
@selectedModel={{@resource.device}}
|
|
60
|
-
@placeholder="Select Device"
|
|
61
|
-
@triggerClass="form-select form-input"
|
|
62
|
-
@infiniteScroll={{false}}
|
|
63
|
-
@renderInPlace={{true}}
|
|
64
|
-
@onChange={{fn (mut @resource.device)}}
|
|
65
|
-
@onChangeId={{fn (mut @resource.device_uuid)}}
|
|
66
|
-
@disabled={{cannot-write @resource}}
|
|
67
|
-
as |model|
|
|
68
|
-
>
|
|
69
|
-
{{model.device_name}}
|
|
70
|
-
</ModelSelect>
|
|
71
|
-
</InputGroup>
|
|
72
|
-
|
|
73
139
|
<InputGroup @name="Warranty">
|
|
74
140
|
<ModelSelect
|
|
75
141
|
@modelName="warranty"
|
|
@@ -86,23 +152,6 @@
|
|
|
86
152
|
{{model.policy_number}} - {{model.provider}}
|
|
87
153
|
</ModelSelect>
|
|
88
154
|
</InputGroup>
|
|
89
|
-
|
|
90
|
-
<div class="col-span-2">
|
|
91
|
-
<label class="flex items-center space-x-2">
|
|
92
|
-
<input
|
|
93
|
-
type="checkbox"
|
|
94
|
-
checked={{@resource.threshold_inclusive}}
|
|
95
|
-
{{on "change" (fn (mut @resource.threshold_inclusive) (not @resource.threshold_inclusive))}}
|
|
96
|
-
disabled={{cannot-write @resource}}
|
|
97
|
-
class="form-checkbox"
|
|
98
|
-
/>
|
|
99
|
-
<span class="text-xs">Threshold Inclusive</span>
|
|
100
|
-
</label>
|
|
101
|
-
</div>
|
|
102
|
-
|
|
103
|
-
<RegistryYield @registry="fleet-ops:component:sensor:form:details" as |RegistryComponent|>
|
|
104
|
-
<RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} />
|
|
105
|
-
</RegistryYield>
|
|
106
155
|
</div>
|
|
107
156
|
</ContentPanel>
|
|
108
157
|
|
|
@@ -111,4 +160,4 @@
|
|
|
111
160
|
<RegistryYield @registry="fleet-ops:component:sensor:form" as |RegistryComponent|>
|
|
112
161
|
<RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} />
|
|
113
162
|
</RegistryYield>
|
|
114
|
-
</div>
|
|
163
|
+
</div>
|
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { task } from 'ember-concurrency';
|
|
2
5
|
|
|
3
6
|
export default class SensorFormComponent extends Component {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
sensorTypeOptions = [
|
|
8
|
-
'temperature',
|
|
9
|
-
'door_status',
|
|
10
|
-
'fuel_level',
|
|
11
|
-
'tire_pressure',
|
|
12
|
-
'humidity',
|
|
13
|
-
'speed',
|
|
14
|
-
'acceleration',
|
|
15
|
-
'gyroscope',
|
|
16
|
-
'gps',
|
|
17
|
-
'battery',
|
|
18
|
-
'voltage',
|
|
19
|
-
'current',
|
|
20
|
-
'pressure',
|
|
21
|
-
'weight',
|
|
22
|
-
'proximity',
|
|
23
|
-
];
|
|
7
|
+
@service fetch;
|
|
8
|
+
@service currentUser;
|
|
9
|
+
@service notifications;
|
|
24
10
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
11
|
+
@action selectTelematic(telematic) {
|
|
12
|
+
this.args.resource.setProperties({
|
|
13
|
+
telematic,
|
|
14
|
+
telematic_uuid: telematic.id,
|
|
15
|
+
provider: telematic.provider,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@task *handlePhotoUpload(file) {
|
|
20
|
+
try {
|
|
21
|
+
yield this.fetch.uploadFile.perform(
|
|
22
|
+
file,
|
|
23
|
+
{
|
|
24
|
+
path: `uploads/${this.currentUser.companyId}/sensors/${this.args.resource.id}`,
|
|
25
|
+
subject_uuid: this.args.resource.id,
|
|
26
|
+
subject_type: 'fleet-ops:sensor',
|
|
27
|
+
type: 'sensor_photo',
|
|
28
|
+
},
|
|
29
|
+
(uploadedFile) => {
|
|
30
|
+
this.args.resource.setProperties({
|
|
31
|
+
photo_uuid: uploadedFile.id,
|
|
32
|
+
photo_url: uploadedFile.url,
|
|
33
|
+
photo: uploadedFile,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
this.notifications.error('Unable to upload photo: ' + err.message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
29
41
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<div class="px-4 py-2">
|
|
2
|
+
<div class="flex flex-1 flex-row items-start justify-between">
|
|
3
|
+
<div class="flex flex-row space-x-3">
|
|
4
|
+
<div class="flex items-start justify-start rounded-full">
|
|
5
|
+
<Image src={{@resource.photo_url}} @fallbackSrc={{config "defaultValues.placeholderImage"}} alt={{@resource.displayName}} height="48" width="48" class="h-14 w-14 rounded-full shadow-sm" />
|
|
6
|
+
<Attach::Tooltip @class="clean" @animation="scale" @placement="top">
|
|
7
|
+
<InputInfo @text={{@resource.displayName}} />
|
|
8
|
+
</Attach::Tooltip>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="flex flex-col">
|
|
11
|
+
<h1 class="text-gray-900 dark:text-white font-semibold text-lg">{{@resource.displayName}}</h1>
|
|
12
|
+
<div>
|
|
13
|
+
<div class="text-xs text-gray-400 dark:text-gray-500">{{n-a (get-fleet-ops-option-label "sensorTypes" @resource.type)}}</div>
|
|
14
|
+
<div class="text-xs text-gray-400 dark:text-gray-500">{{n-a @resource.serial_number}}</div>
|
|
15
|
+
</div>
|
|
16
|
+
<Badge @status={{if @resource.online "online" "offline"}}>{{if @resource.online (t "common.online") (t "common.offline")}}</Badge>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="next-view-header-right">
|
|
20
|
+
<Layout::Resource::Panel::HeaderActions
|
|
21
|
+
@resource={{@resource}}
|
|
22
|
+
@saveTask={{@saveTask}}
|
|
23
|
+
@saveOptions={{@saveOptions}}
|
|
24
|
+
@saveDisabled={{@saveDisabled}}
|
|
25
|
+
@pojoResource={{@pojoResource}}
|
|
26
|
+
@authSchema={{@authSchema}}
|
|
27
|
+
@actionButtons={{@actionButtons}}
|
|
28
|
+
@onPressCancel={{@onPressCancel}}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
@@ -1,16 +1,45 @@
|
|
|
1
1
|
<div class="details-wrapper" ...attributes>
|
|
2
|
-
<ContentPanel @title="
|
|
3
|
-
<div class="
|
|
4
|
-
<div class="field-
|
|
5
|
-
|
|
6
|
-
<div class="
|
|
2
|
+
<ContentPanel @title="Telematics Provider" @open={{true}} @wrapperClass="bordered-top">
|
|
3
|
+
<div class="field-info-container">
|
|
4
|
+
<div class="field-name">Provider</div>
|
|
5
|
+
<div class="field-value">
|
|
6
|
+
<div class="flex flew-row space-x-2 text-sm">
|
|
7
|
+
<div class="pt-1.5">
|
|
8
|
+
<Image src={{@resource.provider_descriptor.icon}} class="w-5 h-5" />
|
|
9
|
+
</div>
|
|
10
|
+
<div>
|
|
11
|
+
<div class="font-semibold">{{@resource.provider_descriptor.label}}</div>
|
|
12
|
+
<div class="text-xs text-gray-400 dark:text-gray-500">{{n-a @resource.provider_descriptor.description}}</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
7
15
|
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</ContentPanel>
|
|
8
18
|
|
|
19
|
+
<ContentPanel @title="Integration Details" @open={{true}} @wrapperClass="bordered-top">
|
|
20
|
+
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
|
|
9
21
|
<div class="field-info-container">
|
|
10
|
-
<div class="field-name">
|
|
11
|
-
<div class="field-value">{{n-a @resource.
|
|
22
|
+
<div class="field-name">Integration Name</div>
|
|
23
|
+
<div class="field-value">{{n-a @resource.name}}</div>
|
|
12
24
|
</div>
|
|
13
25
|
|
|
26
|
+
{{#if @resource.provider_descriptor.supportsWebhooks}}
|
|
27
|
+
<InputGroup @name="Webhook URL" @helpText={{concat "Configure the URL in your " @resource.provider_descriptor.label " dashboard to receive real-time updates."}}>
|
|
28
|
+
<ClickToCopy @value={{@resource.provider_descriptor.webhook_url}}>
|
|
29
|
+
<Input @value={{@resource.provider_descriptor.webhook_url}} class="form-input" readonly />
|
|
30
|
+
</ClickToCopy>
|
|
31
|
+
<small class="form-text text-muted">
|
|
32
|
+
Configure this URL in your
|
|
33
|
+
{{@resource.provider_descriptor.label}}
|
|
34
|
+
dashboard to receive real-time updates.
|
|
35
|
+
</small>
|
|
36
|
+
</InputGroup>
|
|
37
|
+
{{/if}}
|
|
38
|
+
</div>
|
|
39
|
+
</ContentPanel>
|
|
40
|
+
|
|
41
|
+
<ContentPanel @title="Telematic Blackbox" @open={{true}} @wrapperClass="bordered-top">
|
|
42
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
|
|
14
43
|
<div class="field-info-container">
|
|
15
44
|
<div class="field-name">Model</div>
|
|
16
45
|
<div class="field-value">{{n-a @resource.model}}</div>
|
|
@@ -53,23 +82,18 @@
|
|
|
53
82
|
<div class="field-value">{{n-a @resource.msisdn}}</div>
|
|
54
83
|
</div>
|
|
55
84
|
|
|
56
|
-
<div class="field-info-container">
|
|
57
|
-
<div class="field-name">Warranty</div>
|
|
58
|
-
<div class="field-value">{{n-a @resource.warranty_name}}</div>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
85
|
<div class="field-info-container">
|
|
62
86
|
<div class="field-name">Last Seen</div>
|
|
63
|
-
<div class="field-value">{{format-date @resource.last_seen_at}}</div>
|
|
87
|
+
<div class="field-value">{{n-a (format-date @resource.last_seen_at)}}</div>
|
|
64
88
|
</div>
|
|
65
89
|
|
|
66
90
|
<div class="field-info-container">
|
|
67
91
|
<div class="field-name">Online Status</div>
|
|
68
92
|
<div class="field-value">
|
|
69
93
|
{{#if @resource.is_online}}
|
|
70
|
-
<Badge @status="
|
|
94
|
+
<Badge @status="online">Online</Badge>
|
|
71
95
|
{{else}}
|
|
72
|
-
<Badge @status="
|
|
96
|
+
<Badge @status="offline">Offline</Badge>
|
|
73
97
|
{{/if}}
|
|
74
98
|
</div>
|
|
75
99
|
</div>
|
|
@@ -82,4 +106,4 @@
|
|
|
82
106
|
</ContentPanel>
|
|
83
107
|
|
|
84
108
|
<CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
|
|
85
|
-
</div>
|
|
109
|
+
</div>
|
|
@@ -1,78 +1,77 @@
|
|
|
1
1
|
<div class="form-wrapper" ...attributes>
|
|
2
|
-
<ContentPanel @title="
|
|
2
|
+
<ContentPanel @title="Telematics Provider" @open={{true}} @wrapperClass="bordered-top">
|
|
3
3
|
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
|
|
4
|
-
<InputGroup @name="
|
|
5
|
-
<Input @value={{@resource.name}} @type="text" class="w-full form-input" placeholder="Name" disabled={{cannot-write @resource}} />
|
|
6
|
-
</InputGroup>
|
|
7
|
-
|
|
8
|
-
<InputGroup @name="Provider">
|
|
9
|
-
<Input @value={{@resource.provider}} @type="text" class="w-full form-input" placeholder="Provider" disabled={{cannot-write @resource}} />
|
|
10
|
-
</InputGroup>
|
|
11
|
-
|
|
12
|
-
<InputGroup @name="Model">
|
|
13
|
-
<Input @value={{@resource.model}} @type="text" class="w-full form-input" placeholder="Model" disabled={{cannot-write @resource}} />
|
|
14
|
-
</InputGroup>
|
|
15
|
-
|
|
16
|
-
<InputGroup @name="Serial Number">
|
|
17
|
-
<Input @value={{@resource.serial_number}} @type="text" class="w-full form-input" placeholder="Serial Number" disabled={{cannot-write @resource}} />
|
|
18
|
-
</InputGroup>
|
|
19
|
-
|
|
20
|
-
<InputGroup @name="Firmware Version">
|
|
21
|
-
<Input @value={{@resource.firmware_version}} @type="text" class="w-full form-input" placeholder="Firmware Version" disabled={{cannot-write @resource}} />
|
|
22
|
-
</InputGroup>
|
|
23
|
-
|
|
24
|
-
<InputGroup @name="IMEI">
|
|
25
|
-
<Input @value={{@resource.imei}} @type="text" class="w-full form-input" placeholder="IMEI" disabled={{cannot-write @resource}} />
|
|
26
|
-
</InputGroup>
|
|
27
|
-
|
|
28
|
-
<InputGroup @name="ICCID">
|
|
29
|
-
<Input @value={{@resource.iccid}} @type="text" class="w-full form-input" placeholder="ICCID" disabled={{cannot-write @resource}} />
|
|
30
|
-
</InputGroup>
|
|
31
|
-
|
|
32
|
-
<InputGroup @name="IMSI">
|
|
33
|
-
<Input @value={{@resource.imsi}} @type="text" class="w-full form-input" placeholder="IMSI" disabled={{cannot-write @resource}} />
|
|
34
|
-
</InputGroup>
|
|
35
|
-
|
|
36
|
-
<InputGroup @name="MSISDN">
|
|
37
|
-
<Input @value={{@resource.msisdn}} @type="text" class="w-full form-input" placeholder="MSISDN" disabled={{cannot-write @resource}} />
|
|
38
|
-
</InputGroup>
|
|
39
|
-
|
|
40
|
-
<InputGroup @name="Status">
|
|
4
|
+
<InputGroup @name="Provider" @wrapperClass="col-span-2">
|
|
41
5
|
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
42
6
|
<PowerSelect
|
|
43
|
-
@options={{this.
|
|
44
|
-
@selected={{
|
|
45
|
-
@onChange={{
|
|
46
|
-
@placeholder="Select
|
|
7
|
+
@options={{this.providers}}
|
|
8
|
+
@selected={{this.selectedProvider}}
|
|
9
|
+
@onChange={{this.selectProvider}}
|
|
10
|
+
@placeholder="Select Provider"
|
|
47
11
|
@triggerClass="form-select form-input"
|
|
48
12
|
@disabled={{cannot-write @resource}}
|
|
49
|
-
as |
|
|
13
|
+
as |provider|
|
|
50
14
|
>
|
|
51
|
-
|
|
15
|
+
<div class="space-x-2 text-sm">
|
|
16
|
+
<div class="inline-block align-top">
|
|
17
|
+
<div class="hide-from-trigger h-1.5 w-full" />
|
|
18
|
+
<Image src={{provider.icon}} class="w-5 h-5" />
|
|
19
|
+
</div>
|
|
20
|
+
<div class="inline-block">
|
|
21
|
+
<div class="font-semibold normalize-in-trigger">{{provider.label}}</div>
|
|
22
|
+
<div class="hide-from-trigger">{{n-a provider.description}}</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
52
25
|
</PowerSelect>
|
|
53
26
|
</div>
|
|
54
27
|
</InputGroup>
|
|
28
|
+
</div>
|
|
29
|
+
</ContentPanel>
|
|
55
30
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
31
|
+
{{#if this.selectedProvider}}
|
|
32
|
+
<ContentPanel @title="Credentials" @open={{true}} @actionButtons={{this.credentialsActionButtons}} @wrapperClass="bordered-top">
|
|
33
|
+
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
|
|
34
|
+
{{#each this.selectedProvider.required_fields as |field|}}
|
|
35
|
+
<InputGroup
|
|
36
|
+
@name={{field.label}}
|
|
37
|
+
@value={{get @resource.credentials field.name}}
|
|
38
|
+
@required={{field.required}}
|
|
39
|
+
@type={{if (eq field.type "password") "text" field.type}}
|
|
40
|
+
@helpText={{field.help_text}}
|
|
41
|
+
{{on "input" (fn this.setCredential field)}}
|
|
42
|
+
/>
|
|
43
|
+
{{/each}}
|
|
44
|
+
</div>
|
|
45
|
+
</ContentPanel>
|
|
46
|
+
|
|
47
|
+
<ContentPanel @title="Configure Integration" @open={{true}} @wrapperClass="bordered-top">
|
|
48
|
+
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
|
|
49
|
+
<InputGroup @value={{@resource.name}} @name="Integration Name" />
|
|
50
|
+
|
|
51
|
+
{{#if this.selectedProvider.supports_webhooks}}
|
|
52
|
+
<InputGroup @name="Webhook URL" @helpText={{concat "Configure the URL in your " this.selectedProvider.label " dashboard to receive real-time updates."}}>
|
|
53
|
+
<ClickToCopy @value={{this.selectedProvider.webhook_url}}>
|
|
54
|
+
<Input @value={{this.selectedProvider.webhook_url}} class="form-input" readonly />
|
|
55
|
+
</ClickToCopy>
|
|
56
|
+
<small class="form-text text-muted">
|
|
57
|
+
Configure this URL in your
|
|
58
|
+
{{this.selectedProvider.label}}
|
|
59
|
+
dashboard to receive real-time updates.
|
|
60
|
+
</small>
|
|
61
|
+
</InputGroup>
|
|
62
|
+
{{/if}}
|
|
63
|
+
</div>
|
|
64
|
+
</ContentPanel>
|
|
65
|
+
{{/if}}
|
|
72
66
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
<ContentPanel @title="Telematic Blackbox" @open={{true}} @wrapperClass="bordered-top">
|
|
68
|
+
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3 gap-2 no-input-group-padding text-xs">
|
|
69
|
+
<InputGroup @value={{@resource.model}} @name="Model" />
|
|
70
|
+
<InputGroup @value={{@resource.serial_number}} @name="Serial Number" />
|
|
71
|
+
<InputGroup @value={{@resource.firmware_version}} @name="Firmware Version" />
|
|
72
|
+
<InputGroup @value={{@resource.imei}} @name="IMEI" />
|
|
73
|
+
<InputGroup @value={{@resource.imsi}} @name="IMSI" />
|
|
74
|
+
<InputGroup @value={{@resource.iccid}} @name="ICCID" />
|
|
76
75
|
</div>
|
|
77
76
|
</ContentPanel>
|
|
78
77
|
|
|
@@ -81,4 +80,4 @@
|
|
|
81
80
|
<RegistryYield @registry="fleet-ops:component:telematic:form" as |RegistryComponent|>
|
|
82
81
|
<RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} />
|
|
83
82
|
</RegistryYield>
|
|
84
|
-
</div>
|
|
83
|
+
</div>
|
|
@@ -1,8 +1,77 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { task } from 'ember-concurrency';
|
|
2
6
|
|
|
3
7
|
export default class TelematicFormComponent extends Component {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
@service fetch;
|
|
9
|
+
@service notifications;
|
|
10
|
+
@tracked providers = [];
|
|
11
|
+
@tracked selectedProvider = this.args.resource?.provider_descriptor ?? null;
|
|
12
|
+
@tracked connectionTestResult;
|
|
13
|
+
|
|
14
|
+
get credentialsActionButtons() {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
size: 'xs',
|
|
18
|
+
icon: 'plug',
|
|
19
|
+
text: 'Test Connection',
|
|
20
|
+
onClick: () => this.testConnection.perform(),
|
|
21
|
+
isLoading: this.testConnection.isRunning,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super(...arguments);
|
|
28
|
+
this.loadProviders.perform();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@action setCredential(field, { target: { value } }) {
|
|
32
|
+
const credentials = this.args.resource.credentials ?? {};
|
|
33
|
+
this.args.resource.set('credentials', {
|
|
34
|
+
...credentials,
|
|
35
|
+
[field.name]: value,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@action selectProvider(provider) {
|
|
40
|
+
this.selectedProvider = provider;
|
|
41
|
+
this.args.resource.setProperties({
|
|
42
|
+
provider: provider.key,
|
|
43
|
+
credentials: (provider.required_fields ?? {}).reduce((acc, item) => {
|
|
44
|
+
acc[item.name] = null;
|
|
45
|
+
return acc;
|
|
46
|
+
}, {}),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@task *loadProviders() {
|
|
51
|
+
try {
|
|
52
|
+
const providers = yield this.fetch.get('telematics/providers');
|
|
53
|
+
this.providers = providers;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
this.notifications.serverError(err);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@task *testConnection() {
|
|
60
|
+
try {
|
|
61
|
+
const result = yield this.fetch.post(`telematics/${this.selectedProvider.key}/test-credentials`, { credentials: this.args.resource.credentials });
|
|
62
|
+
this.connectionTestResult = result;
|
|
63
|
+
|
|
64
|
+
if (result.success) {
|
|
65
|
+
this.notifications.success('Connection successful!');
|
|
66
|
+
} else {
|
|
67
|
+
this.notifications.error(result.message);
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
this.connectionTestResult = {
|
|
71
|
+
success: false,
|
|
72
|
+
message: error.message || 'Connection test failed',
|
|
73
|
+
};
|
|
74
|
+
this.notifications.error('Connection test failed');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
8
77
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<Layout::Resource::Card ...attributes as |Card|>
|
|
2
2
|
<Card.header class={{@headerClass}}>
|
|
3
3
|
<div class="font-semibold">{{or @resource.name @resource.yearMakeModel}}</div>
|
|
4
|
-
<div class="text-gray-300 dark:text-gray-500 text-sm">{{or @resource.plate_number @resource.vin @resource.serial_number @resource.call_sign}}</div>
|
|
4
|
+
<div class="text-gray-300 dark:text-gray-500 text-sm">{{or @resource.plate_number @resource.vin @resource.serial_number @resource.call_sign @resource.public_id}}</div>
|
|
5
5
|
{{#if (has-block "header")}}
|
|
6
6
|
{{yield to="header"}}
|
|
7
7
|
{{/if}}
|