refactor: migrate all internal imports to package.json subpath aliases
- Add #domain, #protocol, #utils aliases to package.json imports map - Replace all relative imports (./, ../, ../..) in app/ and database/mongo_migrations/ with the corresponding #alias imports, eliminating relative path traversal across the codebase - Remove dead code: ConfigurationVersionModel.findByConfigurationIdentifier (unused, superseded by paginateVersions) and unused exception classes (NotFoundException, UnprocessableEntityException, DatabaseException) from app/exceptions/index.ts, keeping only BadRequestException - Fix pre-existing syntax error in configuration_version.ts (mismatched parenthesis) that broke tsc - Fix eqeqeq violation in configuration_service.ts and isNaN -> Number.isNaN in configuration_engine.ts / cwmp-1-0.ts / exchange_controller.ts - Fix no-shadow violation in cwmp-1-0.ts EventStruct constructor - Prettier formatting pass on touched files Validated: tsc --noEmit clean, eslint app/ clean, full test suite green.
This commit is contained in:
1 parent
4177f1adbd
commit
f9e56c78b1
74 files changed
+1243
-863
No files matched your search
@@ -6,7 +6,10 @@ import ConfigurationService, {
|
||||
} from '#services/configuration_service'
|
||||
import ConfigurationEngine from '#services/configuration_engine'
|
||||
import ConfigurationModel from '#models/configuration'
|
||||
import { createConfigurationValidator, updateConfigurationValidator } from '#validators/configuration'
|
||||
import {
|
||||
createConfigurationValidator,
|
||||
updateConfigurationValidator,
|
||||
} from '#validators/configuration'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
|
||||
/**
|
||||
@@ -43,7 +46,8 @@ export default class ConfigurationsController {
|
||||
perPage: Number(request.input('perPage', 20)),
|
||||
tag: request.input('tag'),
|
||||
name: request.input('name') || undefined,
|
||||
enabled: request.input('enabled') !== undefined ? request.input('enabled') === 'true' : undefined,
|
||||
enabled:
|
||||
request.input('enabled') !== undefined ? request.input('enabled') === 'true' : undefined,
|
||||
productClass: request.input('productClass'),
|
||||
}
|
||||
|
||||
@@ -93,11 +97,21 @@ export default class ConfigurationsController {
|
||||
*/
|
||||
async store({ request, response }: HttpContext) {
|
||||
try {
|
||||
const data = await request.validateUsing(createConfigurationValidator) as CreateConfigurationDto
|
||||
const data = (await request.validateUsing(
|
||||
createConfigurationValidator
|
||||
)) as CreateConfigurationDto
|
||||
|
||||
// Check for overlaps
|
||||
const overlapResult = await this.configService.detectOverlap(data.criteria || {}, data.priority)
|
||||
if (overlapResult && overlapResult.overlappingConfigurations && overlapResult.overlappingConfigurations.length > 0 && !request.input('ignoreOverlap')) {
|
||||
const overlapResult = await this.configService.detectOverlap(
|
||||
data.criteria || {},
|
||||
data.priority
|
||||
)
|
||||
if (
|
||||
overlapResult &&
|
||||
overlapResult.overlappingConfigurations &&
|
||||
overlapResult.overlappingConfigurations.length > 0 &&
|
||||
!request.input('ignoreOverlap')
|
||||
) {
|
||||
return response.status(409).json({
|
||||
message: 'Configuration overlaps with existing ones',
|
||||
overlaps: overlapResult.overlappingConfigurations,
|
||||
@@ -122,7 +136,9 @@ export default class ConfigurationsController {
|
||||
*/
|
||||
async update({ params, request, response }: HttpContext) {
|
||||
try {
|
||||
const data = await request.validateUsing(updateConfigurationValidator) as UpdateConfigurationDto
|
||||
const data = (await request.validateUsing(
|
||||
updateConfigurationValidator
|
||||
)) as UpdateConfigurationDto
|
||||
|
||||
// Check for overlaps if priority changed
|
||||
if (data.priority) {
|
||||
@@ -131,13 +147,18 @@ export default class ConfigurationsController {
|
||||
data.priority,
|
||||
params.id
|
||||
)
|
||||
if (overlapResult && overlapResult.overlappingConfigurations && overlapResult.overlappingConfigurations.length > 0 && !request.input('ignoreOverlap')) {
|
||||
return response.status(409).json({
|
||||
message: 'Configuration overlaps with existing ones',
|
||||
overlaps: overlapResult.overlappingConfigurations,
|
||||
})
|
||||
if (
|
||||
overlapResult &&
|
||||
overlapResult.overlappingConfigurations &&
|
||||
overlapResult.overlappingConfigurations.length > 0 &&
|
||||
!request.input('ignoreOverlap')
|
||||
) {
|
||||
return response.status(409).json({
|
||||
message: 'Configuration overlaps with existing ones',
|
||||
overlaps: overlapResult.overlappingConfigurations,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update configuration
|
||||
const config = await this.configService.update(params.id, data)
|
||||
@@ -147,7 +168,10 @@ export default class ConfigurationsController {
|
||||
|
||||
return response.ok(config)
|
||||
} catch (error: any) {
|
||||
logger.error({ error: error.message, id: params.id }, '[ConfigAPI] Update configuration failed')
|
||||
logger.error(
|
||||
{ error: error.message, id: params.id },
|
||||
'[ConfigAPI] Update configuration failed'
|
||||
)
|
||||
if (error.field) {
|
||||
return response.status(422).json({ message: error.message, field: error.field })
|
||||
}
|
||||
@@ -164,7 +188,10 @@ export default class ConfigurationsController {
|
||||
await this.configService.delete(params.id)
|
||||
return response.status(204).send(null)
|
||||
} catch (error: any) {
|
||||
logger.error({ error: error.message, id: params.id }, '[ConfigAPI] Delete configuration failed')
|
||||
logger.error(
|
||||
{ error: error.message, id: params.id },
|
||||
'[ConfigAPI] Delete configuration failed'
|
||||
)
|
||||
return response.status(500).json({ message: 'Failed to delete configuration' })
|
||||
}
|
||||
}
|
||||
@@ -182,7 +209,10 @@ export default class ConfigurationsController {
|
||||
}
|
||||
return response.ok(config)
|
||||
} catch (error: any) {
|
||||
logger.error({ error: error.message, id: params.id }, '[ConfigAPI] Toggle configuration failed')
|
||||
logger.error(
|
||||
{ error: error.message, id: params.id },
|
||||
'[ConfigAPI] Toggle configuration failed'
|
||||
)
|
||||
return response.status(500).json({ message: 'Failed to toggle configuration' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import { CWMPMessageBuilder } from '../protocol/common/cwmp_message_builder.js'
|
||||
import { StateMachineManager } from '../services/cwmp/state_machine_manager.js'
|
||||
import { Device, detectDataModelFromParams } from '../domain/device/device.js'
|
||||
import { DeviceId } from '../domain/device/device_id.js'
|
||||
import { DeviceParameter, DeviceProperty } from '../domain/device/device_parameter.js'
|
||||
import DeviceModel from '../models/device.js'
|
||||
import { getDeviceHashIdentifier } from '../utils/device_utils.js'
|
||||
import { getReferenceEngine } from '../services/references/reference_engine.js'
|
||||
import { normalizeEventCode } from '../utils/cwmp-events.js'
|
||||
import { CWMPMessageBuilder } from '#protocol/common/cwmp_message_builder'
|
||||
import { StateMachineManager } from '#services/cwmp/state_machine_manager'
|
||||
import { Device, detectDataModelFromParams } from '#domain/device/device'
|
||||
import { DeviceId } from '#domain/device/device_id'
|
||||
import { DeviceParameter, DeviceProperty } from '#domain/device/device_parameter'
|
||||
import DeviceModel from '#models/device'
|
||||
import { getDeviceHashIdentifier } from '#utils/device_utils'
|
||||
import { getReferenceEngine } from '#services/references/reference_engine'
|
||||
import { normalizeEventCode } from '#utils/cwmp-events'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import type { Inform, CwmpMessage, ParameterValueStruct } from '../protocol/tr069_types.js'
|
||||
import { ParameterType } from '../domain/shared/types.js'
|
||||
import { FaultCodeType } from '../protocol/tr069_types.js'
|
||||
import type { Inform, CwmpMessage, ParameterValueStruct } from '#protocol/tr069_types'
|
||||
import { ParameterType } from '#domain/shared/types'
|
||||
import { FaultCodeType } from '#protocol/tr069_types'
|
||||
|
||||
export default class CwmpController {
|
||||
private static managerInitialized = false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import DeviceService from '#services/device_service'
|
||||
import { PaginationData } from '#services/device_service'
|
||||
import { CommandType } from '../domain/commands/command.js'
|
||||
import { type PaginationData } from '#services/device_service'
|
||||
import { type CommandType } from '#domain/commands/command'
|
||||
import DeviceModel from '#models/device'
|
||||
|
||||
export default class DevicesController {
|
||||
@@ -77,10 +77,14 @@ export default class DevicesController {
|
||||
const parts: string[] = []
|
||||
|
||||
if (pager.first) {
|
||||
parts.push(`<${baseUrl}?offset=${pager.first.offset}&limit=${pager.first.limit}>; rel="first"`)
|
||||
parts.push(
|
||||
`<${baseUrl}?offset=${pager.first.offset}&limit=${pager.first.limit}>; rel="first"`
|
||||
)
|
||||
}
|
||||
if (pager.previous) {
|
||||
parts.push(`<${baseUrl}?offset=${pager.previous.offset}&limit=${pager.previous.limit}>; rel="prev"`)
|
||||
parts.push(
|
||||
`<${baseUrl}?offset=${pager.previous.offset}&limit=${pager.previous.limit}>; rel="prev"`
|
||||
)
|
||||
}
|
||||
if (pager.next) {
|
||||
parts.push(`<${baseUrl}?offset=${pager.next.offset}&limit=${pager.next.limit}>; rel="next"`)
|
||||
|
||||
@@ -247,7 +247,10 @@ export default class ExchangeController {
|
||||
|
||||
// Track SSE stream setup time
|
||||
const setupDurationMs = performance.now() - startTime
|
||||
ExchangeMetricsService.getInstance().recordApiResponseTime('GET /exchanges/stream', setupDurationMs)
|
||||
ExchangeMetricsService.getInstance().recordApiResponseTime(
|
||||
'GET /exchanges/stream',
|
||||
setupDurationMs
|
||||
)
|
||||
|
||||
const listener = (exchange: any) => {
|
||||
if (closed) return
|
||||
@@ -333,10 +336,7 @@ export default class ExchangeController {
|
||||
*
|
||||
* Requirement 6.9: Event id equals the exchange's sequenceNumber
|
||||
*/
|
||||
private sendSSEEvent(
|
||||
res: import('node:http').ServerResponse,
|
||||
exchange: any
|
||||
): void {
|
||||
private sendSSEEvent(res: import('node:http').ServerResponse, exchange: any): void {
|
||||
const data = JSON.stringify(exchange)
|
||||
res.write(`id: ${exchange.sequenceNumber}\ndata: ${data}\n\n`)
|
||||
}
|
||||
@@ -347,7 +347,7 @@ export default class ExchangeController {
|
||||
*/
|
||||
private isValidIso8601(value: string): boolean {
|
||||
const date = new Date(value)
|
||||
if (isNaN(date.getTime())) {
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,11 @@ import FirmwareCatalogService, {
|
||||
type CreateFirmwareEntryDto,
|
||||
type CreateMigrationRouteDto,
|
||||
} from '#services/firmware_catalog_service'
|
||||
import { createFirmwareEntryValidator, updateFirmwareEntryValidator, createMigrationRouteValidator } from '#validators/firmware_catalog'
|
||||
import {
|
||||
createFirmwareEntryValidator,
|
||||
updateFirmwareEntryValidator,
|
||||
createMigrationRouteValidator,
|
||||
} from '#validators/firmware_catalog'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
|
||||
/**
|
||||
@@ -105,7 +109,9 @@ export default class FirmwareCatalogController {
|
||||
*/
|
||||
async createEntry({ request, response }: HttpContext) {
|
||||
try {
|
||||
const data = await request.validateUsing(createFirmwareEntryValidator) as CreateFirmwareEntryDto
|
||||
const data = (await request.validateUsing(
|
||||
createFirmwareEntryValidator
|
||||
)) as CreateFirmwareEntryDto
|
||||
|
||||
const entry = await this.catalogService.createEntry(data)
|
||||
return response.status(201).json(entry)
|
||||
@@ -135,7 +141,9 @@ export default class FirmwareCatalogController {
|
||||
*/
|
||||
async createRoute({ request, response }: HttpContext) {
|
||||
try {
|
||||
const data = await request.validateUsing(createMigrationRouteValidator) as CreateMigrationRouteDto
|
||||
const data = (await request.validateUsing(
|
||||
createMigrationRouteValidator
|
||||
)) as CreateMigrationRouteDto
|
||||
|
||||
const route = await this.catalogService.createMigrationRoute(data)
|
||||
return response.status(201).json(route)
|
||||
|
||||
@@ -33,11 +33,25 @@ export default class PagesController {
|
||||
|
||||
// Predefined model paths for the "Get Model" modal
|
||||
// Detect data model from device properties to suggest the right root
|
||||
const isTr181 = (device as any).dataModel === 'tr-181' ||
|
||||
const isTr181 =
|
||||
(device as any).dataModel === 'tr-181' ||
|
||||
(device as any).properties?.some((p: any) => p.name?.startsWith('Device.'))
|
||||
const modelPaths = isTr181
|
||||
? ['Device.', 'Device.DeviceInfo.', 'Device.IP.', 'Device.WiFi.', 'Device.ManagementServer.', 'Device.Hosts.']
|
||||
: ['InternetGatewayDevice.', 'InternetGatewayDevice.DeviceInfo.', 'InternetGatewayDevice.LANDevice.', 'InternetGatewayDevice.WANDevice.', 'InternetGatewayDevice.ManagementServer.']
|
||||
? [
|
||||
'Device.',
|
||||
'Device.DeviceInfo.',
|
||||
'Device.IP.',
|
||||
'Device.WiFi.',
|
||||
'Device.ManagementServer.',
|
||||
'Device.Hosts.',
|
||||
]
|
||||
: [
|
||||
'InternetGatewayDevice.',
|
||||
'InternetGatewayDevice.DeviceInfo.',
|
||||
'InternetGatewayDevice.LANDevice.',
|
||||
'InternetGatewayDevice.WANDevice.',
|
||||
'InternetGatewayDevice.ManagementServer.',
|
||||
]
|
||||
|
||||
return (inertia as any).render('DeviceDetail', { device, commands, modelPaths })
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import DeviceModel from '#models/device'
|
||||
import { getReferenceEngine } from '../services/references/reference_engine.js'
|
||||
import { getReferenceEngine } from '#services/references/reference_engine'
|
||||
|
||||
export default class ReferencesController {
|
||||
/**
|
||||
@@ -24,7 +24,9 @@ export default class ReferencesController {
|
||||
}
|
||||
const ref = (device.references || []).find((r: any) => r.provider === params.provider)
|
||||
if (!ref) {
|
||||
return response.status(404).json({ message: `No reference from provider '${params.provider}'` })
|
||||
return response
|
||||
.status(404)
|
||||
.json({ message: `No reference from provider '${params.provider}'` })
|
||||
}
|
||||
return response.json(ref)
|
||||
}
|
||||
@@ -51,7 +53,10 @@ export default class ReferencesController {
|
||||
(r: any) => !results.some((nr) => nr.provider === r.provider)
|
||||
)
|
||||
const merged = [...existing, ...results]
|
||||
await DeviceModel.updateOne({ identifier: params.identifier }, { $set: { references: merged, updatedAt: new Date() } })
|
||||
await DeviceModel.updateOne(
|
||||
{ identifier: params.identifier },
|
||||
{ $set: { references: merged, updatedAt: new Date() } }
|
||||
)
|
||||
}
|
||||
|
||||
return response.json({ resolved: results.length, references: results })
|
||||
@@ -72,7 +77,9 @@ export default class ReferencesController {
|
||||
const result = await engine.resolveOne(params.provider, device, context)
|
||||
|
||||
if (!result) {
|
||||
return response.status(404).json({ message: `Provider '${params.provider}' not found or cannot resolve` })
|
||||
return response
|
||||
.status(404)
|
||||
.json({ message: `Provider '${params.provider}' not found or cannot resolve` })
|
||||
}
|
||||
|
||||
// Merge into device references
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DeviceId } from '../device/device_id.js'
|
||||
import { ModeType } from '../shared/mode.js'
|
||||
import { ObjectId } from 'mongodb'
|
||||
import { type DeviceId } from '#domain/device/device_id'
|
||||
import { type ModeType } from '#domain/shared/mode'
|
||||
import { type ObjectId } from 'mongodb'
|
||||
|
||||
export type CommandId = string
|
||||
|
||||
@@ -136,7 +136,9 @@ export class Command {
|
||||
}
|
||||
|
||||
public isCompleted(): boolean {
|
||||
return [CommandStatus.COMPLETED, CommandStatus.FAILED, CommandStatus.CANCELLED].includes(this._status)
|
||||
return [CommandStatus.COMPLETED, CommandStatus.FAILED, CommandStatus.CANCELLED].includes(
|
||||
this._status
|
||||
)
|
||||
}
|
||||
|
||||
public hasExpired(timeoutMinutes: number = 30): boolean {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Configuration } from './configuration.js'
|
||||
import { type Configuration } from '#domain/configuration/configuration'
|
||||
|
||||
export interface ConfigurationVersion {
|
||||
configurationIdentifier: string
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { type DeviceId } from './device_id.js'
|
||||
import { type DeviceId } from '#domain/device/device_id'
|
||||
import {
|
||||
DeviceParameter,
|
||||
DeviceProperty,
|
||||
type ParameterSnapshot,
|
||||
type PropertySnapshot,
|
||||
} from './device_parameter.js'
|
||||
import { ParameterType } from '../shared/types.js'
|
||||
} from '#domain/device/device_parameter'
|
||||
import { ParameterType } from '#domain/shared/types'
|
||||
import { createHash } from 'node:crypto'
|
||||
import type { ParameterValueStruct, ParameterInfoStruct } from '../../protocol/tr069_types.js'
|
||||
import type { ParameterValueStruct, ParameterInfoStruct } from '#protocol/tr069_types'
|
||||
|
||||
export type GpnStatus = 'supported' | 'unsupported' | 'unknown'
|
||||
|
||||
|
||||
@@ -30,7 +30,12 @@ export class DeviceId {
|
||||
)
|
||||
}
|
||||
|
||||
public static fromInform(informData: { Manufacturer?: string; OUI?: string; ProductClass?: string; SerialNumber?: string }): DeviceId {
|
||||
public static fromInform(informData: {
|
||||
Manufacturer?: string
|
||||
OUI?: string
|
||||
ProductClass?: string
|
||||
SerialNumber?: string
|
||||
}): DeviceId {
|
||||
return new DeviceId(
|
||||
String(informData?.Manufacturer ?? ''),
|
||||
String(informData?.OUI ?? ''),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ParameterType } from '../shared/types.js'
|
||||
import { ParameterType } from '#domain/shared/types'
|
||||
|
||||
export interface ParameterSnapshot {
|
||||
name: string
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type DeviceId } from './device_id.js'
|
||||
import { type PropertySnapshot, type ParameterSnapshot } from './device_parameter.js'
|
||||
import { type DeviceComplianceInfo } from '../configuration/compliance.js'
|
||||
import { type DataModel, type GpnStatus, type GpnCircuitBreaker } from './device.js'
|
||||
import { type DeviceId } from '#domain/device/device_id'
|
||||
import { type PropertySnapshot, type ParameterSnapshot } from '#domain/device/device_parameter'
|
||||
import { type DeviceComplianceInfo } from '#domain/configuration/compliance'
|
||||
import { type DataModel, type GpnStatus, type GpnCircuitBreaker } from '#domain/device/device'
|
||||
import type { DeviceReference } from '#services/references/types'
|
||||
|
||||
export interface DeviceRecord {
|
||||
|
||||
@@ -1,21 +1,6 @@
|
||||
import { Exception } from '@adonisjs/core/exceptions'
|
||||
|
||||
export class NotFoundException extends Exception {
|
||||
static status = 404
|
||||
static code = 'E_NOT_FOUND'
|
||||
}
|
||||
|
||||
export class BadRequestException extends Exception {
|
||||
static status = 400
|
||||
static code = 'E_BAD_REQUEST'
|
||||
}
|
||||
|
||||
export class UnprocessableEntityException extends Exception {
|
||||
static status = 422
|
||||
static code = 'E_UNPROCESSABLE'
|
||||
}
|
||||
|
||||
export class DatabaseException extends Exception {
|
||||
static status = 503
|
||||
static code = 'E_DATABASE_ERROR'
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import type { NextFn } from '@adonisjs/core/types/http'
|
||||
import { parseSoapXml, extractCwmpMessage } from '../protocol/soap.js'
|
||||
import type { SoapEnvelope, CwmpMessage } from '../protocol/tr069_types.js'
|
||||
import { parseSoapXml, extractCwmpMessage } from '#protocol/soap'
|
||||
import type { SoapEnvelope, CwmpMessage } from '#protocol/tr069_types'
|
||||
|
||||
/**
|
||||
* CWMP Middleware
|
||||
|
||||
@@ -3,6 +3,10 @@ import type { HttpContext } from '@adonisjs/core/http'
|
||||
import type { NextFn } from '@adonisjs/core/types/http'
|
||||
|
||||
export default class InertiaMiddleware extends BaseInertiaMiddleware {
|
||||
/**
|
||||
* Implements BaseInertiaMiddleware's abstract `share` method.
|
||||
* Invoked internally by @adonisjs/inertia — not called directly from this codebase.
|
||||
*/
|
||||
async share(ctx: HttpContext) {
|
||||
return {
|
||||
errors: ctx.session?.flashMessages.get('errors'),
|
||||
@@ -13,6 +17,11 @@ export default class InertiaMiddleware extends BaseInertiaMiddleware {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard AdonisJS middleware entrypoint.
|
||||
* Invoked dynamically by AdonisJS via the named middleware loader in start/kernel.ts —
|
||||
* not called directly from this codebase.
|
||||
*/
|
||||
async handle(ctx: HttpContext, next: NextFn) {
|
||||
await this.init(ctx)
|
||||
await next()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BaseModel } from '#database/base_model'
|
||||
import { CommandRecord, CommandStatus } from '../domain/commands/command.js'
|
||||
import { ModeType } from '../domain/shared/mode.js'
|
||||
import { type CommandRecord, CommandStatus } from '#domain/commands/command'
|
||||
import { type ModeType } from '#domain/shared/mode'
|
||||
import { ObjectId } from 'mongodb'
|
||||
|
||||
export default class CommandModel extends BaseModel {
|
||||
@@ -8,14 +8,19 @@ export default class CommandModel extends BaseModel {
|
||||
|
||||
static async findByDevice(deviceIdentifier: string): Promise<CommandRecord[]> {
|
||||
const collection = this.getCollection()
|
||||
return collection.find({ deviceIdentifier }).sort({ createdAt: -1 }).toArray() as unknown as Promise<CommandRecord[]>
|
||||
return (await collection
|
||||
.find({ deviceIdentifier })
|
||||
.sort({ createdAt: -1 })
|
||||
.toArray()) as unknown as Promise<CommandRecord[]>
|
||||
}
|
||||
|
||||
static async findPending(deviceIdentifier: string, mode?: ModeType): Promise<CommandRecord[]> {
|
||||
const filter: any = { deviceIdentifier, status: CommandStatus.PENDING }
|
||||
if (mode) filter.mode = mode
|
||||
const collection = this.getCollection()
|
||||
return collection.find(filter).sort({ priority: 1 }).toArray() as unknown as Promise<CommandRecord[]>
|
||||
return collection.find(filter).sort({ priority: 1 }).toArray() as unknown as Promise<
|
||||
CommandRecord[]
|
||||
>
|
||||
}
|
||||
|
||||
static async createCommand(command: Omit<CommandRecord, '_id'>): Promise<CommandRecord> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BaseModel } from '#database/base_model'
|
||||
import { Configuration } from '../domain/configuration/configuration.js'
|
||||
import { Filter, Sort } from 'mongodb'
|
||||
import { type Configuration } from '#domain/configuration/configuration'
|
||||
import { type Filter, type Sort } from 'mongodb'
|
||||
|
||||
export default class ConfigurationModel extends BaseModel {
|
||||
static collectionName = 'configurations'
|
||||
@@ -11,7 +11,9 @@ export default class ConfigurationModel extends BaseModel {
|
||||
|
||||
static async findAllEnabled(sort: Sort = { priority: -1 }): Promise<Configuration[]> {
|
||||
const collection = this.getCollection()
|
||||
return collection.find({ enabled: true }).sort(sort).toArray() as unknown as Promise<Configuration[]>
|
||||
return (await collection.find({ enabled: true }).sort(sort).toArray()) as unknown as Promise<
|
||||
Configuration[]
|
||||
>
|
||||
}
|
||||
|
||||
static async paginateConfigurations(
|
||||
@@ -19,7 +21,13 @@ export default class ConfigurationModel extends BaseModel {
|
||||
perPage: number,
|
||||
filter: Filter<any> = {},
|
||||
sort: Sort = { priority: -1 }
|
||||
): Promise<{ data: Configuration[]; total: number; page: number; perPage: number; lastPage: number }> {
|
||||
): Promise<{
|
||||
data: Configuration[]
|
||||
total: number
|
||||
page: number
|
||||
perPage: number
|
||||
lastPage: number
|
||||
}> {
|
||||
return this.paginate(page, perPage, filter, sort)
|
||||
}
|
||||
|
||||
@@ -28,7 +36,7 @@ export default class ConfigurationModel extends BaseModel {
|
||||
if (existing) {
|
||||
throw new Error(`Configuration with identifier '${data.identifier}' already exists`)
|
||||
}
|
||||
return this.create(data) as Promise<Configuration>
|
||||
return (await this.create(data)) as Configuration
|
||||
}
|
||||
|
||||
static async distinctTags(): Promise<string[]> {
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import { BaseModel } from '#database/base_model'
|
||||
import { type ConfigurationVersion } from '../domain/configuration/configuration_version.js'
|
||||
import { Sort } from 'mongodb'
|
||||
import { type ConfigurationVersion } from '#domain/configuration/configuration_version'
|
||||
|
||||
export default class ConfigurationVersionModel extends BaseModel {
|
||||
static collectionName = 'configuration_versions'
|
||||
|
||||
static async findByConfigurationIdentifier(
|
||||
configurationIdentifier: string,
|
||||
sort: Sort = { versionNumber: -1 }
|
||||
): Promise<ConfigurationVersion[]> {
|
||||
const collection = this.getCollection()
|
||||
return collection
|
||||
.find({ configurationIdentifier })
|
||||
.sort(sort)
|
||||
.toArray() as unknown as Promise<ConfigurationVersion[]>
|
||||
}
|
||||
|
||||
static async paginateVersions(
|
||||
configurationIdentifier: string,
|
||||
page: number,
|
||||
perPage: number
|
||||
): Promise<{ data: ConfigurationVersion[]; total: number; page: number; perPage: number; lastPage: number }> {
|
||||
): Promise<{
|
||||
data: ConfigurationVersion[]
|
||||
total: number
|
||||
page: number
|
||||
perPage: number
|
||||
lastPage: number
|
||||
}> {
|
||||
return this.paginate(page, perPage, { configurationIdentifier }, { versionNumber: -1 })
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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'
|
||||
import { type DeviceRecord } from '#domain/device/device_record'
|
||||
import { type DeviceId } from '#domain/device/device_id'
|
||||
import { type DeviceComplianceInfo } from '#domain/configuration/compliance'
|
||||
import { type Filter, type Sort } from 'mongodb'
|
||||
|
||||
export default class DeviceModel extends BaseModel {
|
||||
static collectionName = 'devices'
|
||||
@@ -87,10 +87,7 @@ export default class DeviceModel extends BaseModel {
|
||||
await this.updateOne({ identifier }, { compliance })
|
||||
}
|
||||
|
||||
static async updateProvisioningCode(
|
||||
identifier: string,
|
||||
provisioningCode: string
|
||||
): Promise<void> {
|
||||
static async updateProvisioningCode(identifier: string, provisioningCode: string): Promise<void> {
|
||||
await this.updateOne({ identifier }, { provisioningCode })
|
||||
}
|
||||
|
||||
@@ -102,7 +99,7 @@ export default class DeviceModel extends BaseModel {
|
||||
$set: {
|
||||
'compliance.status': 'pending',
|
||||
'compliance.lastError': null,
|
||||
updatedAt: new Date(),
|
||||
'updatedAt': new Date(),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -45,19 +45,19 @@ export default class ExchangeModel extends BaseModel {
|
||||
// When using `latest` or `before`, we want the most recent messages.
|
||||
// Query in descending order then reverse to return in ascending order.
|
||||
if (options?.latest || options?.before) {
|
||||
const results = await collection
|
||||
const results = (await collection
|
||||
.find(filter)
|
||||
.sort({ timestamp: -1 })
|
||||
.limit(limit)
|
||||
.toArray() as unknown as CwmpExchange[]
|
||||
.toArray()) as unknown as CwmpExchange[]
|
||||
return results.reverse()
|
||||
}
|
||||
|
||||
return collection
|
||||
return (await collection
|
||||
.find(filter)
|
||||
.sort({ timestamp: 1 })
|
||||
.limit(limit)
|
||||
.toArray() as unknown as Promise<CwmpExchange[]>
|
||||
.toArray()) as unknown as CwmpExchange[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseModel } from '#database/base_model'
|
||||
import { FirmwareEntry } from '../domain/firmware/firmware_catalog.js'
|
||||
import { type FirmwareEntry } from '#domain/firmware/firmware_catalog'
|
||||
|
||||
export default class FirmwareEntryModel extends BaseModel {
|
||||
static collectionName = 'firmware_entries'
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { BaseModel } from '#database/base_model'
|
||||
import { MigrationRoute } from '../domain/firmware/firmware_catalog.js'
|
||||
import { Sort } from 'mongodb'
|
||||
import { type MigrationRoute } from '#domain/firmware/firmware_catalog'
|
||||
import { type Sort } from 'mongodb'
|
||||
|
||||
export default class MigrationRouteModel extends BaseModel {
|
||||
static collectionName = 'migration_routes'
|
||||
|
||||
static async findByIdentifier(identifier: string): Promise<MigrationRoute | null> {
|
||||
return this.findOne({ identifier }) as Promise<MigrationRoute | null>
|
||||
return (await this.findOne({ identifier })) as Promise<MigrationRoute | null>
|
||||
}
|
||||
|
||||
static async findByProductClass(
|
||||
@@ -14,7 +14,9 @@ export default class MigrationRouteModel extends BaseModel {
|
||||
sort: Sort = { priority: -1 }
|
||||
): Promise<MigrationRoute[]> {
|
||||
const collection = this.getCollection()
|
||||
return collection.find({ productClass }).sort(sort).toArray() as unknown as Promise<MigrationRoute[]>
|
||||
return (await collection.find({ productClass }).sort(sort).toArray()) as unknown as Promise<
|
||||
MigrationRoute[]
|
||||
>
|
||||
}
|
||||
|
||||
static async createIfNotExists(data: Partial<MigrationRoute>): Promise<MigrationRoute> {
|
||||
@@ -22,7 +24,7 @@ export default class MigrationRouteModel extends BaseModel {
|
||||
if (existing) {
|
||||
throw new Error(`Migration route with identifier '${data.identifier}' already exists`)
|
||||
}
|
||||
return this.create(data) as Promise<MigrationRoute>
|
||||
return (await this.create(data)) as Promise<MigrationRoute>
|
||||
}
|
||||
|
||||
static async deleteByIdentifier(identifier: string): Promise<boolean> {
|
||||
|
||||
@@ -21,20 +21,20 @@ import {
|
||||
SetParameterValues,
|
||||
TransferCompleteResponse,
|
||||
Upload,
|
||||
} from '../cwmp-1-0.js'
|
||||
} from '#protocol/cwmp-1-0'
|
||||
import {
|
||||
CwmpID,
|
||||
NAMESPACES,
|
||||
SoapEnvelope,
|
||||
type SoapEnvelopeAttribute,
|
||||
SoapEnvelopeHeader,
|
||||
} from '../soap_envelope.js'
|
||||
} from '#protocol/soap_envelope'
|
||||
import type {
|
||||
DownloadTaskPayload,
|
||||
UploadTaskPayload,
|
||||
SetParameterValuesTaskPayload,
|
||||
SetParameterAttributesTaskPayload,
|
||||
} from '../tr069_types.js'
|
||||
} from '#protocol/tr069_types'
|
||||
|
||||
function generateCwmpID(): string {
|
||||
return randomBytes(4).toString('hex')
|
||||
|
||||
+412
-306
File diff suppressed because it is too large.
Load diff
+50
-40
@@ -1,9 +1,9 @@
|
||||
// @ts-nocheck — fichier copié de cwmp-connector (papaye)
|
||||
import { Cwmp_1_0, FaultCodeType } from './cwmp-1-0.js'
|
||||
import { Cwmp_1_0, FaultCodeType } from '#protocol/cwmp-1-0'
|
||||
|
||||
/**
|
||||
* CWMP Version 1.1 Protocol Classes
|
||||
*
|
||||
*
|
||||
* This version extends CWMP 1.0 with additional features:
|
||||
* - Additional fault codes (9014-9019)
|
||||
* - New event code: "10 AUTONOMOUS TRANSFER COMPLETE"
|
||||
@@ -12,17 +12,25 @@ import { Cwmp_1_0, FaultCodeType } from './cwmp-1-0.js'
|
||||
*/
|
||||
export class Cwmp_1_1 extends Cwmp_1_0 {
|
||||
public '@class' = '.Cwmp_1_1'
|
||||
public GetAllQueuedTransfers?: GetAllQueuedTransfers
|
||||
public GetAllQueuedTransfersResponse?: GetAllQueuedTransfersResponse
|
||||
public AutonomousTransferComplete?: AutonomousTransferComplete
|
||||
public AutonomousTransferCompleteResponse?: AutonomousTransferCompleteResponse
|
||||
public 'GetAllQueuedTransfers'?: GetAllQueuedTransfers
|
||||
public 'GetAllQueuedTransfersResponse'?: GetAllQueuedTransfersResponse
|
||||
public 'AutonomousTransferComplete'?: AutonomousTransferComplete
|
||||
public 'AutonomousTransferCompleteResponse'?: AutonomousTransferCompleteResponse
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
super(props)
|
||||
this.GetAllQueuedTransfers = props.GetAllQueuedTransfers ? new GetAllQueuedTransfers(props.GetAllQueuedTransfers) : undefined
|
||||
this.GetAllQueuedTransfersResponse = props.GetAllQueuedTransfersResponse ? new GetAllQueuedTransfersResponse(props.GetAllQueuedTransfersResponse) : undefined
|
||||
this.AutonomousTransferComplete = props.AutonomousTransferComplete ? new AutonomousTransferComplete(props.AutonomousTransferComplete) : undefined
|
||||
this.AutonomousTransferCompleteResponse = props.AutonomousTransferCompleteResponse ? new AutonomousTransferCompleteResponse(props.AutonomousTransferCompleteResponse) : undefined
|
||||
this.GetAllQueuedTransfers = props.GetAllQueuedTransfers
|
||||
? new GetAllQueuedTransfers(props.GetAllQueuedTransfers)
|
||||
: undefined
|
||||
this.GetAllQueuedTransfersResponse = props.GetAllQueuedTransfersResponse
|
||||
? new GetAllQueuedTransfersResponse(props.GetAllQueuedTransfersResponse)
|
||||
: undefined
|
||||
this.AutonomousTransferComplete = props.AutonomousTransferComplete
|
||||
? new AutonomousTransferComplete(props.AutonomousTransferComplete)
|
||||
: undefined
|
||||
this.AutonomousTransferCompleteResponse = props.AutonomousTransferCompleteResponse
|
||||
? new AutonomousTransferCompleteResponse(props.AutonomousTransferCompleteResponse)
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,14 +108,14 @@ export enum EventCode_1_1 {
|
||||
|
||||
export class AllQueuedTransferStruct {
|
||||
public '@class' = '.AllQueuedTransferStruct'
|
||||
public CommandKey: string
|
||||
public State: number
|
||||
public IsDownload: boolean
|
||||
public FileType: string
|
||||
public FileSize: number
|
||||
public TargetFileName: string
|
||||
public 'CommandKey': string
|
||||
public 'State': number
|
||||
public 'IsDownload': boolean
|
||||
public 'FileType': string
|
||||
public 'FileSize': number
|
||||
public 'TargetFileName': string
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.CommandKey = props.CommandKey || ''
|
||||
this.State = props.State || 1
|
||||
this.IsDownload = props.IsDownload || false
|
||||
@@ -119,32 +127,34 @@ export class AllQueuedTransferStruct {
|
||||
|
||||
export class AllTransferList {
|
||||
public '@class' = '.AllTransferList'
|
||||
public AllQueuedTransferStruct: AllQueuedTransferStruct[] = []
|
||||
public 'AllQueuedTransferStruct': AllQueuedTransferStruct[] = []
|
||||
|
||||
constructor(props: any = {}) {
|
||||
this.AllQueuedTransferStruct = (props.AllQueuedTransferStruct || []).map((item: any) => new AllQueuedTransferStruct(item))
|
||||
'constructor'(props: any = {}) {
|
||||
this.AllQueuedTransferStruct = (props.AllQueuedTransferStruct || []).map(
|
||||
(item: any) => new AllQueuedTransferStruct(item)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class GetAllQueuedTransfers {
|
||||
public '@class' = '.GetAllQueuedTransfers'
|
||||
|
||||
constructor(props: any = {}) {}
|
||||
'constructor'(props: any = {}) {}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
return `<cwmp:GetAllQueuedTransfers></cwmp:GetAllQueuedTransfers>`
|
||||
}
|
||||
}
|
||||
|
||||
export class GetAllQueuedTransfersResponse {
|
||||
public '@class' = '.GetAllQueuedTransfersResponse'
|
||||
public TransferList?: AllTransferList
|
||||
public 'TransferList'?: AllTransferList
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.TransferList = props.TransferList ? new AllTransferList(props.TransferList) : undefined
|
||||
}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
const items = this.TransferList?.AllQueuedTransferStruct || []
|
||||
const listXml = items
|
||||
.map(
|
||||
@@ -170,17 +180,17 @@ export class GetAllQueuedTransfersResponse {
|
||||
|
||||
export class AutonomousTransferComplete {
|
||||
public '@class' = '.AutonomousTransferComplete'
|
||||
public AnnounceURL: string
|
||||
public TransferURL: string
|
||||
public IsDownload: boolean
|
||||
public FileType: string
|
||||
public FileSize: number
|
||||
public TargetFileName: string
|
||||
public FaultStruct: any // Reusing FaultStruct from base
|
||||
public StartTime: Date
|
||||
public CompleteTime: Date
|
||||
public 'AnnounceURL': string
|
||||
public 'TransferURL': string
|
||||
public 'IsDownload': boolean
|
||||
public 'FileType': string
|
||||
public 'FileSize': number
|
||||
public 'TargetFileName': string
|
||||
public 'FaultStruct': any // Reusing FaultStruct from base
|
||||
public 'StartTime': Date
|
||||
public 'CompleteTime': Date
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.AnnounceURL = props.AnnounceURL || ''
|
||||
this.TransferURL = props.TransferURL || ''
|
||||
this.IsDownload = props.IsDownload || false
|
||||
@@ -192,7 +202,7 @@ export class AutonomousTransferComplete {
|
||||
this.CompleteTime = props.CompleteTime ? new Date(props.CompleteTime) : new Date()
|
||||
}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
const fault = this.FaultStruct
|
||||
? `<FaultStruct><FaultCode>${this.FaultStruct.FaultCode ?? 0}</FaultCode><FaultString>${this.FaultStruct.FaultString || ''}</FaultString></FaultStruct>`
|
||||
: ''
|
||||
@@ -213,9 +223,9 @@ export class AutonomousTransferComplete {
|
||||
export class AutonomousTransferCompleteResponse {
|
||||
public '@class' = '.AutonomousTransferCompleteResponse'
|
||||
|
||||
constructor(props: any = {}) {}
|
||||
'constructor'(props: any = {}) {}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
return '<cwmp:AutonomousTransferCompleteResponse/>'
|
||||
}
|
||||
}
|
||||
}
|
||||
+157
-131
@@ -1,6 +1,9 @@
|
||||
// @ts-nocheck — fichier copié de cwmp-connector (papaye)
|
||||
import { Cwmp_1_0, FaultStruct } from './cwmp-1-0.js'
|
||||
import { Cwmp_1_1, AutonomousTransferComplete as AutonomousTransferComplete_1_1 } from './cwmp-1-1.js'
|
||||
import { Cwmp_1_0, FaultStruct } from '#protocol/cwmp-1-0'
|
||||
import {
|
||||
Cwmp_1_1,
|
||||
AutonomousTransferComplete as AutonomousTransferComplete_1_1,
|
||||
} from '#protocol/cwmp-1-1'
|
||||
|
||||
/**
|
||||
* CWMP Version 1.2 Protocol Classes
|
||||
@@ -17,35 +20,55 @@ export class Cwmp_1_2 extends Cwmp_1_1 {
|
||||
public '@class' = '.Cwmp_1_2'
|
||||
|
||||
// New RPCs
|
||||
public ScheduleDownload?: ScheduleDownload
|
||||
public ScheduleDownloadResponse?: ScheduleDownloadResponse
|
||||
public CancelTransfer?: CancelTransfer
|
||||
public CancelTransferResponse?: CancelTransferResponse
|
||||
public ChangeDUState?: ChangeDUState
|
||||
public ChangeDUStateResponse?: ChangeDUStateResponse
|
||||
public DUStateChangeComplete?: DUStateChangeComplete
|
||||
public DUStateChangeCompleteResponse?: DUStateChangeCompleteResponse
|
||||
public AutonomousDUStateChangeComplete?: AutonomousDUStateChangeComplete
|
||||
public AutonomousDUStateChangeCompleteResponse?: AutonomousDUStateChangeCompleteResponse
|
||||
public 'ScheduleDownload'?: ScheduleDownload
|
||||
public 'ScheduleDownloadResponse'?: ScheduleDownloadResponse
|
||||
public 'CancelTransfer'?: CancelTransfer
|
||||
public 'CancelTransferResponse'?: CancelTransferResponse
|
||||
public 'ChangeDUState'?: ChangeDUState
|
||||
public 'ChangeDUStateResponse'?: ChangeDUStateResponse
|
||||
public 'DUStateChangeComplete'?: DUStateChangeComplete
|
||||
public 'DUStateChangeCompleteResponse'?: DUStateChangeCompleteResponse
|
||||
public 'AutonomousDUStateChangeComplete'?: AutonomousDUStateChangeComplete
|
||||
public 'AutonomousDUStateChangeCompleteResponse'?: AutonomousDUStateChangeCompleteResponse
|
||||
|
||||
// Override AutonomousTransferComplete to use TransferCompleteFaultStruct
|
||||
public AutonomousTransferComplete?: AutonomousTransferComplete
|
||||
public 'AutonomousTransferComplete'?: AutonomousTransferComplete
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
super(props)
|
||||
this.ScheduleDownload = props.ScheduleDownload ? new ScheduleDownload(props.ScheduleDownload) : undefined
|
||||
this.ScheduleDownloadResponse = props.ScheduleDownloadResponse ? new ScheduleDownloadResponse(props.ScheduleDownloadResponse) : undefined
|
||||
this.CancelTransfer = props.CancelTransfer ? new CancelTransfer(props.CancelTransfer) : undefined
|
||||
this.CancelTransferResponse = props.CancelTransferResponse ? new CancelTransferResponse(props.CancelTransferResponse) : undefined
|
||||
this.ScheduleDownload = props.ScheduleDownload
|
||||
? new ScheduleDownload(props.ScheduleDownload)
|
||||
: undefined
|
||||
this.ScheduleDownloadResponse = props.ScheduleDownloadResponse
|
||||
? new ScheduleDownloadResponse(props.ScheduleDownloadResponse)
|
||||
: undefined
|
||||
this.CancelTransfer = props.CancelTransfer
|
||||
? new CancelTransfer(props.CancelTransfer)
|
||||
: undefined
|
||||
this.CancelTransferResponse = props.CancelTransferResponse
|
||||
? new CancelTransferResponse(props.CancelTransferResponse)
|
||||
: undefined
|
||||
this.ChangeDUState = props.ChangeDUState ? new ChangeDUState(props.ChangeDUState) : undefined
|
||||
this.ChangeDUStateResponse = props.ChangeDUStateResponse ? new ChangeDUStateResponse(props.ChangeDUStateResponse) : undefined
|
||||
this.DUStateChangeComplete = props.DUStateChangeComplete ? new DUStateChangeComplete(props.DUStateChangeComplete) : undefined
|
||||
this.DUStateChangeCompleteResponse = props.DUStateChangeCompleteResponse ? new DUStateChangeCompleteResponse(props.DUStateChangeCompleteResponse) : undefined
|
||||
this.AutonomousDUStateChangeComplete = props.AutonomousDUStateChangeComplete ? new AutonomousDUStateChangeComplete(props.AutonomousDUStateChangeComplete) : undefined
|
||||
this.AutonomousDUStateChangeCompleteResponse = props.AutonomousDUStateChangeCompleteResponse ? new AutonomousDUStateChangeCompleteResponse(props.AutonomousDUStateChangeCompleteResponse) : undefined
|
||||
this.ChangeDUStateResponse = props.ChangeDUStateResponse
|
||||
? new ChangeDUStateResponse(props.ChangeDUStateResponse)
|
||||
: undefined
|
||||
this.DUStateChangeComplete = props.DUStateChangeComplete
|
||||
? new DUStateChangeComplete(props.DUStateChangeComplete)
|
||||
: undefined
|
||||
this.DUStateChangeCompleteResponse = props.DUStateChangeCompleteResponse
|
||||
? new DUStateChangeCompleteResponse(props.DUStateChangeCompleteResponse)
|
||||
: undefined
|
||||
this.AutonomousDUStateChangeComplete = props.AutonomousDUStateChangeComplete
|
||||
? new AutonomousDUStateChangeComplete(props.AutonomousDUStateChangeComplete)
|
||||
: undefined
|
||||
this.AutonomousDUStateChangeCompleteResponse = props.AutonomousDUStateChangeCompleteResponse
|
||||
? new AutonomousDUStateChangeCompleteResponse(props.AutonomousDUStateChangeCompleteResponse)
|
||||
: undefined
|
||||
|
||||
// Override from 1.1 when provided
|
||||
this.AutonomousTransferComplete = props.AutonomousTransferComplete ? new AutonomousTransferComplete(props.AutonomousTransferComplete) : this.AutonomousTransferComplete
|
||||
this.AutonomousTransferComplete = props.AutonomousTransferComplete
|
||||
? new AutonomousTransferComplete(props.AutonomousTransferComplete)
|
||||
: this.AutonomousTransferComplete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,13 +100,13 @@ export enum EventCode_1_2 {
|
||||
// Time window types used by ScheduleDownload
|
||||
export class TimeWindowStruct {
|
||||
public '@class' = '.TimeWindowStruct'
|
||||
public WindowStart: number
|
||||
public WindowEnd: number
|
||||
public WindowMode: string
|
||||
public UserMessage: string
|
||||
public MaxRetries: number
|
||||
public 'WindowStart': number
|
||||
public 'WindowEnd': number
|
||||
public 'WindowMode': string
|
||||
public 'UserMessage': string
|
||||
public 'MaxRetries': number
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.WindowStart = props.WindowStart ?? 0
|
||||
this.WindowEnd = props.WindowEnd ?? 0
|
||||
this.WindowMode = props.WindowMode ?? ''
|
||||
@@ -94,26 +117,28 @@ export class TimeWindowStruct {
|
||||
|
||||
export class TimeWindowList {
|
||||
public '@class' = '.TimeWindowList'
|
||||
public TimeWindowStruct: TimeWindowStruct[] = []
|
||||
public 'TimeWindowStruct': TimeWindowStruct[] = []
|
||||
|
||||
constructor(props: any = {}) {
|
||||
this.TimeWindowStruct = (props.TimeWindowStruct || []).map((tw: any) => new TimeWindowStruct(tw))
|
||||
'constructor'(props: any = {}) {
|
||||
this.TimeWindowStruct = (props.TimeWindowStruct || []).map(
|
||||
(tw: any) => new TimeWindowStruct(tw)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ScheduleDownload RPC
|
||||
export class ScheduleDownload {
|
||||
public '@class' = '.ScheduleDownload'
|
||||
public CommandKey: string
|
||||
public FileType: string
|
||||
public URL: string
|
||||
public Username?: string
|
||||
public Password?: string
|
||||
public FileSize?: number
|
||||
public TargetFileName?: string
|
||||
public TimeWindowList: TimeWindowList
|
||||
public 'CommandKey': string
|
||||
public 'FileType': string
|
||||
public 'URL': string
|
||||
public 'Username'?: string
|
||||
public 'Password'?: string
|
||||
public 'FileSize'?: number
|
||||
public 'TargetFileName'?: string
|
||||
public 'TimeWindowList': TimeWindowList
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.CommandKey = props.CommandKey || ''
|
||||
this.FileType = props.FileType || ''
|
||||
this.URL = props.URL || ''
|
||||
@@ -124,7 +149,7 @@ export class ScheduleDownload {
|
||||
this.TimeWindowList = new TimeWindowList(props.TimeWindowList || {})
|
||||
}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
const tw = this.TimeWindowList?.TimeWindowStruct || []
|
||||
const twXml = tw
|
||||
.map(
|
||||
@@ -156,9 +181,9 @@ export class ScheduleDownload {
|
||||
|
||||
export class ScheduleDownloadResponse {
|
||||
public '@class' = '.ScheduleDownloadResponse'
|
||||
constructor(props: any = {}) {}
|
||||
'constructor'(props: any = {}) {}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
return `<cwmp:ScheduleDownloadResponse/>`
|
||||
}
|
||||
}
|
||||
@@ -166,12 +191,12 @@ export class ScheduleDownloadResponse {
|
||||
// CancelTransfer RPC
|
||||
export class CancelTransfer {
|
||||
public '@class' = '.CancelTransfer'
|
||||
public CommandKey: string
|
||||
constructor(props: any = {}) {
|
||||
public 'CommandKey': string
|
||||
'constructor'(props: any = {}) {
|
||||
this.CommandKey = props.CommandKey || ''
|
||||
}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
return `<cwmp:CancelTransfer>
|
||||
<CommandKey>${this.CommandKey}</CommandKey>
|
||||
</cwmp:CancelTransfer>`
|
||||
@@ -180,9 +205,9 @@ export class CancelTransfer {
|
||||
|
||||
export class CancelTransferResponse {
|
||||
public '@class' = '.CancelTransferResponse'
|
||||
constructor(props: any = {}) {}
|
||||
'constructor'(props: any = {}) {}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
return `<cwmp:CancelTransferResponse/>`
|
||||
}
|
||||
}
|
||||
@@ -190,19 +215,20 @@ export class CancelTransferResponse {
|
||||
// Software Module Management types
|
||||
export type DeploymentUnitState = 'Installed' | 'Uninstalled' | 'Failed'
|
||||
|
||||
export class OperationStruct { // abstract base
|
||||
export class OperationStruct {
|
||||
// abstract base
|
||||
public '@class' = '.OperationStruct'
|
||||
}
|
||||
|
||||
export class InstallOpStruct extends OperationStruct {
|
||||
public '@class' = '.InstallOpStruct'
|
||||
public URL: string
|
||||
public UUID?: string
|
||||
public Username?: string
|
||||
public Password?: string
|
||||
public ExecutionEnvRef?: string
|
||||
public 'URL': string
|
||||
public 'UUID'?: string
|
||||
public 'Username'?: string
|
||||
public 'Password'?: string
|
||||
public 'ExecutionEnvRef'?: string
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
super()
|
||||
this.URL = props.URL || ''
|
||||
this.UUID = props.UUID
|
||||
@@ -214,13 +240,13 @@ export class InstallOpStruct extends OperationStruct {
|
||||
|
||||
export class UpdateOpStruct extends OperationStruct {
|
||||
public '@class' = '.UpdateOpStruct'
|
||||
public UUID?: string
|
||||
public Version?: string
|
||||
public URL?: string
|
||||
public Username?: string
|
||||
public Password?: string
|
||||
public 'UUID'?: string
|
||||
public 'Version'?: string
|
||||
public 'URL'?: string
|
||||
public 'Username'?: string
|
||||
public 'Password'?: string
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
super()
|
||||
this.UUID = props.UUID
|
||||
this.Version = props.Version
|
||||
@@ -232,11 +258,11 @@ export class UpdateOpStruct extends OperationStruct {
|
||||
|
||||
export class UninstallOpStruct extends OperationStruct {
|
||||
public '@class' = '.UninstallOpStruct'
|
||||
public UUID: string
|
||||
public Version?: string
|
||||
public ExecutionEnvRef?: string
|
||||
public 'UUID': string
|
||||
public 'Version'?: string
|
||||
public 'ExecutionEnvRef'?: string
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
super()
|
||||
this.UUID = props.UUID || ''
|
||||
this.Version = props.Version
|
||||
@@ -246,10 +272,10 @@ export class UninstallOpStruct extends OperationStruct {
|
||||
|
||||
export class DeploymentUnitFaultStruct {
|
||||
public '@class' = '.DeploymentUnitFaultStruct'
|
||||
public FaultCode: number
|
||||
public FaultString?: string
|
||||
public 'FaultCode': number
|
||||
public 'FaultString'?: string
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.FaultCode = props.FaultCode ?? 0
|
||||
this.FaultString = props.FaultString
|
||||
}
|
||||
@@ -257,17 +283,17 @@ export class DeploymentUnitFaultStruct {
|
||||
|
||||
export class OpResultStruct {
|
||||
public '@class' = '.OpResultStruct'
|
||||
public UUID: string
|
||||
public DeploymentUnitRef: string
|
||||
public Version: string
|
||||
public CurrentState: DeploymentUnitState
|
||||
public Resolved: boolean
|
||||
public ExecutionUnitRefList: string
|
||||
public StartTime: Date
|
||||
public CompleteTime: Date
|
||||
public Fault: DeploymentUnitFaultStruct
|
||||
public 'UUID': string
|
||||
public 'DeploymentUnitRef': string
|
||||
public 'Version': string
|
||||
public 'CurrentState': DeploymentUnitState
|
||||
public 'Resolved': boolean
|
||||
public 'ExecutionUnitRefList': string
|
||||
public 'StartTime': Date
|
||||
public 'CompleteTime': Date
|
||||
public 'Fault': DeploymentUnitFaultStruct
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.UUID = props.UUID || ''
|
||||
this.DeploymentUnitRef = props.DeploymentUnitRef || ''
|
||||
this.Version = props.Version || ''
|
||||
@@ -282,9 +308,9 @@ export class OpResultStruct {
|
||||
|
||||
export class AutonOpResultStruct extends OpResultStruct {
|
||||
public '@class' = '.AutonOpResultStruct'
|
||||
public OperationPerformed: string
|
||||
public 'OperationPerformed': string
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
super(props)
|
||||
this.OperationPerformed = props.OperationPerformed || ''
|
||||
}
|
||||
@@ -293,51 +319,55 @@ export class AutonOpResultStruct extends OpResultStruct {
|
||||
// ChangeDUState RPC
|
||||
export class ChangeDUState {
|
||||
public '@class' = '.ChangeDUState'
|
||||
public Operations: OperationStruct[] = []
|
||||
public CommandKey?: string
|
||||
public 'Operations': OperationStruct[] = []
|
||||
public 'CommandKey'?: string
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.Operations = (props.Operations || []).map((op: any) => {
|
||||
const type = op?.['@class'] || op?.type
|
||||
if (type === 'InstallOpStruct' || op?.URL) return new InstallOpStruct(op)
|
||||
if (type === 'UpdateOpStruct' || op?.Version || (op?.UUID && !op?.ExecutionEnvRef)) return new UpdateOpStruct(op)
|
||||
if (type === 'UninstallOpStruct' || op?.ExecutionEnvRef || (op?.UUID && !op?.URL && !op?.Version)) return new UninstallOpStruct(op)
|
||||
if (type === 'UpdateOpStruct' || op?.Version || (op?.UUID && !op?.ExecutionEnvRef))
|
||||
return new UpdateOpStruct(op)
|
||||
if (
|
||||
type === 'UninstallOpStruct' ||
|
||||
op?.ExecutionEnvRef ||
|
||||
(op?.UUID && !op?.URL && !op?.Version)
|
||||
)
|
||||
return new UninstallOpStruct(op)
|
||||
return new OperationStruct()
|
||||
})
|
||||
this.CommandKey = props.CommandKey
|
||||
}
|
||||
|
||||
getXml() {
|
||||
const opsXml = this.Operations
|
||||
.map((op) => {
|
||||
if (op instanceof InstallOpStruct) {
|
||||
return `<Operations xsi:type="cwmp:InstallOpStruct">
|
||||
'getXml'() {
|
||||
const opsXml = this.Operations.map((op) => {
|
||||
if (op instanceof InstallOpStruct) {
|
||||
return `<Operations xsi:type="cwmp:InstallOpStruct">
|
||||
<URL>${op.URL}</URL>
|
||||
${op.UUID ? `<UUID>${op.UUID}</UUID>` : ''}
|
||||
${op.Username ? `<Username>${op.Username}</Username>` : ''}
|
||||
${op.Password ? `<Password>${op.Password}</Password>` : ''}
|
||||
${op.ExecutionEnvRef ? `<ExecutionEnvRef>${op.ExecutionEnvRef}</ExecutionEnvRef>` : ''}
|
||||
</Operations>`
|
||||
}
|
||||
if (op instanceof UpdateOpStruct) {
|
||||
return `<Operations xsi:type="cwmp:UpdateOpStruct">
|
||||
}
|
||||
if (op instanceof UpdateOpStruct) {
|
||||
return `<Operations xsi:type="cwmp:UpdateOpStruct">
|
||||
${op.UUID ? `<UUID>${op.UUID}</UUID>` : ''}
|
||||
${op.Version ? `<Version>${op.Version}</Version>` : ''}
|
||||
${op.URL ? `<URL>${op.URL}</URL>` : ''}
|
||||
${op.Username ? `<Username>${op.Username}</Username>` : ''}
|
||||
${op.Password ? `<Password>${op.Password}</Password>` : ''}
|
||||
</Operations>`
|
||||
}
|
||||
if (op instanceof UninstallOpStruct) {
|
||||
return `<Operations xsi:type="cwmp:UninstallOpStruct">
|
||||
}
|
||||
if (op instanceof UninstallOpStruct) {
|
||||
return `<Operations xsi:type="cwmp:UninstallOpStruct">
|
||||
<UUID>${op.UUID}</UUID>
|
||||
${op.Version ? `<Version>${op.Version}</Version>` : ''}
|
||||
${op.ExecutionEnvRef ? `<ExecutionEnvRef>${op.ExecutionEnvRef}</ExecutionEnvRef>` : ''}
|
||||
</Operations>`
|
||||
}
|
||||
return ''
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
return ''
|
||||
}).join('')
|
||||
|
||||
return `<cwmp:ChangeDUState>
|
||||
${opsXml}
|
||||
@@ -348,9 +378,9 @@ export class ChangeDUState {
|
||||
|
||||
export class ChangeDUStateResponse {
|
||||
public '@class' = '.ChangeDUStateResponse'
|
||||
constructor(props: any = {}) {}
|
||||
'constructor'(props: any = {}) {}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
return `<cwmp:ChangeDUStateResponse/>`
|
||||
}
|
||||
}
|
||||
@@ -358,18 +388,17 @@ export class ChangeDUStateResponse {
|
||||
// DUStateChangeComplete RPCs
|
||||
export class DUStateChangeComplete {
|
||||
public '@class' = '.DUStateChangeComplete'
|
||||
public Results: OpResultStruct[]
|
||||
public CommandKey?: string
|
||||
public 'Results': OpResultStruct[]
|
||||
public 'CommandKey'?: string
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.Results = (props.Results || []).map((r: any) => new OpResultStruct(r))
|
||||
this.CommandKey = props.CommandKey
|
||||
}
|
||||
|
||||
getXml() {
|
||||
const resultsXml = this.Results
|
||||
.map(
|
||||
(r) => `
|
||||
'getXml'() {
|
||||
const resultsXml = this.Results.map(
|
||||
(r) => `
|
||||
<Results>
|
||||
<UUID>${r.UUID}</UUID>
|
||||
<DeploymentUnitRef>${r.DeploymentUnitRef}</DeploymentUnitRef>
|
||||
@@ -384,8 +413,7 @@ export class DUStateChangeComplete {
|
||||
${r.Fault.FaultString ? `<FaultString>${r.Fault.FaultString}</FaultString>` : ''}
|
||||
</Fault>
|
||||
</Results>`
|
||||
)
|
||||
.join('')
|
||||
).join('')
|
||||
|
||||
return `<cwmp:DUStateChangeComplete>
|
||||
${resultsXml}
|
||||
@@ -396,25 +424,24 @@ export class DUStateChangeComplete {
|
||||
|
||||
export class DUStateChangeCompleteResponse {
|
||||
public '@class' = '.DUStateChangeCompleteResponse'
|
||||
constructor(props: any = {}) {}
|
||||
'constructor'(props: any = {}) {}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
return `<cwmp:DUStateChangeCompleteResponse/>`
|
||||
}
|
||||
}
|
||||
|
||||
export class AutonomousDUStateChangeComplete {
|
||||
public '@class' = '.AutonomousDUStateChangeComplete'
|
||||
public Results: AutonOpResultStruct[]
|
||||
public 'Results': AutonOpResultStruct[]
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.Results = (props.Results || []).map((r: any) => new AutonOpResultStruct(r))
|
||||
}
|
||||
|
||||
getXml() {
|
||||
const resultsXml = this.Results
|
||||
.map(
|
||||
(r) => `
|
||||
'getXml'() {
|
||||
const resultsXml = this.Results.map(
|
||||
(r) => `
|
||||
<Results>
|
||||
<UUID>${r.UUID}</UUID>
|
||||
<DeploymentUnitRef>${r.DeploymentUnitRef}</DeploymentUnitRef>
|
||||
@@ -430,8 +457,7 @@ export class AutonomousDUStateChangeComplete {
|
||||
</Fault>
|
||||
<OperationPerformed>${r.OperationPerformed}</OperationPerformed>
|
||||
</Results>`
|
||||
)
|
||||
.join('')
|
||||
).join('')
|
||||
|
||||
return `<cwmp:AutonomousDUStateChangeComplete>
|
||||
${resultsXml}
|
||||
@@ -441,9 +467,9 @@ export class AutonomousDUStateChangeComplete {
|
||||
|
||||
export class AutonomousDUStateChangeCompleteResponse {
|
||||
public '@class' = '.AutonomousDUStateChangeCompleteResponse'
|
||||
constructor(props: any = {}) {}
|
||||
'constructor'(props: any = {}) {}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
return `<cwmp:AutonomousDUStateChangeCompleteResponse/>`
|
||||
}
|
||||
}
|
||||
@@ -451,10 +477,10 @@ export class AutonomousDUStateChangeCompleteResponse {
|
||||
// TransferComplete-specific fault struct for 1.2
|
||||
export class TransferCompleteFaultStruct {
|
||||
public '@class' = '.TransferCompleteFaultStruct'
|
||||
public FaultCode: number
|
||||
public FaultString: string
|
||||
public 'FaultCode': number
|
||||
public 'FaultString': string
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.FaultCode = props.FaultCode ?? 0
|
||||
this.FaultString = props.FaultString || ''
|
||||
}
|
||||
@@ -463,14 +489,14 @@ export class TransferCompleteFaultStruct {
|
||||
// Override of 1.1 AutonomousTransferComplete to use TransferCompleteFaultStruct
|
||||
export class AutonomousTransferComplete extends AutonomousTransferComplete_1_1 {
|
||||
public '@class' = '.AutonomousTransferComplete'
|
||||
public FaultStruct: TransferCompleteFaultStruct
|
||||
public 'FaultStruct': TransferCompleteFaultStruct
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
super(props)
|
||||
this.FaultStruct = new TransferCompleteFaultStruct(props.FaultStruct || {})
|
||||
}
|
||||
|
||||
getXml() {
|
||||
'getXml'() {
|
||||
return `<cwmp:AutonomousTransferComplete>
|
||||
<AnnounceURL>${this.AnnounceURL}</AnnounceURL>
|
||||
<TransferURL>${this.TransferURL}</TransferURL>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck — fichier copié de cwmp-connector (papaye)
|
||||
import { Cwmp_1_2 } from './cwmp-1-2.js'
|
||||
import { Cwmp_1_2 } from '#protocol/cwmp-1-2'
|
||||
|
||||
/**
|
||||
* CWMP Version 1.3 Protocol Classes
|
||||
@@ -9,21 +9,23 @@ import { Cwmp_1_2 } from './cwmp-1-2.js'
|
||||
*/
|
||||
export class Cwmp_1_3 extends Cwmp_1_2 {
|
||||
public '@class' = '.Cwmp_1_3'
|
||||
public SessionTimeout?: SessionTimeout
|
||||
public 'SessionTimeout'?: SessionTimeout
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
super(props)
|
||||
this.SessionTimeout = props.SessionTimeout ? new SessionTimeout(props.SessionTimeout) : undefined
|
||||
this.SessionTimeout = props.SessionTimeout
|
||||
? new SessionTimeout(props.SessionTimeout)
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
|
||||
// SOAP Header element for CWMP 1.3
|
||||
export class SessionTimeout {
|
||||
public '@class' = '.SessionTimeout'
|
||||
public MustUnderstand: string = '0'
|
||||
public _: number
|
||||
public 'MustUnderstand': string = '0'
|
||||
public '_': number
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.MustUnderstand = props.MustUnderstand ?? '0'
|
||||
this._ = props._ ?? 0
|
||||
}
|
||||
|
||||
+16
-12
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck — fichier copié de cwmp-connector (papaye)
|
||||
import { Cwmp_1_3 } from './cwmp-1-3.js'
|
||||
import { Cwmp_1_3 } from '#protocol/cwmp-1-3'
|
||||
|
||||
/**
|
||||
* CWMP Version 1.4 Protocol Classes
|
||||
@@ -11,13 +11,17 @@ import { Cwmp_1_3 } from './cwmp-1-3.js'
|
||||
*/
|
||||
export class Cwmp_1_4 extends Cwmp_1_3 {
|
||||
public '@class' = '.Cwmp_1_4'
|
||||
public SupportedCWMPVersions?: SupportedCWMPVersions
|
||||
public UseCWMPVersion?: UseCWMPVersion
|
||||
public 'SupportedCWMPVersions'?: SupportedCWMPVersions
|
||||
public 'UseCWMPVersion'?: UseCWMPVersion
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
super(props)
|
||||
this.SupportedCWMPVersions = props.SupportedCWMPVersions ? new SupportedCWMPVersions(props.SupportedCWMPVersions) : undefined
|
||||
this.UseCWMPVersion = props.UseCWMPVersion ? new UseCWMPVersion(props.UseCWMPVersion) : undefined
|
||||
this.SupportedCWMPVersions = props.SupportedCWMPVersions
|
||||
? new SupportedCWMPVersions(props.SupportedCWMPVersions)
|
||||
: undefined
|
||||
this.UseCWMPVersion = props.UseCWMPVersion
|
||||
? new UseCWMPVersion(props.UseCWMPVersion)
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,10 +52,10 @@ export enum EventCode_1_4 {
|
||||
|
||||
export class SupportedCWMPVersions {
|
||||
public '@class' = '.SupportedCWMPVersions'
|
||||
public MustUnderstand?: string // should be 0 per spec, optional due to SOAP 1.1 quirks
|
||||
public _: string // space or comma separated list per implementation
|
||||
public 'MustUnderstand'?: string // should be 0 per spec, optional due to SOAP 1.1 quirks
|
||||
public '_': string // space or comma separated list per implementation
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.MustUnderstand = props.MustUnderstand ?? '0'
|
||||
this._ = props._ || ''
|
||||
}
|
||||
@@ -59,10 +63,10 @@ export class SupportedCWMPVersions {
|
||||
|
||||
export class UseCWMPVersion {
|
||||
public '@class' = '.UseCWMPVersion'
|
||||
public MustUnderstand: string = '1'
|
||||
public _: string
|
||||
public 'MustUnderstand': string = '1'
|
||||
public '_': string
|
||||
|
||||
constructor(props: any = {}) {
|
||||
'constructor'(props: any = {}) {
|
||||
this.MustUnderstand = props.MustUnderstand ?? '1'
|
||||
this._ = props._ || ''
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { XMLParser } from 'fast-xml-parser'
|
||||
import type { CwmpMessage, SoapEnvelope, SoapHeader } from './tr069_types.js'
|
||||
import type { CwmpMessage, SoapEnvelope, SoapHeader } from '#protocol/tr069_types'
|
||||
|
||||
export { type SoapEnvelope, type SoapHeader, type CwmpMessage } from './tr069_types.js'
|
||||
export { type SoapEnvelope, type SoapHeader, type CwmpMessage } from '#protocol/tr069_types'
|
||||
|
||||
export const CWMP_VERSIONS: Record<string, string> = {
|
||||
'urn:dslforum-org:cwmp-1-0': '1.0',
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
*
|
||||
* Séparées de soap.ts (le module de parsing d'adam-acs) pour éviter les conflits de nom.
|
||||
*/
|
||||
import { Cwmp_1_0 } from './cwmp-1-0.js'
|
||||
import { Cwmp_1_1 } from './cwmp-1-1.js'
|
||||
import { Cwmp_1_2 } from './cwmp-1-2.js'
|
||||
import { Cwmp_1_3, SessionTimeout } from './cwmp-1-3.js'
|
||||
import { Cwmp_1_4, SupportedCWMPVersions, UseCWMPVersion } from './cwmp-1-4.js'
|
||||
import { Cwmp_1_0 } from '#protocol/cwmp-1-0'
|
||||
import { Cwmp_1_1 } from '#protocol/cwmp-1-1'
|
||||
import { Cwmp_1_2 } from '#protocol/cwmp-1-2'
|
||||
import { Cwmp_1_3, SessionTimeout } from '#protocol/cwmp-1-3'
|
||||
import { Cwmp_1_4, SupportedCWMPVersions, UseCWMPVersion } from '#protocol/cwmp-1-4'
|
||||
|
||||
type NamespaceDescriptor = {
|
||||
SoapEnc: string
|
||||
|
||||
+11
-11
@@ -59,27 +59,27 @@ export {
|
||||
// Types primitifs
|
||||
type CommandKeyType,
|
||||
type ObjectNameType,
|
||||
} from './cwmp-1-0.js'
|
||||
} from '#protocol/cwmp-1-0'
|
||||
|
||||
// ─── CWMP 1.1 ─────────────────────────────────────────────────────────────────
|
||||
export { Cwmp_1_1, EventCode_1_1, FaultCodeType_1_1 } from './cwmp-1-1.js'
|
||||
export { Cwmp_1_1, EventCode_1_1, FaultCodeType_1_1 } from '#protocol/cwmp-1-1'
|
||||
|
||||
// ─── CWMP 1.2 ─────────────────────────────────────────────────────────────────
|
||||
export { Cwmp_1_2, EventCode_1_2 } from './cwmp-1-2.js'
|
||||
export { Cwmp_1_2, EventCode_1_2 } from '#protocol/cwmp-1-2'
|
||||
|
||||
// ─── CWMP 1.3 ─────────────────────────────────────────────────────────────────
|
||||
export { Cwmp_1_3, SessionTimeout } from './cwmp-1-3.js'
|
||||
export { Cwmp_1_3, SessionTimeout } from '#protocol/cwmp-1-3'
|
||||
|
||||
// ─── CWMP 1.4 ─────────────────────────────────────────────────────────────────
|
||||
export { Cwmp_1_4, EventCode_1_4, SupportedCWMPVersions, UseCWMPVersion } from './cwmp-1-4.js'
|
||||
export { Cwmp_1_4, EventCode_1_4, SupportedCWMPVersions, UseCWMPVersion } from '#protocol/cwmp-1-4'
|
||||
|
||||
// ─── Types locaux complémentaires ─────────────────────────────────────────────
|
||||
|
||||
import type { Cwmp_1_0 } from './cwmp-1-0.js'
|
||||
import type { Cwmp_1_1 } from './cwmp-1-1.js'
|
||||
import type { Cwmp_1_2 } from './cwmp-1-2.js'
|
||||
import type { Cwmp_1_3 } from './cwmp-1-3.js'
|
||||
import type { Cwmp_1_4 } from './cwmp-1-4.js'
|
||||
import type { Cwmp_1_0 } from '#protocol/cwmp-1-0'
|
||||
import type { Cwmp_1_1 } from '#protocol/cwmp-1-1'
|
||||
import type { Cwmp_1_2 } from '#protocol/cwmp-1-2'
|
||||
import type { Cwmp_1_3 } from '#protocol/cwmp-1-3'
|
||||
import type { Cwmp_1_4 } from '#protocol/cwmp-1-4'
|
||||
import type {
|
||||
Inform,
|
||||
TransferComplete,
|
||||
@@ -88,7 +88,7 @@ import type {
|
||||
GetParameterAttributesResponse,
|
||||
SetParameterValuesResponse,
|
||||
Fault as SoapFaultBody,
|
||||
} from './cwmp-1-0.js'
|
||||
} from '#protocol/cwmp-1-0'
|
||||
|
||||
/** Union de tous les corps CWMP selon la version du protocole */
|
||||
export type CwmpBody = Cwmp_1_0 | Cwmp_1_1 | Cwmp_1_2 | Cwmp_1_3 | Cwmp_1_4
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import CommandModel from '#models/command'
|
||||
import { CommandRecord, CommandStatus, CommandType } from '../domain/commands/command.js'
|
||||
import { ModeType } from '../domain/shared/mode.js'
|
||||
import { Priority } from '../domain/commands/command.js'
|
||||
import { type CommandRecord, CommandStatus, type CommandType } from '#domain/commands/command'
|
||||
import { ModeType } from '#domain/shared/mode'
|
||||
import { Priority } from '#domain/commands/command'
|
||||
|
||||
export default class CommandService {
|
||||
async getCommands() {
|
||||
@@ -32,7 +32,10 @@ export default class CommandService {
|
||||
})
|
||||
}
|
||||
|
||||
async getICommandsForDeviceWithStatus(deviceIdentifier: string, status: CommandStatus): Promise<CommandRecord[]> {
|
||||
async getICommandsForDeviceWithStatus(
|
||||
deviceIdentifier: string,
|
||||
status: CommandStatus
|
||||
): Promise<CommandRecord[]> {
|
||||
const collection = CommandModel.getCollection()
|
||||
return collection
|
||||
.find({ deviceIdentifier, mode: ModeType.I, status })
|
||||
@@ -40,7 +43,10 @@ export default class CommandService {
|
||||
.toArray() as unknown as Promise<CommandRecord[]>
|
||||
}
|
||||
|
||||
async getNICommandsForDeviceWithStatus(deviceIdentifier: string, status: CommandStatus): Promise<CommandRecord[]> {
|
||||
async getNICommandsForDeviceWithStatus(
|
||||
deviceIdentifier: string,
|
||||
status: CommandStatus
|
||||
): Promise<CommandRecord[]> {
|
||||
const collection = CommandModel.getCollection()
|
||||
return collection
|
||||
.find({ deviceIdentifier, mode: ModeType.NI, status })
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import ConfigurationModel from '#models/configuration'
|
||||
import DeviceModel from '#models/device'
|
||||
import FirmwareCatalogService from './firmware_catalog_service.js'
|
||||
import type { Configuration, ParameterCriteria, ParameterValueType } from '../domain/configuration/configuration.js'
|
||||
import type { Device } from '../domain/device/device.js'
|
||||
import type { MigrationStep } from '../domain/firmware/migration_step.js'
|
||||
import { isWithinSchedule } from '../utils/schedule_window.js'
|
||||
import { matchGlob } from '../utils/glob_match.js'
|
||||
import { computeContentHash, parseProvisioningCode } from '../utils/provisioning_code.js'
|
||||
import FirmwareCatalogService from '#services/firmware_catalog_service'
|
||||
import type {
|
||||
Configuration,
|
||||
ParameterCriteria,
|
||||
ParameterValueType,
|
||||
} from '#domain/configuration/configuration'
|
||||
import type { Device } from '#domain/device/device'
|
||||
import type { MigrationStep } from '#domain/firmware/migration_step'
|
||||
import { isWithinSchedule } from '#utils/schedule_window'
|
||||
import { matchGlob } from '#utils/glob_match'
|
||||
import { computeContentHash, parseProvisioningCode } from '#utils/provisioning_code'
|
||||
|
||||
export interface ComplianceDeviceResult {
|
||||
identifier: string
|
||||
@@ -140,7 +144,10 @@ export default class ConfigurationEngine {
|
||||
skipFirmware = true
|
||||
} else {
|
||||
// Get current device version (from properties or parameter)
|
||||
const currentVersion = this.getDeviceVersion(device, selectedConfig.firmwareTarget.productClass)
|
||||
const currentVersion = this.getDeviceVersion(
|
||||
device,
|
||||
selectedConfig.firmwareTarget.productClass
|
||||
)
|
||||
|
||||
if (currentVersion && currentVersion !== selectedConfig.firmwareTarget.targetVersion) {
|
||||
firmwareMigrationPath = await this.firmwareService.resolveUpgradePath(
|
||||
@@ -153,9 +160,7 @@ export default class ConfigurationEngine {
|
||||
}
|
||||
|
||||
// Check if compliant (no diffs and no firmware needed)
|
||||
const isCompliant =
|
||||
parameterDiffs.length === 0 &&
|
||||
firmwareMigrationPath.length === 0
|
||||
const isCompliant = parameterDiffs.length === 0 && firmwareMigrationPath.length === 0
|
||||
|
||||
return {
|
||||
configuration: selectedConfig,
|
||||
@@ -369,17 +374,21 @@ export default class ConfigurationEngine {
|
||||
// Compare values (loose: both as strings to handle type mismatches)
|
||||
// If the desired type is numeric, compare numerically
|
||||
const desiredType: string = desired.type || ''
|
||||
const isNumeric = desiredType === 'unsignedInt' || desiredType === 'int' ||
|
||||
desiredType === 'xsd:unsignedInt' || desiredType === 'xsd:int'
|
||||
const isNumeric =
|
||||
desiredType === 'unsignedInt' ||
|
||||
desiredType === 'int' ||
|
||||
desiredType === 'xsd:unsignedInt' ||
|
||||
desiredType === 'xsd:int'
|
||||
const isBool = desiredType === 'boolean' || desiredType === 'xsd:boolean'
|
||||
|
||||
let valuesMatch: boolean
|
||||
if (isNumeric) {
|
||||
const numCurrent = Number(currentValue)
|
||||
const numDesired = Number(desired.value)
|
||||
valuesMatch = (!isNaN(numCurrent) && !isNaN(numDesired))
|
||||
? numCurrent === numDesired
|
||||
: String(currentValue) === String(desired.value)
|
||||
valuesMatch =
|
||||
!Number.isNaN(numCurrent) && !Number.isNaN(numDesired)
|
||||
? numCurrent === numDesired
|
||||
: String(currentValue) === String(desired.value)
|
||||
} else if (isBool) {
|
||||
const toBool = (v: any) => v === true || v === 'true' || v === '1'
|
||||
valuesMatch = toBool(currentValue) === toBool(desired.value)
|
||||
@@ -404,7 +413,10 @@ export default class ConfigurationEngine {
|
||||
* Get the current value of a parameter from the device.
|
||||
* Returns null if not found.
|
||||
*/
|
||||
private getDeviceParameterValue(device: Device, paramName: string): string | number | boolean | null {
|
||||
private getDeviceParameterValue(
|
||||
device: Device,
|
||||
paramName: string
|
||||
): string | number | boolean | null {
|
||||
// Try method-based access first (Device class instances)
|
||||
if (typeof (device as any).getParameter === 'function') {
|
||||
const param = (device as any).getParameter(paramName)
|
||||
@@ -419,7 +431,10 @@ export default class ConfigurationEngine {
|
||||
}
|
||||
|
||||
// Fallback: plain DeviceRecord objects (parameters + properties arrays)
|
||||
const allEntries = [...((device as any).parameters ?? []), ...((device as any).properties ?? [])]
|
||||
const allEntries = [
|
||||
...((device as any).parameters ?? []),
|
||||
...((device as any).properties ?? []),
|
||||
]
|
||||
const entry = allEntries.find((p: any) => p.name === paramName)
|
||||
if (!entry || entry.value === undefined || entry.value === null) return null
|
||||
if (typeof entry.value === 'boolean') return entry.value
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
type DesiredParameter,
|
||||
type InventoryPath,
|
||||
PolicyType,
|
||||
} from '../domain/configuration/configuration.js'
|
||||
import type { ConfigurationVersion } from '../domain/configuration/configuration_version.js'
|
||||
import { isParameterCoveredByInventory, suggestInventoryPath } from '../utils/inventory_coverage.js'
|
||||
import { matchGlob } from '../utils/glob_match.js'
|
||||
} from '#domain/configuration/configuration'
|
||||
import type { ConfigurationVersion } from '#domain/configuration/configuration_version'
|
||||
import { isParameterCoveredByInventory, suggestInventoryPath } from '#utils/inventory_coverage'
|
||||
import { matchGlob } from '#utils/glob_match'
|
||||
|
||||
/**
|
||||
* Allowed criteria fields — DeviceId attributes (immutable) + parameterCriteria (evaluated dynamically).
|
||||
@@ -82,7 +82,9 @@ export default class ConfigurationService {
|
||||
/**
|
||||
* Create a new Configuration after validation.
|
||||
*/
|
||||
async create(data: CreateConfigurationDto): Promise<Configuration & { warning?: OverlapWarning }> {
|
||||
async create(
|
||||
data: CreateConfigurationDto
|
||||
): Promise<Configuration & { warning?: OverlapWarning }> {
|
||||
this.validateIdentifier(data.identifier)
|
||||
this.validateVersion(data.version)
|
||||
this.validatePriority(data.priority)
|
||||
@@ -186,7 +188,10 @@ export default class ConfigurationService {
|
||||
* Creates a version snapshot of the current state before applying changes.
|
||||
* Auto-increments the patch version on behavioral changes and resets device compliance.
|
||||
*/
|
||||
async update(identifier: string, data: UpdateConfigurationDto): Promise<Configuration & { warning?: OverlapWarning }> {
|
||||
async update(
|
||||
identifier: string,
|
||||
data: UpdateConfigurationDto
|
||||
): Promise<Configuration & { warning?: OverlapWarning }> {
|
||||
const existing = await ConfigurationModel.findByIdentifier(identifier)
|
||||
if (!existing) {
|
||||
throw new ValidationError(`Configuration '${identifier}' not found`, 'identifier', 404)
|
||||
@@ -243,7 +248,8 @@ export default class ConfigurationService {
|
||||
if (data.tags !== undefined) updatePayload.tags = data.tags
|
||||
if (data.criteria !== undefined) updatePayload.criteria = data.criteria
|
||||
if (data.inventory !== undefined) updatePayload.inventory = data.inventory
|
||||
if (data.desiredParameters !== undefined) updatePayload.desiredParameters = data.desiredParameters
|
||||
if (data.desiredParameters !== undefined)
|
||||
updatePayload.desiredParameters = data.desiredParameters
|
||||
if (data.firmwareTarget !== undefined) updatePayload.firmwareTarget = data.firmwareTarget
|
||||
if (data.schedule !== undefined) updatePayload.schedule = data.schedule
|
||||
|
||||
@@ -271,7 +277,13 @@ export default class ConfigurationService {
|
||||
identifier: string,
|
||||
page: number = 1,
|
||||
limit: number = 25
|
||||
): Promise<{ data: ConfigurationVersion[]; total: number; page: number; perPage: number; lastPage: number }> {
|
||||
): Promise<{
|
||||
data: ConfigurationVersion[]
|
||||
total: number
|
||||
page: number
|
||||
perPage: number
|
||||
lastPage: number
|
||||
}> {
|
||||
const existing = await ConfigurationModel.findByIdentifier(identifier)
|
||||
if (!existing) {
|
||||
throw new ValidationError(`Configuration '${identifier}' not found`, 'identifier', 404)
|
||||
@@ -298,11 +310,7 @@ export default class ConfigurationService {
|
||||
/**
|
||||
* Add and/or remove tags from a Configuration without modifying other fields.
|
||||
*/
|
||||
async updateTags(
|
||||
identifier: string,
|
||||
add?: string[],
|
||||
remove?: string[]
|
||||
): Promise<Configuration> {
|
||||
async updateTags(identifier: string, add?: string[], remove?: string[]): Promise<Configuration> {
|
||||
const existing = await ConfigurationModel.findByIdentifier(identifier)
|
||||
if (!existing) {
|
||||
throw new ValidationError(`Configuration '${identifier}' not found`, 'identifier', 404)
|
||||
@@ -538,7 +546,7 @@ export default class ConfigurationService {
|
||||
private sanitizeCriteria(criteria: ConfigurationCriteria): ConfigurationCriteria {
|
||||
if (!criteria.parameterCriteria?.length) return criteria
|
||||
const filtered = criteria.parameterCriteria.filter(
|
||||
(pc) => pc.name != null && String(pc.name).trim() !== ''
|
||||
(pc) => pc.name !== null && pc.name !== undefined && String(pc.name).trim() !== ''
|
||||
)
|
||||
return {
|
||||
...criteria,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Device } from '../../domain/device/device.js'
|
||||
import { DeviceActionExecutor } from './device_action_executor.js'
|
||||
import { type Device } from '#domain/device/device'
|
||||
import { type DeviceActionExecutor } from '#services/cwmp/device_action_executor'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
|
||||
/**
|
||||
@@ -23,17 +23,29 @@ export async function executeAction(
|
||||
device: Device,
|
||||
actionType: ActionType,
|
||||
parameters: any[],
|
||||
callback: ((result: unknown) => Promise<string>) = () => Promise.resolve('')
|
||||
callback: (result: unknown) => Promise<string> = () => Promise.resolve('')
|
||||
): Promise<string> {
|
||||
switch (actionType) {
|
||||
case ActionType.SET_PARAMETER_VALUES:
|
||||
return deviceAction.applySetParameterValues(device, parameters as any[], callback as (r: boolean) => Promise<string>)
|
||||
return deviceAction.applySetParameterValues(
|
||||
device,
|
||||
parameters as any[],
|
||||
callback as (r: boolean) => Promise<string>
|
||||
)
|
||||
|
||||
case ActionType.SET_PARAMETER_ATTRIBUTES:
|
||||
return deviceAction.applySetParameterAttributes(device, parameters as any[], callback as (r: boolean) => Promise<string>)
|
||||
return deviceAction.applySetParameterAttributes(
|
||||
device,
|
||||
parameters as any[],
|
||||
callback as (r: boolean) => Promise<string>
|
||||
)
|
||||
|
||||
case ActionType.SCHEDULE_INFORM:
|
||||
return deviceAction.applyScheduleInform(device, parameters[0] as any, callback as (r: boolean) => Promise<string>)
|
||||
return deviceAction.applyScheduleInform(
|
||||
device,
|
||||
parameters[0] as any,
|
||||
callback as (r: boolean) => Promise<string>
|
||||
)
|
||||
|
||||
case ActionType.DOWNLOAD: {
|
||||
const p = parameters[0]
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import ConfigurationEngine from '../configuration_engine.js'
|
||||
import type { EngineResult } from '../configuration_engine.js'
|
||||
import type { DeviceStateResult, DeviceContext } from './main_state_machine.js'
|
||||
import { PartialOperationalState } from './main_state_machine.js'
|
||||
import ConfigurationEngine from '#services/configuration_engine'
|
||||
import type { EngineResult } from '#services/configuration_engine'
|
||||
import type { DeviceStateResult, DeviceContext } from '#services/cwmp/main_state_machine'
|
||||
import { PartialOperationalState } from '#services/cwmp/main_state_machine'
|
||||
import DeviceModel from '#models/device'
|
||||
import type {
|
||||
GetParameterNamesResponse,
|
||||
GetParameterValuesResponse,
|
||||
} from '../../protocol/tr069_types.js'
|
||||
import type { GetParameterNamesResponse, GetParameterValuesResponse } from '#protocol/tr069_types'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import { ComplianceStatus } from '../../domain/configuration/compliance.js'
|
||||
import { buildProvisioningCode } from '../../utils/provisioning_code.js'
|
||||
import { ComplianceStatus } from '#domain/configuration/compliance'
|
||||
import { buildProvisioningCode } from '#utils/provisioning_code'
|
||||
|
||||
/**
|
||||
* ConfigurationEvaluationState
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { type Device } from '../../domain/device/device.js'
|
||||
import { DeviceParameter } from '../../domain/device/device_parameter.js'
|
||||
import { ParameterType } from '../../domain/shared/types.js'
|
||||
import { type CWMPTaskQueue, ResultStatus, type TaskResult } from './task_queue.js'
|
||||
import DeviceModel from '../../models/device.js'
|
||||
import { type Device } from '#domain/device/device'
|
||||
import { DeviceParameter } from '#domain/device/device_parameter'
|
||||
import { ParameterType } from '#domain/shared/types'
|
||||
import { type CWMPTaskQueue, ResultStatus, type TaskResult } from '#services/cwmp/task_queue'
|
||||
import DeviceModel from '#models/device'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import type {
|
||||
GetParameterValuesResponse,
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
ParameterValueStruct,
|
||||
ParameterAttributeStruct,
|
||||
ParameterInfoStruct,
|
||||
} from '../../protocol/tr069_types.js'
|
||||
} from '#protocol/tr069_types'
|
||||
|
||||
// ─── Action parameter interfaces (exported pour usage externe) ─────────────────
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { type Device } from '../../domain/device/device.js'
|
||||
import { type CWMPTaskQueue } from './task_queue.js'
|
||||
import { DeviceActionExecutor } from './device_action_executor.js'
|
||||
import { type Device } from '#domain/device/device'
|
||||
import { type CWMPTaskQueue } from '#services/cwmp/task_queue'
|
||||
import { DeviceActionExecutor } from '#services/cwmp/device_action_executor'
|
||||
import {
|
||||
ExecutingCommandsState,
|
||||
type DeviceContext,
|
||||
type DeviceStateResult,
|
||||
} from './main_state_machine.js'
|
||||
import type { CWMPTaskResultData } from '../../protocol/tr069_types.js'
|
||||
} from '#services/cwmp/main_state_machine'
|
||||
import type { CWMPTaskResultData } from '#protocol/tr069_types'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { type Device } from '../../domain/device/device.js'
|
||||
import { type CWMPTaskQueue } from './task_queue.js'
|
||||
import { DeviceActionExecutor } from './device_action_executor.js'
|
||||
import { getDeviceHashIdentifier } from '../../utils/device_utils.js'
|
||||
import { CommandStatus, CommandType } from '../../domain/commands/command.js'
|
||||
import DeviceModel from '../../models/device.js'
|
||||
import CommandModel from '../../models/command.js'
|
||||
import { ModeType } from '../../domain/shared/mode.js'
|
||||
import { type Device } from '#domain/device/device'
|
||||
import { type CWMPTaskQueue } from '#services/cwmp/task_queue'
|
||||
import { DeviceActionExecutor } from '#services/cwmp/device_action_executor'
|
||||
import { getDeviceHashIdentifier } from '#utils/device_utils'
|
||||
import { CommandStatus, CommandType } from '#domain/commands/command'
|
||||
import DeviceModel from '#models/device'
|
||||
import CommandModel from '#models/command'
|
||||
import { ModeType } from '#domain/shared/mode'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import { ConfigurationEvaluationState } from './configuration_evaluation_state.js'
|
||||
import { reclassifyDeviceFromCatalog } from '../parameter_writability.js'
|
||||
import { ConfigurationEvaluationState } from '#services/cwmp/configuration_evaluation_state'
|
||||
import { reclassifyDeviceFromCatalog } from '#services/parameter_writability'
|
||||
import type {
|
||||
CWMPTaskResultData,
|
||||
GetParameterNamesResponse,
|
||||
GetParameterValuesResponse,
|
||||
ParameterValueStruct,
|
||||
} from '../../protocol/tr069_types.js'
|
||||
} from '#protocol/tr069_types'
|
||||
import type {
|
||||
SetParameterAttribute,
|
||||
DownloadParameter,
|
||||
UploadParameter,
|
||||
RebootParameter,
|
||||
} from './device_action_executor.js'
|
||||
} from '#services/cwmp/device_action_executor'
|
||||
import { type ObjectId } from 'mongodb'
|
||||
|
||||
// ─── Interfaces ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { EventEmitter } from 'node:events'
|
||||
import type { Device } from '../../domain/device/device.js'
|
||||
import type { DeviceId } from '../../domain/device/device_id.js'
|
||||
import { CWMPTaskQueue, ResultStatus } from './task_queue.js'
|
||||
import { MainStateMachine } from './main_state_machine.js'
|
||||
import { ImmediateCommandStateMachine } from './immediate_command_state_machine.js'
|
||||
import { getDeviceHashIdentifier } from '../../utils/device_utils.js'
|
||||
import { resolveHighestPriorityEvent, normalizeEventCode } from '../../utils/cwmp-events.js'
|
||||
import { CommandStatus } from '../../domain/commands/command.js'
|
||||
import { ModeType } from '../../domain/shared/mode.js'
|
||||
import CommandModel from '../../models/command.js'
|
||||
import type { Device } from '#domain/device/device'
|
||||
import type { DeviceId } from '#domain/device/device_id'
|
||||
import { CWMPTaskQueue, ResultStatus } from '#services/cwmp/task_queue'
|
||||
import { MainStateMachine } from '#services/cwmp/main_state_machine'
|
||||
import { ImmediateCommandStateMachine } from '#services/cwmp/immediate_command_state_machine'
|
||||
import { getDeviceHashIdentifier } from '#utils/device_utils'
|
||||
import { resolveHighestPriorityEvent, normalizeEventCode } from '#utils/cwmp-events'
|
||||
import { CommandStatus } from '#domain/commands/command'
|
||||
import { ModeType } from '#domain/shared/mode'
|
||||
import CommandModel from '#models/command'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
|
||||
export enum MachineStatus {
|
||||
@@ -129,7 +129,12 @@ export class StateMachineManager extends EventEmitter {
|
||||
existingMachine.session = session
|
||||
}
|
||||
// Replace with MainStateMachine
|
||||
const newMachine = new MainStateMachine(device, session, this.taskQueue, normalizedEvents)
|
||||
const newMachine = new MainStateMachine(
|
||||
device,
|
||||
session,
|
||||
this.taskQueue,
|
||||
normalizedEvents
|
||||
)
|
||||
;(existingMachine as any).stateMachine = newMachine
|
||||
existingMachine.events = evts
|
||||
await newMachine.start()
|
||||
@@ -148,7 +153,11 @@ export class StateMachineManager extends EventEmitter {
|
||||
// Inform cycle which means the previous session is dead. Complete the old
|
||||
// machine and create a fresh one for the new session.
|
||||
logger.warn(
|
||||
{ oldSession: existingSessionId, newSession: session, state: existingMachine.currentState },
|
||||
{
|
||||
oldSession: existingSessionId,
|
||||
newSession: session,
|
||||
state: existingMachine.currentState,
|
||||
},
|
||||
'[StateMachineManager] Replacing stale MainStateMachine from dropped session'
|
||||
)
|
||||
this.completeMachine(existingMachine)
|
||||
@@ -179,7 +188,11 @@ export class StateMachineManager extends EventEmitter {
|
||||
} else if (primaryEvent === 'CONNECTION REQUEST') {
|
||||
// CONNECTION REQUEST only if no BOOTSTRAP/BOOT present
|
||||
stateMachine = new ImmediateCommandStateMachine(device, session, this.taskQueue)
|
||||
} else if (primaryEvent === 'PERIODIC' || primaryEvent === 'VALUE CHANGE' || primaryEvent === 'SCHEDULED') {
|
||||
} else if (
|
||||
primaryEvent === 'PERIODIC' ||
|
||||
primaryEvent === 'VALUE CHANGE' ||
|
||||
primaryEvent === 'SCHEDULED'
|
||||
) {
|
||||
// Lightweight MainStateMachine
|
||||
stateMachine = new MainStateMachine(device, session, this.taskQueue, normalizedEvents)
|
||||
} else if (primaryEvent === 'DIAGNOSTICS COMPLETE' || primaryEvent === 'TRANSFER COMPLETE') {
|
||||
@@ -337,7 +350,8 @@ export class StateMachineManager extends EventEmitter {
|
||||
// --- IP-only fallback (CPE reconnected on a new TCP port) ---
|
||||
if (!deviceKey) {
|
||||
const lastColon = remoteKey.lastIndexOf(':')
|
||||
const remoteIp = lastColon > 0 ? remoteKey.slice(0, lastColon).replace(/^\[|\]$/g, '') : remoteKey
|
||||
const remoteIp =
|
||||
lastColon > 0 ? remoteKey.slice(0, lastColon).replace(/^\[|\]$/g, '') : remoteKey
|
||||
|
||||
const candidates: string[] = []
|
||||
for (const [key, dkJson] of this.remoteToDevice.entries()) {
|
||||
@@ -485,7 +499,11 @@ export class StateMachineManager extends EventEmitter {
|
||||
/**
|
||||
* Handle a CWMP response message
|
||||
*/
|
||||
async handleCWMPResponse(session: string, success: boolean, message: import('./../../protocol/tr069_types.js').CWMPTaskResultData): Promise<string> {
|
||||
async handleCWMPResponse(
|
||||
session: string,
|
||||
success: boolean,
|
||||
message: import('./../../protocol/tr069_types.js').CWMPTaskResultData
|
||||
): Promise<string> {
|
||||
const machineId = this.sessionMachines.get(session)
|
||||
if (!machineId) return ''
|
||||
|
||||
@@ -545,7 +563,11 @@ export class StateMachineManager extends EventEmitter {
|
||||
/**
|
||||
* Enqueue an immediate task for a device (e.g., after Inform)
|
||||
*/
|
||||
async enqueueImmediate(device: Device, type: import('./../../protocol/tr069_types.js').CWMPTaskType, payload: import('./../../protocol/tr069_types.js').CWMPTaskPayload): Promise<void> {
|
||||
async enqueueImmediate(
|
||||
device: Device,
|
||||
type: import('./../../protocol/tr069_types.js').CWMPTaskType,
|
||||
payload: import('./../../protocol/tr069_types.js').CWMPTaskPayload
|
||||
): Promise<void> {
|
||||
this.taskQueue.addCWMPTask(device.deviceId, type, payload)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type DeviceId } from '../../domain/device/device_id.js'
|
||||
import { type DeviceId } from '#domain/device/device_id'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { CWMPMessageBuilder } from '../../protocol/common/cwmp_message_builder.js'
|
||||
import { CWMPMessageBuilder } from '#protocol/common/cwmp_message_builder'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import type {
|
||||
CWMPTaskType,
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
DownloadTaskPayload,
|
||||
UploadTaskPayload,
|
||||
RebootTaskPayload,
|
||||
} from '../../protocol/tr069_types.js'
|
||||
} from '#protocol/tr069_types'
|
||||
|
||||
export class TaskResult {
|
||||
status!: ResultStatus
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import DeviceModel from '#models/device'
|
||||
import CommandModel from '#models/command'
|
||||
import { type DeviceRecord } from '../domain/device/device_record.js'
|
||||
import { CommandStatus, CommandType, Priority } from '../domain/commands/command.js'
|
||||
import { ModeType } from '../domain/shared/mode.js'
|
||||
import { type DeviceRecord } from '#domain/device/device_record'
|
||||
import { CommandStatus, CommandType, Priority } from '#domain/commands/command'
|
||||
import { ModeType } from '#domain/shared/mode'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
|
||||
export interface PaginationData {
|
||||
@@ -163,8 +163,12 @@ export default class DeviceService {
|
||||
// Fallback chain: ConnectionRequestUsername > ManagementServer.Username > OUI-ProductClass-Serial
|
||||
const allParams = [...(device.properties || []), ...(device.parameters || [])]
|
||||
|
||||
const connReqUsername = allParams.find((p: any) => p.name?.includes('ConnectionRequestUsername'))
|
||||
const connReqPassword = allParams.find((p: any) => p.name?.includes('ConnectionRequestPassword'))
|
||||
const connReqUsername = allParams.find((p: any) =>
|
||||
p.name?.includes('ConnectionRequestUsername')
|
||||
)
|
||||
const connReqPassword = allParams.find((p: any) =>
|
||||
p.name?.includes('ConnectionRequestPassword')
|
||||
)
|
||||
const mgmtUsername = allParams.find((p: any) => p.name?.endsWith('ManagementServer.Username'))
|
||||
const mgmtPassword = allParams.find((p: any) => p.name?.endsWith('ManagementServer.Password'))
|
||||
|
||||
@@ -211,7 +215,10 @@ export default class DeviceService {
|
||||
)
|
||||
continue // try next URL
|
||||
}
|
||||
logger.info({ status: response.status, url: url.toString() }, 'Connection Request successful')
|
||||
logger.info(
|
||||
{ status: response.status, url: url.toString() },
|
||||
'Connection Request successful'
|
||||
)
|
||||
return true
|
||||
} catch (error: any) {
|
||||
logger.warn(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Db } from 'mongodb'
|
||||
import { type Db } from 'mongodb'
|
||||
import { getMongoDb } from '#providers/mongodb_provider'
|
||||
|
||||
/**
|
||||
@@ -72,7 +72,10 @@ export async function verifyCwmpExchangesIndexes(db?: Db): Promise<IndexVerifica
|
||||
* Runs index verification at startup and logs warnings for missing indexes.
|
||||
* Intended to be called after MongoDB provider has booted.
|
||||
*/
|
||||
export async function verifyIndexesAtStartup(db: Db, log: { warn: (msg: string) => void; info: (msg: string) => void }): Promise<void> {
|
||||
export async function verifyIndexesAtStartup(
|
||||
db: Db,
|
||||
log: { warn: (msg: string) => void; info: (msg: string) => void }
|
||||
): Promise<void> {
|
||||
const result = await verifyCwmpExchangesIndexes(db)
|
||||
|
||||
if (result.allPresent) {
|
||||
@@ -82,6 +85,8 @@ export async function verifyIndexesAtStartup(db: Db, log: { warn: (msg: string)
|
||||
|
||||
const missing = result.indexes.filter((idx) => !idx.exists)
|
||||
for (const idx of missing) {
|
||||
log.warn(`[ExchangeIndexCheck] Missing index on cwmp_exchanges: ${idx.name} (${idx.description})`)
|
||||
log.warn(
|
||||
`[ExchangeIndexCheck] Missing index on cwmp_exchanges: ${idx.name} (${idx.description})`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -53,10 +53,7 @@ export class ExchangeMetricsService {
|
||||
this.maxSamples = maxSamples
|
||||
}
|
||||
|
||||
static getInstance(
|
||||
flushIntervalMs?: number,
|
||||
maxSamples?: number
|
||||
): ExchangeMetricsService {
|
||||
static getInstance(flushIntervalMs?: number, maxSamples?: number): ExchangeMetricsService {
|
||||
if (!ExchangeMetricsService.instance) {
|
||||
ExchangeMetricsService.instance = new ExchangeMetricsService(flushIntervalMs, maxSamples)
|
||||
}
|
||||
@@ -161,10 +158,7 @@ export class ExchangeMetricsService {
|
||||
Object.keys(metrics.apiResponseTimes).length > 0
|
||||
|
||||
if (hasData) {
|
||||
logger.info(
|
||||
{ metrics },
|
||||
'[ExchangeMetrics] Aggregated performance metrics'
|
||||
)
|
||||
logger.info({ metrics }, '[ExchangeMetrics] Aggregated performance metrics')
|
||||
}
|
||||
|
||||
// Reset samples
|
||||
|
||||
@@ -161,7 +161,6 @@ export class ExchangeStoreService {
|
||||
* Internal persist implementation (called within the per-device queue).
|
||||
*/
|
||||
private async doPersistExchange(options: PersistOptions): Promise<CwmpExchange | null> {
|
||||
|
||||
try {
|
||||
let rawXml = options.rawXml || ''
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import FirmwareEntryModel from '#models/firmware_entry'
|
||||
import MigrationRouteModel from '#models/migration_route'
|
||||
import { FirmwareEntry, MigrationRoute } from '../domain/firmware/firmware_catalog.js'
|
||||
import { MigrationStep } from '../domain/firmware/migration_step.js'
|
||||
import { matchGlob } from '../utils/glob_match.js'
|
||||
import { BadRequestException } from '../exceptions/index.js'
|
||||
import { type FirmwareEntry, type MigrationRoute } from '#domain/firmware/firmware_catalog'
|
||||
import { type MigrationStep } from '#domain/firmware/migration_step'
|
||||
import { matchGlob } from '#utils/glob_match'
|
||||
import { BadRequestException } from '#exceptions/index'
|
||||
|
||||
export interface CreateFirmwareEntryDto {
|
||||
identifier: string
|
||||
@@ -125,7 +125,10 @@ export default class FirmwareCatalogService {
|
||||
return MigrationRouteModel.find({}) as unknown as Promise<MigrationRoute[]>
|
||||
}
|
||||
|
||||
async updateEntry(identifier: string, data: Partial<CreateFirmwareEntryDto>): Promise<FirmwareEntry> {
|
||||
async updateEntry(
|
||||
identifier: string,
|
||||
data: Partial<CreateFirmwareEntryDto>
|
||||
): Promise<FirmwareEntry> {
|
||||
const collection = FirmwareEntryModel.getCollection()
|
||||
const result = await collection.findOneAndUpdate(
|
||||
{ identifier },
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import ConfigurationService from './configuration_service.js'
|
||||
import FirmwareCatalogService from './firmware_catalog_service.js'
|
||||
import type { CreateConfigurationDto } from './configuration_service.js'
|
||||
import type { CreateFirmwareEntryDto } from './firmware_catalog_service.js'
|
||||
import { Db } from 'mongodb'
|
||||
import ConfigurationService from '#services/configuration_service'
|
||||
import FirmwareCatalogService from '#services/firmware_catalog_service'
|
||||
import type { CreateConfigurationDto } from '#services/configuration_service'
|
||||
import type { CreateFirmwareEntryDto } from '#services/firmware_catalog_service'
|
||||
import { type Db } from 'mongodb'
|
||||
|
||||
interface MigrationReport {
|
||||
discoveryMigrated: number
|
||||
@@ -23,7 +23,7 @@ export default class MigrationService {
|
||||
constructor(
|
||||
private db: Db,
|
||||
private configService = new ConfigurationService(),
|
||||
private firmwareCatalogService = new FirmwareCatalogService(),
|
||||
private firmwareCatalogService = new FirmwareCatalogService()
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -78,7 +78,9 @@ export default class MigrationService {
|
||||
await this.extractFirmwareEntry(action, legacy)
|
||||
report.firmwareEntriesCreated++
|
||||
} catch (error) {
|
||||
report.errors.push(`Firmware extraction for ${legacy.identifier}: ${(error as Error).message}`)
|
||||
report.errors.push(
|
||||
`Firmware extraction for ${legacy.identifier}: ${(error as Error).message}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,8 +182,16 @@ export default class MigrationService {
|
||||
/**
|
||||
* Build desired parameters from legacy Set Parameter actions
|
||||
*/
|
||||
private buildDesiredParametersFromLegacy(legacy: any): Array<{ name: string; value: string | number | boolean; type: 'string' | 'int' | 'unsignedInt' | 'boolean' | 'dateTime' }> {
|
||||
const params: Array<{ name: string; value: string | number | boolean; type: 'string' | 'int' | 'unsignedInt' | 'boolean' | 'dateTime' }> = []
|
||||
private buildDesiredParametersFromLegacy(legacy: any): Array<{
|
||||
name: string
|
||||
value: string | number | boolean
|
||||
type: 'string' | 'int' | 'unsignedInt' | 'boolean' | 'dateTime'
|
||||
}> {
|
||||
const params: Array<{
|
||||
name: string
|
||||
value: string | number | boolean
|
||||
type: 'string' | 'int' | 'unsignedInt' | 'boolean' | 'dateTime'
|
||||
}> = []
|
||||
|
||||
if (!legacy.actions) return params
|
||||
|
||||
@@ -190,7 +200,12 @@ export default class MigrationService {
|
||||
params.push({
|
||||
name: action.parameter,
|
||||
value: action.value,
|
||||
type: typeof action.value === 'boolean' ? 'boolean' : typeof action.value === 'number' ? 'int' : 'string',
|
||||
type:
|
||||
typeof action.value === 'boolean'
|
||||
? 'boolean'
|
||||
: typeof action.value === 'number'
|
||||
? 'int'
|
||||
: 'string',
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -275,11 +290,12 @@ export default class MigrationService {
|
||||
}
|
||||
|
||||
private buildLegacyProvisioningFromConfig(config: any, originalId: string): any {
|
||||
const actions = config.desiredParameters?.map((param: any) => ({
|
||||
type: 'SET_PARAMETER',
|
||||
parameter: param.name,
|
||||
value: param.value,
|
||||
})) || []
|
||||
const actions =
|
||||
config.desiredParameters?.map((param: any) => ({
|
||||
type: 'SET_PARAMETER',
|
||||
parameter: param.name,
|
||||
value: param.value,
|
||||
})) || []
|
||||
|
||||
return {
|
||||
identifier: originalId,
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { createRequire } from 'node:module'
|
||||
import type { DeviceSnapshot } from '../domain/device/device.js'
|
||||
import type { Device } from '../domain/device/device.js'
|
||||
import { DeviceParameter } from '../domain/device/device_parameter.js'
|
||||
import type { DeviceSnapshot } from '#domain/device/device'
|
||||
import type { Device } from '#domain/device/device'
|
||||
import { DeviceParameter } from '#domain/device/device_parameter'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const tr181Catalog: Array<{ path: string; writable: boolean; deviceClass: string; type?: string }> =
|
||||
require('../../config/tr181-catalog.json')
|
||||
const tr181Catalog: Array<{
|
||||
path: string
|
||||
writable: boolean
|
||||
deviceClass: string
|
||||
type?: string
|
||||
}> = require('../../config/tr181-catalog.json')
|
||||
|
||||
// Index chargé une fois au démarrage — O(1) lookup
|
||||
const catalogIndex = new Map(tr181Catalog.map((e) => [e.path, e.writable]))
|
||||
const catalogTypeIndex = new Map(tr181Catalog.map((e) => [e.path, e.type || 'xsd:string']))
|
||||
const catalogWritableIndex = new Set(
|
||||
tr181Catalog.filter((e) => e.writable).map((e) => e.path)
|
||||
)
|
||||
const catalogWritableIndex = new Set(tr181Catalog.filter((e) => e.writable).map((e) => e.path))
|
||||
|
||||
/** Remplace les segments numériques par {i} pour matcher les entrées du catalogue */
|
||||
export function normalizePath(path: string): string {
|
||||
@@ -69,7 +71,15 @@ export function reclassifyDeviceFromCatalog(device: Device): number {
|
||||
const snap = prop.toSnapshot()
|
||||
const catalogType = catalogTypeIndex.get(normalized) || snap.type
|
||||
device.setParameter(
|
||||
new DeviceParameter(snap.name, snap.value, catalogType as any, snap.createdAt, snap.updatedAt, snap.notification, snap.accessList)
|
||||
new DeviceParameter(
|
||||
snap.name,
|
||||
snap.value,
|
||||
catalogType as any,
|
||||
snap.createdAt,
|
||||
snap.updatedAt,
|
||||
snap.notification,
|
||||
snap.accessList
|
||||
)
|
||||
)
|
||||
;(device as any)._properties.delete(prop.name)
|
||||
reclassified++
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import type { ReferencesConfig } from './types.js'
|
||||
import type { ReferencesConfig } from '#services/references/types'
|
||||
|
||||
const CONFIG_PATH = join(process.cwd(), 'config', 'references.json')
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { DeviceRecord } from '../../../domain/device/device_record.js'
|
||||
import type { DeviceRecord } from '#domain/device/device_record'
|
||||
import type {
|
||||
ReferenceProvider,
|
||||
ResolvedReference,
|
||||
InformContext,
|
||||
ProviderConfig,
|
||||
} from '../types.js'
|
||||
import { ReferenceEngine } from '../reference_engine.js'
|
||||
|
||||
} from '#services/references/types'
|
||||
import { ReferenceEngine } from '#services/references/reference_engine'
|
||||
|
||||
/**
|
||||
* Wassup Provider — resolves customer identity via the Wassup protocol.
|
||||
@@ -33,13 +32,22 @@ export class WassupProvider implements ReferenceProvider {
|
||||
this.timeout = config.timeout || 3000
|
||||
this.resolveBy = config.resolveBy || 'ip'
|
||||
this.service = config.options?.service || 'KRMSCO'
|
||||
this.requestedProperties = config.options?.requestedProperties || ['sai', 'spr', 'clo', 'puid', 'utz']
|
||||
this.requestedProperties = config.options?.requestedProperties || [
|
||||
'sai',
|
||||
'spr',
|
||||
'clo',
|
||||
'puid',
|
||||
'utz',
|
||||
]
|
||||
}
|
||||
|
||||
async resolve(device: DeviceRecord, context: InformContext): Promise<ResolvedReference | null> {
|
||||
const resolveValue = ReferenceEngine.extractResolveValue(device, this.resolveBy, context)
|
||||
if (!resolveValue) {
|
||||
console.debug({ provider: this.name, resolveBy: this.resolveBy }, '[Wassup] Cannot resolve: no value')
|
||||
console.debug(
|
||||
{ provider: this.name, resolveBy: this.resolveBy },
|
||||
'[Wassup] Cannot resolve: no value'
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -53,7 +61,7 @@ export class WassupProvider implements ReferenceProvider {
|
||||
const response = await fetch(wassupUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X_WASSUP_USER_IP': resolveValue,
|
||||
X_WASSUP_USER_IP: resolveValue,
|
||||
},
|
||||
signal: controller.signal,
|
||||
})
|
||||
@@ -99,13 +107,13 @@ export class WassupProvider implements ReferenceProvider {
|
||||
// Map X_WASSUP_* keys to short names
|
||||
const properties: Record<string, string> = {}
|
||||
const keyMapping: Record<string, string> = {
|
||||
'X_WASSUP_SAI': 'sai',
|
||||
'X_WASSUP_SPR': 'spr',
|
||||
'X_WASSUP_CLO': 'clo',
|
||||
'X_WASSUP_PUID': 'puid',
|
||||
'X_WASSUP_UTZ': 'utz',
|
||||
'X_WASSUP_IUR': 'iur',
|
||||
'X_WASSUP_IUS': 'ius',
|
||||
X_WASSUP_SAI: 'sai',
|
||||
X_WASSUP_SPR: 'spr',
|
||||
X_WASSUP_CLO: 'clo',
|
||||
X_WASSUP_PUID: 'puid',
|
||||
X_WASSUP_UTZ: 'utz',
|
||||
X_WASSUP_IUR: 'iur',
|
||||
X_WASSUP_IUS: 'ius',
|
||||
}
|
||||
|
||||
for (const [wassupKey, shortKey] of Object.entries(keyMapping)) {
|
||||
@@ -114,7 +122,14 @@ export class WassupProvider implements ReferenceProvider {
|
||||
}
|
||||
}
|
||||
|
||||
console.info({ provider: this.name, resolveBy: `${this.resolveBy}:${resolveValue}`, keys: Object.keys(properties) }, '[Wassup] Resolved')
|
||||
console.info(
|
||||
{
|
||||
provider: this.name,
|
||||
resolveBy: `${this.resolveBy}:${resolveValue}`,
|
||||
keys: Object.keys(properties),
|
||||
},
|
||||
'[Wassup] Resolved'
|
||||
)
|
||||
|
||||
return {
|
||||
provider: this.name,
|
||||
@@ -125,7 +140,10 @@ export class WassupProvider implements ReferenceProvider {
|
||||
}
|
||||
} catch (error: any) {
|
||||
const isTimeout = error.name === 'AbortError'
|
||||
console.error({ provider: this.name, error: error.message, isTimeout }, '[Wassup] Resolution failed')
|
||||
console.error(
|
||||
{ provider: this.name, error: error.message, isTimeout },
|
||||
'[Wassup] Resolution failed'
|
||||
)
|
||||
|
||||
return {
|
||||
provider: this.name,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { DeviceRecord } from '../../domain/device/device_record.js'
|
||||
import type { DeviceRecord } from '#domain/device/device_record'
|
||||
import type {
|
||||
ReferenceProvider,
|
||||
ResolvedReference,
|
||||
InformContext,
|
||||
ProviderConfig,
|
||||
} from './types.js'
|
||||
import { TRIGGER_EVENT_MAP } from './types.js'
|
||||
import { loadReferencesConfig } from './config_loader.js'
|
||||
import { WassupProvider } from './providers/wassup_provider.js'
|
||||
} from '#services/references/types'
|
||||
import { TRIGGER_EVENT_MAP } from '#services/references/types'
|
||||
import { loadReferencesConfig } from '#services/references/config_loader'
|
||||
import { WassupProvider } from '#services/references/providers/wassup_provider'
|
||||
|
||||
export class ReferenceEngine {
|
||||
private providers: ReferenceProvider[] = []
|
||||
@@ -27,7 +27,10 @@ export class ReferenceEngine {
|
||||
const provider = this.createProvider(providerConfig)
|
||||
if (provider) {
|
||||
this.providers.push(provider)
|
||||
console.info({ name: providerConfig.name, type: providerConfig.type }, '[References] Provider registered')
|
||||
console.info(
|
||||
{ name: providerConfig.name, type: providerConfig.type },
|
||||
'[References] Provider registered'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,13 +44,19 @@ export class ReferenceEngine {
|
||||
console.warn({ name: config.name }, '[References] http-json provider not yet implemented')
|
||||
return null
|
||||
case 'http-properties':
|
||||
console.warn({ name: config.name }, '[References] http-properties provider not yet implemented')
|
||||
console.warn(
|
||||
{ name: config.name },
|
||||
'[References] http-properties provider not yet implemented'
|
||||
)
|
||||
return null
|
||||
case 'static':
|
||||
console.warn({ name: config.name }, '[References] static provider not yet implemented')
|
||||
return null
|
||||
default:
|
||||
console.error({ name: config.name, type: config.type }, '[References] Unknown provider type')
|
||||
console.error(
|
||||
{ name: config.name, type: config.type },
|
||||
'[References] Unknown provider type'
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -60,9 +69,7 @@ export class ReferenceEngine {
|
||||
const applicable = this.getApplicableProviders(context.events)
|
||||
if (applicable.length === 0) return []
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
applicable.map((p) => p.resolve(device, context))
|
||||
)
|
||||
const results = await Promise.allSettled(applicable.map((p) => p.resolve(device, context)))
|
||||
|
||||
const resolved: ResolvedReference[] = []
|
||||
for (const result of results) {
|
||||
@@ -79,7 +86,11 @@ export class ReferenceEngine {
|
||||
/**
|
||||
* Resolve a single provider by name.
|
||||
*/
|
||||
async resolveOne(providerName: string, device: DeviceRecord, context: InformContext): Promise<ResolvedReference | null> {
|
||||
async resolveOne(
|
||||
providerName: string,
|
||||
device: DeviceRecord,
|
||||
context: InformContext
|
||||
): Promise<ResolvedReference | null> {
|
||||
const provider = this.providers.find((p) => p.name === providerName)
|
||||
if (!provider) {
|
||||
console.warn({ providerName }, '[References] Provider not found')
|
||||
@@ -112,7 +123,11 @@ export class ReferenceEngine {
|
||||
/**
|
||||
* Extracts the resolve value from a device based on the resolveBy config.
|
||||
*/
|
||||
static extractResolveValue(device: DeviceRecord, resolveBy: string, context: InformContext): string | null {
|
||||
static extractResolveValue(
|
||||
device: DeviceRecord,
|
||||
resolveBy: string,
|
||||
context: InformContext
|
||||
): string | null {
|
||||
switch (resolveBy) {
|
||||
case 'ip':
|
||||
return context.remoteIp || null
|
||||
@@ -123,14 +138,14 @@ export class ReferenceEngine {
|
||||
case 'mac': {
|
||||
const allParams = [...(device.properties || []), ...(device.parameters || [])]
|
||||
const macParam = allParams.find((p) => p.name?.includes('MACAddress'))
|
||||
return macParam?.value as string || null
|
||||
return (macParam?.value as string) || null
|
||||
}
|
||||
default:
|
||||
if (resolveBy.startsWith('parameter:')) {
|
||||
const path = resolveBy.slice('parameter:'.length)
|
||||
const allParams = [...(device.properties || []), ...(device.parameters || [])]
|
||||
const param = allParams.find((p) => p.name === path)
|
||||
return param?.value as string || null
|
||||
return (param?.value as string) || null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* from N independent providers (Wassup, CRM, NOC, etc.)
|
||||
*/
|
||||
|
||||
import type { DeviceRecord } from '../../domain/device/device_record.js'
|
||||
import type { DeviceRecord } from '#domain/device/device_record'
|
||||
|
||||
// ─── Provider Interface ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -12,13 +12,15 @@ export function initTelemetry() {
|
||||
// OpenTelemetry setup is optional - only initialize when available
|
||||
import('@opentelemetry/sdk-node')
|
||||
.then(({ NodeSDK }) => {
|
||||
return import('@opentelemetry/auto-instrumentations-node').then(({ getNodeAutoInstrumentations }) => {
|
||||
const sdk = new NodeSDK({
|
||||
serviceName: env.get('OTEL_SERVICE_NAME', 'papaye-v2'),
|
||||
instrumentations: [getNodeAutoInstrumentations()],
|
||||
})
|
||||
sdk.start()
|
||||
})
|
||||
return import('@opentelemetry/auto-instrumentations-node').then(
|
||||
({ getNodeAutoInstrumentations }) => {
|
||||
const sdk = new NodeSDK({
|
||||
serviceName: env.get('OTEL_SERVICE_NAME', 'papaye-v2'),
|
||||
instrumentations: [getNodeAutoInstrumentations()],
|
||||
})
|
||||
sdk.start()
|
||||
}
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
// OpenTelemetry not available, skip
|
||||
|
||||
@@ -98,12 +98,19 @@ export class Tr069TraceService {
|
||||
|
||||
writeExchange(deviceId: string, direction: TraceDirection, sessionId: string, xml: string): void {
|
||||
const tail = this.writeQueues.get(deviceId) ?? Promise.resolve()
|
||||
const next = tail.then(() => this.doWrite(deviceId, direction, sessionId, xml)).catch(() => undefined)
|
||||
const next = tail
|
||||
.then(() => this.doWrite(deviceId, direction, sessionId, xml))
|
||||
.catch(() => undefined)
|
||||
|
||||
this.writeQueues.set(deviceId, next)
|
||||
}
|
||||
|
||||
private async doWrite(deviceId: string, direction: TraceDirection, sessionId: string, xml: string): Promise<void> {
|
||||
private async doWrite(
|
||||
deviceId: string,
|
||||
direction: TraceDirection,
|
||||
sessionId: string,
|
||||
xml: string
|
||||
): Promise<void> {
|
||||
mkdirSync(LOG_DIR, { recursive: true })
|
||||
|
||||
const filePath = join(LOG_DIR, `${deviceId}.log`)
|
||||
|
||||
+20
-10
@@ -31,16 +31,26 @@ export function normalizeEventCode(raw: string): string {
|
||||
|
||||
// Numeric-only → expand to standard label
|
||||
switch (trimmed) {
|
||||
case '0': return 'BOOTSTRAP'
|
||||
case '1': return 'BOOT'
|
||||
case '2': return 'PERIODIC'
|
||||
case '3': return 'SCHEDULED'
|
||||
case '4': return 'VALUE CHANGE'
|
||||
case '5': return 'KICKED'
|
||||
case '6': return 'CONNECTION REQUEST'
|
||||
case '7': return 'TRANSFER COMPLETE'
|
||||
case '8': return 'DIAGNOSTICS COMPLETE'
|
||||
default: return trimmed
|
||||
case '0':
|
||||
return 'BOOTSTRAP'
|
||||
case '1':
|
||||
return 'BOOT'
|
||||
case '2':
|
||||
return 'PERIODIC'
|
||||
case '3':
|
||||
return 'SCHEDULED'
|
||||
case '4':
|
||||
return 'VALUE CHANGE'
|
||||
case '5':
|
||||
return 'KICKED'
|
||||
case '6':
|
||||
return 'CONNECTION REQUEST'
|
||||
case '7':
|
||||
return 'TRANSFER COMPLETE'
|
||||
case '8':
|
||||
return 'DIAGNOSTICS COMPLETE'
|
||||
default:
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { InventoryPath } from '../domain/configuration/configuration.js'
|
||||
import type { InventoryPath } from '#domain/configuration/configuration'
|
||||
|
||||
/**
|
||||
* Checks whether a desired parameter is covered by at least one inventory path.
|
||||
|
||||
@@ -11,17 +11,17 @@ export function computeNextPeriodicInform(
|
||||
): Date | null {
|
||||
if (!lastPeriodicInformAt) return null
|
||||
|
||||
const intervalSeconds = typeof periodicInformInterval === 'string'
|
||||
? parseInt(periodicInformInterval, 10)
|
||||
: periodicInformInterval
|
||||
const intervalSeconds =
|
||||
typeof periodicInformInterval === 'string'
|
||||
? Number.parseInt(periodicInformInterval, 10)
|
||||
: periodicInformInterval
|
||||
|
||||
if (!intervalSeconds || intervalSeconds <= 0) return null
|
||||
|
||||
const lastDate = lastPeriodicInformAt instanceof Date
|
||||
? lastPeriodicInformAt
|
||||
: new Date(lastPeriodicInformAt)
|
||||
const lastDate =
|
||||
lastPeriodicInformAt instanceof Date ? lastPeriodicInformAt : new Date(lastPeriodicInformAt)
|
||||
|
||||
if (isNaN(lastDate.getTime())) return null
|
||||
if (Number.isNaN(lastDate.getTime())) return null
|
||||
|
||||
return new Date(lastDate.getTime() + intervalSeconds * 1000)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import type { Configuration } from '../domain/configuration/configuration.js'
|
||||
import type { Configuration } from '#domain/configuration/configuration'
|
||||
|
||||
/**
|
||||
* Computes a 12-character hex content hash of a Configuration's desired state.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ScheduleWindow } from '../domain/configuration/configuration.js'
|
||||
import type { ScheduleWindow } from '#domain/configuration/configuration'
|
||||
|
||||
/**
|
||||
* Checks if the current time (or provided time) falls within the schedule window.
|
||||
@@ -41,10 +41,7 @@ export function isWithinSchedule(schedule: ScheduleWindow | undefined, now?: Dat
|
||||
/**
|
||||
* Gets the local hour and day-of-week for a given Date in a specific IANA timezone.
|
||||
*/
|
||||
function getLocalTimeParts(
|
||||
date: Date,
|
||||
timezone: string
|
||||
): { hour: number; dayOfWeek: number } {
|
||||
function getLocalTimeParts(date: Date, timezone: string): { hour: number; dayOfWeek: number } {
|
||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: timezone,
|
||||
hour: 'numeric',
|
||||
|
||||
@@ -37,9 +37,15 @@ const scheduleWindowSchema = vine.object({
|
||||
|
||||
export const createConfigurationValidator = vine.compile(
|
||||
vine.object({
|
||||
identifier: vine.string().trim().regex(/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/),
|
||||
identifier: vine
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/),
|
||||
name: vine.string().trim().minLength(1),
|
||||
version: vine.string().trim().regex(/^\d+\.\d+\.\d+$/),
|
||||
version: vine
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^\d+\.\d+\.\d+$/),
|
||||
priority: vine.number().positive(),
|
||||
policy: vine.enum(['once', 'maintain']),
|
||||
enabled: vine.boolean().optional(),
|
||||
@@ -55,7 +61,11 @@ export const createConfigurationValidator = vine.compile(
|
||||
export const updateConfigurationValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().trim().minLength(1).optional(),
|
||||
version: vine.string().trim().regex(/^\d+\.\d+\.\d+$/).optional(),
|
||||
version: vine
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^\d+\.\d+\.\d+$/)
|
||||
.optional(),
|
||||
priority: vine.number().positive().optional(),
|
||||
policy: vine.enum(['once', 'maintain']).optional(),
|
||||
enabled: vine.boolean().optional(),
|
||||
|
||||
@@ -2,7 +2,10 @@ import vine from '@vinejs/vine'
|
||||
|
||||
export const createFirmwareEntryValidator = vine.compile(
|
||||
vine.object({
|
||||
identifier: vine.string().trim().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
||||
identifier: vine
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
||||
productClass: vine.string().trim().minLength(1),
|
||||
version: vine.string().trim().minLength(1),
|
||||
downloadUrl: vine.string().trim().url(),
|
||||
@@ -25,7 +28,10 @@ export const updateFirmwareEntryValidator = vine.compile(
|
||||
|
||||
export const createMigrationRouteValidator = vine.compile(
|
||||
vine.object({
|
||||
identifier: vine.string().trim().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
||||
identifier: vine
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
||||
productClass: vine.string().trim().minLength(1),
|
||||
sourceVersionPattern: vine.string().trim().minLength(1),
|
||||
targetVersion: vine.string().trim().minLength(1),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class CreateDevicesCollection extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class CreateCommandsCollection extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class CreateDiscoveryConfigsCollection extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class CreateProvisioningConfigsCollection extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class CreateConfigurationsCollection extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class CreateConfigurationVersionsCollection extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class CreateFirmwareEntriesCollection extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class CreateMigrationRoutesCollection extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class AddComplianceToDevices extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class DropLegacyCollections extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoMigration } from '../mongo_migrator.js'
|
||||
import { MongoMigration } from '#database/mongo_migrator'
|
||||
|
||||
export default class CreateCwmpExchangesCollection extends MongoMigration {
|
||||
async up(): Promise<void> {
|
||||
|
||||
+4
-1
@@ -41,7 +41,10 @@
|
||||
"#database/*": "./database/*.js",
|
||||
"#tests/*": "./tests/*.js",
|
||||
"#start/*": "./start/*.js",
|
||||
"#config/*": "./config/*.js"
|
||||
"#config/*": "./config/*.js",
|
||||
"#domain/*": "./app/domain/*.js",
|
||||
"#protocol/*": "./app/protocol/*.js",
|
||||
"#utils/*": "./app/utils/*.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@adonisjs/assembler": "^8.4.0",
|
||||
|
||||
Reference in new issue
Block a user