@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,95 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action, get } from '@ember/object';
|
|
4
|
+
import { inject as service } from '@ember/service';
|
|
5
|
+
import { debug } from '@ember/debug';
|
|
6
|
+
import { task } from 'ember-concurrency';
|
|
7
|
+
import getModelName from '@fleetbase/ember-core/utils/get-model-name';
|
|
8
|
+
|
|
9
|
+
export default class DeviceManagerComponent extends Component {
|
|
10
|
+
@service store;
|
|
11
|
+
@service modalsManager;
|
|
12
|
+
@service notifications;
|
|
13
|
+
@tracked devices = [];
|
|
14
|
+
|
|
15
|
+
get resourceName() {
|
|
16
|
+
const record = this.args.resource;
|
|
17
|
+
if (!record) return 'resource';
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
get(record, this.args.namePath ?? 'name') ??
|
|
21
|
+
get(record, 'display_name') ??
|
|
22
|
+
get(record, 'displayName') ??
|
|
23
|
+
get(record, 'tracking') ??
|
|
24
|
+
get(record, 'public_id') ??
|
|
25
|
+
getModelName(record)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
super(...arguments);
|
|
31
|
+
this.loadDevices.perform();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@action addDevice() {
|
|
35
|
+
this.modalsManager.show('modals/attach-device', {
|
|
36
|
+
title: 'Select device to attach',
|
|
37
|
+
acceptButtonText: 'Confirm & Attach Device',
|
|
38
|
+
selectedDevice: null,
|
|
39
|
+
confirm: async (modal) => {
|
|
40
|
+
const selectedDevice = modal.getOption('selectedDevice');
|
|
41
|
+
if (!selectedDevice) return;
|
|
42
|
+
|
|
43
|
+
selectedDevice.setProperties({
|
|
44
|
+
attachable_uuid: this.args.resource.id,
|
|
45
|
+
attachable_type: `fleet-ops:${getModelName(this.args.resource)}`,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
modal.startLoading();
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await selectedDevice.save();
|
|
52
|
+
await this.loadDevices.perform();
|
|
53
|
+
this.notifications.success('Device attached successfully.');
|
|
54
|
+
modal.done();
|
|
55
|
+
} catch (err) {
|
|
56
|
+
this.notifications.serverError(err);
|
|
57
|
+
modal.stopLoading();
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@action removeDevice(device) {
|
|
64
|
+
this.modalsManager.confirm({
|
|
65
|
+
title: `Are you sure you want to detach this device (${device.name}) from (${this.resourceName})?`,
|
|
66
|
+
body: `Removing this device will stop all telematic updates and events for this ${getModelName(this.args.resource)}.`,
|
|
67
|
+
confirm: async (modal) => {
|
|
68
|
+
modal.startLoading();
|
|
69
|
+
|
|
70
|
+
device.setProperties({ attachable_uuid: null, attachable_type: null });
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await device.save();
|
|
74
|
+
await this.loadDevices.perform();
|
|
75
|
+
this.notifications.success('Device removed.');
|
|
76
|
+
modal.done();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.notifications.serverError(error);
|
|
79
|
+
modal.stopLoading();
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@task *loadDevices() {
|
|
86
|
+
if (!this.args.resource) return;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const devices = yield this.store.query('device', { attachable_uuid: this.args.resource.id });
|
|
90
|
+
this.devices = devices;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
debug('Unable to load devices: ' + err.message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -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">{{concat (n-a (titleize @resource.provider)) " " (n-a (get-fleet-ops-option-label "deviceTypes" @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>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{{#let (or @device @resource) as |resource|}}
|
|
2
|
+
<Pill
|
|
3
|
+
@resource={{resource}}
|
|
4
|
+
@imageSrc={{resource.photo_url}}
|
|
5
|
+
@subtitle={{or resource.device_id resource.serial_number resource.imei resource.public_id}}
|
|
6
|
+
@showOnlineIndicator={{true}}
|
|
7
|
+
@onClick={{@onClick}}
|
|
8
|
+
@anchorClass={{@anchorClass}}
|
|
9
|
+
@imageClass={{@imageClass}}
|
|
10
|
+
@imageWrapperClass={{@imageWrapperClass}}
|
|
11
|
+
@contentWrapperClass={{@contentWrapperClass}}
|
|
12
|
+
@titleClass={{@titleClass}}
|
|
13
|
+
@subtitleClass={{@subtitleClass}}
|
|
14
|
+
...attributes
|
|
15
|
+
/>
|
|
16
|
+
{{/let}}
|
|
@@ -53,4 +53,8 @@
|
|
|
53
53
|
</ContentPanel>
|
|
54
54
|
|
|
55
55
|
<CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
|
|
56
|
+
|
|
57
|
+
<ContentPanel @title={{t "common.metadata"}} @open={{true}} @actionButtons={{this.metadataButtons}} @wrapperClass="bordered-top" @panelBodyWrapperClass={{unless (is-object-empty @resource.meta) "px-0i" ""}}>
|
|
58
|
+
<MetadataViewer @metadata={{@resource.meta}} />
|
|
59
|
+
</ContentPanel>
|
|
56
60
|
</div>
|
|
@@ -1,3 +1,21 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
2
3
|
|
|
3
|
-
export default class DriverDetailsComponent extends Component {
|
|
4
|
+
export default class DriverDetailsComponent extends Component {
|
|
5
|
+
@service resourceMetadata;
|
|
6
|
+
|
|
7
|
+
get metadataButtons() {
|
|
8
|
+
return [
|
|
9
|
+
{
|
|
10
|
+
type: 'default',
|
|
11
|
+
text: 'Edit',
|
|
12
|
+
icon: 'pencil',
|
|
13
|
+
iconPrefix: 'fas',
|
|
14
|
+
permission: 'fleet-ops update driver',
|
|
15
|
+
onClick: () => {
|
|
16
|
+
this.resourceMetadata.edit(this.args.resource);
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -51,9 +51,16 @@
|
|
|
51
51
|
<ContentPanel @title={{t "driver.fields.driver-details"}} @open={{true}} @wrapperClass="bordered-top">
|
|
52
52
|
<div class="flex flex-row space-x-3 mb-3">
|
|
53
53
|
<div class="flex items-center justify-start rounded-full">
|
|
54
|
-
<Image
|
|
54
|
+
<Image
|
|
55
|
+
src={{@resource.photo_url}}
|
|
56
|
+
@fallbackSrc={{config "defaultValues.driverImage"}}
|
|
57
|
+
alt={{@resource.name}}
|
|
58
|
+
height="48"
|
|
59
|
+
width="48"
|
|
60
|
+
class="h-14 w-14 rounded-full shadow-sm"
|
|
61
|
+
/>
|
|
55
62
|
<Attach::Tooltip @class="clean" @animation="scale" @placement="top">
|
|
56
|
-
<InputInfo @text={{t "
|
|
63
|
+
<InputInfo @text={{t "common.upload-new-photo"}} />
|
|
57
64
|
</Attach::Tooltip>
|
|
58
65
|
</div>
|
|
59
66
|
<div>
|
|
@@ -133,7 +140,7 @@
|
|
|
133
140
|
</InputGroup>
|
|
134
141
|
|
|
135
142
|
<InputGroup @name={{t "common.status"}}>
|
|
136
|
-
|
|
143
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
137
144
|
<PowerSelect
|
|
138
145
|
@options={{get-fleet-ops-options "driverStatuses"}}
|
|
139
146
|
@selected={{find-by "value" @resource.status (get-fleet-ops-options "driverStatuses")}}
|
|
@@ -168,4 +175,8 @@
|
|
|
168
175
|
<ContentPanel @title={{t "avatar-picker.avatar"}} @open={{true}} @wrapperClass="bordered-top">
|
|
169
176
|
<AvatarPicker @model={{@resource}} @defaultAvatar={{config "defaultValues.driverAvatar"}} />
|
|
170
177
|
</ContentPanel>
|
|
178
|
+
|
|
179
|
+
<ContentPanel @title={{t "common.metadata"}} @open={{true}} @wrapperClass="bordered-top">
|
|
180
|
+
<MetadataEditor @value={{@resource.meta}} @onChange={{fn (mut @resource.meta)}} />
|
|
181
|
+
</ContentPanel>
|
|
171
182
|
</div>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
|
-
import { tracked } from '@glimmer/tracking';
|
|
3
2
|
import { inject as service } from '@ember/service';
|
|
4
3
|
import { task } from 'ember-concurrency';
|
|
5
4
|
|
|
@@ -9,55 +8,58 @@ export default class DriverFormComponent extends Component {
|
|
|
9
8
|
@service currentUser;
|
|
10
9
|
@service notifications;
|
|
11
10
|
@service modalsManager;
|
|
12
|
-
@tracked userAccountActionButtons = [
|
|
13
|
-
{
|
|
14
|
-
icon: 'user-plus',
|
|
15
|
-
size: 'xs',
|
|
16
|
-
permission: 'iam create user',
|
|
17
|
-
onClick: () => {
|
|
18
|
-
const user = this.store.createRecord('user', {
|
|
19
|
-
status: 'pending',
|
|
20
|
-
type: 'user',
|
|
21
|
-
});
|
|
22
11
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
type: 'user_photo',
|
|
35
|
-
},
|
|
36
|
-
(uploadedFile) => {
|
|
37
|
-
user.setProperties({
|
|
38
|
-
avatar_uuid: uploadedFile.id,
|
|
39
|
-
avatar_url: uploadedFile.url,
|
|
40
|
-
avatar: uploadedFile,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
},
|
|
45
|
-
confirm: async (modal) => {
|
|
46
|
-
modal.startLoading();
|
|
12
|
+
get userAccountActionButtons() {
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
icon: 'user-plus',
|
|
16
|
+
size: 'xs',
|
|
17
|
+
permission: 'iam create user',
|
|
18
|
+
onClick: () => {
|
|
19
|
+
const user = this.store.createRecord('user', {
|
|
20
|
+
status: 'pending',
|
|
21
|
+
type: 'user',
|
|
22
|
+
});
|
|
47
23
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
24
|
+
this.modalsManager.show('modals/user-form', {
|
|
25
|
+
title: 'Create a new user',
|
|
26
|
+
user,
|
|
27
|
+
formPermission: 'iam create user',
|
|
28
|
+
uploadNewPhoto: (file) => {
|
|
29
|
+
this.fetch.uploadFile.perform(
|
|
30
|
+
file,
|
|
31
|
+
{
|
|
32
|
+
path: `uploads/${this.currentUser.companyId}/users/${user.slug}`,
|
|
33
|
+
subject_uui: user.id,
|
|
34
|
+
subject_type: 'user',
|
|
35
|
+
type: 'user_photo',
|
|
36
|
+
},
|
|
37
|
+
(uploadedFile) => {
|
|
38
|
+
user.setProperties({
|
|
39
|
+
avatar_uuid: uploadedFile.id,
|
|
40
|
+
avatar_url: uploadedFile.url,
|
|
41
|
+
avatar: uploadedFile,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
confirm: async (modal) => {
|
|
47
|
+
modal.startLoading();
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await user.save();
|
|
51
|
+
this.notifications.success('New user created successfully!');
|
|
52
|
+
modal.done();
|
|
53
|
+
} catch (error) {
|
|
54
|
+
this.notifications.serverError(error);
|
|
55
|
+
modal.stopLoading();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
},
|
|
58
60
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
];
|
|
62
|
+
}
|
|
61
63
|
|
|
62
64
|
@task *handlePhotoUpload(file) {
|
|
63
65
|
try {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{{#let (or @driver @resource) as |resource|}}
|
|
2
|
+
<Pill
|
|
3
|
+
@resource={{resource}}
|
|
4
|
+
@imageSrc={{avatar-url resource.photo_url}}
|
|
5
|
+
@subtitle={{n-a resource.phone "No Phone"}}
|
|
6
|
+
@fallbackImageType="driverImage"
|
|
7
|
+
@showOnlineIndicator={{true}}
|
|
8
|
+
@onClick={{@onClick}}
|
|
9
|
+
@anchorClass={{@anchorClass}}
|
|
10
|
+
@imageClass={{@imageClass}}
|
|
11
|
+
@imageWrapperClass={{@imageWrapperClass}}
|
|
12
|
+
@contentWrapperClass={{@contentWrapperClass}}
|
|
13
|
+
@titleClass={{@titleClass}}
|
|
14
|
+
@subtitleClass={{@subtitleClass}}
|
|
15
|
+
...attributes
|
|
16
|
+
/>
|
|
17
|
+
{{/let}}
|
|
@@ -25,11 +25,7 @@
|
|
|
25
25
|
<InputGroup @name={{t "common.internal-id"}} @value={{@resource.internal_id}} @helpText={{t "modals.entity-form.id-text"}} />
|
|
26
26
|
<InputGroup @name={{t "modals.entity-form.sku"}} @value={{@resource.sku}} @helpText={{t "modals.entity-form.sku-text"}} />
|
|
27
27
|
<div></div>
|
|
28
|
-
<InputGroup
|
|
29
|
-
@name={{t "modals.entity-form.description"}}
|
|
30
|
-
@helpText={{t "modals.entity-form.description-text"}}
|
|
31
|
-
@wrapperClass="col-span-2"
|
|
32
|
-
>
|
|
28
|
+
<InputGroup @name={{t "modals.entity-form.description"}} @helpText={{t "modals.entity-form.description-text"}} @wrapperClass="col-span-2">
|
|
33
29
|
<Textarea @value={{@resource.description}} type="text" class="w-full form-input" placeholder={{t "modals.entity-form.description"}} />
|
|
34
30
|
</InputGroup>
|
|
35
31
|
</div>
|
|
@@ -83,4 +79,10 @@
|
|
|
83
79
|
</div>
|
|
84
80
|
</div>
|
|
85
81
|
</ContentPanel>
|
|
82
|
+
|
|
83
|
+
<CustomField::Yield @subject={{@resource}} @wrapperClass="bordered-top" />
|
|
84
|
+
|
|
85
|
+
<RegistryYield @registry="fleet-ops:component:entity:form" as |RegistryComponent|>
|
|
86
|
+
<RegistryComponent @resource={{@resource}} @controller={{this.controller}} @permission={{get-write-permission @resource}} />
|
|
87
|
+
</RegistryYield>
|
|
86
88
|
</div>
|
|
@@ -171,14 +171,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
171
171
|
permission: 'fleet-ops list device-event',
|
|
172
172
|
visible: this.abilities.can('fleet-ops see device-event'),
|
|
173
173
|
},
|
|
174
|
-
{
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
},
|
|
174
|
+
// {
|
|
175
|
+
// intl: 'menu.tracking',
|
|
176
|
+
// title: this.intl.t('menu.tracking'),
|
|
177
|
+
// icon: 'map-marked-alt',
|
|
178
|
+
// route: 'connectivity.tracking',
|
|
179
|
+
// permission: 'fleet-ops list device',
|
|
180
|
+
// visible: this.abilities.can('fleet-ops see device'),
|
|
181
|
+
// },
|
|
182
182
|
];
|
|
183
183
|
|
|
184
184
|
const maintenanceItems = [
|
|
@@ -284,10 +284,10 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
284
284
|
// open: this.appCache.get('fleet-ops:sidebar:maintenance:open', false),
|
|
285
285
|
// onToggle: (open) => this.appCache.set('fleet-ops:sidebar:maintenance:open', open),
|
|
286
286
|
// }),
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
287
|
+
createPanel('menu.connectivity', 'connectivity', connectivityItems, {
|
|
288
|
+
open: this.appCache.get('fleet-ops:sidebar:connectivity:open', false),
|
|
289
|
+
onToggle: (open) => this.appCache.set('fleet-ops:sidebar:connectivity:open', open),
|
|
290
|
+
}),
|
|
291
291
|
createPanel('menu.analytics', 'analytics', analyticsItems, {
|
|
292
292
|
open: this.appCache.get('fleet-ops:sidebar:analytics:open', false),
|
|
293
293
|
onToggle: (open) => this.appCache.set('fleet-ops:sidebar:analytics:open', open),
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<div class="flex flex-row items-center justify-between w-full px-5 py-2 border-b border-gray-200 dark:border-gray-700">
|
|
2
|
+
<div class="flex flex-row items-center space-x-2">
|
|
3
|
+
<div>
|
|
4
|
+
<ModelSelect
|
|
5
|
+
@modelName="telematic"
|
|
6
|
+
@selectedModel={{this.telematic}}
|
|
7
|
+
@placeholder="Filter by Telematic"
|
|
8
|
+
@triggerClass="form-select form-input form-input-sm w-48"
|
|
9
|
+
@infiniteScroll={{false}}
|
|
10
|
+
@renderInPlace={{true}}
|
|
11
|
+
@onChange={{this.onTelematicSelected}}
|
|
12
|
+
as |model|
|
|
13
|
+
>
|
|
14
|
+
<div class="space-x-2 text-sm">
|
|
15
|
+
<div class="inline-block align-top">
|
|
16
|
+
<div class="hide-from-trigger h-1.5 w-full" />
|
|
17
|
+
<Image src={{model.provider_descriptor.icon}} class="w-5 h-5" />
|
|
18
|
+
</div>
|
|
19
|
+
<div class="inline-block">
|
|
20
|
+
<div class="font-semibold normalize-in-trigger">{{or model.name model.public_id}}</div>
|
|
21
|
+
<div class="hide-from-trigger">{{n-a model.provider_descriptor.label (titleize model.provider)}}</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</ModelSelect>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div>
|
|
28
|
+
<ModelSelect
|
|
29
|
+
@modelName="device"
|
|
30
|
+
@selectedModel={{this.device}}
|
|
31
|
+
@placeholder="Filter by Device"
|
|
32
|
+
@triggerClass="form-select form-input form-input-sm w-48"
|
|
33
|
+
@infiniteScroll={{false}}
|
|
34
|
+
@renderInPlace={{true}}
|
|
35
|
+
@onChange={{this.onDeviceSelected}}
|
|
36
|
+
as |model|
|
|
37
|
+
>
|
|
38
|
+
<div class="text-sm">
|
|
39
|
+
<div class="font-semibold normalize-in-trigger">{{model.name}}</div>
|
|
40
|
+
<div class="hide-from-trigger">{{n-a model.serial_number}}</div>
|
|
41
|
+
</div>
|
|
42
|
+
</ModelSelect>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="w-48">
|
|
46
|
+
<DatePicker
|
|
47
|
+
@value={{this.dateFilter}}
|
|
48
|
+
@range={{true}}
|
|
49
|
+
@onSelect={{this.onDateRangeChanged}}
|
|
50
|
+
@autoClose={{true}}
|
|
51
|
+
@placeholder="Select date range"
|
|
52
|
+
class="w-full form-input form-input-sm w-48"
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{{#if this.loadEvents.isRunning}}
|
|
57
|
+
<div>
|
|
58
|
+
<Spinner />
|
|
59
|
+
</div>
|
|
60
|
+
{{/if}}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<Table @rows={{this.events}} @columns={{this.columns}} />
|
|
64
|
+
<Spacer @height="200px" />
|
|
@@ -0,0 +1,181 @@
|
|
|
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';
|
|
6
|
+
import { isArray } from '@ember/array';
|
|
7
|
+
import { startOfWeek, endOfWeek, format } from 'date-fns';
|
|
8
|
+
|
|
9
|
+
export default class MapDrawerDeviceEventListingComponent extends Component {
|
|
10
|
+
@service store;
|
|
11
|
+
@service hostRouter;
|
|
12
|
+
@service notifications;
|
|
13
|
+
@service deviceEventActions;
|
|
14
|
+
@service deviceActions;
|
|
15
|
+
@service intl;
|
|
16
|
+
|
|
17
|
+
@tracked events = [];
|
|
18
|
+
@tracked telematic = null;
|
|
19
|
+
@tracked device = null;
|
|
20
|
+
@tracked dateFilter = [format(startOfWeek(new Date(), { weekStartsOn: 1 }), 'yyyy-MM-dd'), format(endOfWeek(new Date(), { weekStartsOn: 1 }), 'yyyy-MM-dd')];
|
|
21
|
+
|
|
22
|
+
get columns() {
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
label: 'Event',
|
|
26
|
+
valuePath: 'event_type',
|
|
27
|
+
cellComponent: 'table/cell/anchor',
|
|
28
|
+
cellClassNames: 'uppercase',
|
|
29
|
+
action: this.deviceEventActions.panel.view,
|
|
30
|
+
permission: 'fleet-ops view device-event',
|
|
31
|
+
resizable: true,
|
|
32
|
+
sortable: true,
|
|
33
|
+
filterable: true,
|
|
34
|
+
filterParam: 'name',
|
|
35
|
+
filterComponent: 'filter/string',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: 'Device',
|
|
39
|
+
valuePath: 'device.displayName',
|
|
40
|
+
cellComponent: 'table/cell/anchor',
|
|
41
|
+
action: this.deviceActions.panel.view,
|
|
42
|
+
permission: 'fleet-ops view device',
|
|
43
|
+
resizable: true,
|
|
44
|
+
sortable: true,
|
|
45
|
+
filterable: true,
|
|
46
|
+
filterComponent: 'filter/model',
|
|
47
|
+
filterComponentPlaceholder: 'Select device',
|
|
48
|
+
filterParam: 'device',
|
|
49
|
+
model: 'device',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: 'Provider',
|
|
53
|
+
valuePath: 'provider',
|
|
54
|
+
resizable: true,
|
|
55
|
+
sortable: true,
|
|
56
|
+
filterable: true,
|
|
57
|
+
filterParam: 'provider',
|
|
58
|
+
filterComponent: 'filter/string',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: 'Severity',
|
|
62
|
+
valuePath: 'severity',
|
|
63
|
+
resizable: true,
|
|
64
|
+
sortable: true,
|
|
65
|
+
filterable: true,
|
|
66
|
+
filterParam: 'severity',
|
|
67
|
+
filterComponent: 'filter/string',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
label: 'IDENT',
|
|
71
|
+
valuePath: 'ident',
|
|
72
|
+
hidden: true,
|
|
73
|
+
resizable: true,
|
|
74
|
+
sortable: true,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
label: 'Protocol',
|
|
78
|
+
valuePath: 'protocol',
|
|
79
|
+
hidden: true,
|
|
80
|
+
resizable: true,
|
|
81
|
+
sortable: true,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: 'State',
|
|
85
|
+
valuePath: 'state',
|
|
86
|
+
hidden: true,
|
|
87
|
+
resizable: true,
|
|
88
|
+
sortable: true,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
label: 'Code',
|
|
92
|
+
valuePath: 'code',
|
|
93
|
+
resizable: true,
|
|
94
|
+
sortable: true,
|
|
95
|
+
filterable: true,
|
|
96
|
+
filterParam: 'code',
|
|
97
|
+
filterComponent: 'filter/string',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
label: this.intl.t('column.created-at'),
|
|
101
|
+
valuePath: 'createdAt',
|
|
102
|
+
sortParam: 'created_at',
|
|
103
|
+
width: '10%',
|
|
104
|
+
resizable: true,
|
|
105
|
+
sortable: true,
|
|
106
|
+
filterable: true,
|
|
107
|
+
filterComponent: 'filter/date',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
label: '',
|
|
111
|
+
cellComponent: 'table/cell/dropdown',
|
|
112
|
+
ddButtonText: false,
|
|
113
|
+
ddButtonIcon: 'ellipsis-h',
|
|
114
|
+
ddButtonIconPrefix: 'fas',
|
|
115
|
+
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.device-event') }),
|
|
116
|
+
cellClassNames: 'overflow-visible',
|
|
117
|
+
wrapperClass: 'flex items-center justify-end mx-2',
|
|
118
|
+
width: '10%',
|
|
119
|
+
actions: [
|
|
120
|
+
{
|
|
121
|
+
label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.device-event') }),
|
|
122
|
+
fn: this.deviceEventActions.panel.view,
|
|
123
|
+
permission: 'fleet-ops view device-event',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
sortable: false,
|
|
127
|
+
filterable: false,
|
|
128
|
+
resizable: false,
|
|
129
|
+
searchable: false,
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
constructor() {
|
|
135
|
+
super(...arguments);
|
|
136
|
+
this.loadEvents.perform();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@action onDeviceSelected(device) {
|
|
140
|
+
this.device = device;
|
|
141
|
+
this.loadEvents.perform();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@action onTelematicSelected(telematic) {
|
|
145
|
+
this.telematic = telematic;
|
|
146
|
+
this.loadEvents.perform();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@action onDateRangeChanged({ formattedDate }) {
|
|
150
|
+
if (isArray(formattedDate) && formattedDate.length === 2) {
|
|
151
|
+
this.dateFilter = formattedDate;
|
|
152
|
+
this.loadEvents.perform();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@task *loadEvents() {
|
|
157
|
+
try {
|
|
158
|
+
const params = {
|
|
159
|
+
limit: 900,
|
|
160
|
+
sort: 'created_at',
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if (this.telematic) {
|
|
164
|
+
params.telematic = this.telematic.id;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (this.device) {
|
|
168
|
+
params.device = this.device.id;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (isArray(this.dateFilter) && this.dateFilter.length === 2) {
|
|
172
|
+
params.created_at = this.dateFilter.join(',');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const events = yield this.store.query('device-event', params);
|
|
176
|
+
this.positions = isArray(events) ? events : [];
|
|
177
|
+
} catch (error) {
|
|
178
|
+
this.notifications.serverError(error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|