179 lines
6.3 KiB
TypeScript
179 lines
6.3 KiB
TypeScript
import { BaseModel } from '#database/base_model'
|
|
import { DeviceRecord } from '../domain/device/device_record.js'
|
|
import { DeviceId } from '../domain/device/device_id.js'
|
|
import { DeviceComplianceInfo } from '../domain/configuration/compliance.js'
|
|
import { Filter, Sort } from 'mongodb'
|
|
|
|
export default class DeviceModel extends BaseModel {
|
|
static collectionName = 'devices'
|
|
|
|
static async findByIdentifier(identifier: string): Promise<DeviceRecord | null> {
|
|
return this.findOne({ identifier })
|
|
}
|
|
|
|
static async findByDeviceId(deviceId: DeviceId): Promise<DeviceRecord | null> {
|
|
return this.findOne({
|
|
'deviceId.manufacturer': deviceId.manufacturer,
|
|
'deviceId.oui': deviceId.oui,
|
|
'deviceId.productClass': deviceId.productClass,
|
|
'deviceId.serialNumber': deviceId.serialNumber,
|
|
})
|
|
}
|
|
|
|
static async paginateDevices(
|
|
offset: number,
|
|
limit: number,
|
|
filter: Filter<any> = {}
|
|
): Promise<{ data: DeviceRecord[]; total: number }> {
|
|
const collection = this.getCollection()
|
|
// Only return documents created by the ACS (must have an identifier field)
|
|
const safeFilter: Filter<any> = { ...filter, identifier: { $exists: true, $ne: null } }
|
|
const total = await collection.countDocuments(safeFilter)
|
|
const data = (await collection
|
|
.find(safeFilter, { projection: { _id: 0 } })
|
|
.skip(offset)
|
|
.limit(limit)
|
|
.sort({ updatedAt: -1 } as Sort)
|
|
.toArray()) as unknown as DeviceRecord[]
|
|
return { data, total }
|
|
}
|
|
|
|
static async createOrUpdate(device: Partial<DeviceRecord>): Promise<DeviceRecord> {
|
|
const collection = this.getCollection()
|
|
let filter: Filter<any> = {}
|
|
|
|
if (device.deviceId) {
|
|
filter = { deviceId: device.deviceId }
|
|
} else if (device.identifier) {
|
|
filter = { identifier: device.identifier }
|
|
} else {
|
|
throw new Error('Either identifier or deviceId must be provided')
|
|
}
|
|
|
|
const result = await collection.findOneAndUpdate(
|
|
filter,
|
|
{
|
|
$set: { ...device, updatedAt: new Date() },
|
|
$setOnInsert: { createdAt: new Date() },
|
|
},
|
|
{ upsert: true, returnDocument: 'after', projection: { _id: 0 } }
|
|
)
|
|
|
|
return result as unknown as DeviceRecord
|
|
}
|
|
|
|
static async updateCompliance(
|
|
identifier: string,
|
|
compliance: DeviceComplianceInfo
|
|
): Promise<void> {
|
|
await this.updateOne({ identifier }, { compliance })
|
|
}
|
|
|
|
static async updateProvisioningCode(
|
|
identifier: string,
|
|
provisioningCode: string
|
|
): Promise<void> {
|
|
await this.updateOne({ identifier }, { provisioningCode })
|
|
}
|
|
|
|
static async findByComplianceStatus(status: string): Promise<DeviceRecord[]> {
|
|
const collection = this.getCollection()
|
|
return collection
|
|
.find({ 'compliance.status': status })
|
|
.toArray() as unknown as Promise<DeviceRecord[]>
|
|
}
|
|
|
|
static async findByConfigurationIdentifier(configurationIdentifier: string): Promise<DeviceRecord[]> {
|
|
const collection = this.getCollection()
|
|
return collection
|
|
.find({ 'compliance.configurationIdentifier': configurationIdentifier })
|
|
.toArray() as unknown as Promise<DeviceRecord[]>
|
|
}
|
|
|
|
static async distinctCriteriaValues(): Promise<{
|
|
manufacturer: string[]
|
|
oui: string[]
|
|
productClass: string[]
|
|
serialNumber: string[]
|
|
modelName: string[]
|
|
hardwareVersion: string[]
|
|
softwareVersion: string[]
|
|
parameterNames: string[]
|
|
}> {
|
|
const collection = this.getCollection()
|
|
|
|
const distinctProperty = async (paths: string[]): Promise<string[]> => {
|
|
const rows = await collection
|
|
.aggregate([
|
|
{ $unwind: '$properties' },
|
|
{ $match: { 'properties.name': { $in: paths } } },
|
|
{ $group: { _id: '$properties.value' } },
|
|
])
|
|
.toArray()
|
|
return rows.map((r) => String(r._id)).filter(Boolean).sort()
|
|
}
|
|
|
|
const [manufacturer, oui, productClass, serialNumber, modelName, hardwareVersion, softwareVersion, parameterNames] =
|
|
await Promise.all([
|
|
collection.distinct('deviceId.manufacturer'),
|
|
collection.distinct('deviceId.oui'),
|
|
collection.distinct('deviceId.productClass'),
|
|
collection.distinct('deviceId.serialNumber'),
|
|
distinctProperty(['Device.DeviceInfo.ModelName', 'InternetGatewayDevice.DeviceInfo.ModelName']),
|
|
distinctProperty(['Device.DeviceInfo.HardwareVersion', 'InternetGatewayDevice.DeviceInfo.HardwareVersion']),
|
|
distinctProperty(['Device.DeviceInfo.SoftwareVersion', 'InternetGatewayDevice.DeviceInfo.SoftwareVersion']),
|
|
collection.distinct('properties.name'),
|
|
])
|
|
|
|
return {
|
|
manufacturer: (manufacturer as string[]).filter(Boolean).sort(),
|
|
oui: (oui as string[]).filter(Boolean).sort(),
|
|
productClass: (productClass as string[]).filter(Boolean).sort(),
|
|
serialNumber: (serialNumber as string[]).filter(Boolean).sort(),
|
|
modelName,
|
|
hardwareVersion,
|
|
softwareVersion,
|
|
parameterNames: (parameterNames as string[]).filter(Boolean).sort(),
|
|
}
|
|
}
|
|
|
|
static async countByComplianceStatus(
|
|
configurationIdentifier: string
|
|
): Promise<{ compliant: number; in_progress: number; failed: number; pending: number }> {
|
|
const collection = this.getCollection()
|
|
const results = await collection
|
|
.aggregate([
|
|
{ $match: { 'compliance.configurationIdentifier': configurationIdentifier } },
|
|
{ $group: { _id: '$compliance.status', count: { $sum: 1 } } },
|
|
])
|
|
.toArray()
|
|
|
|
const counts = { compliant: 0, in_progress: 0, failed: 0, pending: 0 }
|
|
for (const r of results) {
|
|
if (r._id === 'compliant') counts.compliant = r.count
|
|
else if (r._id === 'in_progress') counts.in_progress = r.count
|
|
else if (r._id === 'failed') counts.failed = r.count
|
|
else if (r._id === 'pending') counts.pending = r.count
|
|
}
|
|
return counts
|
|
}
|
|
|
|
/**
|
|
* Reset compliance status to 'pending' for all devices linked to a configuration.
|
|
* Called after a behavioral configuration update to force re-evaluation on next Inform.
|
|
*/
|
|
static async resetComplianceForConfiguration(configurationIdentifier: string): Promise<void> {
|
|
const collection = this.getCollection()
|
|
await collection.updateMany(
|
|
{ 'compliance.configurationIdentifier': configurationIdentifier },
|
|
{
|
|
$set: {
|
|
'compliance.status': 'pending',
|
|
'compliance.lastError': null,
|
|
updatedAt: new Date(),
|
|
},
|
|
}
|
|
)
|
|
}
|
|
}
|