@fleetbase/fleetops-engine 0.6.68 → 0.6.69
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/RELEASE.md +11 -9
- package/addon/components/contact/form.hbs +13 -2
- package/addon/components/customer/details.hbs +17 -17
- package/addon/components/customer/form.hbs +13 -2
- package/addon/components/customer/form.js +0 -56
- package/addon/components/driver/details.hbs +13 -19
- package/addon/components/driver/form.hbs +20 -35
- package/addon/components/driver/form.js +0 -58
- package/addon/components/modals/driver-form.hbs +2 -2
- package/addon/components/modals/reset-profile-credentials.hbs +23 -0
- package/addon/components/modals/reset-profile-credentials.js +70 -0
- package/addon/components/profile-identity-hint.hbs +13 -0
- package/addon/components/profile-identity-hint.js +49 -0
- package/addon/controllers/management/drivers/index.js +4 -0
- package/addon/extension.js +4 -2
- package/addon/services/contact-actions.js +50 -89
- package/addon/services/driver-actions.js +98 -0
- package/addon/utils/profile-login-actions.js +172 -0
- package/app/components/modals/{reset-customer-credentials.js → reset-profile-credentials.js} +1 -1
- package/app/components/profile-identity-hint.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/scripts/pest-bootstrap.php +12 -0
- package/server/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php +132 -0
- package/server/resources/views/mail/driver-credentials.blade.php +21 -0
- package/server/src/Console/Commands/FixCustomerCompanies.php +1 -1
- package/server/src/Console/Commands/FixDriverCompanies.php +1 -1
- package/server/src/Exceptions/ProfileIdentityConflictException.php +34 -0
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +14 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +56 -39
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +5 -17
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +40 -4
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +182 -162
- package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +47 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +156 -27
- package/server/src/Http/Requests/CreateDriverRequest.php +6 -2
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +9 -18
- package/server/src/Http/Resources/v1/Contact.php +3 -0
- package/server/src/Http/Resources/v1/Driver.php +3 -0
- package/server/src/Mail/DriverCredentialsMail.php +55 -0
- package/server/src/Models/Contact.php +55 -90
- package/server/src/Models/Sensor.php +3 -5
- package/server/src/Observers/ContactObserver.php +2 -22
- package/server/src/Observers/DriverObserver.php +5 -6
- package/server/src/Providers/FleetOpsServiceProvider.php +15 -0
- package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +62 -5
- package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +24 -5
- package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +6 -2
- package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +40 -7
- package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +86 -24
- package/server/src/Support/Ai/FleetOpsAiConsoleCommands.php +122 -0
- package/server/src/Support/Ai/Tools/CreateOrderTool.php +86 -0
- package/server/src/Support/Ai/Tools/ImportOrdersTool.php +53 -0
- package/server/src/Support/Ai/Tools/OptimizeOrderRouteTool.php +70 -0
- package/server/src/Support/Ai/Tools/SearchResourcesTool.php +73 -0
- package/server/src/Support/ProfileAccountManager.php +405 -0
- package/server/src/routes.php +5 -0
- package/server/tests/AiOperationalQueryCapabilityTest.php +1 -0
- package/server/tests/ApiCustomerControllerContractsTest.php +21 -0
- package/server/tests/ApiDriverControllerContractsTest.php +102 -20
- package/server/tests/CommandContractsTest.php +4 -4
- package/server/tests/ControllerHelperContractsTest.php +3 -11
- package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +5 -0
- package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +36 -0
- package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +19 -6
- package/server/tests/Feature/Http/Api/ProfileOnlyUserConversionMigrationTest.php +201 -0
- package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +6 -4
- package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +6 -2
- package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +23 -0
- package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +42 -17
- package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +168 -277
- package/server/tests/Feature/Http/Internal/DriverControllerLoginManagementTest.php +716 -0
- package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +10 -3
- package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +18 -0
- package/server/tests/Feature/Http/Internal/OrderControllerTenantScopingTest.php +411 -0
- package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +8 -20
- package/server/tests/InternalCustomerControllerContractsTest.php +32 -1
- package/server/tests/ModelAccessorContractsTest.php +82 -20
- package/server/tests/ObserverContractsTest.php +27 -2
- package/server/tests/RequestContractsTest.php +19 -11
- package/server/tests/SupportJobAndAiCoverageTest.php +3 -3
- package/server/tests/TelematicControllerContractsTest.php +22 -4
- package/server/tests/Unit/Models/ContactCustomerUserTest.php +90 -14
- package/server/tests/Unit/Models/ContactTest.php +48 -5
- package/server/tests/Unit/Models/SensorTest.php +2 -2
- package/server/tests/Unit/Models/SmallModelSurfacesTest.php +31 -32
- package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +7 -2
- package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +15 -2
- package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +42 -0
- package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +19 -1
- package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +85 -1
- package/server/tests/Unit/Support/Ai/FleetOpsAiConsoleCommandsTest.php +31 -0
- package/server/tests/Unit/Support/Ai/Tools/FleetOpsAiToolsTest.php +246 -0
- package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +3 -3
- package/server/tests/Unit/Support/ProfileAccountManagerTest.php +643 -0
- package/translations/en-us.yaml +37 -2
- package/translations/uk-ua.yaml +3475 -0
- package/addon/components/modals/reset-customer-credentials.hbs +0 -18
- package/addon/components/modals/reset-customer-credentials.js +0 -55
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import ResourceActionService from '@fleetbase/ember-core/services/resource-action';
|
|
2
2
|
import { inject as service } from '@ember/service';
|
|
3
|
-
import { action
|
|
3
|
+
import { action } from '@ember/object';
|
|
4
4
|
import { PANEL_DEFAULTS, closePanelsThen, registeredPanelTabs } from '../utils/context-panel';
|
|
5
|
-
|
|
6
|
-
const INTERNAL_NAMESPACE = 'int/v1';
|
|
5
|
+
import { confirmLoginAction, hasInactiveLogin, hasLinkedUser, hasManagedLogin, linkedUserStatus, updateProfileFromResponse } from '../utils/profile-login-actions';
|
|
7
6
|
|
|
8
7
|
export default class ContactActionsService extends ResourceActionService {
|
|
9
8
|
@service fetch;
|
|
@@ -124,17 +123,23 @@ export default class ContactActionsService extends ResourceActionService {
|
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
hasLinkedUser(contact) {
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
return hasLinkedUser(contact);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* True when the contact has a login account managed from Fleet-Ops. A
|
|
131
|
+
* contact owned by a staff member is managed from IAM instead.
|
|
132
|
+
*/
|
|
133
|
+
hasManagedLogin(contact) {
|
|
134
|
+
return hasManagedLogin(contact);
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
linkedUserStatus(contact) {
|
|
132
|
-
|
|
133
|
-
return (user ? (get(user, 'status') ?? get(user, 'session_status')) : null) ?? 'active';
|
|
138
|
+
return linkedUserStatus(contact);
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
hasInactiveLogin(contact) {
|
|
137
|
-
return
|
|
142
|
+
return hasInactiveLogin(contact);
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
isCustomerPortalInstalled() {
|
|
@@ -156,7 +161,7 @@ export default class ContactActionsService extends ResourceActionService {
|
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
accountActionButton(contact, options = {}) {
|
|
159
|
-
if (!this.
|
|
164
|
+
if (!this.hasManagedLogin(contact)) {
|
|
160
165
|
return null;
|
|
161
166
|
}
|
|
162
167
|
|
|
@@ -171,23 +176,23 @@ export default class ContactActionsService extends ResourceActionService {
|
|
|
171
176
|
accountActionItems(contact, options = {}) {
|
|
172
177
|
const actions = [
|
|
173
178
|
{
|
|
174
|
-
text: '
|
|
179
|
+
text: this.intl.t('profile-account.actions.reset-password'),
|
|
175
180
|
icon: 'key',
|
|
176
181
|
fn: () => this.openResetPasswordModal(contact),
|
|
177
182
|
},
|
|
178
183
|
{
|
|
179
|
-
text: '
|
|
184
|
+
text: this.intl.t('profile-account.actions.send-credentials'),
|
|
180
185
|
icon: 'paper-plane',
|
|
181
186
|
fn: () => this.confirmSendCredentials(contact),
|
|
182
187
|
},
|
|
183
188
|
this.hasInactiveLogin(contact)
|
|
184
189
|
? {
|
|
185
|
-
text: '
|
|
190
|
+
text: this.intl.t('profile-account.actions.reactivate-login'),
|
|
186
191
|
icon: 'unlock',
|
|
187
192
|
fn: () => this.confirmReactivatePortalLogin(contact),
|
|
188
193
|
}
|
|
189
194
|
: {
|
|
190
|
-
text: '
|
|
195
|
+
text: this.intl.t('profile-account.actions.deactivate-login'),
|
|
191
196
|
icon: 'lock',
|
|
192
197
|
fn: () => this.confirmDeactivatePortalLogin(contact),
|
|
193
198
|
class: 'text-red-500 hover:text-red-600',
|
|
@@ -200,7 +205,7 @@ export default class ContactActionsService extends ResourceActionService {
|
|
|
200
205
|
separator: true,
|
|
201
206
|
},
|
|
202
207
|
{
|
|
203
|
-
text: '
|
|
208
|
+
text: this.intl.t('profile-account.actions.convert-to-vendor'),
|
|
204
209
|
icon: 'building',
|
|
205
210
|
fn: () => this.openConvertToVendorModal(contact, options),
|
|
206
211
|
}
|
|
@@ -212,42 +217,42 @@ export default class ContactActionsService extends ResourceActionService {
|
|
|
212
217
|
|
|
213
218
|
accountRowActionItems(options = {}) {
|
|
214
219
|
const hasCustomerPortal = this.isCustomerPortalInstalled();
|
|
215
|
-
const
|
|
220
|
+
const hasManagedLogin = (contact) => this.hasManagedLogin(contact);
|
|
216
221
|
|
|
217
222
|
const actions = [
|
|
218
223
|
{
|
|
219
|
-
label: '
|
|
224
|
+
label: this.intl.t('profile-account.actions.reset-password'),
|
|
220
225
|
icon: 'key',
|
|
221
226
|
fn: (contact) => this.openResetPasswordModal(contact),
|
|
222
|
-
isVisible:
|
|
227
|
+
isVisible: hasManagedLogin,
|
|
223
228
|
},
|
|
224
229
|
{
|
|
225
|
-
label: '
|
|
230
|
+
label: this.intl.t('profile-account.actions.send-credentials'),
|
|
226
231
|
icon: 'paper-plane',
|
|
227
232
|
fn: (contact) => this.confirmSendCredentials(contact),
|
|
228
|
-
isVisible:
|
|
233
|
+
isVisible: hasManagedLogin,
|
|
229
234
|
},
|
|
230
235
|
{
|
|
231
|
-
label: '
|
|
236
|
+
label: this.intl.t('profile-account.actions.deactivate-login'),
|
|
232
237
|
icon: 'lock',
|
|
233
238
|
class: 'text-red-500 hover:text-red-600',
|
|
234
239
|
fn: (contact) => this.confirmDeactivatePortalLogin(contact),
|
|
235
|
-
isVisible: (contact) =>
|
|
240
|
+
isVisible: (contact) => hasManagedLogin(contact) && !this.hasInactiveLogin(contact),
|
|
236
241
|
},
|
|
237
242
|
{
|
|
238
|
-
label: '
|
|
243
|
+
label: this.intl.t('profile-account.actions.reactivate-login'),
|
|
239
244
|
icon: 'unlock',
|
|
240
245
|
fn: (contact) => this.confirmReactivatePortalLogin(contact),
|
|
241
|
-
isVisible: (contact) =>
|
|
246
|
+
isVisible: (contact) => hasManagedLogin(contact) && this.hasInactiveLogin(contact),
|
|
242
247
|
},
|
|
243
248
|
];
|
|
244
249
|
|
|
245
250
|
if (hasCustomerPortal) {
|
|
246
251
|
actions.push({
|
|
247
|
-
label: '
|
|
252
|
+
label: this.intl.t('profile-account.actions.convert-to-vendor'),
|
|
248
253
|
icon: 'building',
|
|
249
254
|
fn: (contact) => this.openConvertToVendorModal(contact, options),
|
|
250
|
-
isVisible:
|
|
255
|
+
isVisible: hasManagedLogin,
|
|
251
256
|
});
|
|
252
257
|
}
|
|
253
258
|
|
|
@@ -255,8 +260,10 @@ export default class ContactActionsService extends ResourceActionService {
|
|
|
255
260
|
}
|
|
256
261
|
|
|
257
262
|
openResetPasswordModal(contact) {
|
|
258
|
-
this.modalsManager.show('modals/reset-
|
|
259
|
-
|
|
263
|
+
this.modalsManager.show('modals/reset-profile-credentials', {
|
|
264
|
+
profile: contact,
|
|
265
|
+
endpoint: 'customers/reset-credentials',
|
|
266
|
+
payload: (profile) => ({ customer: profile?.id }),
|
|
260
267
|
});
|
|
261
268
|
}
|
|
262
269
|
|
|
@@ -278,86 +285,40 @@ export default class ContactActionsService extends ResourceActionService {
|
|
|
278
285
|
|
|
279
286
|
confirmSendCredentials(contact) {
|
|
280
287
|
this.confirmLoginAction(contact, {
|
|
281
|
-
title: '
|
|
282
|
-
body: '
|
|
283
|
-
acceptButtonText: '
|
|
288
|
+
title: this.intl.t('profile-account.prompts.send-portal-credentials-title'),
|
|
289
|
+
body: this.intl.t('profile-account.prompts.send-portal-credentials-body'),
|
|
290
|
+
acceptButtonText: this.intl.t('profile-account.actions.send-credentials'),
|
|
284
291
|
endpoint: 'customers/send-credentials',
|
|
285
|
-
successMessage: '
|
|
292
|
+
successMessage: this.intl.t('profile-account.prompts.send-portal-credentials-success'),
|
|
286
293
|
});
|
|
287
294
|
}
|
|
288
295
|
|
|
289
296
|
confirmDeactivatePortalLogin(contact) {
|
|
290
297
|
this.confirmLoginAction(contact, {
|
|
291
|
-
title: '
|
|
292
|
-
body:
|
|
293
|
-
acceptButtonText: '
|
|
298
|
+
title: this.intl.t('profile-account.actions.deactivate-login'),
|
|
299
|
+
body: this.intl.t('profile-account.prompts.deactivate-portal-login-body'),
|
|
300
|
+
acceptButtonText: this.intl.t('profile-account.actions.deactivate-login'),
|
|
294
301
|
acceptButtonScheme: 'danger',
|
|
295
302
|
endpoint: 'customers/deactivate-portal-login',
|
|
296
|
-
successMessage: '
|
|
303
|
+
successMessage: this.intl.t('profile-account.prompts.deactivate-portal-login-success'),
|
|
297
304
|
});
|
|
298
305
|
}
|
|
299
306
|
|
|
300
307
|
confirmReactivatePortalLogin(contact) {
|
|
301
308
|
this.confirmLoginAction(contact, {
|
|
302
|
-
title: '
|
|
303
|
-
body:
|
|
304
|
-
acceptButtonText: '
|
|
309
|
+
title: this.intl.t('profile-account.actions.reactivate-login'),
|
|
310
|
+
body: this.intl.t('profile-account.prompts.reactivate-portal-login-body'),
|
|
311
|
+
acceptButtonText: this.intl.t('profile-account.actions.reactivate-login'),
|
|
305
312
|
endpoint: 'customers/reactivate-portal-login',
|
|
306
|
-
successMessage: '
|
|
313
|
+
successMessage: this.intl.t('profile-account.prompts.reactivate-portal-login-success'),
|
|
307
314
|
});
|
|
308
315
|
}
|
|
309
316
|
|
|
310
|
-
confirmLoginAction(contact,
|
|
311
|
-
this
|
|
312
|
-
title,
|
|
313
|
-
body,
|
|
314
|
-
acceptButtonText,
|
|
315
|
-
acceptButtonScheme,
|
|
316
|
-
confirm: async (modal) => {
|
|
317
|
-
modal.startLoading();
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
const response = await this.fetch.post(endpoint, { customer: contact?.id }, { namespace: INTERNAL_NAMESPACE });
|
|
321
|
-
await this.updateContactFromResponse(contact, response);
|
|
322
|
-
this.notifications.success(successMessage);
|
|
323
|
-
modal.done();
|
|
324
|
-
} catch (error) {
|
|
325
|
-
this.notifications.serverError(error);
|
|
326
|
-
modal.stopLoading();
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
});
|
|
317
|
+
confirmLoginAction(contact, options = {}) {
|
|
318
|
+
return confirmLoginAction(this, contact, { payload: { customer: contact?.id }, ...options });
|
|
330
319
|
}
|
|
331
320
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (!payload || typeof contact?.setProperties !== 'function') {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
contact.setProperties(
|
|
340
|
-
this.withoutIdentityFields({
|
|
341
|
-
user_uuid: payload.user_uuid,
|
|
342
|
-
})
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
if (payload.user) {
|
|
346
|
-
const user = await contact.user;
|
|
347
|
-
|
|
348
|
-
if (typeof user?.setProperties === 'function') {
|
|
349
|
-
user.setProperties(this.withoutIdentityFields(payload.user));
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
withoutIdentityFields(payload = {}) {
|
|
355
|
-
const attributes = { ...payload };
|
|
356
|
-
|
|
357
|
-
delete attributes.id;
|
|
358
|
-
delete attributes.uuid;
|
|
359
|
-
delete attributes.public_id;
|
|
360
|
-
|
|
361
|
-
return attributes;
|
|
321
|
+
updateContactFromResponse(contact, response) {
|
|
322
|
+
return updateProfileFromResponse(contact, response);
|
|
362
323
|
}
|
|
363
324
|
}
|
|
@@ -5,6 +5,7 @@ import { action } from '@ember/object';
|
|
|
5
5
|
import { isArray } from '@ember/array';
|
|
6
6
|
import { dasherize } from '@ember/string';
|
|
7
7
|
import { PANEL_DEFAULTS, closePanelsThen } from '../utils/context-panel';
|
|
8
|
+
import { confirmLoginAction, hasInactiveLogin, hasManagedLogin } from '../utils/profile-login-actions';
|
|
8
9
|
|
|
9
10
|
export default class DriverActionsService extends ResourceActionService {
|
|
10
11
|
@service('universe/menu-service') menuService;
|
|
@@ -422,9 +423,106 @@ export default class DriverActionsService extends ResourceActionService {
|
|
|
422
423
|
{ separator: true },
|
|
423
424
|
{ text: this.intl.t('driver.actions.locate-driver'), icon: 'location-dot', fn: () => this.locate(driver), permission: 'fleet-ops view driver' },
|
|
424
425
|
{ text: this.intl.t('driver.actions.create-issue'), icon: 'triangle-exclamation', fn: () => this.createIssue(driver), permission: 'fleet-ops create issue' },
|
|
426
|
+
...this.accountMenuItems(driver),
|
|
425
427
|
];
|
|
426
428
|
}
|
|
427
429
|
|
|
430
|
+
/**
|
|
431
|
+
* The login account section of a driver's actions menu. Only offered when
|
|
432
|
+
* the driver has a login managed from Fleet-Ops; a staff-linked driver's
|
|
433
|
+
* login is managed from IAM.
|
|
434
|
+
*/
|
|
435
|
+
accountMenuItems(driver) {
|
|
436
|
+
if (!hasManagedLogin(driver)) {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const items = this.accountRowActionItems()
|
|
441
|
+
.filter(({ isVisible }) => isVisible(driver))
|
|
442
|
+
// eslint-disable-next-line no-unused-vars
|
|
443
|
+
.map(({ label, fn, isVisible, ...item }) => ({ ...item, text: label, fn: () => fn(driver) }));
|
|
444
|
+
|
|
445
|
+
return [{ separator: true }, ...items];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Login account actions for the drivers table row menu.
|
|
450
|
+
*/
|
|
451
|
+
accountRowActionItems() {
|
|
452
|
+
const permission = 'fleet-ops update driver';
|
|
453
|
+
|
|
454
|
+
return [
|
|
455
|
+
{
|
|
456
|
+
label: this.intl.t('profile-account.actions.reset-password'),
|
|
457
|
+
icon: 'key',
|
|
458
|
+
fn: (driver) => this.resetCredentials(driver),
|
|
459
|
+
permission,
|
|
460
|
+
isVisible: (driver) => hasManagedLogin(driver),
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
label: this.intl.t('profile-account.actions.send-credentials'),
|
|
464
|
+
icon: 'paper-plane',
|
|
465
|
+
fn: (driver) => this.sendCredentials(driver),
|
|
466
|
+
permission,
|
|
467
|
+
isVisible: (driver) => hasManagedLogin(driver),
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
label: this.intl.t('profile-account.actions.deactivate-login'),
|
|
471
|
+
icon: 'lock',
|
|
472
|
+
class: 'text-red-500 hover:text-red-600',
|
|
473
|
+
fn: (driver) => this.deactivateLogin(driver),
|
|
474
|
+
permission,
|
|
475
|
+
isVisible: (driver) => hasManagedLogin(driver) && !hasInactiveLogin(driver),
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
label: this.intl.t('profile-account.actions.reactivate-login'),
|
|
479
|
+
icon: 'unlock',
|
|
480
|
+
fn: (driver) => this.reactivateLogin(driver),
|
|
481
|
+
permission,
|
|
482
|
+
isVisible: (driver) => hasManagedLogin(driver) && hasInactiveLogin(driver),
|
|
483
|
+
},
|
|
484
|
+
];
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
@action resetCredentials(driver, options = {}) {
|
|
488
|
+
return this.modalsManager.show('modals/reset-profile-credentials', {
|
|
489
|
+
profile: driver,
|
|
490
|
+
endpoint: `drivers/${driver.id}/reset-credentials`,
|
|
491
|
+
...options,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
@action sendCredentials(driver) {
|
|
496
|
+
return confirmLoginAction(this, driver, {
|
|
497
|
+
title: this.intl.t('profile-account.prompts.send-credentials-title'),
|
|
498
|
+
body: this.intl.t('profile-account.prompts.send-credentials-body', { name: driver.name }),
|
|
499
|
+
acceptButtonText: this.intl.t('profile-account.actions.send-credentials'),
|
|
500
|
+
endpoint: `drivers/${driver.id}/send-credentials`,
|
|
501
|
+
successMessage: this.intl.t('profile-account.prompts.send-credentials-success', { name: driver.name }),
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
@action deactivateLogin(driver) {
|
|
506
|
+
return confirmLoginAction(this, driver, {
|
|
507
|
+
title: this.intl.t('profile-account.actions.deactivate-login'),
|
|
508
|
+
body: this.intl.t('profile-account.prompts.deactivate-login-body', { name: driver.name }),
|
|
509
|
+
acceptButtonText: this.intl.t('profile-account.actions.deactivate-login'),
|
|
510
|
+
acceptButtonScheme: 'danger',
|
|
511
|
+
endpoint: `drivers/${driver.id}/deactivate-login`,
|
|
512
|
+
successMessage: this.intl.t('profile-account.prompts.deactivate-login-success', { name: driver.name }),
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
@action reactivateLogin(driver) {
|
|
517
|
+
return confirmLoginAction(this, driver, {
|
|
518
|
+
title: this.intl.t('profile-account.actions.reactivate-login'),
|
|
519
|
+
body: this.intl.t('profile-account.prompts.reactivate-login-body', { name: driver.name }),
|
|
520
|
+
acceptButtonText: this.intl.t('profile-account.actions.reactivate-login'),
|
|
521
|
+
endpoint: `drivers/${driver.id}/reactivate-login`,
|
|
522
|
+
successMessage: this.intl.t('profile-account.prompts.reactivate-login-success', { name: driver.name }),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
428
526
|
@action createIssue(driver) {
|
|
429
527
|
return this.issueActions.modal.create({
|
|
430
528
|
driver,
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { get } from '@ember/object';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared helpers for the login account behind a driver, contact or customer
|
|
5
|
+
* profile. The account itself is created, updated and removed by the server
|
|
6
|
+
* from the profile's name/email/phone; the console only offers the account
|
|
7
|
+
* actions (reset password, send credentials, deactivate/reactivate login).
|
|
8
|
+
*
|
|
9
|
+
* A profile owned by a staff member (`is_staff_linked`) has its login
|
|
10
|
+
* managed from IAM, so no account actions are offered for it here.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const INTERNAL_NAMESPACE = 'int/v1';
|
|
14
|
+
export const INACTIVE_LOGIN_STATUSES = ['inactive', 'disabled', 'suspended'];
|
|
15
|
+
|
|
16
|
+
function read(subject, key) {
|
|
17
|
+
if (!subject) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
return get(subject, key);
|
|
23
|
+
} catch {
|
|
24
|
+
return subject[key];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The linked user record when it is already loaded, without triggering a
|
|
30
|
+
* fetch for an async relationship.
|
|
31
|
+
*/
|
|
32
|
+
export function loadedUser(profile) {
|
|
33
|
+
if (typeof profile?.belongsTo === 'function') {
|
|
34
|
+
try {
|
|
35
|
+
return profile.belongsTo('user').value();
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const user = read(profile, 'user');
|
|
42
|
+
if (!user) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return typeof user.then === 'function' ? (read(user, 'content') ?? null) : user;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isStaffLinked(profile) {
|
|
50
|
+
return Boolean(read(profile, 'is_staff_linked'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function hasLinkedUser(profile) {
|
|
54
|
+
if (!profile) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (read(profile, 'user_uuid') || read(profile, 'login_status')) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const user = loadedUser(profile);
|
|
63
|
+
return Boolean(user && (read(user, 'id') || read(user, 'uuid')));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* True when the profile has a login account that is managed from Fleet-Ops,
|
|
68
|
+
* i.e. a linked user which is not a staff member's account.
|
|
69
|
+
*/
|
|
70
|
+
export function hasManagedLogin(profile) {
|
|
71
|
+
return hasLinkedUser(profile) && !isStaffLinked(profile);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The login status, preferring the server-provided `login_status` over the
|
|
76
|
+
* embedded user's status.
|
|
77
|
+
*/
|
|
78
|
+
export function linkedUserStatus(profile) {
|
|
79
|
+
const loginStatus = read(profile, 'login_status');
|
|
80
|
+
if (loginStatus) {
|
|
81
|
+
return loginStatus;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const user = loadedUser(profile);
|
|
85
|
+
return (user ? (read(user, 'status') ?? read(user, 'session_status')) : null) ?? 'active';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function hasInactiveLogin(profile) {
|
|
89
|
+
return INACTIVE_LOGIN_STATUSES.includes(linkedUserStatus(profile));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function withoutIdentityFields(payload = {}) {
|
|
93
|
+
const attributes = { ...payload };
|
|
94
|
+
|
|
95
|
+
delete attributes.id;
|
|
96
|
+
delete attributes.uuid;
|
|
97
|
+
delete attributes.public_id;
|
|
98
|
+
|
|
99
|
+
return attributes;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Applies the profile payload returned by an account action endpoint
|
|
104
|
+
* (`{ driver: {...} }`, `{ customer: {...} }` or `{ contact: {...} }`).
|
|
105
|
+
*/
|
|
106
|
+
export function updateProfileFromResponse(profile, response = {}) {
|
|
107
|
+
const payload = response?.driver ?? response?.customer ?? response?.contact;
|
|
108
|
+
|
|
109
|
+
if (!payload || typeof profile?.setProperties !== 'function') {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const attributes = {};
|
|
114
|
+
if ('user_uuid' in payload) {
|
|
115
|
+
attributes.user_uuid = payload.user_uuid;
|
|
116
|
+
}
|
|
117
|
+
if ('is_staff_linked' in payload) {
|
|
118
|
+
attributes.is_staff_linked = Boolean(payload.is_staff_linked);
|
|
119
|
+
}
|
|
120
|
+
if ('login_status' in payload) {
|
|
121
|
+
attributes.login_status = payload.login_status;
|
|
122
|
+
} else if (payload.user && 'status' in payload.user) {
|
|
123
|
+
attributes.login_status = payload.user.status;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
profile.setProperties(attributes);
|
|
127
|
+
|
|
128
|
+
if (payload.user) {
|
|
129
|
+
const user = loadedUser(profile);
|
|
130
|
+
|
|
131
|
+
if (typeof user?.setProperties === 'function') {
|
|
132
|
+
user.setProperties(withoutIdentityFields(payload.user));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Opens a confirm modal which POSTs to an account action endpoint and applies
|
|
139
|
+
* the response to the profile.
|
|
140
|
+
*
|
|
141
|
+
* @param {Object} services { modalsManager, fetch, notifications }
|
|
142
|
+
* @param {Object} profile the driver or contact record
|
|
143
|
+
* @param {Object} options { title, body, acceptButtonText, acceptButtonScheme, endpoint, payload, successMessage, onSuccess }
|
|
144
|
+
*/
|
|
145
|
+
export function confirmLoginAction({ modalsManager, fetch, notifications }, profile, options = {}) {
|
|
146
|
+
const { title, body, acceptButtonText, acceptButtonScheme, endpoint, payload = {}, successMessage, onSuccess } = options;
|
|
147
|
+
|
|
148
|
+
return modalsManager.confirm({
|
|
149
|
+
title,
|
|
150
|
+
body,
|
|
151
|
+
acceptButtonText,
|
|
152
|
+
acceptButtonScheme,
|
|
153
|
+
confirm: async (modal) => {
|
|
154
|
+
modal.startLoading();
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const response = await fetch.post(endpoint, payload, { namespace: INTERNAL_NAMESPACE });
|
|
158
|
+
updateProfileFromResponse(profile, response);
|
|
159
|
+
notifications.success(successMessage);
|
|
160
|
+
|
|
161
|
+
if (typeof onSuccess === 'function') {
|
|
162
|
+
onSuccess(response);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
modal.done();
|
|
166
|
+
} catch (error) {
|
|
167
|
+
notifications.serverError(error);
|
|
168
|
+
modal.stopLoading();
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
package/app/components/modals/{reset-customer-credentials.js → reset-profile-credentials.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from '@fleetbase/fleetops-engine/components/modals/reset-
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/components/modals/reset-profile-credentials';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/components/profile-identity-hint';
|
package/composer.json
CHANGED
package/extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetbase/fleetops-engine",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.69",
|
|
4
4
|
"description": "Fleet & Transport Management Extension for Fleetbase",
|
|
5
5
|
"fleetbase": {
|
|
6
6
|
"route": "fleet-ops"
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@babel/core": "^7.23.2",
|
|
47
47
|
"@fleetbase/ember-core": "^0.3.24",
|
|
48
|
-
"@fleetbase/ember-ui": "^0.4.
|
|
49
|
-
"@fleetbase/fleetops-data": "^0.2.
|
|
48
|
+
"@fleetbase/ember-ui": "^0.4.3",
|
|
49
|
+
"@fleetbase/fleetops-data": "^0.2.2",
|
|
50
50
|
"@fleetbase/leaflet-routing-machine": "^3.2.17",
|
|
51
51
|
"@fortawesome/ember-fontawesome": "^2.0.0",
|
|
52
52
|
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
|
@@ -382,6 +382,18 @@ if (!interface_exists('Fleetbase\Ai\Contracts\AIActionCapabilityInterface')) {
|
|
|
382
382
|
eval('namespace Fleetbase\Ai\Contracts; interface AIActionCapabilityInterface {}');
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
if (!interface_exists('Fleetbase\Ai\Contracts\AIToolCapabilityInterface')) {
|
|
386
|
+
eval('namespace Fleetbase\Ai\Contracts; interface AIToolCapabilityInterface {}');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!class_exists('Fleetbase\Ai\Support\AiAudience')) {
|
|
390
|
+
eval('namespace Fleetbase\Ai\Support; class AiAudience { public function __construct(public bool $isSystemAdmin = false, public array $granted = []) {} public function can(string $permission): bool { return $this->isSystemAdmin || in_array($permission, $this->granted, true); } public function canAll(array $permissions): bool { foreach ($permissions as $permission) { if (!$this->can($permission)) { return false; } } return true; } }');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!class_exists('Fleetbase\Ai\Support\AiToolContext')) {
|
|
394
|
+
eval('namespace Fleetbase\Ai\Support; class AiToolContext { public array $actionPreviews = []; public array $uiActions = []; public function __construct(public $task, public $audience) {} public function addActionPreview($capability, array $preview): array { $preview = array_merge(["preview_id" => "preview-" . (count($this->actionPreviews) + 1), "key" => $capability->key()], $preview); $this->actionPreviews[] = $preview; return $preview; } }');
|
|
395
|
+
}
|
|
396
|
+
|
|
385
397
|
if (!class_exists('Fleetbase\Ai\Models\AiTask')) {
|
|
386
398
|
eval('namespace Fleetbase\Ai\Models; class AiTask { public function __construct(array $attributes = []) { foreach ($attributes as $key => $value) { $this->{$key} = $value; } } }');
|
|
387
399
|
}
|