@fleetbase/fleetops-engine 0.6.52 → 0.6.53

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.
Files changed (60) hide show
  1. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
  2. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +589 -0
  3. package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
  4. package/addon/components/layout/fleet-ops-sidebar.js +287 -394
  5. package/addon/components/maintenance-schedule/form.js +1 -0
  6. package/addon/components/order/form/payload.js +3 -1
  7. package/addon/controllers/connectivity/devices/index.js +4 -1
  8. package/addon/controllers/maintenance/index.js +100 -0
  9. package/addon/controllers/management/fuel-transactions/index.js +1 -1
  10. package/addon/controllers/management/index.js +88 -0
  11. package/addon/controllers/settings/index.js +142 -0
  12. package/addon/extension.js +21 -0
  13. package/addon/routes/analytics/index.js +3 -0
  14. package/addon/routes/connectivity/devices/index.js +3 -0
  15. package/addon/routes/maintenance/index.js +3 -0
  16. package/addon/routes/maintenance/work-orders/index.js +2 -0
  17. package/addon/routes/settings/index.js +3 -0
  18. package/addon/styles/fleetops-engine.css +1801 -2
  19. package/addon/templates/analytics/index.hbs +14 -0
  20. package/addon/templates/analytics/reports/index.hbs +4 -4
  21. package/addon/templates/maintenance/index.hbs +113 -0
  22. package/addon/templates/management/fuel-transactions/index/details.hbs +1 -1
  23. package/addon/templates/management/index.hbs +109 -73
  24. package/addon/templates/operations/orders/index.hbs +1 -1
  25. package/addon/templates/operations/service-rates/index.hbs +1 -1
  26. package/addon/templates/settings/index.hbs +83 -0
  27. package/addon/templates/settings/routing.hbs +2 -2
  28. package/app/controllers/maintenance/index.js +1 -0
  29. package/app/controllers/management/index.js +1 -0
  30. package/app/controllers/settings/index.js +1 -0
  31. package/app/routes/analytics/index.js +1 -0
  32. package/app/routes/maintenance/index.js +1 -0
  33. package/app/routes/settings/index.js +1 -0
  34. package/app/templates/analytics/index.js +1 -0
  35. package/app/templates/maintenance/index.js +1 -0
  36. package/app/templates/management/index.js +1 -0
  37. package/app/templates/settings/index.js +1 -0
  38. package/composer.json +1 -1
  39. package/extension.json +1 -1
  40. package/package.json +3 -3
  41. package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
  42. package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
  43. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +75 -11
  44. package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
  45. package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
  46. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -1
  47. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +94 -10
  48. package/server/src/Http/Filter/DeviceFilter.php +74 -0
  49. package/server/src/Http/Filter/DriverFilter.php +6 -0
  50. package/server/src/Http/Filter/VehicleFilter.php +6 -0
  51. package/server/src/Jobs/SyncTelematicDevicesJob.php +82 -24
  52. package/server/src/Models/Device.php +1 -1
  53. package/server/src/Models/Driver.php +1 -1
  54. package/server/src/Providers/FleetOpsServiceProvider.php +10 -0
  55. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +51 -13
  56. package/server/src/routes.php +9 -0
  57. package/server/tests/HubRoutesTest.php +62 -0
  58. package/server/tests/TelematicsHardeningTest.php +94 -3
  59. package/translations/ar-ae.yml +1 -1
  60. package/translations/vi-vn.yaml +1 -1
@@ -2,18 +2,24 @@ import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { inject as service } from '@ember/service';
4
4
  import { action } from '@ember/object';
5
- import FleetListingComponent from './fleet-ops-sidebar/fleet-listing';
6
- import DriverListingComponent from './fleet-ops-sidebar/driver-listing';
5
+
6
+ const SECTION_REGISTRY_KEYS = {
7
+ operations: 'universeOperationsMenuItems',
8
+ management: 'universeManagementMenuItems',
9
+ maintenance: 'universeMaintenanceMenuItems',
10
+ connectivity: 'universeConnectivityMenuItems',
11
+ analytics: 'universeAnalyticsMenuItems',
12
+ settings: 'universeSettingsMenuItems',
13
+ };
7
14
 
8
15
  export default class LayoutFleetOpsSidebarComponent extends Component {
9
16
  @service universe;
10
17
  @service('universe/menu-service') menuService;
11
- @service store;
12
18
  @service intl;
13
19
  @service abilities;
14
- @service appCache;
20
+ @service fetch;
21
+
15
22
  @tracked routePrefix = 'console.fleet-ops.';
16
- @tracked menuPanels = [];
17
23
  @tracked universeMenuItems = [];
18
24
  @tracked universeOperationsMenuItems = [];
19
25
  @tracked universeManagementMenuItems = [];
@@ -26,11 +32,207 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
26
32
  constructor() {
27
33
  super(...arguments);
28
34
  this.createMenuItemsFromUniverseRegistry();
29
- this.createMenuPanels();
35
+ }
36
+
37
+ get createOrderAction() {
38
+ return {
39
+ label: this.intl.t('common.create-new-resource', { resource: this.intl.t('resource.order')?.toLowerCase() }),
40
+ icon: 'paper-plane',
41
+ iconPrefix: 'fas',
42
+ buttonClass: 'fleet-ops-sidebar-primary-action',
43
+ permission: 'fleet-ops create order',
44
+ onClick: this.onClickCreateOrder,
45
+ };
46
+ }
47
+
48
+ get navigationItems() {
49
+ const coreBranches = [
50
+ this.createBranch({
51
+ id: 'operations',
52
+ label: this.intl.t('menu.operations'),
53
+ icon: 'circle-nodes',
54
+ route: 'operations',
55
+ defaultRoute: 'operations.orders',
56
+ keywords: ['dispatch', 'orders', 'live map', 'track vehicles', 'drivers online', 'vehicles online'],
57
+ children: this.operationsItems,
58
+ }),
59
+ this.createBranch({
60
+ id: 'resources',
61
+ label: this.intl.t('menu.resources'),
62
+ icon: 'truck',
63
+ route: 'management',
64
+ defaultRoute: 'management.index',
65
+ keywords: ['drivers', 'vehicles', 'fleets', 'contacts', 'places'],
66
+ children: this.resourcesItems,
67
+ }),
68
+ this.createBranch({
69
+ id: 'maintenance',
70
+ label: this.intl.t('menu.maintenance'),
71
+ icon: 'cog',
72
+ route: 'maintenance',
73
+ defaultRoute: 'maintenance.index',
74
+ keywords: ['work orders', 'equipment', 'parts'],
75
+ children: this.maintenanceItems,
76
+ }),
77
+ this.createBranch({
78
+ id: 'connectivity',
79
+ label: this.intl.t('menu.connectivity'),
80
+ icon: 'satellite-dish',
81
+ route: 'connectivity',
82
+ defaultRoute: 'connectivity.telematics',
83
+ keywords: ['telematics', 'fuel integrations', 'devices', 'sensors'],
84
+ children: this.connectivityItems,
85
+ }),
86
+ this.createBranch({
87
+ id: 'analytics',
88
+ label: this.intl.t('menu.analytics'),
89
+ icon: 'chart-line',
90
+ route: 'analytics',
91
+ defaultRoute: 'analytics.index',
92
+ keywords: ['reports', 'metrics'],
93
+ children: this.analyticsItems,
94
+ }),
95
+ this.createBranch({
96
+ id: 'settings',
97
+ label: this.intl.t('menu.settings'),
98
+ icon: 'gear',
99
+ route: 'settings',
100
+ defaultRoute: 'settings.index',
101
+ keywords: ['configuration', 'navigator', 'map', 'routing', 'notifications'],
102
+ children: this.settingsItems,
103
+ }),
104
+ ];
105
+
106
+ return [...coreBranches, ...this.registryRootItems, ...this.registryPanelItems].filter((item) => item.children?.length || item.route || item.url || item.onClick);
107
+ }
108
+
109
+ get operationsItems() {
110
+ return this.withRegistryItems('operations', [
111
+ this.createItem('menu.orders', 'map-location-dot', 'operations.orders', 'fleet-ops list order', 'fleet-ops see order', [
112
+ 'dashboard',
113
+ 'orders',
114
+ 'dispatch',
115
+ 'tracking',
116
+ 'create order',
117
+ 'live map',
118
+ ]),
119
+ this.createItem('menu.orchestrator', 'circle-nodes', 'operations.orchestrator', 'fleet-ops list order', 'fleet-ops see order', ['optimize', 'allocate', 'dispatch']),
120
+ this.createItem('menu.scheduler', 'calendar-day', 'operations.scheduler', 'fleet-ops list order', 'fleet-ops see order', ['schedule', 'calendar']),
121
+ this.createItem('menu.order-config', 'diagram-project', 'operations.order-config', 'fleet-ops list order-config', 'fleet-ops see order-config', [
122
+ 'configuration',
123
+ 'fields',
124
+ 'entities',
125
+ ]),
126
+ this.createItem('menu.service-rates', 'file-invoice-dollar', 'operations.service-rates', 'fleet-ops list service-rate', 'fleet-ops see service-rate', ['rates', 'pricing']),
127
+ ]);
128
+ }
129
+
130
+ get resourcesItems() {
131
+ return this.withRegistryItems('management', [
132
+ this.createHubItem('Resources Hub', 'layer-group', 'management.index', 'fleet-ops list driver', 'fleet-ops see driver', [
133
+ 'resources hub',
134
+ 'resource dashboard',
135
+ 'resource readiness',
136
+ ]),
137
+ this.createItem('menu.drivers', 'id-card', 'management.drivers', 'fleet-ops list driver', 'fleet-ops see driver', ['driver', 'online drivers']),
138
+ this.createItem('menu.vehicles', 'truck', 'management.vehicles', 'fleet-ops list vehicle', 'fleet-ops see vehicle', ['vehicle', 'track vehicles', 'online vehicles']),
139
+ this.createItem('menu.fleets', 'user-group', 'management.fleets', 'fleet-ops list fleet', 'fleet-ops see fleet', ['fleet', 'teams']),
140
+ this.createItem('menu.vendors', 'warehouse', 'management.vendors', 'fleet-ops list vendor', 'fleet-ops see vendor'),
141
+ this.createItem('menu.contacts', 'address-book', 'management.contacts', 'fleet-ops list contact', 'fleet-ops see contact'),
142
+ this.createItem('menu.places', 'location-dot', 'management.places', 'fleet-ops list place', 'fleet-ops see place'),
143
+ this.createItem('menu.fuel-reports', 'gas-pump', 'management.fuel-reports', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report'),
144
+ this.createItem('menu.fuel-transactions', 'credit-card', 'management.fuel-transactions', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report'),
145
+ this.createItem('menu.issues', 'triangle-exclamation', 'management.issues', 'fleet-ops list issue', 'fleet-ops see issue'),
146
+ ]);
147
+ }
148
+
149
+ get maintenanceItems() {
150
+ return this.withRegistryItems('maintenance', [
151
+ this.createHubItem('Maintenance Hub', 'wrench', 'maintenance.index', 'fleet-ops list maintenance-schedule', 'fleet-ops see maintenance-schedule', [
152
+ 'maintenance hub',
153
+ 'service readiness',
154
+ 'maintenance control panel',
155
+ ]),
156
+ this.createItem('menu.schedules', 'calendar-alt', 'maintenance.schedules', 'fleet-ops list maintenance-schedule', 'fleet-ops see maintenance-schedule'),
157
+ this.createItem('menu.work-orders', 'clipboard-list', 'maintenance.work-orders', 'fleet-ops list work-order', 'fleet-ops see work-order'),
158
+ this.createItem('menu.maintenances', 'history', 'maintenance.maintenances', 'fleet-ops list maintenance', 'fleet-ops see maintenance'),
159
+ this.createItem('menu.equipment', 'trailer', 'maintenance.equipment', 'fleet-ops list equipment', 'fleet-ops see equipment'),
160
+ this.createItem('menu.parts', 'cog', 'maintenance.parts', 'fleet-ops list part', 'fleet-ops see part'),
161
+ ]);
162
+ }
163
+
164
+ get connectivityItems() {
165
+ return this.withRegistryItems('connectivity', [
166
+ this.createItem('menu.telematics', 'satellite-dish', 'connectivity.telematics', 'fleet-ops list telematic', 'fleet-ops see telematic', ['connectivity hub']),
167
+ this.createItem('menu.fuel-providers', 'gas-pump', 'connectivity.fuel-providers', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report', ['fuel integrations']),
168
+ this.createItem('menu.devices', 'hard-drive', 'connectivity.devices', 'fleet-ops list device', 'fleet-ops see device'),
169
+ this.createItem('menu.sensors', 'temperature-full', 'connectivity.sensors', 'fleet-ops list sensor', 'fleet-ops see sensor'),
170
+ this.createItem('menu.events', 'stream', 'connectivity.events', 'fleet-ops list device-event', 'fleet-ops see device-event'),
171
+ ]);
172
+ }
173
+
174
+ get analyticsItems() {
175
+ return this.withRegistryItems('analytics', [
176
+ this.createHubItem('Dashboard', 'chart-line', 'analytics.index', 'iam list report', 'fleet-ops see report', ['dashboard', 'fleetops dashboard', 'metrics']),
177
+ this.createItem('menu.reports', 'file-import', 'analytics.reports', 'iam list report', 'fleet-ops see report'),
178
+ ]);
179
+ }
180
+
181
+ get settingsItems() {
182
+ return this.withRegistryItems('settings', [
183
+ this.createHubItem('Settings Hub', 'sliders', 'settings.index', 'fleet-ops view navigator-settings', 'fleet-ops see navigator-settings', [
184
+ 'settings hub',
185
+ 'configuration dashboard',
186
+ 'setup focus',
187
+ ]),
188
+ this.createItem('menu.navigator-app', 'location-arrow', 'settings.navigator-app', 'fleet-ops view navigator-settings', 'fleet-ops see navigator-settings'),
189
+ this.createItem('menu.map', 'map', 'settings.map', 'fleet-ops view map-settings', 'fleet-ops see map-settings'),
190
+ this.createItem('menu.payments', 'cash-register', 'settings.payments', 'fleet-ops view payments', 'fleet-ops see payments'),
191
+ this.createItem('menu.notifications', 'bell', 'settings.notifications', 'fleet-ops view notification-settings', 'fleet-ops see notification-settings'),
192
+ this.createItem('menu.routing', 'route', 'settings.routing', 'fleet-ops view routing-settings', 'fleet-ops see routing-settings'),
193
+ this.createItem('menu.orchestrator', 'circle-nodes', 'settings.orchestrator', 'fleet-ops view routing-settings', 'fleet-ops see routing-settings'),
194
+ this.createItem('menu.scheduling', 'calendar-days', 'settings.scheduling', 'fleet-ops view scheduling-settings', 'fleet-ops see scheduling-settings'),
195
+ this.createItem('menu.custom-fields', 'pen-to-square', 'settings.custom-fields', 'fleet-ops view custom-field', 'fleet-ops see custom-field'),
196
+ this.createItem('menu.avatars', 'icons', 'settings.avatars', 'fleet-ops view avatar', 'fleet-ops see avatar'),
197
+ ]);
198
+ }
199
+
200
+ get registryRootItems() {
201
+ return this.universeMenuItems.filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item));
202
+ }
203
+
204
+ get registryPanelItems() {
205
+ return this.universeMenuPanels.map((panel) => {
206
+ const children = (panel.items ?? []).filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item));
207
+
208
+ return {
209
+ id: panel.id ?? panel.slug ?? panel.title,
210
+ label: panel.intl ? this.intl.t(panel.intl) : panel.title,
211
+ icon: panel.icon,
212
+ visible: panel.visible,
213
+ permission: panel.permission,
214
+ keywords: [panel.slug, panel.title, panel.intl].filter(Boolean),
215
+ children,
216
+ };
217
+ });
218
+ }
219
+
220
+ get footerRegistryComponents() {
221
+ const sectionItems = Object.entries(SECTION_REGISTRY_KEYS).reduce((components, [section, propertyName]) => {
222
+ components[section] = this[propertyName].filter((item) => item.renderComponentInPlace);
223
+ return components;
224
+ }, {});
225
+
226
+ return {
227
+ root: this.universeMenuItems.filter((item) => item.renderComponentInPlace),
228
+ resources: sectionItems.management,
229
+ ...sectionItems,
230
+ };
30
231
  }
31
232
 
32
233
  createMenuItemsFromUniverseRegistry() {
33
234
  const registeredMenuItems = this.menuService.getMenuItems('engine:fleet-ops');
235
+
34
236
  this.universeMenuPanels = this.menuService.getMenuPanels('engine:fleet-ops');
35
237
  this.universeMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === undefined);
36
238
  this.universeOperationsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'operations');
@@ -41,400 +243,91 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
41
243
  this.universeSettingsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'settings');
42
244
  }
43
245
 
44
- /* eslint-disable no-unused-vars */
45
- createMenuPanels() {
46
- const operationsItems = [
47
- {
48
- priority: 0,
49
- intl: 'menu.dashboard',
50
- title: this.intl.t('menu.dashboard'),
51
- icon: 'home',
52
- route: 'operations.orders',
53
- permission: 'fleet-ops list order',
54
- visible: this.abilities.can('fleet-ops see order'),
55
- },
56
- {
57
- priority: 1,
58
- intl: 'menu.orchestrator',
59
- title: this.intl.t('menu.orchestrator'),
60
- icon: 'circle-nodes',
61
- route: 'operations.orchestrator',
62
- permission: 'fleet-ops list order',
63
- visible: this.abilities.can('fleet-ops see order'),
64
- },
65
- {
66
- priority: 2,
67
- intl: 'menu.scheduler',
68
- title: this.intl.t('menu.scheduler'),
69
- icon: 'calendar-day',
70
- route: 'operations.scheduler',
71
- permission: 'fleet-ops list order',
72
- visible: this.abilities.can('fleet-ops see order'),
73
- },
74
- {
75
- priority: 3,
76
- intl: 'menu.order-config',
77
- title: this.intl.t('menu.order-config'),
78
- icon: 'diagram-project',
79
- route: 'operations.order-config',
80
- permission: 'fleet-ops list order-config',
81
- visible: this.abilities.can('fleet-ops see order-config'),
82
- },
83
- {
84
- priority: 4,
85
- intl: 'menu.service-rates',
86
- title: this.intl.t('menu.service-rates'),
87
- icon: 'file-invoice-dollar',
88
- route: 'operations.service-rates',
89
- permission: 'fleet-ops list service-rate',
90
- visible: this.abilities.can('fleet-ops see service-rate'),
91
- },
92
- ...(this.universeOperationsMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
93
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
94
-
95
- const resourcesItems = [
96
- {
97
- priority: 0,
98
- intl: 'menu.drivers',
99
- title: this.intl.t('menu.drivers'),
100
- icon: 'id-card',
101
- route: 'management.drivers',
102
- renderComponentInPlace: true,
103
- component: DriverListingComponent,
104
- permission: 'fleet-ops list driver',
105
- visible: this.abilities.can('fleet-ops see driver'),
106
- },
107
- {
108
- priority: 1,
109
- intl: 'menu.vehicles',
110
- title: this.intl.t('menu.vehicles'),
111
- icon: 'truck',
112
- route: 'management.vehicles',
113
- permission: 'fleet-ops list vehicle',
114
- visible: this.abilities.can('fleet-ops see vehicle'),
115
- },
116
- {
117
- priority: 2,
118
- intl: 'menu.fleets',
119
- title: this.intl.t('menu.fleets'),
120
- icon: 'user-group',
121
- route: 'management.fleets',
122
- renderComponentInPlace: true,
123
- component: FleetListingComponent,
124
- permission: 'fleet-ops list fleet',
125
- visible: this.abilities.can('fleet-ops see fleet'),
126
- },
127
- {
128
- priority: 3,
129
- intl: 'menu.vendors',
130
- title: this.intl.t('menu.vendors'),
131
- icon: 'warehouse',
132
- route: 'management.vendors',
133
- permission: 'fleet-ops list vendor',
134
- visible: this.abilities.can('fleet-ops see vendor'),
135
- },
136
- {
137
- priority: 4,
138
- intl: 'menu.contacts',
139
- title: this.intl.t('menu.contacts'),
140
- icon: 'address-book',
141
- route: 'management.contacts',
142
- permission: 'fleet-ops list contact',
143
- visible: this.abilities.can('fleet-ops see contact'),
144
- },
145
- {
146
- priority: 5,
147
- intl: 'menu.places',
148
- title: this.intl.t('menu.places'),
149
- icon: 'location-dot',
150
- route: 'management.places',
151
- permission: 'fleet-ops list place',
152
- visible: this.abilities.can('fleet-ops see place'),
153
- },
154
- {
155
- priority: 6,
156
- intl: 'menu.fuel-reports',
157
- title: this.intl.t('menu.fuel-reports'),
158
- icon: 'gas-pump',
159
- route: 'management.fuel-reports',
160
- permission: 'fleet-ops list fuel-report',
161
- visible: this.abilities.can('fleet-ops see fuel-report'),
162
- },
163
- {
164
- priority: 7,
165
- intl: 'menu.fuel-transactions',
166
- title: this.intl.t('menu.fuel-transactions'),
167
- icon: 'credit-card',
168
- route: 'management.fuel-transactions',
169
- permission: 'fleet-ops list fuel-report',
170
- visible: this.abilities.can('fleet-ops see fuel-report'),
171
- },
172
- {
173
- priority: 8,
174
- intl: 'menu.issues',
175
- title: this.intl.t('menu.issues'),
176
- icon: 'triangle-exclamation',
177
- route: 'management.issues',
178
- permission: 'fleet-ops list issue',
179
- visible: this.abilities.can('fleet-ops see issue'),
180
- },
181
- ...(this.universeManagementMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
182
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
183
-
184
- const connectivityItems = [
185
- {
186
- priority: 0,
187
- intl: 'menu.telematics',
188
- title: this.intl.t('menu.telematics'),
189
- icon: 'satellite-dish',
190
- route: 'connectivity.telematics',
191
- permission: 'fleet-ops list telematic',
192
- visible: this.abilities.can('fleet-ops see telematic'),
193
- },
194
- {
195
- priority: 1,
196
- intl: 'menu.fuel-providers',
197
- title: this.intl.t('menu.fuel-providers'),
198
- icon: 'gas-pump',
199
- route: 'connectivity.fuel-providers',
200
- permission: 'fleet-ops list fuel-report',
201
- visible: this.abilities.can('fleet-ops see fuel-report'),
202
- },
203
- {
204
- priority: 2,
205
- intl: 'menu.devices',
206
- title: this.intl.t('menu.devices'),
207
- icon: 'hard-drive',
208
- route: 'connectivity.devices',
209
- permission: 'fleet-ops list device',
210
- visible: this.abilities.can('fleet-ops see device'),
211
- },
212
- {
213
- priority: 3,
214
- intl: 'menu.sensors',
215
- title: this.intl.t('menu.sensors'),
216
- icon: 'temperature-full',
217
- route: 'connectivity.sensors',
218
- permission: 'fleet-ops list sensor',
219
- visible: this.abilities.can('fleet-ops see sensor'),
220
- },
221
- {
222
- priority: 4,
223
- intl: 'menu.events',
224
- title: this.intl.t('menu.events'),
225
- icon: 'stream',
226
- route: 'connectivity.events',
227
- permission: 'fleet-ops list device-event',
228
- visible: this.abilities.can('fleet-ops see device-event'),
229
- },
230
- ...(this.universeConnectivityMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
231
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
232
-
233
- const maintenanceItems = [
234
- {
235
- priority: 0,
236
- intl: 'menu.schedules',
237
- title: this.intl.t('menu.schedules'),
238
- icon: 'calendar-alt',
239
- route: 'maintenance.schedules',
240
- permission: 'fleet-ops list maintenance-schedule',
241
- visible: this.abilities.can('fleet-ops see maintenance-schedule'),
242
- },
243
- {
244
- priority: 1,
245
- intl: 'menu.work-orders',
246
- title: this.intl.t('menu.work-orders'),
247
- icon: 'clipboard-list',
248
- route: 'maintenance.work-orders',
249
- permission: 'fleet-ops list work-order',
250
- visible: this.abilities.can('fleet-ops see work-order'),
251
- },
252
- {
253
- priority: 2,
254
- intl: 'menu.maintenances',
255
- title: this.intl.t('menu.maintenances'),
256
- icon: 'history',
257
- route: 'maintenance.maintenances',
258
- permission: 'fleet-ops list maintenance',
259
- visible: this.abilities.can('fleet-ops see maintenance'),
260
- },
261
- {
262
- priority: 3,
263
- intl: 'menu.equipment',
264
- title: this.intl.t('menu.equipment'),
265
- icon: 'trailer',
266
- route: 'maintenance.equipment',
267
- permission: 'fleet-ops list equipment',
268
- visible: this.abilities.can('fleet-ops see equipment'),
269
- },
270
- {
271
- priority: 4,
272
- intl: 'menu.parts',
273
- title: this.intl.t('menu.parts'),
274
- icon: 'cog',
275
- route: 'maintenance.parts',
276
- permission: 'fleet-ops list part',
277
- visible: this.abilities.can('fleet-ops see part'),
278
- },
279
- ...(this.universeMaintenanceMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
280
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
281
-
282
- const analyticsItems = [
283
- {
284
- priority: 0,
285
- intl: 'menu.reports',
286
- title: this.intl.t('menu.reports'),
287
- icon: 'file-import',
288
- route: 'analytics.reports',
289
- permission: 'iam list report',
290
- visible: this.abilities.can('fleet-ops see report'),
291
- },
292
- ...(this.universeAnalyticsMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
293
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
294
-
295
- const settingsItems = [
296
- {
297
- priority: 0,
298
- intl: 'menu.navigator-app',
299
- title: this.intl.t('menu.navigator-app'),
300
- icon: 'location-arrow',
301
- route: 'settings.navigator-app',
302
- permission: 'fleet-ops view navigator-settings',
303
- visible: this.abilities.can('fleet-ops see navigator-settings'),
304
- },
305
- {
306
- priority: 1,
307
- intl: 'menu.payments',
308
- title: this.intl.t('menu.payments'),
309
- icon: 'cash-register',
310
- route: 'settings.payments',
311
- permission: 'fleet-ops view payments',
312
- visible: this.abilities.can('fleet-ops see payments'),
313
- },
314
- {
315
- priority: 2,
316
- intl: 'menu.notifications',
317
- title: this.intl.t('menu.notifications'),
318
- icon: 'bell',
319
- route: 'settings.notifications',
320
- permission: 'fleet-ops view notification-settings',
321
- visible: this.abilities.can('fleet-ops see notification-settings'),
322
- },
323
- {
324
- priority: 3,
325
- intl: 'menu.routing',
326
- title: this.intl.t('menu.routing'),
327
- icon: 'route',
328
- route: 'settings.routing',
329
- permission: 'fleet-ops view routing-settings',
330
- visible: this.abilities.can('fleet-ops see routing-settings'),
331
- },
332
- {
333
- intl: 'menu.map',
334
- title: this.intl.t('menu.map'),
335
- icon: 'map',
336
- route: 'settings.map',
337
- permission: 'fleet-ops view map-settings',
338
- visible: this.abilities.can('fleet-ops see map-settings'),
339
- },
340
- {
341
- priority: 4,
342
- intl: 'menu.orchestrator',
343
- title: this.intl.t('menu.orchestrator'),
344
- icon: 'circle-nodes',
345
- route: 'settings.orchestrator',
346
- permission: 'fleet-ops view routing-settings',
347
- visible: this.abilities.can('fleet-ops see routing-settings'),
348
- },
349
- {
350
- priority: 5,
351
- intl: 'menu.scheduling',
352
- title: this.intl.t('menu.scheduling'),
353
- icon: 'calendar-days',
354
- route: 'settings.scheduling',
355
- permission: 'fleet-ops view scheduling-settings',
356
- visible: this.abilities.can('fleet-ops see scheduling-settings'),
357
- },
358
- {
359
- priority: 6,
360
- intl: 'menu.custom-fields',
361
- title: this.intl.t('menu.custom-fields'),
362
- icon: 'rectangle-list',
363
- route: 'settings.custom-fields',
364
- permission: 'fleet-ops view custom-field',
365
- visible: this.abilities.can('fleet-ops see custom-field'),
366
- },
367
- {
368
- priority: 7,
369
- intl: 'menu.avatars',
370
- title: this.intl.t('menu.avatars'),
371
- icon: 'icons',
372
- route: 'settings.avatars',
373
- permission: 'fleet-ops view avatar',
374
- visible: this.abilities.can('fleet-ops see avatar'),
375
- },
376
- ...(this.universeSettingsMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
377
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
378
-
379
- const createPanel = (intl, routePrefix, items = [], options = {}) => ({
380
- intl,
381
- title: this.intl.t(intl),
382
- routePrefix,
383
- open: options.open ?? true,
384
- items,
385
- onToggle: options.onToggle,
386
- });
246
+ createBranch({ id, label, icon, route, defaultRoute, children, keywords = [] }) {
247
+ return {
248
+ id,
249
+ label,
250
+ icon,
251
+ route: this.fullRoute(route),
252
+ defaultRoute: this.fullRoute(defaultRoute),
253
+ children: children.filter((item) => item.visible !== false),
254
+ keywords,
255
+ };
256
+ }
387
257
 
388
- this.menuPanels = this.removeEmptyMenuPanels([
389
- createPanel('menu.operations', 'operations', operationsItems, {
390
- open: this.appCache.get('fleet-ops:sidebar:operations:open', true),
391
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:operations:open', open),
392
- }),
393
- createPanel('menu.resources', 'management', resourcesItems, {
394
- open: this.appCache.get('fleet-ops:sidebar:management:open', true),
395
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:management:open', open),
396
- }),
397
- createPanel('menu.maintenance', 'maintenance', maintenanceItems, {
398
- open: this.appCache.get('fleet-ops:sidebar:maintenance:open', false),
399
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:maintenance:open', open),
400
- }),
401
- createPanel('menu.connectivity', 'connectivity', connectivityItems, {
402
- open: this.appCache.get('fleet-ops:sidebar:connectivity:open', false),
403
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:connectivity:open', open),
404
- }),
405
- createPanel('menu.analytics', 'analytics', analyticsItems, {
406
- open: this.appCache.get('fleet-ops:sidebar:analytics:open', false),
407
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:analytics:open', open),
408
- }),
409
- createPanel('menu.settings', 'settings', settingsItems, {
410
- open: this.appCache.get('fleet-ops:sidebar:settings:open', true),
411
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:settings:open', open),
412
- }),
413
- ]);
258
+ createItem(intl, icon, route, permission, ability, keywords = []) {
259
+ return {
260
+ label: this.intl.t(intl),
261
+ description: this.intl.t(intl),
262
+ icon,
263
+ route: this.fullRoute(route),
264
+ permission,
265
+ visible: this.abilities.can(ability),
266
+ keywords: [intl, route, ...keywords].filter(Boolean),
267
+ };
414
268
  }
415
269
 
416
- /**
417
- * Action handler for creating an order.
418
- */
419
- @action onClickCreateOrder() {
420
- const { onClickCreateOrder } = this.args;
270
+ createHubItem(label, icon, route, permission, ability, keywords = []) {
271
+ return {
272
+ label,
273
+ description: label,
274
+ icon,
275
+ route: this.fullRoute(route),
276
+ permission,
277
+ visible: this.abilities.can(ability),
278
+ keywords: [label, route, ...keywords].filter(Boolean),
279
+ };
280
+ }
281
+
282
+ registryItem(menuItem) {
283
+ return {
284
+ ...menuItem,
285
+ label: menuItem.intl ? this.intl.t(menuItem.intl) : (menuItem.title ?? menuItem.label),
286
+ description: menuItem.description,
287
+ icon: menuItem.icon,
288
+ iconPrefix: menuItem.iconPrefix,
289
+ permission: menuItem.permission,
290
+ visible: menuItem.visible,
291
+ keywords: [menuItem.slug, menuItem.view, menuItem.section, menuItem.title, menuItem.label, menuItem.intl, ...(menuItem.keywords ?? [])].filter(Boolean),
292
+ onClick: () => this.universe.transitionMenuItem(`${this.routePrefix}virtual`, menuItem),
293
+ };
294
+ }
421
295
 
422
- if (typeof onClickCreateOrder === 'function') {
423
- onClickCreateOrder();
296
+ withRegistryItems(section, items) {
297
+ const registryProperty = SECTION_REGISTRY_KEYS[section];
298
+ const registryItems = (this[registryProperty] ?? []).filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item));
299
+
300
+ return [...items, ...registryItems];
301
+ }
302
+
303
+ fullRoute(route) {
304
+ if (!route || route.startsWith('console.')) {
305
+ return route;
424
306
  }
307
+
308
+ return `${this.routePrefix}${route}`;
425
309
  }
426
310
 
427
- /**
428
- * Filters menuPanels, leaving only menuPanels with visible items
429
- *
430
- * @param {Array} [menuPanels=[]]
431
- * @return {Array}
432
- * @memberof LayoutFleetOpsSidebarComponent
433
- */
434
- removeEmptyMenuPanels(menuPanels = []) {
435
- return menuPanels.filter((menuPanel) => {
436
- const visibleItems = menuPanel.items.filter((item) => item.visible);
437
- return visibleItems.length > 0;
438
- });
311
+ @action onClickCreateOrder() {
312
+ if (typeof this.args.onClickCreateOrder === 'function') {
313
+ this.args.onClickCreateOrder();
314
+ }
315
+ }
316
+
317
+ @action
318
+ async searchNavigation({ query, limit = 12 }) {
319
+ const trimmedQuery = query?.trim();
320
+
321
+ if (!trimmedQuery) {
322
+ return [];
323
+ }
324
+
325
+ try {
326
+ const response = await this.fetch.get('search', { query: trimmedQuery, limit }, { namespace: 'int/v1' });
327
+
328
+ return response.results ?? [];
329
+ } catch (_) {
330
+ return [];
331
+ }
439
332
  }
440
333
  }