@fleetbase/fleetops-engine 0.6.52 → 0.6.54
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/device/details.hbs +106 -135
- package/addon/components/device/details.js +174 -1
- package/addon/components/device/panel-header.hbs +2 -2
- package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
- package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +654 -0
- package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
- package/addon/components/layout/fleet-ops-sidebar.js +364 -395
- package/addon/components/maintenance-schedule/form.js +1 -0
- package/addon/components/order/form/details.hbs +1 -43
- package/addon/components/order/form/details.js +18 -53
- package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
- package/addon/components/order/form/orchestrator-constraints.js +61 -0
- package/addon/components/order/form/payload.js +50 -2
- package/addon/components/order/form/route.js +15 -0
- package/addon/components/order/form/service-rate.hbs +62 -11
- package/addon/components/order/form/service-rate.js +124 -2
- package/addon/components/order/form.hbs +2 -0
- package/addon/components/telematic/details.hbs +7 -1
- package/addon/components/telematic/form.js +16 -8
- package/addon/components/work-order/details.hbs +5 -1
- package/addon/components/work-order/form.hbs +26 -4
- package/addon/components/work-order/form.js +5 -1
- package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
- package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
- package/addon/controllers/connectivity/devices/index/details/vehicle.js +74 -0
- package/addon/controllers/connectivity/devices/index/details.js +12 -0
- package/addon/controllers/connectivity/devices/index.js +4 -1
- package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
- package/addon/controllers/connectivity/telematics/details/devices.js +90 -41
- package/addon/controllers/connectivity/telematics/details/events.js +39 -11
- package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
- package/addon/controllers/maintenance/index.js +100 -0
- package/addon/controllers/maintenance/work-orders/index.js +18 -1
- package/addon/controllers/management/fuel-transactions/index.js +1 -1
- package/addon/controllers/management/index.js +88 -0
- package/addon/controllers/settings/index.js +142 -0
- package/addon/extension.js +21 -0
- package/addon/routes/analytics/index.js +3 -0
- package/addon/routes/connectivity/devices/index/details/events.js +37 -1
- package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
- package/addon/routes/connectivity/devices/index/details/vehicle.js +7 -0
- package/addon/routes/connectivity/devices/index.js +3 -0
- package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
- package/addon/routes/connectivity/telematics/details/devices.js +4 -0
- package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
- package/addon/routes/connectivity/telematics/details.js +1 -1
- package/addon/routes/connectivity/telematics/edit.js +1 -1
- package/addon/routes/maintenance/index.js +3 -0
- package/addon/routes/maintenance/work-orders/index.js +3 -0
- package/addon/routes/settings/index.js +3 -0
- package/addon/routes.js +2 -0
- package/addon/services/order-creation.js +54 -1
- package/addon/services/order-validation.js +3 -2
- package/addon/services/service-rate-actions.js +1 -0
- package/addon/styles/fleetops-engine.css +1821 -6
- package/addon/templates/analytics/index.hbs +14 -0
- package/addon/templates/analytics/reports/index.hbs +4 -4
- package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
- package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
- package/addon/templates/connectivity/devices/index/details/vehicle.hbs +84 -0
- package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
- package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
- package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
- package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
- package/addon/templates/maintenance/index.hbs +113 -0
- package/addon/templates/management/fuel-transactions/index/details.hbs +1 -1
- package/addon/templates/management/index.hbs +109 -73
- package/addon/templates/management/vehicles/index/edit.hbs +1 -1
- package/addon/templates/operations/orders/index.hbs +1 -1
- package/addon/templates/operations/service-rates/index.hbs +1 -1
- package/addon/templates/settings/index.hbs +83 -0
- package/addon/templates/settings/routing.hbs +2 -2
- package/addon/utils/fleet-ops-options.js +38 -0
- package/app/components/order/form/orchestrator-constraints.js +1 -0
- package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
- package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/controllers/maintenance/index.js +1 -0
- package/app/controllers/management/index.js +1 -0
- package/app/controllers/settings/index.js +1 -0
- package/app/routes/analytics/index.js +1 -0
- package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
- package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/routes/maintenance/index.js +1 -0
- package/app/routes/settings/index.js +1 -0
- package/app/templates/analytics/index.js +1 -0
- package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
- package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/templates/maintenance/index.js +1 -0
- package/app/templates/management/index.js +1 -0
- package/app/templates/settings/index.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
- package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
- package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
- package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
- package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
- package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
- package/server/src/Console/Commands/SyncTelematics.php +89 -0
- package/server/src/Exceptions/TelematicProviderException.php +13 -2
- package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +112 -11
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +16 -5
- package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
- package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +177 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
- package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +94 -10
- package/server/src/Http/Filter/DeviceEventFilter.php +44 -0
- package/server/src/Http/Filter/DeviceFilter.php +131 -0
- package/server/src/Http/Filter/DriverFilter.php +6 -0
- package/server/src/Http/Filter/SensorFilter.php +125 -0
- package/server/src/Http/Filter/VehicleFilter.php +19 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
- package/server/src/Http/Resources/v1/Driver.php +11 -2
- package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
- package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
- package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
- package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +181 -66
- package/server/src/Models/Device.php +15 -1
- package/server/src/Models/DeviceEvent.php +1 -1
- package/server/src/Models/Driver.php +17 -1
- package/server/src/Models/Sensor.php +15 -2
- package/server/src/Models/Telematic.php +1 -1
- package/server/src/Models/Vehicle.php +5 -1
- package/server/src/Models/WorkOrder.php +5 -2
- package/server/src/Observers/DriverObserver.php +3 -3
- package/server/src/Observers/FleetObserver.php +23 -0
- package/server/src/Observers/VehicleObserver.php +3 -3
- package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
- package/server/src/Support/LiveCacheService.php +1 -1
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +188 -28
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +44 -3
- package/server/src/Support/Telematics/TelematicService.php +330 -30
- package/server/src/routes.php +10 -0
- package/server/tests/DeviceFilterTest.php +36 -0
- package/server/tests/HubRoutesTest.php +62 -0
- package/server/tests/OperationsMonitorEndpointTest.php +38 -0
- package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
- package/server/tests/TelematicsHardeningTest.php +455 -3
- package/server/tests/VehicleDriverAssignmentTest.php +27 -0
- package/translations/ar-ae.yml +1 -1
- package/translations/en-us.yaml +1 -0
- package/translations/vi-vn.yaml +1 -1
|
@@ -1,18 +1,72 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
|
-
import { task } from 'ember-concurrency';
|
|
4
|
+
import { task, timeout } from 'ember-concurrency';
|
|
5
|
+
import { SERVICE_QUOTE_REFRESH_REQUESTED } from '../../../services/order-creation';
|
|
6
|
+
|
|
7
|
+
const SERVICE_QUOTE_REFRESH_DEBOUNCE_MS = 500;
|
|
5
8
|
|
|
6
9
|
export default class OrderFormServiceRateComponent extends Component {
|
|
7
10
|
@service serviceRateActions;
|
|
11
|
+
@service orderCreation;
|
|
8
12
|
@tracked selectedRate;
|
|
9
13
|
@tracked serviceRates = [];
|
|
10
14
|
@tracked serviceQuotes = [];
|
|
15
|
+
@tracked isAutoRefreshingServiceQuotes = false;
|
|
16
|
+
serviceQuoteRefreshRequestId = 0;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
super(...arguments);
|
|
20
|
+
this._serviceQuoteRefreshRequested = (event) => this.handleServiceQuoteRefreshRequest(event);
|
|
21
|
+
this.orderCreation.on(SERVICE_QUOTE_REFRESH_REQUESTED, this._serviceQuoteRefreshRequested);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
willDestroy() {
|
|
25
|
+
this.orderCreation.off(SERVICE_QUOTE_REFRESH_REQUESTED, this._serviceQuoteRefreshRequested);
|
|
26
|
+
this.refreshServiceQuotes.cancelAll();
|
|
27
|
+
super.willDestroy(...arguments);
|
|
28
|
+
}
|
|
11
29
|
|
|
12
30
|
get isServicable() {
|
|
13
31
|
return this.args.resource?.order_config && this.args.resource?.payloadCoordinates?.length >= 2;
|
|
14
32
|
}
|
|
15
33
|
|
|
34
|
+
get canRefreshServiceQuotes() {
|
|
35
|
+
return this.args.resource?.servicable && this.selectedRate && this.args.resource?.payloadCoordinates?.length >= 2;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get hasServiceQuotes() {
|
|
39
|
+
return this.serviceQuotes?.length > 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get isLoadingServiceQuotes() {
|
|
43
|
+
return this.getServiceQuotes.isRunning || this.isAutoRefreshingServiceQuotes;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get shouldShowServiceQuotesLoader() {
|
|
47
|
+
return this.isLoadingServiceQuotes && !this.hasServiceQuotes;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get serviceQuoteOverride() {
|
|
51
|
+
return this.orderCreation.getServiceQuoteOverride(this.args.resource);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get hasServiceQuoteOverride() {
|
|
55
|
+
return this.serviceQuoteOverride?.mode === 'locked';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get overrideQuote() {
|
|
59
|
+
return this.serviceQuoteOverride?.quote;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get overrideQuoteItems() {
|
|
63
|
+
return this.overrideQuote?.items ?? [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get overrideCurrency() {
|
|
67
|
+
return this.overrideQuote?.currency ?? this.serviceQuoteOverride?.currency ?? 'USD';
|
|
68
|
+
}
|
|
69
|
+
|
|
16
70
|
@task *queryServiceRates(toggled) {
|
|
17
71
|
this.args.resource.servicable = toggled;
|
|
18
72
|
if (!toggled) return;
|
|
@@ -21,6 +75,74 @@ export default class OrderFormServiceRateComponent extends Component {
|
|
|
21
75
|
|
|
22
76
|
@task *getServiceQuotes(serviceRate) {
|
|
23
77
|
this.selectedRate = serviceRate;
|
|
24
|
-
|
|
78
|
+
yield this.loadServiceQuotes(serviceRate);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@task *refreshServiceQuotes(refreshRequestId) {
|
|
82
|
+
this.isAutoRefreshingServiceQuotes = true;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
yield timeout(SERVICE_QUOTE_REFRESH_DEBOUNCE_MS);
|
|
86
|
+
|
|
87
|
+
if (!this.canRefreshServiceQuotes) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
yield this.loadServiceQuotes(this.selectedRate);
|
|
92
|
+
} finally {
|
|
93
|
+
if (refreshRequestId === this.serviceQuoteRefreshRequestId) {
|
|
94
|
+
this.isAutoRefreshingServiceQuotes = false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
handleServiceQuoteRefreshRequest({ order } = {}) {
|
|
100
|
+
if (order && order !== this.args.resource) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (this.hasServiceQuoteOverride) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (this.args.resource?.servicable && !this.serviceRates?.length && !this.queryServiceRates.isRunning) {
|
|
109
|
+
this.queryServiceRates.perform(true);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!this.canRefreshServiceQuotes) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.refreshServiceQuotes.cancelAll();
|
|
117
|
+
this.serviceQuoteRefreshRequestId++;
|
|
118
|
+
this.isAutoRefreshingServiceQuotes = true;
|
|
119
|
+
this.refreshServiceQuotes.perform(this.serviceQuoteRefreshRequestId);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async loadServiceQuotes(serviceRate) {
|
|
123
|
+
const serviceQuotes = await this.serviceRateActions.getServiceQuotes.perform(serviceRate, this.args.resource);
|
|
124
|
+
this.serviceQuotes = serviceQuotes ?? [];
|
|
125
|
+
this.clearStaleSelectedServiceQuote();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
clearStaleSelectedServiceQuote() {
|
|
129
|
+
const selectedServiceQuote = this.args.resource?.service_quote_uuid;
|
|
130
|
+
|
|
131
|
+
if (!selectedServiceQuote) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!this.serviceQuotes?.length) {
|
|
136
|
+
this.args.resource.service_quote_uuid = null;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const selectedQuoteExists = this.serviceQuotes.some((serviceQuote) => {
|
|
141
|
+
return serviceQuote?.uuid === selectedServiceQuote || serviceQuote?.id === selectedServiceQuote;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!selectedQuoteExists) {
|
|
145
|
+
this.args.resource.service_quote_uuid = null;
|
|
146
|
+
}
|
|
25
147
|
}
|
|
26
148
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
ServiceRate=(component "order/form/service-rate" resource=@resource)
|
|
11
11
|
Notes=(component "order/form/notes" resource=@resource)
|
|
12
12
|
Documents=(component "order/form/documents" resource=@resource)
|
|
13
|
+
OrchestratorConstraints=(component "order/form/orchestrator-constraints" resource=@resource)
|
|
13
14
|
Metadata=(component "order/form/metadata" resource=@resource)
|
|
14
15
|
)
|
|
15
16
|
}}
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
<Order::Form::ServiceRate @resource={{@resource}} />
|
|
24
25
|
<Order::Form::Notes @resource={{@resource}} />
|
|
25
26
|
<Order::Form::Documents @resource={{@resource}} />
|
|
27
|
+
<Order::Form::OrchestratorConstraints @resource={{@resource}} />
|
|
26
28
|
<Order::Form::Metadata @resource={{@resource}} />
|
|
27
29
|
{{/if}}
|
|
28
30
|
</div>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
{{#if this.attentionItems.length}}
|
|
33
33
|
<section class="flex flex-col gap-3">
|
|
34
34
|
{{#each this.attentionItems as |item|}}
|
|
35
|
-
<div class="w-full rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-700 dark:bg-
|
|
35
|
+
<div class="w-full rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-700 dark:bg-yellow-900/20">
|
|
36
36
|
<div class="flex items-start gap-3">
|
|
37
37
|
<div class="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-md bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300">
|
|
38
38
|
<FaIcon @icon={{item.icon}} />
|
|
@@ -88,6 +88,12 @@
|
|
|
88
88
|
<div class="text-yellow-700 dark:text-yellow-300">Webhook URL unavailable until this integration has a public ID.</div>
|
|
89
89
|
{{/if}}
|
|
90
90
|
</div>
|
|
91
|
+
{{else}}
|
|
92
|
+
<div class="rounded-md border border-gray-200 p-3 dark:border-gray-700 md:col-span-2">
|
|
93
|
+
<div class="text-gray-400 dark:text-gray-500">Update Mode</div>
|
|
94
|
+
<div class="mt-1 font-semibold text-gray-900 dark:text-gray-100">Provider polling</div>
|
|
95
|
+
<div class="mt-1 text-gray-500 dark:text-gray-400">FleetOps polls this provider for device snapshots and telemetry updates.</div>
|
|
96
|
+
</div>
|
|
91
97
|
{{/if}}
|
|
92
98
|
</div>
|
|
93
99
|
</div>
|
|
@@ -415,13 +415,17 @@ export default class TelematicFormComponent extends Component {
|
|
|
415
415
|
this.args.resource.setProperties({
|
|
416
416
|
name: this.args.resource.name ?? provider.label,
|
|
417
417
|
provider: provider.key,
|
|
418
|
-
credentials: (provider
|
|
419
|
-
acc[item.name] = item.advanced || item.is_endpoint ? null : (item.default_value ?? null);
|
|
420
|
-
return acc;
|
|
421
|
-
}, {}),
|
|
418
|
+
credentials: this.buildProviderCredentials(provider),
|
|
422
419
|
});
|
|
423
420
|
}
|
|
424
421
|
|
|
422
|
+
buildProviderCredentials(provider) {
|
|
423
|
+
return (provider.required_fields ?? []).reduce((acc, item) => {
|
|
424
|
+
acc[item.name] = item.default_value ?? null;
|
|
425
|
+
return acc;
|
|
426
|
+
}, {});
|
|
427
|
+
}
|
|
428
|
+
|
|
425
429
|
@action goToStep(index) {
|
|
426
430
|
if (!this.canReachStep(index)) {
|
|
427
431
|
return;
|
|
@@ -553,10 +557,7 @@ export default class TelematicFormComponent extends Component {
|
|
|
553
557
|
this.lastConnectionTestCompletedAt = null;
|
|
554
558
|
|
|
555
559
|
try {
|
|
556
|
-
const result = yield this.fetch.post(`telematics/${this.selectedProvider.key}/test-credentials`,
|
|
557
|
-
credentials: this.args.resource.credentials,
|
|
558
|
-
telematic_id: this.args.resource?.id,
|
|
559
|
-
});
|
|
560
|
+
const result = yield this.fetch.post(`telematics/${this.selectedProvider.key}/test-credentials`, this.getConnectionTestPayload());
|
|
560
561
|
this.connectionTestResult = result;
|
|
561
562
|
this.lastConnectionTestCompletedAt = new Date();
|
|
562
563
|
this.updateResourceConnectionTestMeta(result);
|
|
@@ -577,6 +578,13 @@ export default class TelematicFormComponent extends Component {
|
|
|
577
578
|
}
|
|
578
579
|
}
|
|
579
580
|
|
|
581
|
+
getConnectionTestPayload() {
|
|
582
|
+
return {
|
|
583
|
+
credentials: this.args.resource.credentials,
|
|
584
|
+
telematic_id: this.args.resource?.id,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
580
588
|
updateResourceConnectionTestMeta(result) {
|
|
581
589
|
const meta = this.args.resource?.meta ?? {};
|
|
582
590
|
const testedAt = this.lastConnectionTestCompletedAt ?? new Date();
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
<div class="field-name">Subject</div>
|
|
21
21
|
<div class="field-value">{{n-a @resource.subject}}</div>
|
|
22
22
|
</div>
|
|
23
|
+
<div class="field-info-container col-span-2">
|
|
24
|
+
<div class="field-name">Category</div>
|
|
25
|
+
<div class="field-value">{{n-a (smart-humanize @resource.category)}}</div>
|
|
26
|
+
</div>
|
|
23
27
|
|
|
24
28
|
{{! — Status & Priority — }}
|
|
25
29
|
<div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
|
|
@@ -185,4 +189,4 @@
|
|
|
185
189
|
<CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
|
|
186
190
|
|
|
187
191
|
<Spacer @height="200px" />
|
|
188
|
-
</div>
|
|
192
|
+
</div>
|
|
@@ -13,6 +13,27 @@
|
|
|
13
13
|
<Input @value={{@resource.subject}} @type="text" class="w-full form-input" placeholder="Brief description of the work order" disabled={{cannot-write @resource}} />
|
|
14
14
|
</InputGroup>
|
|
15
15
|
|
|
16
|
+
{{! Work Classification }}
|
|
17
|
+
<div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
|
|
18
|
+
Work Order Classification
|
|
19
|
+
</div>
|
|
20
|
+
<InputGroup @name="Category" @wrapperClass="col-span-1 lg:col-span-2">
|
|
21
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
22
|
+
<PowerSelect
|
|
23
|
+
@options={{this.categoryOptions}}
|
|
24
|
+
@selected={{find-by "value" @resource.category this.categoryOptions}}
|
|
25
|
+
@onChange={{set-model-attr @resource "category"}}
|
|
26
|
+
@placeholder="Select Category"
|
|
27
|
+
@triggerClass="form-select form-input"
|
|
28
|
+
@disabled={{cannot-write @resource}}
|
|
29
|
+
as |category|
|
|
30
|
+
>
|
|
31
|
+
<div class="font-semibold normalize-in-trigger">{{category.label}}</div>
|
|
32
|
+
<div class="hide-from-trigger">{{category.description}}</div>
|
|
33
|
+
</PowerSelect>
|
|
34
|
+
</div>
|
|
35
|
+
</InputGroup>
|
|
36
|
+
|
|
16
37
|
{{! Status & Priority }}
|
|
17
38
|
<div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
|
|
18
39
|
Status & Priority
|
|
@@ -21,14 +42,15 @@
|
|
|
21
42
|
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
22
43
|
<PowerSelect
|
|
23
44
|
@options={{this.statusOptions}}
|
|
24
|
-
@selected={{@resource.status}}
|
|
25
|
-
@onChange={{
|
|
45
|
+
@selected={{find-by "value" @resource.status this.statusOptions}}
|
|
46
|
+
@onChange={{set-model-attr @resource "status"}}
|
|
26
47
|
@placeholder="Select Status"
|
|
27
48
|
@triggerClass="form-select form-input"
|
|
28
49
|
@disabled={{cannot-write @resource}}
|
|
29
50
|
as |status|
|
|
30
51
|
>
|
|
31
|
-
{{
|
|
52
|
+
<div class="font-semibold normalize-in-trigger">{{status.label}}</div>
|
|
53
|
+
<div class="hide-from-trigger">{{status.description}}</div>
|
|
32
54
|
</PowerSelect>
|
|
33
55
|
</div>
|
|
34
56
|
</InputGroup>
|
|
@@ -243,4 +265,4 @@
|
|
|
243
265
|
<ContentPanel @title="Metadata" @open={{false}} @wrapperClass="bordered-top">
|
|
244
266
|
<MetadataEditor @value={{@resource.meta}} @onChange={{fn (mut @resource.meta)}} />
|
|
245
267
|
</ContentPanel>
|
|
246
|
-
</div>
|
|
268
|
+
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { action } from '@ember/object';
|
|
4
|
+
import { workOrderCategories, workOrderStatuses } from '../../utils/fleet-ops-options';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Maps a user-facing polymorphic type string to the Ember Data model name
|
|
@@ -42,7 +43,10 @@ const ASSIGNEE_MODEL_TO_TYPE = {
|
|
|
42
43
|
|
|
43
44
|
export default class WorkOrderFormComponent extends Component {
|
|
44
45
|
/** Status options for work orders. */
|
|
45
|
-
statusOptions =
|
|
46
|
+
statusOptions = workOrderStatuses;
|
|
47
|
+
|
|
48
|
+
/** Operational category options for work orders. */
|
|
49
|
+
categoryOptions = workOrderCategories;
|
|
46
50
|
|
|
47
51
|
/** Priority options for work orders. */
|
|
48
52
|
priorityOptions = ['low', 'medium', 'high', 'critical'];
|
|
@@ -1,3 +1,246 @@
|
|
|
1
1
|
import Controller from '@ember/controller';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { tracked } from '@glimmer/tracking';
|
|
5
|
+
import { task, timeout } from 'ember-concurrency';
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
const severityOptions = [
|
|
8
|
+
{ label: 'Info', value: 'info' },
|
|
9
|
+
{ label: 'Warning', value: 'warning' },
|
|
10
|
+
{ label: 'Error', value: 'error' },
|
|
11
|
+
{ label: 'Critical', value: 'critical' },
|
|
12
|
+
{ label: 'High', value: 'high' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const warningSeverities = ['warning', 'error', 'critical', 'high'];
|
|
16
|
+
|
|
17
|
+
export default class ConnectivityDevicesIndexDetailsEventsController extends Controller {
|
|
18
|
+
@service deviceEventActions;
|
|
19
|
+
@service hostRouter;
|
|
20
|
+
@service intl;
|
|
21
|
+
|
|
22
|
+
@tracked queryParams = [
|
|
23
|
+
'events_page',
|
|
24
|
+
'events_limit',
|
|
25
|
+
'events_sort',
|
|
26
|
+
'events_query',
|
|
27
|
+
'events_event_type',
|
|
28
|
+
'events_severity',
|
|
29
|
+
'events_processed',
|
|
30
|
+
'events_occurred_at',
|
|
31
|
+
'events_created_at',
|
|
32
|
+
];
|
|
33
|
+
@tracked device;
|
|
34
|
+
@tracked events_page = 1;
|
|
35
|
+
@tracked events_limit;
|
|
36
|
+
@tracked events_sort = '-created_at';
|
|
37
|
+
@tracked events_query;
|
|
38
|
+
@tracked events_event_type;
|
|
39
|
+
@tracked events_severity;
|
|
40
|
+
@tracked events_processed;
|
|
41
|
+
@tracked events_occurred_at;
|
|
42
|
+
@tracked events_created_at;
|
|
43
|
+
|
|
44
|
+
@tracked bulkActions = [];
|
|
45
|
+
|
|
46
|
+
get events() {
|
|
47
|
+
return Array.from(this.model ?? []);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get totalEvents() {
|
|
51
|
+
return this.model?.meta?.total ?? this.events.length;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get warningEventsCount() {
|
|
55
|
+
return this.events.filter((event) => warningSeverities.includes(String(event.severity ?? '').toLowerCase())).length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get unprocessedEventsCount() {
|
|
59
|
+
return this.events.filter((event) => !event.processed_at && !event.is_processed).length;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get processedEventsCount() {
|
|
63
|
+
return Math.max(this.events.length - this.unprocessedEventsCount, 0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get hasHealthyEventState() {
|
|
67
|
+
return this.events.length > 0 && this.warningEventsCount === 0 && this.unprocessedEventsCount === 0 && !this.hasActiveFilters;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get hasActiveFilters() {
|
|
71
|
+
return Boolean(this.events_query || this.events_event_type || this.events_severity || this.events_processed || this.events_occurred_at || this.events_created_at);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get metrics() {
|
|
75
|
+
return [
|
|
76
|
+
{ label: 'Recent events', value: this.totalEvents, icon: 'list', accentClass: 'fleetops-connectivity-kpi-accent-blue' },
|
|
77
|
+
{
|
|
78
|
+
label: 'Warnings',
|
|
79
|
+
value: this.warningEventsCount,
|
|
80
|
+
icon: 'triangle-exclamation',
|
|
81
|
+
accentClass: this.warningEventsCount ? 'fleetops-connectivity-kpi-accent-amber' : 'fleetops-connectivity-kpi-accent-green',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: 'Unprocessed',
|
|
85
|
+
value: this.unprocessedEventsCount,
|
|
86
|
+
icon: 'clock',
|
|
87
|
+
accentClass: this.unprocessedEventsCount ? 'fleetops-connectivity-kpi-accent-rose' : 'fleetops-connectivity-kpi-accent-green',
|
|
88
|
+
},
|
|
89
|
+
{ label: 'Processed', value: this.processedEventsCount, icon: 'check', accentClass: 'fleetops-connectivity-kpi-accent-green' },
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get actionButtons() {
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
icon: 'refresh',
|
|
97
|
+
size: 'sm',
|
|
98
|
+
onClick: this.refresh,
|
|
99
|
+
helpText: this.intl.t('common.refresh'),
|
|
100
|
+
wrapperClass: 'fleetops-telematics-action-button',
|
|
101
|
+
isLoading: this.refreshTask.isRunning,
|
|
102
|
+
disabled: this.refreshTask.isRunning,
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get columns() {
|
|
108
|
+
return [
|
|
109
|
+
{
|
|
110
|
+
sticky: true,
|
|
111
|
+
label: 'Event',
|
|
112
|
+
valuePath: 'event_type',
|
|
113
|
+
cellComponent: 'table/cell/anchor',
|
|
114
|
+
action: this.deviceEventActions.transition.view,
|
|
115
|
+
permission: 'fleet-ops view device-event',
|
|
116
|
+
resizable: true,
|
|
117
|
+
sortable: true,
|
|
118
|
+
filterable: true,
|
|
119
|
+
filterParam: 'events_event_type',
|
|
120
|
+
filterComponent: 'filter/string',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
label: 'Severity',
|
|
124
|
+
valuePath: 'severity',
|
|
125
|
+
cellComponent: 'table/cell/status',
|
|
126
|
+
resizable: true,
|
|
127
|
+
sortable: true,
|
|
128
|
+
filterable: true,
|
|
129
|
+
filterParam: 'events_severity',
|
|
130
|
+
filterComponent: 'filter/multi-option',
|
|
131
|
+
filterOptions: severityOptions,
|
|
132
|
+
filterOptionLabel: 'label',
|
|
133
|
+
filterOptionValue: 'value',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: 'Message',
|
|
137
|
+
valuePath: 'message',
|
|
138
|
+
resizable: true,
|
|
139
|
+
sortable: false,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
label: 'Code',
|
|
143
|
+
valuePath: 'code',
|
|
144
|
+
resizable: true,
|
|
145
|
+
sortable: true,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
label: 'Processed',
|
|
149
|
+
valuePath: 'processedAt',
|
|
150
|
+
sortParam: 'processed_at',
|
|
151
|
+
resizable: true,
|
|
152
|
+
sortable: true,
|
|
153
|
+
filterable: true,
|
|
154
|
+
filterParam: 'events_processed',
|
|
155
|
+
filterComponent: 'filter/multi-option',
|
|
156
|
+
filterOptions: [
|
|
157
|
+
{ label: 'Processed', value: 'processed' },
|
|
158
|
+
{ label: 'Unprocessed', value: 'unprocessed' },
|
|
159
|
+
],
|
|
160
|
+
filterOptionLabel: 'label',
|
|
161
|
+
filterOptionValue: 'value',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: 'Occurred',
|
|
165
|
+
valuePath: 'occurredAt',
|
|
166
|
+
sortParam: 'occurred_at',
|
|
167
|
+
resizable: true,
|
|
168
|
+
sortable: true,
|
|
169
|
+
filterable: true,
|
|
170
|
+
filterParam: 'events_occurred_at',
|
|
171
|
+
filterComponent: 'filter/date',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
label: this.intl.t('column.created-at'),
|
|
175
|
+
valuePath: 'createdAt',
|
|
176
|
+
sortParam: 'created_at',
|
|
177
|
+
resizable: true,
|
|
178
|
+
sortable: true,
|
|
179
|
+
filterable: true,
|
|
180
|
+
filterParam: 'events_created_at',
|
|
181
|
+
filterComponent: 'filter/date',
|
|
182
|
+
hidden: true,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
label: '',
|
|
186
|
+
cellComponent: 'table/cell/dropdown',
|
|
187
|
+
ddButtonText: false,
|
|
188
|
+
ddButtonIcon: 'ellipsis-h',
|
|
189
|
+
ddButtonIconPrefix: 'fas',
|
|
190
|
+
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.device-event') }),
|
|
191
|
+
cellClassNames: 'overflow-visible align-middle',
|
|
192
|
+
wrapperClass: 'flex items-center justify-end mx-2',
|
|
193
|
+
sticky: 'right',
|
|
194
|
+
width: 60,
|
|
195
|
+
actions: [
|
|
196
|
+
{
|
|
197
|
+
label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.device-event') }),
|
|
198
|
+
fn: this.deviceEventActions.transition.view,
|
|
199
|
+
permission: 'fleet-ops view device-event',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
label: 'Mark processed',
|
|
203
|
+
fn: this.markProcessed,
|
|
204
|
+
permission: 'fleet-ops update device-event',
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
sortable: false,
|
|
208
|
+
filterable: false,
|
|
209
|
+
resizable: false,
|
|
210
|
+
searchable: false,
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@action refresh() {
|
|
216
|
+
if (this.refreshTask.isRunning) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return this.refreshTask.perform();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@action async markProcessed(deviceEvent) {
|
|
224
|
+
await this.deviceEventActions.markProcessed(deviceEvent);
|
|
225
|
+
await this.hostRouter.refresh();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@task *refreshTask() {
|
|
229
|
+
yield this.hostRouter.refresh();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@task({ restartable: true }) *searchTask(event) {
|
|
233
|
+
const {
|
|
234
|
+
target: { value },
|
|
235
|
+
} = event;
|
|
236
|
+
|
|
237
|
+
if (!value) {
|
|
238
|
+
this.events_query = null;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
yield timeout(250);
|
|
243
|
+
if (this.events_page > 1) this.events_page = 1;
|
|
244
|
+
this.events_query = value;
|
|
245
|
+
}
|
|
246
|
+
}
|