@fleetbase/fleetops-engine
Version:
Fleet & Transport Management Extension for Fleetbase
414 lines (338 loc) • 14 kB
JavaScript
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
export default class ConnectivityTelematicsDetailsAttachmentsController extends Controller {
deviceActions;
fetch;
hostRouter;
intl;
modalsManager;
notifications;
queryParams = ['query', 'status', 'vehicle', 'sort'];
telematic;
query;
status;
vehicle;
sort = '-updated_at';
selectedDevice = null;
selectedVehicle = null;
isRefreshing = false;
get syncedDevices() {
return Array.from(this.model?.devices ?? this.model ?? []);
}
get loadError() {
return this.model?.error;
}
get totalSyncedDevices() {
return this.model?.meta?.total ?? this.syncedDevices.length;
}
get devices() {
return [...this.unattachedDevices, ...this.attachedDevices];
}
get unattachedDevices() {
return this.syncedDevices.filter((device) => !device.attachable_uuid && this.deviceMatchesQuery(device) && this.deviceMatchesStatus(device));
}
get attachedDevices() {
return this.syncedDevices.filter((device) => device.attachable_uuid && this.deviceMatchesQuery(device) && this.deviceMatchesStatus(device) && this.deviceMatchesVehicle(device));
}
get onlineDevicesCount() {
return this.syncedDevices.filter((device) => device.is_online || device.connection_status === 'online' || device.status === 'online' || device.status === 'active').length;
}
get attachedDevicesCount() {
return this.syncedDevices.filter((device) => device.attachable_uuid).length;
}
get unattachedDevicesCount() {
return this.syncedDevices.filter((device) => !device.attachable_uuid).length;
}
get mappedVehiclesCount() {
const vehicleIds = new Set(this.syncedDevices.filter((device) => device.attachable_uuid).map((device) => device.attachable_uuid));
return vehicleIds.size;
}
get vehicleGroups() {
const groups = new Map();
for (const device of this.attachedDevices) {
this.addDeviceToVehicleGroup(groups, device);
}
return this.sortVehicleGroups(groups);
}
addDeviceToVehicleGroup(groups, device) {
const key = device.attachable_uuid;
if (!groups.has(key)) {
const vehicle = device.attachable;
groups.set(key, {
id: key,
name: this.getDeviceVehicleName(device),
vehicle,
devices: [],
});
}
groups.get(key).devices.push(device);
}
sortVehicleGroups(groups) {
return Array.from(groups.values()).sort((a, b) => String(a.name).localeCompare(String(b.name)));
}
get normalizedQuery() {
return String(this.query ?? '')
.trim()
.toLowerCase();
}
deviceMatchesQuery(device) {
const query = this.normalizedQuery;
if (!query) {
return true;
}
return [
device.displayName,
device.name,
device.device_id,
device.serial_number,
device.imei,
device.public_id,
device.attached_to_name,
device.attachable?.displayName,
device.attachable?.display_name,
device.attachable?.name,
device.status,
device.connection_status,
]
.filter(Boolean)
.some((value) => String(value).toLowerCase().includes(query));
}
deviceMatchesStatus(device) {
return !this.status || device.status === this.status || device.connection_status === this.status;
}
deviceMatchesVehicle(device) {
return !this.vehicle || device.attachable_uuid === this.vehicle;
}
get hasActiveFilters() {
return Boolean(this.query || this.status || this.vehicle);
}
get hasSyncedDevices() {
return this.syncedDevices.length > 0;
}
get hasVisibleDevices() {
return this.devices.length > 0;
}
get hasOnlyUnattachedDevices() {
return this.unattachedDevices.length > 0 && this.vehicleGroups.length === 0 && !this.hasActiveFilters;
}
get allMapped() {
return this.hasSyncedDevices && this.unattachedDevices.length === 0 && this.vehicleGroups.length > 0 && !this.hasActiveFilters;
}
get emptyStateVariant() {
if (this.loadError) {
return 'error';
}
if (!this.hasSyncedDevices) {
return 'not_synced';
}
if (this.hasActiveFilters && !this.hasVisibleDevices) {
return 'filtered_empty';
}
if (this.allMapped) {
return 'empty';
}
if (this.hasOnlyUnattachedDevices) {
return 'unattached_only';
}
return null;
}
get emptyStateContent() {
switch (this.emptyStateVariant) {
case 'error':
return {
tone: 'danger',
icon: 'triangle-exclamation',
title: 'Unable to load device attachments',
message: 'Fleetbase could not load the full provider device list. Refresh the tab or review the connection logs.',
primaryText: 'Refresh',
primaryIcon: 'refresh',
primaryAction: this.refresh,
secondaryText: 'Go to Logs',
secondaryIcon: 'history',
secondaryAction: this.goToLogs,
};
case 'not_synced':
return {
tone: 'warning',
icon: 'satellite-dish',
title: 'Sync devices before mapping vehicles',
message: 'Vehicle attachment starts after provider devices are synced into this connection.',
primaryText: 'Go to Devices',
primaryIcon: 'microchip',
primaryAction: this.goToDevices,
};
case 'filtered_empty':
return {
tone: 'info',
icon: 'filter',
title: 'No mappings match these filters',
message: 'Clear the search and filters to return to all synced devices and vehicle mappings.',
primaryText: 'Clear filters',
primaryIcon: 'filter',
primaryAction: this.clearFilters,
};
case 'empty':
return {
tone: 'success',
icon: 'check',
title: 'All synced devices are mapped',
message: 'Every synced provider device in this connection is currently attached to a vehicle.',
};
case 'unattached_only':
return {
tone: 'warning',
icon: 'link-slash',
title: 'Attach synced devices to vehicles',
message: 'Use the unattached devices section below to map each device deliberately. Bulk auto-attachment is intentionally not available here.',
};
default:
return null;
}
}
get metrics() {
return [
{ label: 'Synced devices', value: this.totalSyncedDevices, icon: 'microchip', accentClass: 'fleetops-connectivity-kpi-accent-blue' },
{ label: 'Vehicles mapped', value: this.mappedVehiclesCount, icon: 'truck', accentClass: 'fleetops-connectivity-kpi-accent-blue' },
{ label: 'Attached devices', value: this.attachedDevicesCount, icon: 'link', accentClass: 'fleetops-connectivity-kpi-accent-green' },
{ label: 'Unattached devices', value: this.unattachedDevicesCount, icon: 'link-slash', accentClass: 'fleetops-connectivity-kpi-accent-amber' },
{ label: 'Online devices', value: this.onlineDevicesCount, icon: 'signal', accentClass: 'fleetops-connectivity-kpi-accent-green' },
];
}
get visibleDevicesCount() {
return this.devices.length;
}
get filteredUnattachedCount() {
return this.unattachedDevices.length;
}
get filteredAttachedCount() {
return this.attachedDevices.length;
}
get selectedDeviceName() {
return this.selectedDevice?.displayName ?? this.selectedDevice?.name ?? this.selectedDevice?.device_id;
}
updateQuery(event) {
this.query = event.target.value;
}
updateStatus(event) {
this.status = event.target.value || null;
}
updateVehicle(valueOrEvent) {
const value = valueOrEvent?.target ? valueOrEvent.target.value : valueOrEvent;
this.vehicle = value || null;
}
updateSelectedVehicle(vehicle) {
this.selectedVehicle = vehicle;
this.vehicle = vehicle?.id ?? null;
}
clearFilters() {
this.query = null;
this.status = null;
this.vehicle = null;
this.selectedVehicle = null;
}
goToDevices() {
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.telematics.details.devices', this.telematic);
}
goToLogs() {
return this.hostRouter.transitionTo('console.fleet-ops.connectivity.telematics.details.logs', this.telematic);
}
async refresh() {
this.isRefreshing = true;
try {
return await this.hostRouter.refresh();
} finally {
this.isRefreshing = false;
}
}
selectUnattachedDevice(device) {
if (this.selectedDevice === device) {
this.selectedDevice = null;
return;
}
this.selectedDevice = device;
}
clearSelectedDevice() {
this.selectedDevice = null;
}
attachSelectedDeviceToGroup(group) {
if (!this.selectedDevice) {
return;
}
return this.attachDeviceToVehicle(this.selectedDevice, group.vehicle ?? { id: group.id, name: group.name });
}
openAttachDeviceModal(device) {
this.modalsManager.show('modals/attach-telematic-device', {
title: this.intl.t('device.prompts.attach-device-to-vehicle-title', { deviceName: device.displayName ?? device.name ?? device.device_id ?? this.intl.t('resource.device') }),
acceptButtonText: this.intl.t('device.actions.attach-to-vehicle'),
device,
selectedVehicle: null,
confirm: async (modal) => {
const selectedVehicle = modal.getOption('selectedVehicle');
if (!selectedVehicle) {
return;
}
modal.startLoading();
try {
await this.attachDeviceToVehicle(device, selectedVehicle);
modal.done();
} catch (error) {
this.notifications.serverError(error);
modal.stopLoading();
}
},
});
}
async attachDeviceToVehicle(device, selectedVehicle) {
const response = await this.fetch.post(`devices/${device.id}/attach`, { vehicle: selectedVehicle.id });
this.applyDeviceAttachment(device, selectedVehicle, response?.device);
this.selectedDevice = null;
this.notifications.success(this.intl.t('device.prompts.attach-to-vehicle-success'));
}
detachDevice(device) {
const deviceName = device.displayName ?? device.name ?? device.device_id ?? this.intl.t('resource.device');
this.modalsManager.confirm({
title: this.intl.t('device.prompts.detach-telematic-device-title', { deviceName }),
body: this.intl.t('device.prompts.detach-telematic-device-body', { deviceName }),
confirm: async (modal) => {
modal.startLoading();
try {
const response = await this.fetch.post(`devices/${device.id}/detach`);
this.applyDeviceDetachment(device, response?.device);
this.notifications.success(this.intl.t('device.prompts.detach-from-vehicle-success'));
modal.done();
} catch (error) {
this.notifications.serverError(error);
modal.stopLoading();
}
},
});
}
getDeviceVehicleName(device) {
return device.attached_to_name ?? device.attachable?.display_name ?? device.attachable?.name ?? 'Unknown vehicle';
}
applyDeviceAttachment(device, selectedVehicle, serverDevice = {}) {
this.updateDevice(device, {
attachable_uuid: serverDevice.attachable_uuid ?? selectedVehicle.id,
attachable_type: serverDevice.attachable_type ?? 'fleet-ops:vehicle',
attached_to_name: serverDevice.attached_to_name ?? selectedVehicle.displayName ?? selectedVehicle.display_name ?? selectedVehicle.name,
attachable: serverDevice.attachable ?? selectedVehicle,
});
}
applyDeviceDetachment(device, serverDevice = {}) {
this.updateDevice(device, {
attachable_uuid: serverDevice.attachable_uuid ?? null,
attachable_type: serverDevice.attachable_type ?? null,
attached_to_name: serverDevice.attached_to_name ?? null,
attachable: serverDevice.attachable ?? null,
});
}
updateDevice(device, properties) {
if (typeof device.setProperties === 'function') {
device.setProperties(properties);
return;
}
Object.assign(device, properties);
}
}