@fleetbase/fleetops-engine 0.6.68 → 0.6.70
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 +44 -9
- package/addon/components/admin/telematics-settings.hbs +143 -0
- package/addon/components/admin/telematics-settings.js +145 -0
- 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/fuel-integration/hub.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +37 -17
- package/addon/components/map/leaflet-live-map.hbs +4 -242
- package/addon/components/map/leaflet-live-map.js +2 -1
- package/addon/components/map/marker-card/driver.hbs +57 -0
- package/addon/components/map/marker-card/driver.js +3 -0
- package/addon/components/map/marker-card/vehicle.hbs +78 -0
- package/addon/components/map/marker-card/vehicle.js +3 -0
- 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/components/telematic/hub.hbs +1 -1
- package/addon/components/widget/live-fleet.hbs +8 -40
- package/addon/controllers/analytics/reports/index.js +5 -0
- package/addon/controllers/connectivity/devices/index.js +4 -0
- package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
- package/addon/controllers/connectivity/sensors/index.js +4 -0
- package/addon/controllers/connectivity/telematics/index.js +4 -0
- package/addon/controllers/maintenance/equipment/index.js +11 -4
- package/addon/controllers/maintenance/inspection-forms/index.js +2 -2
- package/addon/controllers/maintenance/inspection-submissions/index.js +8 -2
- package/addon/controllers/maintenance/maintenances/index.js +4 -0
- package/addon/controllers/maintenance/parts/index.js +11 -4
- package/addon/controllers/maintenance/schedules/index.js +17 -4
- package/addon/controllers/maintenance/work-orders/index.js +11 -4
- package/addon/controllers/management/contacts/index.js +4 -0
- package/addon/controllers/management/drivers/index.js +8 -0
- package/addon/controllers/management/fleets/index.js +4 -0
- package/addon/controllers/management/fuel-reports/index.js +4 -0
- package/addon/controllers/management/fuel-transactions/index.js +9 -7
- package/addon/controllers/management/issues/index.js +4 -0
- package/addon/controllers/management/places/index.js +4 -0
- package/addon/controllers/management/vehicles/index.js +4 -0
- package/addon/controllers/management/vendors/index.js +4 -0
- package/addon/controllers/operations/orders/index/details.js +30 -1
- package/addon/controllers/operations/orders/index.js +7 -0
- package/addon/controllers/operations/service-rates/index.js +3 -0
- package/addon/controllers/settings/index.js +13 -0
- package/addon/controllers/settings/telematics.js +113 -0
- package/addon/extension.js +42 -8
- package/addon/routes/analytics/index.js +14 -1
- package/addon/routes/analytics/reports/index/details.js +1 -1
- package/addon/routes/analytics/reports/index/edit.js +1 -1
- package/addon/routes/analytics/reports/index/new.js +14 -1
- package/addon/routes/analytics/reports.js +14 -1
- package/addon/routes/connectivity/devices/index/new.js +14 -1
- package/addon/routes/connectivity/devices.js +14 -1
- package/addon/routes/connectivity/events.js +14 -1
- package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
- package/addon/routes/connectivity/fuel-providers/details.js +9 -0
- package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
- package/addon/routes/connectivity/fuel-providers/new.js +14 -1
- package/addon/routes/connectivity/fuel-providers.js +14 -1
- package/addon/routes/connectivity/sensors/index/new.js +14 -1
- package/addon/routes/connectivity/sensors.js +14 -1
- package/addon/routes/connectivity/telematics/new.js +14 -1
- package/addon/routes/connectivity/telematics.js +14 -1
- package/addon/routes/connectivity/tracking.js +14 -1
- package/addon/routes/maintenance/equipment/index/details.js +2 -2
- package/addon/routes/maintenance/equipment/index/edit.js +2 -2
- package/addon/routes/maintenance/equipment/index/new.js +14 -1
- package/addon/routes/maintenance/equipment.js +14 -1
- package/addon/routes/maintenance/index.js +14 -1
- package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
- package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
- package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
- package/addon/routes/maintenance/inspection-forms.js +14 -1
- package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
- package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
- package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
- package/addon/routes/maintenance/inspection-submissions.js +14 -1
- package/addon/routes/maintenance/maintenances/index/details.js +2 -2
- package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
- package/addon/routes/maintenance/maintenances/index/new.js +14 -1
- package/addon/routes/maintenance/maintenances.js +14 -1
- package/addon/routes/maintenance/parts/index/details.js +2 -2
- package/addon/routes/maintenance/parts/index/edit.js +2 -2
- package/addon/routes/maintenance/parts/index/new.js +14 -1
- package/addon/routes/maintenance/parts.js +14 -1
- package/addon/routes/maintenance/schedules/index/details.js +2 -2
- package/addon/routes/maintenance/schedules/index/edit.js +2 -2
- package/addon/routes/maintenance/schedules/index/new.js +15 -1
- package/addon/routes/maintenance/schedules.js +15 -1
- package/addon/routes/maintenance/work-orders/index/details.js +2 -2
- package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
- package/addon/routes/maintenance/work-orders/index/new.js +14 -1
- package/addon/routes/maintenance/work-orders.js +14 -1
- package/addon/routes/management/fuel-transactions/index/details.js +9 -0
- package/addon/routes/management/fuel-transactions.js +14 -1
- package/addon/routes/management/index.js +13 -0
- package/addon/routes/management/trailers.js +16 -0
- package/addon/routes/management/vendors/integrated/details.js +14 -1
- package/addon/routes/management/vendors/integrated/edit.js +14 -1
- package/addon/routes/management/vendors/integrated/new.js +14 -1
- package/addon/routes/management/vendors/integrated.js +14 -1
- package/addon/routes/operations/orders.js +40 -1
- package/addon/routes/operations/routes/index/details.js +14 -1
- package/addon/routes/operations/routes/index/new.js +13 -0
- package/addon/routes/operations/routes.js +14 -1
- package/addon/routes/operations/service-rates/index/new.js +11 -0
- package/addon/routes/operations/service-rates.js +14 -1
- package/addon/routes/settings/avatars.js +14 -1
- package/addon/routes/settings/custom-fields.js +14 -1
- package/addon/routes/settings/map.js +14 -1
- package/addon/routes/settings/notifications.js +11 -0
- package/addon/routes/settings/orchestrator.js +1 -1
- package/addon/routes/settings/payments/index.js +1 -1
- package/addon/routes/settings/routing.js +14 -1
- package/addon/routes/settings/scheduling.js +14 -1
- package/addon/routes/settings/telematics.js +21 -0
- package/addon/routes/virtual.js +11 -0
- package/addon/routes.js +1 -0
- package/addon/services/contact-actions.js +50 -89
- package/addon/services/driver-actions.js +98 -0
- package/addon/templates/settings/telematics.hbs +70 -0
- package/addon/utils/profile-login-actions.js +172 -0
- package/app/components/admin/telematics-settings.js +1 -0
- package/app/components/{modals/reset-customer-credentials.js → map/marker-card/driver.js} +1 -1
- package/app/components/map/marker-card/vehicle.js +1 -0
- package/app/components/modals/reset-profile-credentials.js +1 -0
- package/app/components/profile-identity-hint.js +1 -0
- package/app/controllers/settings/telematics.js +1 -0
- package/app/routes/settings/telematics.js +1 -0
- package/app/templates/settings/telematics.js +1 -0
- package/composer.json +2 -2
- package/docs/AFAQY.md +1 -1
- package/docs/TELEMATICS_QUEUES.md +1 -1
- package/docs/TELEMETRY_ARCHITECTURE.md +72 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/scripts/pest-bootstrap.php +12 -0
- package/server/config/telemetry.php +12 -0
- package/server/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php +132 -0
- package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
- package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
- package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
- package/server/resources/views/mail/driver-credentials.blade.php +21 -0
- package/server/src/Auth/Schemas/FleetOps.php +104 -5
- package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
- package/server/src/Console/Commands/FixCustomerCompanies.php +1 -1
- package/server/src/Console/Commands/FixDriverCompanies.php +1 -1
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
- package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
- 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/Api/v1/OrchestrationController.php +9 -0
- package/server/src/Http/Controllers/FleetOpsController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +16 -17
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +54 -4
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +204 -162
- package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +47 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
- package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +178 -27
- package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
- package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
- 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/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
- package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
- package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
- package/server/src/Mail/CustomerCredentialsMail.php +1 -1
- 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/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +18 -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/Analytics/LiveFleet.php +18 -2
- package/server/src/Support/Authorization.php +103 -0
- package/server/src/Support/Database/TableIndexes.php +66 -0
- package/server/src/Support/OrderTracker.php +2 -2
- package/server/src/Support/ProfileAccountManager.php +405 -0
- package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
- package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
- package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
- package/server/src/Support/Telematics/TelematicService.php +12 -2
- package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
- package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
- package/server/src/Tracking/TrackingOptions.php +2 -2
- package/server/src/Traits/AuthorizesMethods.php +37 -0
- package/server/src/routes.php +14 -1
- package/server/tests/AiOperationalQueryCapabilityTest.php +1 -0
- package/server/tests/AnalyticsRoutesTest.php +29 -2
- 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 +21 -1
- 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/Api/TelematicsRetentionIndexMigrationTest.php +115 -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/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
- package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
- package/server/tests/InternalCustomerControllerContractsTest.php +32 -1
- package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
- package/server/tests/ModelAccessorContractsTest.php +82 -20
- package/server/tests/NavigatorControllerContractsTest.php +36 -4
- package/server/tests/NotificationAndMailContractsTest.php +31 -0
- package/server/tests/ObserverContractsTest.php +28 -3
- package/server/tests/OrchestratorConsumableApiTest.php +42 -0
- package/server/tests/ProviderContractsTest.php +10 -0
- package/server/tests/ReportSchemaContractsTest.php +120 -6
- package/server/tests/RequestContractsTest.php +19 -11
- package/server/tests/SettingControllerContractsTest.php +429 -18
- package/server/tests/SmallControllerContractsTest.php +8 -5
- package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
- package/server/tests/SupportJobAndAiCoverageTest.php +3 -3
- package/server/tests/TelematicControllerContractsTest.php +22 -4
- package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
- package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
- package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
- package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
- 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/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
- 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/Analytics/LiveFleetTest.php +6 -0
- package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
- package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
- package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
- package/server/tests/Unit/Support/ProfileAccountManagerTest.php +643 -0
- package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
- package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
- package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
- package/translations/en-us.yaml +85 -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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<Layout::Section::Header @title={{t "settings.telematics.telematics-settings"}}>
|
|
2
|
+
<Button
|
|
3
|
+
@type="primary"
|
|
4
|
+
@size="sm"
|
|
5
|
+
@icon="save"
|
|
6
|
+
@text={{t "common.save-changes"}}
|
|
7
|
+
@onClick={{perform this.saveSettings}}
|
|
8
|
+
@disabled={{or this.saveSettings.isRunning this.getSettings.isRunning (not this.settingsLoaded)}}
|
|
9
|
+
@isLoading={{or this.saveSettings.isRunning this.getSettings.isRunning}}
|
|
10
|
+
@permission="fleet-ops update telematics-settings"
|
|
11
|
+
/>
|
|
12
|
+
</Layout::Section::Header>
|
|
13
|
+
<Layout::Section::Body class="overflow-y-scroll-i h-full">
|
|
14
|
+
<div class="container mx-auto">
|
|
15
|
+
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
|
16
|
+
<InfoBlock @type="info" @icon="circle-info">
|
|
17
|
+
{{t "settings.telematics.intro"}}
|
|
18
|
+
</InfoBlock>
|
|
19
|
+
|
|
20
|
+
<ContentPanel @title={{t "settings.telematics.device-events"}} @open={{true}} @wrapperClass="bordered-classic">
|
|
21
|
+
<InputGroup>
|
|
22
|
+
<Toggle
|
|
23
|
+
@label={{t "settings.telematics.use-system-default"}}
|
|
24
|
+
@isToggled={{this.useDefaultEventRetention}}
|
|
25
|
+
@onToggle={{this.setUseDefaultEventRetention}}
|
|
26
|
+
@disabled={{not this.settingsLoaded}}
|
|
27
|
+
/>
|
|
28
|
+
</InputGroup>
|
|
29
|
+
<InputGroup @name={{t "settings.telematics.event-retention-days"}} @helpText={{t "settings.telematics.event-retention-days-help"}}>
|
|
30
|
+
<Input
|
|
31
|
+
@type="number"
|
|
32
|
+
@value={{this.eventRetentionDays}}
|
|
33
|
+
min={{this.eventRetentionMinimum}}
|
|
34
|
+
max={{this.eventRetentionMaximum}}
|
|
35
|
+
disabled={{or this.useDefaultEventRetention (not this.settingsLoaded)}}
|
|
36
|
+
class="w-full form-input"
|
|
37
|
+
/>
|
|
38
|
+
</InputGroup>
|
|
39
|
+
{{#if this.settingsLoaded}}
|
|
40
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">{{this.eventRetentionPolicyHelp}}</p>
|
|
41
|
+
{{/if}}
|
|
42
|
+
</ContentPanel>
|
|
43
|
+
|
|
44
|
+
<ContentPanel @title={{t "settings.telematics.positions"}} @open={{true}} @wrapperClass="bordered-classic">
|
|
45
|
+
<InputGroup>
|
|
46
|
+
<Toggle
|
|
47
|
+
@label={{t "settings.telematics.use-system-default"}}
|
|
48
|
+
@isToggled={{this.useDefaultPositionRetention}}
|
|
49
|
+
@onToggle={{this.setUseDefaultPositionRetention}}
|
|
50
|
+
@disabled={{not this.settingsLoaded}}
|
|
51
|
+
/>
|
|
52
|
+
</InputGroup>
|
|
53
|
+
<InputGroup @name={{t "settings.telematics.position-retention-days"}} @helpText={{t "settings.telematics.position-retention-days-help"}}>
|
|
54
|
+
<Input
|
|
55
|
+
@type="number"
|
|
56
|
+
@value={{this.positionRetentionDays}}
|
|
57
|
+
min={{this.positionRetentionMinimum}}
|
|
58
|
+
max={{this.positionRetentionMaximum}}
|
|
59
|
+
disabled={{or this.useDefaultPositionRetention (not this.settingsLoaded)}}
|
|
60
|
+
class="w-full form-input"
|
|
61
|
+
/>
|
|
62
|
+
</InputGroup>
|
|
63
|
+
{{#if this.settingsLoaded}}
|
|
64
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">{{this.positionRetentionPolicyHelp}}</p>
|
|
65
|
+
{{/if}}
|
|
66
|
+
</ContentPanel>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<Spacer @height="200px" />
|
|
70
|
+
</Layout::Section::Body>
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/components/admin/telematics-settings';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from '@fleetbase/fleetops-engine/components/
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/components/map/marker-card/driver';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/components/map/marker-card/vehicle';
|
|
@@ -0,0 +1 @@
|
|
|
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';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/controllers/settings/telematics';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/routes/settings/telematics';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/templates/settings/telematics';
|
package/composer.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fleetbase/fleetops-api",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.70",
|
|
4
4
|
"description": "Fleet & Transport Management Extension for Fleetbase",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fleetbase-extension",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"barryvdh/laravel-dompdf": "^3.1",
|
|
26
26
|
"brick/geo": "0.7.2",
|
|
27
27
|
"cknow/laravel-money": "^7.1",
|
|
28
|
-
"fleetbase/core-api": "
|
|
28
|
+
"fleetbase/core-api": ">=1.6.65",
|
|
29
29
|
"geocoder-php/google-maps-places-provider": "^1.4",
|
|
30
30
|
"giggsey/libphonenumber-for-php": "^8.13",
|
|
31
31
|
"league/geotools": "^1.1.0",
|
package/docs/AFAQY.md
CHANGED
|
@@ -57,7 +57,7 @@ Existing provider interfaces and vehicle/trailer event names remain compatible.
|
|
|
57
57
|
|
|
58
58
|
The public receiver remains `POST webhooks/telematics/afaqy?telematic=...&key=...` under the application's API prefix. A new `device.telemetry_updated` broadcast identifies the device; an open device panel reloads its authorized resource and refetches after socket reconnection. Company broadcast identity comes from the persisted asset, not a worker session. Map movement uses the existing vehicle/trailer events, rejects older source timestamps, and reloads visible telemetry assets after socket reconnection with at most five concurrent requests.
|
|
59
59
|
|
|
60
|
-
Configure shared queues, retention, limits, and freshness under `telematics.telemetry` using `server/config/telemetry.php`. `TELEMATICS_BATCH_SIZE` controls units per ingestion delivery (default 100); `TELEMATICS_REQUEST_TIMEOUT_SECONDS` controls the bounded polling request timeout (default 45 seconds). AFAQY's adapter switches and optional queue overrides remain under `telematics.afaqy` in `server/config/afaqy.php`. Defaults: two MiB per webhook, 10,000 pending deliveries per connection, processed
|
|
60
|
+
Configure shared queues, retention, limits, and freshness under `telematics.telemetry` using `server/config/telemetry.php`. `TELEMATICS_BATCH_SIZE` controls units per ingestion delivery (default 100); `TELEMATICS_REQUEST_TIMEOUT_SECONDS` controls the bounded polling request timeout (default 45 seconds). AFAQY's adapter switches and optional queue overrides remain under `telematics.afaqy` in `server/config/afaqy.php`. Defaults: two MiB per webhook, 10,000 pending deliveries per connection, stale-position thresholds 120 seconds with ignition on and 600 seconds with ignition off/unknown. Retention (processed payloads 24 hours, quarantined deliveries seven days, device events 30 days, positions 90 days) is applied by `fleetops:prune-telematics-data`. Administrators configure retention policy, logging, system storage diagnostics and system cleanup in Admin → Fleet-Ops Config → Telematics. Organizations choose only event and position history preferences within those limits in Fleet-Ops → Settings → Telematics; config values are the fallback. See `docs/TELEMETRY_ARCHITECTURE.md` § Retention. Each device's metadata records the effective threshold. Connection status categories remain unchanged.
|
|
61
61
|
|
|
62
62
|
## Incident verification
|
|
63
63
|
|
|
@@ -14,7 +14,7 @@ nothing changes: every job and broadcast stays on the same queue as before.
|
|
|
14
14
|
| Setting | Default | Work routed |
|
|
15
15
|
| --- | --- | --- |
|
|
16
16
|
| `TELEMATICS_POLL_QUEUE` | `default` | Scheduled and manual polls (`PollTelematicTelemetry`), legacy device discovery (`SyncTelematicDevicesJob`), and queued connection tests (`TestTelematicConnectionJob`). |
|
|
17
|
-
| `TELEMATICS_INGESTION_QUEUE` | `default` | Applying polled or pushed positions (`ProcessTelematicDelivery`), including jobs re-queued by `fleetops:drain-telematic-inbox` and manual replays. |
|
|
17
|
+
| `TELEMATICS_INGESTION_QUEUE` | `default` | Applying polled or pushed positions (`ProcessTelematicDelivery`), including jobs re-queued by `fleetops:drain-telematic-inbox` and manual replays, and on-demand retention runs (`PruneTelematicsDataJob`, queued by "Run system cleanup" in Admin → Fleet-Ops Config → Telematics). |
|
|
18
18
|
| `TELEMATICS_BROADCAST_QUEUE` | unset | Live-map broadcasts created by telematics ingestion: `DeviceTelemetryUpdated`, and `VehicleLocationChanged` / `TrailerLocationChanged` when a device is attached. When unset, broadcasts use the queue connection's default queue. |
|
|
19
19
|
|
|
20
20
|
`TELEMATICS_BROADCAST_QUEUE` affects only broadcasts that telematics ingestion
|
|
@@ -48,11 +48,82 @@ persistence. The performance requirement did not justify those dependencies.
|
|
|
48
48
|
## Tradeoffs
|
|
49
49
|
|
|
50
50
|
Three shared tables separate run-level diagnostics, high-volume delivery retention,
|
|
51
|
-
and connection-level secrets with different lifetimes. Provider-specific protocol
|
|
51
|
+
and connection-level secrets with different lifetimes (see Retention below). Provider-specific protocol
|
|
52
52
|
classes remain appropriate. Capability opt-in avoids silently changing every
|
|
53
53
|
existing integration's ingestion or webhook behavior. Production throughput and
|
|
54
54
|
real AFAQY payload verification remain separate deployment gates.
|
|
55
55
|
|
|
56
|
+
## Retention
|
|
57
|
+
|
|
58
|
+
Every poll writes one `device_events` row, one `positions` row and a delivery
|
|
59
|
+
envelope per batch for each reporting device. Without a ceiling those tables grow
|
|
60
|
+
until the disk fills, so retention is enforced by `fleetops:prune-telematics-data`
|
|
61
|
+
(scheduled every fifteen minutes, `withoutOverlapping(14)`) using a per-company
|
|
62
|
+
policy resolved by `Support\Telematics\Retention\RetentionPolicy`:
|
|
63
|
+
|
|
64
|
+
| Layer | Source | Where it is edited |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| Package config | `server/config/telemetry.php` | deployment |
|
|
67
|
+
| System policy | setting `fleet-ops.telematics-settings` | Admin console → Fleet-Ops Config → Telematics |
|
|
68
|
+
| Company history preferences | `company.<uuid>.fleet-ops.telematics-settings` | Fleet-Ops → Settings → Telematics |
|
|
69
|
+
|
|
70
|
+
Defaults: device events 30 days, raw payload/meta stripped ("compacted") after
|
|
71
|
+
7 days, positions 90 days, processed deliveries 24 hours, quarantined deliveries
|
|
72
|
+
7 days, sync runs 7 days. Organizations can choose only device event and position
|
|
73
|
+
history retention, or inherit the system defaults. Administrators own raw payload,
|
|
74
|
+
delivery, sync-run retention and telemetry activity logging.
|
|
75
|
+
|
|
76
|
+
`max_event_retention_days` and `max_position_retention_days` constrain every
|
|
77
|
+
organization's history, including inherited defaults. A maximum of `0` allows
|
|
78
|
+
unlimited history; a history preference of `0` keeps records forever only while
|
|
79
|
+
that maximum is unset. Setting a finite maximum replaces saved unlimited or longer
|
|
80
|
+
preferences with that maximum. Missing preferences continue to inherit. Values
|
|
81
|
+
are also clamped to `RetentionPolicy::LIMITS`.
|
|
82
|
+
|
|
83
|
+
Admin storage diagnostics cover the entire system, including organizations the
|
|
84
|
+
administrator does not belong to. System cleanup queues work for every
|
|
85
|
+
organization plus orphaned records, with each organization's effective policy
|
|
86
|
+
applied independently. Storage sizes include table data and indexes; database row
|
|
87
|
+
estimates are labeled. Cleanup is asynchronous, so refreshing usage does not mean
|
|
88
|
+
all queued work has finished.
|
|
89
|
+
|
|
90
|
+
Manual system cleanup requires an asynchronous queue connection and a worker for
|
|
91
|
+
`telematics.telemetry.ingestion_queue`. A synchronous or disabled queue is rejected
|
|
92
|
+
instead of running every organization's cleanup inside the HTTP request.
|
|
93
|
+
|
|
94
|
+
Deploy the frontend and backend together and reload Octane and queue workers after
|
|
95
|
+
updating the backend. In linked development checkouts, Octane's watch paths must
|
|
96
|
+
include `packages/fleetops/server`; watching only the host API directory leaves
|
|
97
|
+
previously loaded controllers and policies running.
|
|
98
|
+
|
|
99
|
+
Rules the sweep follows:
|
|
100
|
+
|
|
101
|
+
- Hard deletes only. The models soft-delete, so `delete()` would grow the table;
|
|
102
|
+
rows already soft-deleted are purged regardless of age.
|
|
103
|
+
- Bounded work: at most `--max-batches` (default 50) statements of `--batch-size`
|
|
104
|
+
(default 1000) rows per table per company per run. A capped run is normal; the
|
|
105
|
+
next tick continues. Operators can catch up faster off-peak with
|
|
106
|
+
`php artisan fleetops:prune-telematics-data --max-batches=500 --no-lock`.
|
|
107
|
+
- Compaction keeps the event (type, severity, location, normalized `data`) and
|
|
108
|
+
nulls `payload` and `meta`, which hold the bulk of each row.
|
|
109
|
+
- `telematic_deliveries` and `telematic_sync_runs` carry no company; they are
|
|
110
|
+
resolved through the company's connections (including trashed ones). In-flight
|
|
111
|
+
sync runs (`fetching`, `ingesting`) are recovered by the drain, never expired.
|
|
112
|
+
- Rows whose company or connection no longer exists are swept with the system
|
|
113
|
+
defaults.
|
|
114
|
+
- Migrations add `(company_uuid, created_at)` on `device_events` and `positions`
|
|
115
|
+
and `(telematic_uuid, updated_at)` on `telematic_sync_runs`; on multi-GB tables
|
|
116
|
+
run them in a maintenance window.
|
|
117
|
+
- Deleting rows frees space inside InnoDB files, not on disk. Run
|
|
118
|
+
`OPTIMIZE TABLE device_events` (requires `innodb_file_per_table`) off-peak to
|
|
119
|
+
return it to the operating system.
|
|
120
|
+
|
|
121
|
+
Two write-amplification fixes accompany the sweep: `device_events.meta` stores only
|
|
122
|
+
the normalized block (the raw unit lives once, in `payload`), and telemetry-driven
|
|
123
|
+
saves run inside `activity()->withoutLogs()` unless the system administrator enables
|
|
124
|
+
"Log telemetry activity". `DrainTelematicInbox` now only recovers deliveries and
|
|
125
|
+
finishes interrupted runs.
|
|
126
|
+
|
|
56
127
|
## Adding an adapter
|
|
57
128
|
|
|
58
129
|
Implement `TelemetryProviderInterface` alongside the existing provider contract.
|
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.70",
|
|
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
|
}
|
|
@@ -13,8 +13,20 @@ return [
|
|
|
13
13
|
'batch_size' => env('TELEMATICS_BATCH_SIZE', 100),
|
|
14
14
|
'max_payload_bytes' => 2097152,
|
|
15
15
|
'max_pending_deliveries' => 10000,
|
|
16
|
+
// Administrators control infrastructure retention and activity logging.
|
|
17
|
+
// Companies may choose event/position history retention within the maximums.
|
|
18
|
+
// A maximum of 0 imposes no cap and permits unlimited history (retention 0).
|
|
16
19
|
'processed_retention_hours' => 24,
|
|
17
20
|
'quarantine_retention_days' => 7,
|
|
21
|
+
'sync_run_retention_days' => 7,
|
|
22
|
+
'event_retention_days' => 30,
|
|
23
|
+
'event_compact_after_days' => 7,
|
|
24
|
+
'position_retention_days' => 90,
|
|
25
|
+
'max_event_retention_days' => 0,
|
|
26
|
+
'max_position_retention_days' => 0,
|
|
27
|
+
'log_telemetry_activity' => false,
|
|
28
|
+
'prune_batch_size' => 1000,
|
|
29
|
+
'prune_max_batches' => 50,
|
|
18
30
|
'stale_engine_on_seconds' => 120,
|
|
19
31
|
'stale_engine_off_seconds' => 600,
|
|
20
32
|
];
|
package/server/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Illuminate\Database\Migrations\Migration;
|
|
4
|
+
use Illuminate\Support\Facades\DB;
|
|
5
|
+
use Illuminate\Support\Facades\Schema;
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Driver and customer login accounts are now managed by their FleetOps
|
|
9
|
+
* profile (user type `driver` or `customer`) instead of in IAM.
|
|
10
|
+
*
|
|
11
|
+
* Before this change the driver form created plain `user` accounts holding only
|
|
12
|
+
* the Driver role, so these accounts show up in IAM and can sign in to the
|
|
13
|
+
* console. This converts them to managed accounts. An account is converted only
|
|
14
|
+
* when it is clearly a profile login and nothing else: it has a driver or
|
|
15
|
+
* customer profile, every organization role it holds is the profile's role, it
|
|
16
|
+
* has no direct permissions or policies, and it owns no organization. Anything
|
|
17
|
+
* else is left as a team member.
|
|
18
|
+
*
|
|
19
|
+
* The previous type is kept in `meta.previous_type` so down() can revert it.
|
|
20
|
+
*/
|
|
21
|
+
return new class extends Migration {
|
|
22
|
+
private const PROFILE_ROLES = [
|
|
23
|
+
'driver' => 'Driver',
|
|
24
|
+
'customer' => 'Fleet-Ops Customer',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
public function up(): void
|
|
28
|
+
{
|
|
29
|
+
if (!$this->hasRequiredTables()) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
DB::table('users')
|
|
34
|
+
->select(['uuid', 'meta'])
|
|
35
|
+
->where('type', 'user')
|
|
36
|
+
->whereNull('deleted_at')
|
|
37
|
+
->where(function ($query) {
|
|
38
|
+
$query->whereExists(function ($query) {
|
|
39
|
+
$query->selectRaw(1)->from('drivers')->whereColumn('drivers.user_uuid', 'users.uuid')->whereNull('drivers.deleted_at');
|
|
40
|
+
})->orWhereExists(function ($query) {
|
|
41
|
+
$query->selectRaw(1)->from('contacts')->whereColumn('contacts.user_uuid', 'users.uuid')->where('contacts.type', 'customer')->whereNull('contacts.deleted_at');
|
|
42
|
+
});
|
|
43
|
+
})
|
|
44
|
+
->whereNotExists(function ($query) {
|
|
45
|
+
$query->selectRaw(1)->from('companies')->whereColumn('companies.owner_uuid', 'users.uuid');
|
|
46
|
+
})
|
|
47
|
+
->orderBy('uuid')
|
|
48
|
+
->chunk(500, function ($users) {
|
|
49
|
+
foreach ($users as $user) {
|
|
50
|
+
$type = $this->managedTypeFor($user->uuid);
|
|
51
|
+
if (!$type) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
$meta = json_decode($user->meta ?? '', true);
|
|
56
|
+
$meta = is_array($meta) ? $meta : [];
|
|
57
|
+
$meta['previous_type'] = 'user';
|
|
58
|
+
|
|
59
|
+
DB::table('users')->where('uuid', $user->uuid)->update(['type' => $type, 'meta' => json_encode($meta)]);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public function down(): void
|
|
65
|
+
{
|
|
66
|
+
if (!Schema::hasTable('users')) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
DB::table('users')
|
|
71
|
+
->select(['uuid', 'meta'])
|
|
72
|
+
->whereIn('type', array_keys(self::PROFILE_ROLES))
|
|
73
|
+
->where('meta', 'like', '%"previous_type"%')
|
|
74
|
+
->orderBy('uuid')
|
|
75
|
+
->chunk(500, function ($users) {
|
|
76
|
+
foreach ($users as $user) {
|
|
77
|
+
$meta = json_decode($user->meta ?? '', true);
|
|
78
|
+
if (!is_array($meta) || ($meta['previous_type'] ?? null) !== 'user') {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
unset($meta['previous_type']);
|
|
83
|
+
DB::table('users')->where('uuid', $user->uuid)->update(['type' => 'user', 'meta' => json_encode($meta)]);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The managed type for the account, or null when it should stay a team member.
|
|
90
|
+
*/
|
|
91
|
+
private function managedTypeFor(string $userUuid): ?string
|
|
92
|
+
{
|
|
93
|
+
$hasDriver = DB::table('drivers')->where('user_uuid', $userUuid)->whereNull('deleted_at')->exists();
|
|
94
|
+
$hasCustomer = DB::table('contacts')->where('user_uuid', $userUuid)->where('type', 'customer')->whereNull('deleted_at')->exists();
|
|
95
|
+
|
|
96
|
+
// An account holding both kinds of profile is a person the organization
|
|
97
|
+
// relates to in two ways; leave it for an administrator to decide.
|
|
98
|
+
if ($hasDriver === $hasCustomer) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
$type = $hasDriver ? 'driver' : 'customer';
|
|
103
|
+
$companyUserUuids = DB::table('company_users')->where('user_uuid', $userUuid)->whereNull('deleted_at')->pluck('uuid');
|
|
104
|
+
$modelUuids = $companyUserUuids->push($userUuid)->all();
|
|
105
|
+
|
|
106
|
+
$roles = DB::table('model_has_roles')
|
|
107
|
+
->join('roles', 'roles.id', '=', 'model_has_roles.role_id')
|
|
108
|
+
->whereIn('model_has_roles.model_uuid', $modelUuids)
|
|
109
|
+
->pluck('roles.name')
|
|
110
|
+
->unique();
|
|
111
|
+
|
|
112
|
+
if ($roles->isEmpty() || $roles->contains(fn ($role) => $role !== self::PROFILE_ROLES[$type])) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
$hasPermissions = DB::table('model_has_permissions')->whereIn('model_uuid', $modelUuids)->exists();
|
|
117
|
+
$hasPolicies = DB::table('model_has_policies')->whereIn('model_uuid', $modelUuids)->exists();
|
|
118
|
+
|
|
119
|
+
return $hasPermissions || $hasPolicies ? null : $type;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private function hasRequiredTables(): bool
|
|
123
|
+
{
|
|
124
|
+
foreach (['users', 'drivers', 'contacts', 'companies', 'company_users', 'roles', 'model_has_roles', 'model_has_permissions', 'model_has_policies'] as $table) {
|
|
125
|
+
if (!Schema::hasTable($table)) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Fleetbase\FleetOps\Support\Database\TableIndexes;
|
|
4
|
+
use Illuminate\Database\Migrations\Migration;
|
|
5
|
+
use Illuminate\Database\Schema\Blueprint;
|
|
6
|
+
use Illuminate\Support\Facades\Schema;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Supports the bounded retention sweep in fleetops:prune-telematics-data,
|
|
10
|
+
* which ranges over these columns for every company on every run.
|
|
11
|
+
*/
|
|
12
|
+
return new class extends Migration {
|
|
13
|
+
private const TABLE = 'device_events';
|
|
14
|
+
private const INDEX = 'device_events_company_created_at_index';
|
|
15
|
+
private const COLUMNS = ['company_uuid', 'created_at'];
|
|
16
|
+
|
|
17
|
+
public function up(): void
|
|
18
|
+
{
|
|
19
|
+
if (!Schema::hasTable(self::TABLE) || !Schema::hasColumns(self::TABLE, self::COLUMNS)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (TableIndexes::covers(self::TABLE, self::COLUMNS)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Schema::table(self::TABLE, fn (Blueprint $table) => $table->index(self::COLUMNS, self::INDEX));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public function down(): void
|
|
31
|
+
{
|
|
32
|
+
if (!Schema::hasTable(self::TABLE)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if ((TableIndexes::for(self::TABLE)[self::INDEX] ?? null) === self::COLUMNS) {
|
|
37
|
+
Schema::table(self::TABLE, fn (Blueprint $table) => $table->dropIndex(self::INDEX));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|