feat: TR-069 per-device SOAP trace logging + log viewer UI

- Remove SOAP from Docker stdout; route to per-device trace files only
- Fix trace service paths using process.cwd() (/app/data/, /app/logs/tr069/)
- Fix Docker volume permissions: dedicated /app/data & /app/logs/tr069 dirs
- Add GET /api/v1/tr069-trace/:id/log endpoint (tail N lines)
- Rewrite Tr069Trace.vue: clickable rows, dark log viewer, 3s auto-refresh
- Add simulator-ui-env.js to public/ (remove from config/)
- TR-181/InternetGatewayDevice dataModel auto-detection
- Parameter writability config with W/R fallback default
- Fix device_action_executor for GetParameterNames detection flow

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Kevin L'HereecandCopilot committed 2026-06-01 14:33:44 +02:00
1 parent 847da1cdf5
commit 6082a0e610
23 files changed
+1360 -180

No files matched your search

-5
View File
@@ -5,11 +5,6 @@
/// <reference path="./manifest.d.ts" />
import type { InferData, InferVariants } from '@adonisjs/core/types/transformers'
import type UserTransformer from '#transformers/user_transformer'
export namespace Data {
export type User = InferData<UserTransformer>
export namespace User {
export type Variants = InferVariants<UserTransformer>
}
}
-1
View File
@@ -4,6 +4,5 @@
*/
/// <reference path="../../adonisrc.ts" />
/// <reference path="../../config/auth.ts" />
/// <reference path="../../config/hash.ts" />
/// <reference path="../../config/logger.ts" />
+306 -18
View File
@@ -6,29 +6,317 @@ import type { ApiDefinition } from './tree.d.ts'
const placeholder: any = {}
const routes = {
'auth.new_account.store': {
'cwmp': {
methods: ["POST"],
pattern: '/api/v1/auth/signup',
tokens: [{"old":"/api/v1/auth/signup","type":0,"val":"api","end":""},{"old":"/api/v1/auth/signup","type":0,"val":"v1","end":""},{"old":"/api/v1/auth/signup","type":0,"val":"auth","end":""},{"old":"/api/v1/auth/signup","type":0,"val":"signup","end":""}],
types: placeholder as Registry['auth.new_account.store']['types'],
pattern: '/cwmp10',
tokens: [{"old":"/cwmp10","type":0,"val":"cwmp10","end":""}],
types: placeholder as Registry['cwmp']['types'],
},
'auth.access_tokens.store': {
methods: ["POST"],
pattern: '/api/v1/auth/login',
tokens: [{"old":"/api/v1/auth/login","type":0,"val":"api","end":""},{"old":"/api/v1/auth/login","type":0,"val":"v1","end":""},{"old":"/api/v1/auth/login","type":0,"val":"auth","end":""},{"old":"/api/v1/auth/login","type":0,"val":"login","end":""}],
types: placeholder as Registry['auth.access_tokens.store']['types'],
},
'profile.profile.show': {
'health.checklife': {
methods: ["GET","HEAD"],
pattern: '/api/v1/account/profile',
tokens: [{"old":"/api/v1/account/profile","type":0,"val":"api","end":""},{"old":"/api/v1/account/profile","type":0,"val":"v1","end":""},{"old":"/api/v1/account/profile","type":0,"val":"account","end":""},{"old":"/api/v1/account/profile","type":0,"val":"profile","end":""}],
types: placeholder as Registry['profile.profile.show']['types'],
pattern: '/api/v1/checklife',
tokens: [{"old":"/api/v1/checklife","type":0,"val":"api","end":""},{"old":"/api/v1/checklife","type":0,"val":"v1","end":""},{"old":"/api/v1/checklife","type":0,"val":"checklife","end":""}],
types: placeholder as Registry['health.checklife']['types'],
},
'profile.access_tokens.destroy': {
'health.checkservice': {
methods: ["GET","HEAD"],
pattern: '/api/v1/checkservice',
tokens: [{"old":"/api/v1/checkservice","type":0,"val":"api","end":""},{"old":"/api/v1/checkservice","type":0,"val":"v1","end":""},{"old":"/api/v1/checkservice","type":0,"val":"checkservice","end":""}],
types: placeholder as Registry['health.checkservice']['types'],
},
'devices.index': {
methods: ["GET","HEAD"],
pattern: '/api/v1/devices',
tokens: [{"old":"/api/v1/devices","type":0,"val":"api","end":""},{"old":"/api/v1/devices","type":0,"val":"v1","end":""},{"old":"/api/v1/devices","type":0,"val":"devices","end":""}],
types: placeholder as Registry['devices.index']['types'],
},
'devices.criteria_values': {
methods: ["GET","HEAD"],
pattern: '/api/v1/devices/criteria-values',
tokens: [{"old":"/api/v1/devices/criteria-values","type":0,"val":"api","end":""},{"old":"/api/v1/devices/criteria-values","type":0,"val":"v1","end":""},{"old":"/api/v1/devices/criteria-values","type":0,"val":"devices","end":""},{"old":"/api/v1/devices/criteria-values","type":0,"val":"criteria-values","end":""}],
types: placeholder as Registry['devices.criteria_values']['types'],
},
'devices.show': {
methods: ["GET","HEAD"],
pattern: '/api/v1/devices/:identifier',
tokens: [{"old":"/api/v1/devices/:identifier","type":0,"val":"api","end":""},{"old":"/api/v1/devices/:identifier","type":0,"val":"v1","end":""},{"old":"/api/v1/devices/:identifier","type":0,"val":"devices","end":""},{"old":"/api/v1/devices/:identifier","type":1,"val":"identifier","end":""}],
types: placeholder as Registry['devices.show']['types'],
},
'devices.destroy': {
methods: ["DELETE"],
pattern: '/api/v1/devices/:identifier',
tokens: [{"old":"/api/v1/devices/:identifier","type":0,"val":"api","end":""},{"old":"/api/v1/devices/:identifier","type":0,"val":"v1","end":""},{"old":"/api/v1/devices/:identifier","type":0,"val":"devices","end":""},{"old":"/api/v1/devices/:identifier","type":1,"val":"identifier","end":""}],
types: placeholder as Registry['devices.destroy']['types'],
},
'devices.execute_command': {
methods: ["POST"],
pattern: '/api/v1/account/logout',
tokens: [{"old":"/api/v1/account/logout","type":0,"val":"api","end":""},{"old":"/api/v1/account/logout","type":0,"val":"v1","end":""},{"old":"/api/v1/account/logout","type":0,"val":"account","end":""},{"old":"/api/v1/account/logout","type":0,"val":"logout","end":""}],
types: placeholder as Registry['profile.access_tokens.destroy']['types'],
pattern: '/api/v1/devices/:identifier/command',
tokens: [{"old":"/api/v1/devices/:identifier/command","type":0,"val":"api","end":""},{"old":"/api/v1/devices/:identifier/command","type":0,"val":"v1","end":""},{"old":"/api/v1/devices/:identifier/command","type":0,"val":"devices","end":""},{"old":"/api/v1/devices/:identifier/command","type":1,"val":"identifier","end":""},{"old":"/api/v1/devices/:identifier/command","type":0,"val":"command","end":""}],
types: placeholder as Registry['devices.execute_command']['types'],
},
'devices.refresh': {
methods: ["POST"],
pattern: '/api/v1/devices/:identifier/refresh',
tokens: [{"old":"/api/v1/devices/:identifier/refresh","type":0,"val":"api","end":""},{"old":"/api/v1/devices/:identifier/refresh","type":0,"val":"v1","end":""},{"old":"/api/v1/devices/:identifier/refresh","type":0,"val":"devices","end":""},{"old":"/api/v1/devices/:identifier/refresh","type":1,"val":"identifier","end":""},{"old":"/api/v1/devices/:identifier/refresh","type":0,"val":"refresh","end":""}],
types: placeholder as Registry['devices.refresh']['types'],
},
'commands.index': {
methods: ["GET","HEAD"],
pattern: '/api/v1/commands',
tokens: [{"old":"/api/v1/commands","type":0,"val":"api","end":""},{"old":"/api/v1/commands","type":0,"val":"v1","end":""},{"old":"/api/v1/commands","type":0,"val":"commands","end":""}],
types: placeholder as Registry['commands.index']['types'],
},
'commands.show': {
methods: ["GET","HEAD"],
pattern: '/api/v1/commands/:id',
tokens: [{"old":"/api/v1/commands/:id","type":0,"val":"api","end":""},{"old":"/api/v1/commands/:id","type":0,"val":"v1","end":""},{"old":"/api/v1/commands/:id","type":0,"val":"commands","end":""},{"old":"/api/v1/commands/:id","type":1,"val":"id","end":""}],
types: placeholder as Registry['commands.show']['types'],
},
'commands.store': {
methods: ["POST"],
pattern: '/api/v1/commands',
tokens: [{"old":"/api/v1/commands","type":0,"val":"api","end":""},{"old":"/api/v1/commands","type":0,"val":"v1","end":""},{"old":"/api/v1/commands","type":0,"val":"commands","end":""}],
types: placeholder as Registry['commands.store']['types'],
},
'tr_069_trace.index': {
methods: ["GET","HEAD"],
pattern: '/api/v1/tr069-trace',
tokens: [{"old":"/api/v1/tr069-trace","type":0,"val":"api","end":""},{"old":"/api/v1/tr069-trace","type":0,"val":"v1","end":""},{"old":"/api/v1/tr069-trace","type":0,"val":"tr069-trace","end":""}],
types: placeholder as Registry['tr_069_trace.index']['types'],
},
'tr_069_trace.store': {
methods: ["POST"],
pattern: '/api/v1/tr069-trace',
tokens: [{"old":"/api/v1/tr069-trace","type":0,"val":"api","end":""},{"old":"/api/v1/tr069-trace","type":0,"val":"v1","end":""},{"old":"/api/v1/tr069-trace","type":0,"val":"tr069-trace","end":""}],
types: placeholder as Registry['tr_069_trace.store']['types'],
},
'tr_069_trace.destroy': {
methods: ["DELETE"],
pattern: '/api/v1/tr069-trace/:id',
tokens: [{"old":"/api/v1/tr069-trace/:id","type":0,"val":"api","end":""},{"old":"/api/v1/tr069-trace/:id","type":0,"val":"v1","end":""},{"old":"/api/v1/tr069-trace/:id","type":0,"val":"tr069-trace","end":""},{"old":"/api/v1/tr069-trace/:id","type":1,"val":"id","end":""}],
types: placeholder as Registry['tr_069_trace.destroy']['types'],
},
'configurations_v_1.index': {
methods: ["GET","HEAD"],
pattern: '/api/v1/configurations',
tokens: [{"old":"/api/v1/configurations","type":0,"val":"api","end":""},{"old":"/api/v1/configurations","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations","type":0,"val":"configurations","end":""}],
types: placeholder as Registry['configurations_v_1.index']['types'],
},
'configurations_v_1.list_tags': {
methods: ["GET","HEAD"],
pattern: '/api/v1/configurations/tags',
tokens: [{"old":"/api/v1/configurations/tags","type":0,"val":"api","end":""},{"old":"/api/v1/configurations/tags","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations/tags","type":0,"val":"configurations","end":""},{"old":"/api/v1/configurations/tags","type":0,"val":"tags","end":""}],
types: placeholder as Registry['configurations_v_1.list_tags']['types'],
},
'configurations_v_1.store': {
methods: ["POST"],
pattern: '/api/v1/configurations',
tokens: [{"old":"/api/v1/configurations","type":0,"val":"api","end":""},{"old":"/api/v1/configurations","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations","type":0,"val":"configurations","end":""}],
types: placeholder as Registry['configurations_v_1.store']['types'],
},
'configurations_v_1.show': {
methods: ["GET","HEAD"],
pattern: '/api/v1/configurations/:id',
tokens: [{"old":"/api/v1/configurations/:id","type":0,"val":"api","end":""},{"old":"/api/v1/configurations/:id","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations/:id","type":0,"val":"configurations","end":""},{"old":"/api/v1/configurations/:id","type":1,"val":"id","end":""}],
types: placeholder as Registry['configurations_v_1.show']['types'],
},
'configurations_v_1.update': {
methods: ["PUT"],
pattern: '/api/v1/configurations/:id',
tokens: [{"old":"/api/v1/configurations/:id","type":0,"val":"api","end":""},{"old":"/api/v1/configurations/:id","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations/:id","type":0,"val":"configurations","end":""},{"old":"/api/v1/configurations/:id","type":1,"val":"id","end":""}],
types: placeholder as Registry['configurations_v_1.update']['types'],
},
'configurations_v_1.destroy': {
methods: ["DELETE"],
pattern: '/api/v1/configurations/:id',
tokens: [{"old":"/api/v1/configurations/:id","type":0,"val":"api","end":""},{"old":"/api/v1/configurations/:id","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations/:id","type":0,"val":"configurations","end":""},{"old":"/api/v1/configurations/:id","type":1,"val":"id","end":""}],
types: placeholder as Registry['configurations_v_1.destroy']['types'],
},
'configurations_v_1.toggle_enabled': {
methods: ["PATCH"],
pattern: '/api/v1/configurations/:id/toggle',
tokens: [{"old":"/api/v1/configurations/:id/toggle","type":0,"val":"api","end":""},{"old":"/api/v1/configurations/:id/toggle","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations/:id/toggle","type":0,"val":"configurations","end":""},{"old":"/api/v1/configurations/:id/toggle","type":1,"val":"id","end":""},{"old":"/api/v1/configurations/:id/toggle","type":0,"val":"toggle","end":""}],
types: placeholder as Registry['configurations_v_1.toggle_enabled']['types'],
},
'configurations_v_1.manage_tags': {
methods: ["PATCH"],
pattern: '/api/v1/configurations/:id/tags',
tokens: [{"old":"/api/v1/configurations/:id/tags","type":0,"val":"api","end":""},{"old":"/api/v1/configurations/:id/tags","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations/:id/tags","type":0,"val":"configurations","end":""},{"old":"/api/v1/configurations/:id/tags","type":1,"val":"id","end":""},{"old":"/api/v1/configurations/:id/tags","type":0,"val":"tags","end":""}],
types: placeholder as Registry['configurations_v_1.manage_tags']['types'],
},
'configurations_v_1.history': {
methods: ["GET","HEAD"],
pattern: '/api/v1/configurations/:id/history',
tokens: [{"old":"/api/v1/configurations/:id/history","type":0,"val":"api","end":""},{"old":"/api/v1/configurations/:id/history","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations/:id/history","type":0,"val":"configurations","end":""},{"old":"/api/v1/configurations/:id/history","type":1,"val":"id","end":""},{"old":"/api/v1/configurations/:id/history","type":0,"val":"history","end":""}],
types: placeholder as Registry['configurations_v_1.history']['types'],
},
'configurations_v_1.dry_run': {
methods: ["POST"],
pattern: '/api/v1/configurations/:id/dry-run',
tokens: [{"old":"/api/v1/configurations/:id/dry-run","type":0,"val":"api","end":""},{"old":"/api/v1/configurations/:id/dry-run","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations/:id/dry-run","type":0,"val":"configurations","end":""},{"old":"/api/v1/configurations/:id/dry-run","type":1,"val":"id","end":""},{"old":"/api/v1/configurations/:id/dry-run","type":0,"val":"dry-run","end":""}],
types: placeholder as Registry['configurations_v_1.dry_run']['types'],
},
'configurations_v_1.compliance': {
methods: ["GET","HEAD"],
pattern: '/api/v1/configurations/:id/compliance',
tokens: [{"old":"/api/v1/configurations/:id/compliance","type":0,"val":"api","end":""},{"old":"/api/v1/configurations/:id/compliance","type":0,"val":"v1","end":""},{"old":"/api/v1/configurations/:id/compliance","type":0,"val":"configurations","end":""},{"old":"/api/v1/configurations/:id/compliance","type":1,"val":"id","end":""},{"old":"/api/v1/configurations/:id/compliance","type":0,"val":"compliance","end":""}],
types: placeholder as Registry['configurations_v_1.compliance']['types'],
},
'firmware_catalog.index': {
methods: ["GET","HEAD"],
pattern: '/api/v1/firmware-catalog',
tokens: [{"old":"/api/v1/firmware-catalog","type":0,"val":"api","end":""},{"old":"/api/v1/firmware-catalog","type":0,"val":"v1","end":""},{"old":"/api/v1/firmware-catalog","type":0,"val":"firmware-catalog","end":""}],
types: placeholder as Registry['firmware_catalog.index']['types'],
},
'firmware_catalog.list_entries': {
methods: ["GET","HEAD"],
pattern: '/api/v1/firmware-catalog/entries',
tokens: [{"old":"/api/v1/firmware-catalog/entries","type":0,"val":"api","end":""},{"old":"/api/v1/firmware-catalog/entries","type":0,"val":"v1","end":""},{"old":"/api/v1/firmware-catalog/entries","type":0,"val":"firmware-catalog","end":""},{"old":"/api/v1/firmware-catalog/entries","type":0,"val":"entries","end":""}],
types: placeholder as Registry['firmware_catalog.list_entries']['types'],
},
'firmware_catalog.show_entry': {
methods: ["GET","HEAD"],
pattern: '/api/v1/firmware-catalog/entries/:id',
tokens: [{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"api","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"v1","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"firmware-catalog","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"entries","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":1,"val":"id","end":""}],
types: placeholder as Registry['firmware_catalog.show_entry']['types'],
},
'firmware_catalog.list_routes': {
methods: ["GET","HEAD"],
pattern: '/api/v1/firmware-catalog/routes',
tokens: [{"old":"/api/v1/firmware-catalog/routes","type":0,"val":"api","end":""},{"old":"/api/v1/firmware-catalog/routes","type":0,"val":"v1","end":""},{"old":"/api/v1/firmware-catalog/routes","type":0,"val":"firmware-catalog","end":""},{"old":"/api/v1/firmware-catalog/routes","type":0,"val":"routes","end":""}],
types: placeholder as Registry['firmware_catalog.list_routes']['types'],
},
'firmware_catalog.create_entry': {
methods: ["POST"],
pattern: '/api/v1/firmware-catalog/entries',
tokens: [{"old":"/api/v1/firmware-catalog/entries","type":0,"val":"api","end":""},{"old":"/api/v1/firmware-catalog/entries","type":0,"val":"v1","end":""},{"old":"/api/v1/firmware-catalog/entries","type":0,"val":"firmware-catalog","end":""},{"old":"/api/v1/firmware-catalog/entries","type":0,"val":"entries","end":""}],
types: placeholder as Registry['firmware_catalog.create_entry']['types'],
},
'firmware_catalog.update_entry': {
methods: ["PUT"],
pattern: '/api/v1/firmware-catalog/entries/:id',
tokens: [{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"api","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"v1","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"firmware-catalog","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"entries","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":1,"val":"id","end":""}],
types: placeholder as Registry['firmware_catalog.update_entry']['types'],
},
'firmware_catalog.delete_entry': {
methods: ["DELETE"],
pattern: '/api/v1/firmware-catalog/entries/:id',
tokens: [{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"api","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"v1","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"firmware-catalog","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":0,"val":"entries","end":""},{"old":"/api/v1/firmware-catalog/entries/:id","type":1,"val":"id","end":""}],
types: placeholder as Registry['firmware_catalog.delete_entry']['types'],
},
'firmware_catalog.create_route': {
methods: ["POST"],
pattern: '/api/v1/firmware-catalog/routes',
tokens: [{"old":"/api/v1/firmware-catalog/routes","type":0,"val":"api","end":""},{"old":"/api/v1/firmware-catalog/routes","type":0,"val":"v1","end":""},{"old":"/api/v1/firmware-catalog/routes","type":0,"val":"firmware-catalog","end":""},{"old":"/api/v1/firmware-catalog/routes","type":0,"val":"routes","end":""}],
types: placeholder as Registry['firmware_catalog.create_route']['types'],
},
'firmware_catalog.update_route': {
methods: ["PUT"],
pattern: '/api/v1/firmware-catalog/routes/:id',
tokens: [{"old":"/api/v1/firmware-catalog/routes/:id","type":0,"val":"api","end":""},{"old":"/api/v1/firmware-catalog/routes/:id","type":0,"val":"v1","end":""},{"old":"/api/v1/firmware-catalog/routes/:id","type":0,"val":"firmware-catalog","end":""},{"old":"/api/v1/firmware-catalog/routes/:id","type":0,"val":"routes","end":""},{"old":"/api/v1/firmware-catalog/routes/:id","type":1,"val":"id","end":""}],
types: placeholder as Registry['firmware_catalog.update_route']['types'],
},
'firmware_catalog.delete_route': {
methods: ["DELETE"],
pattern: '/api/v1/firmware-catalog/routes/:id',
tokens: [{"old":"/api/v1/firmware-catalog/routes/:id","type":0,"val":"api","end":""},{"old":"/api/v1/firmware-catalog/routes/:id","type":0,"val":"v1","end":""},{"old":"/api/v1/firmware-catalog/routes/:id","type":0,"val":"firmware-catalog","end":""},{"old":"/api/v1/firmware-catalog/routes/:id","type":0,"val":"routes","end":""},{"old":"/api/v1/firmware-catalog/routes/:id","type":1,"val":"id","end":""}],
types: placeholder as Registry['firmware_catalog.delete_route']['types'],
},
'pages.home': {
methods: ["GET","HEAD"],
pattern: '/',
tokens: [{"old":"/","type":0,"val":"/","end":""}],
types: placeholder as Registry['pages.home']['types'],
},
'pages.device_detail': {
methods: ["GET","HEAD"],
pattern: '/devices/:identifier',
tokens: [{"old":"/devices/:identifier","type":0,"val":"devices","end":""},{"old":"/devices/:identifier","type":1,"val":"identifier","end":""}],
types: placeholder as Registry['pages.device_detail']['types'],
},
'pages.refresh_device': {
methods: ["POST"],
pattern: '/devices/:identifier/refresh',
tokens: [{"old":"/devices/:identifier/refresh","type":0,"val":"devices","end":""},{"old":"/devices/:identifier/refresh","type":1,"val":"identifier","end":""},{"old":"/devices/:identifier/refresh","type":0,"val":"refresh","end":""}],
types: placeholder as Registry['pages.refresh_device']['types'],
},
'pages.get_model': {
methods: ["POST"],
pattern: '/devices/:identifier/get-model',
tokens: [{"old":"/devices/:identifier/get-model","type":0,"val":"devices","end":""},{"old":"/devices/:identifier/get-model","type":1,"val":"identifier","end":""},{"old":"/devices/:identifier/get-model","type":0,"val":"get-model","end":""}],
types: placeholder as Registry['pages.get_model']['types'],
},
'commands.page': {
methods: ["GET","HEAD"],
pattern: '/commands',
tokens: [{"old":"/commands","type":0,"val":"commands","end":""}],
types: placeholder as Registry['commands.page']['types'],
},
'pages.commands_fleet': {
methods: ["GET","HEAD"],
pattern: '/commands/fleet',
tokens: [{"old":"/commands/fleet","type":0,"val":"commands","end":""},{"old":"/commands/fleet","type":0,"val":"fleet","end":""}],
types: placeholder as Registry['pages.commands_fleet']['types'],
},
'pages.tr_069_trace': {
methods: ["GET","HEAD"],
pattern: '/tr069-trace',
tokens: [{"old":"/tr069-trace","type":0,"val":"tr069-trace","end":""}],
types: placeholder as Registry['pages.tr_069_trace']['types'],
},
'pages.store_fleet_devices': {
methods: ["POST"],
pattern: '/commands/fleet',
tokens: [{"old":"/commands/fleet","type":0,"val":"commands","end":""},{"old":"/commands/fleet","type":0,"val":"fleet","end":""}],
types: placeholder as Registry['pages.store_fleet_devices']['types'],
},
'pages.store_fleet_command': {
methods: ["POST"],
pattern: '/commands/fleet/command',
tokens: [{"old":"/commands/fleet/command","type":0,"val":"commands","end":""},{"old":"/commands/fleet/command","type":0,"val":"fleet","end":""},{"old":"/commands/fleet/command","type":0,"val":"command","end":""}],
types: placeholder as Registry['pages.store_fleet_command']['types'],
},
'pages.configurations_list': {
methods: ["GET","HEAD"],
pattern: '/configurations',
tokens: [{"old":"/configurations","type":0,"val":"configurations","end":""}],
types: placeholder as Registry['pages.configurations_list']['types'],
},
'pages.configuration_create': {
methods: ["GET","HEAD"],
pattern: '/configurations/create',
tokens: [{"old":"/configurations/create","type":0,"val":"configurations","end":""},{"old":"/configurations/create","type":0,"val":"create","end":""}],
types: placeholder as Registry['pages.configuration_create']['types'],
},
'pages.configuration_edit': {
methods: ["GET","HEAD"],
pattern: '/configurations/:id/edit',
tokens: [{"old":"/configurations/:id/edit","type":0,"val":"configurations","end":""},{"old":"/configurations/:id/edit","type":1,"val":"id","end":""},{"old":"/configurations/:id/edit","type":0,"val":"edit","end":""}],
types: placeholder as Registry['pages.configuration_edit']['types'],
},
'pages.configuration_detail': {
methods: ["GET","HEAD"],
pattern: '/configurations/:id/detail',
tokens: [{"old":"/configurations/:id/detail","type":0,"val":"configurations","end":""},{"old":"/configurations/:id/detail","type":1,"val":"id","end":""},{"old":"/configurations/:id/detail","type":0,"val":"detail","end":""}],
types: placeholder as Registry['pages.configuration_detail']['types'],
},
'pages.firmware_catalog': {
methods: ["GET","HEAD"],
pattern: '/firmware-catalog',
tokens: [{"old":"/firmware-catalog","type":0,"val":"firmware-catalog","end":""}],
types: placeholder as Registry['pages.firmware_catalog']['types'],
},
'pages.firmware_catalog_create': {
methods: ["GET","HEAD"],
pattern: '/firmware-catalog/create',
tokens: [{"old":"/firmware-catalog/create","type":0,"val":"firmware-catalog","end":""},{"old":"/firmware-catalog/create","type":0,"val":"create","end":""}],
types: placeholder as Registry['pages.firmware_catalog_create']['types'],
},
'pages.firmware_catalog_edit': {
methods: ["GET","HEAD"],
pattern: '/firmware-catalog/:id/edit',
tokens: [{"old":"/firmware-catalog/:id/edit","type":0,"val":"firmware-catalog","end":""},{"old":"/firmware-catalog/:id/edit","type":1,"val":"id","end":""},{"old":"/firmware-catalog/:id/edit","type":0,"val":"edit","end":""}],
types: placeholder as Registry['pages.firmware_catalog_edit']['types'],
},
} as const satisfies Record<string, AdonisEndpoint>
+603 -27
View File
@@ -7,52 +7,628 @@ import type { InferInput, SimpleError } from '@vinejs/vine/types'
export type ParamValue = string | number | bigint | boolean
export interface Registry {
'auth.new_account.store': {
'cwmp': {
methods: ["POST"]
pattern: '/api/v1/auth/signup'
pattern: '/cwmp10'
types: {
body: ExtractBody<InferInput<(typeof import('#validators/user').signupValidator)>>
body: {}
paramsTuple: []
params: {}
query: ExtractQuery<InferInput<(typeof import('#validators/user').signupValidator)>>
response: ExtractResponse<Awaited<ReturnType<import('#controllers/new_account_controller').default['store']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/new_account_controller').default['store']>>> | { status: 422; response: { errors: SimpleError[] } }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/cwmp_controller').default['handle']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/cwmp_controller').default['handle']>>>
}
}
'auth.access_tokens.store': {
methods: ["POST"]
pattern: '/api/v1/auth/login'
types: {
body: ExtractBody<InferInput<(typeof import('#validators/user').loginValidator)>>
paramsTuple: []
params: {}
query: ExtractQuery<InferInput<(typeof import('#validators/user').loginValidator)>>
response: ExtractResponse<Awaited<ReturnType<import('#controllers/access_tokens_controller').default['store']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/access_tokens_controller').default['store']>>> | { status: 422; response: { errors: SimpleError[] } }
}
}
'profile.profile.show': {
'health.checklife': {
methods: ["GET","HEAD"]
pattern: '/api/v1/account/profile'
pattern: '/api/v1/checklife'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/profile_controller').default['show']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/profile_controller').default['show']>>>
response: ExtractResponse<Awaited<ReturnType<import('#controllers/health_controller').default['checklife']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/health_controller').default['checklife']>>>
}
}
'profile.access_tokens.destroy': {
methods: ["POST"]
pattern: '/api/v1/account/logout'
'health.checkservice': {
methods: ["GET","HEAD"]
pattern: '/api/v1/checkservice'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/access_tokens_controller').default['destroy']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/access_tokens_controller').default['destroy']>>>
response: ExtractResponse<Awaited<ReturnType<import('#controllers/health_controller').default['checkservice']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/health_controller').default['checkservice']>>>
}
}
'devices.index': {
methods: ["GET","HEAD"]
pattern: '/api/v1/devices'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['index']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['index']>>>
}
}
'devices.criteria_values': {
methods: ["GET","HEAD"]
pattern: '/api/v1/devices/criteria-values'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['criteriaValues']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['criteriaValues']>>>
}
}
'devices.show': {
methods: ["GET","HEAD"]
pattern: '/api/v1/devices/:identifier'
types: {
body: {}
paramsTuple: [ParamValue]
params: { identifier: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['show']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['show']>>>
}
}
'devices.destroy': {
methods: ["DELETE"]
pattern: '/api/v1/devices/:identifier'
types: {
body: {}
paramsTuple: [ParamValue]
params: { identifier: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['destroy']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['destroy']>>>
}
}
'devices.execute_command': {
methods: ["POST"]
pattern: '/api/v1/devices/:identifier/command'
types: {
body: {}
paramsTuple: [ParamValue]
params: { identifier: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['executeCommand']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['executeCommand']>>>
}
}
'devices.refresh': {
methods: ["POST"]
pattern: '/api/v1/devices/:identifier/refresh'
types: {
body: {}
paramsTuple: [ParamValue]
params: { identifier: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['refresh']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/devices_controller').default['refresh']>>>
}
}
'commands.index': {
methods: ["GET","HEAD"]
pattern: '/api/v1/commands'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/commands_controller').default['index']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/commands_controller').default['index']>>>
}
}
'commands.show': {
methods: ["GET","HEAD"]
pattern: '/api/v1/commands/:id'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/commands_controller').default['show']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/commands_controller').default['show']>>>
}
}
'commands.store': {
methods: ["POST"]
pattern: '/api/v1/commands'
types: {
body: ExtractBody<InferInput<(typeof import('#validators/command').createCommandValidator)>>
paramsTuple: []
params: {}
query: ExtractQuery<InferInput<(typeof import('#validators/command').createCommandValidator)>>
response: ExtractResponse<Awaited<ReturnType<import('#controllers/commands_controller').default['store']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/commands_controller').default['store']>>> | { status: 422; response: { errors: SimpleError[] } }
}
}
'tr_069_trace.index': {
methods: ["GET","HEAD"]
pattern: '/api/v1/tr069-trace'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['index']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['index']>>>
}
}
'tr_069_trace.store': {
methods: ["POST"]
pattern: '/api/v1/tr069-trace'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['store']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['store']>>>
}
}
'tr_069_trace.destroy': {
methods: ["DELETE"]
pattern: '/api/v1/tr069-trace/:id'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['destroy']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['destroy']>>>
}
}
'configurations_v_1.index': {
methods: ["GET","HEAD"]
pattern: '/api/v1/configurations'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['index']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['index']>>>
}
}
'configurations_v_1.list_tags': {
methods: ["GET","HEAD"]
pattern: '/api/v1/configurations/tags'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['listTags']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['listTags']>>>
}
}
'configurations_v_1.store': {
methods: ["POST"]
pattern: '/api/v1/configurations'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['store']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['store']>>>
}
}
'configurations_v_1.show': {
methods: ["GET","HEAD"]
pattern: '/api/v1/configurations/:id'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['show']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['show']>>>
}
}
'configurations_v_1.update': {
methods: ["PUT"]
pattern: '/api/v1/configurations/:id'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['update']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['update']>>>
}
}
'configurations_v_1.destroy': {
methods: ["DELETE"]
pattern: '/api/v1/configurations/:id'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['destroy']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['destroy']>>>
}
}
'configurations_v_1.toggle_enabled': {
methods: ["PATCH"]
pattern: '/api/v1/configurations/:id/toggle'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['toggleEnabled']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['toggleEnabled']>>>
}
}
'configurations_v_1.manage_tags': {
methods: ["PATCH"]
pattern: '/api/v1/configurations/:id/tags'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['manageTags']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['manageTags']>>>
}
}
'configurations_v_1.history': {
methods: ["GET","HEAD"]
pattern: '/api/v1/configurations/:id/history'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['history']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['history']>>>
}
}
'configurations_v_1.dry_run': {
methods: ["POST"]
pattern: '/api/v1/configurations/:id/dry-run'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['dryRun']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['dryRun']>>>
}
}
'configurations_v_1.compliance': {
methods: ["GET","HEAD"]
pattern: '/api/v1/configurations/:id/compliance'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['compliance']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/configurations_v1_controller').default['compliance']>>>
}
}
'firmware_catalog.index': {
methods: ["GET","HEAD"]
pattern: '/api/v1/firmware-catalog'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['index']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['index']>>>
}
}
'firmware_catalog.list_entries': {
methods: ["GET","HEAD"]
pattern: '/api/v1/firmware-catalog/entries'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['listEntries']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['listEntries']>>>
}
}
'firmware_catalog.show_entry': {
methods: ["GET","HEAD"]
pattern: '/api/v1/firmware-catalog/entries/:id'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['showEntry']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['showEntry']>>>
}
}
'firmware_catalog.list_routes': {
methods: ["GET","HEAD"]
pattern: '/api/v1/firmware-catalog/routes'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['listRoutes']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['listRoutes']>>>
}
}
'firmware_catalog.create_entry': {
methods: ["POST"]
pattern: '/api/v1/firmware-catalog/entries'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['createEntry']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['createEntry']>>>
}
}
'firmware_catalog.update_entry': {
methods: ["PUT"]
pattern: '/api/v1/firmware-catalog/entries/:id'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['updateEntry']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['updateEntry']>>>
}
}
'firmware_catalog.delete_entry': {
methods: ["DELETE"]
pattern: '/api/v1/firmware-catalog/entries/:id'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['deleteEntry']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['deleteEntry']>>>
}
}
'firmware_catalog.create_route': {
methods: ["POST"]
pattern: '/api/v1/firmware-catalog/routes'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['createRoute']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['createRoute']>>>
}
}
'firmware_catalog.update_route': {
methods: ["PUT"]
pattern: '/api/v1/firmware-catalog/routes/:id'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['updateRoute']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['updateRoute']>>>
}
}
'firmware_catalog.delete_route': {
methods: ["DELETE"]
pattern: '/api/v1/firmware-catalog/routes/:id'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['deleteRoute']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/firmware_catalog_controller').default['deleteRoute']>>>
}
}
'pages.home': {
methods: ["GET","HEAD"]
pattern: '/'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['home']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['home']>>>
}
}
'pages.device_detail': {
methods: ["GET","HEAD"]
pattern: '/devices/:identifier'
types: {
body: {}
paramsTuple: [ParamValue]
params: { identifier: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['deviceDetail']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['deviceDetail']>>>
}
}
'pages.refresh_device': {
methods: ["POST"]
pattern: '/devices/:identifier/refresh'
types: {
body: {}
paramsTuple: [ParamValue]
params: { identifier: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['refreshDevice']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['refreshDevice']>>>
}
}
'pages.get_model': {
methods: ["POST"]
pattern: '/devices/:identifier/get-model'
types: {
body: {}
paramsTuple: [ParamValue]
params: { identifier: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['getModel']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['getModel']>>>
}
}
'commands.page': {
methods: ["GET","HEAD"]
pattern: '/commands'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['commands']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['commands']>>>
}
}
'pages.commands_fleet': {
methods: ["GET","HEAD"]
pattern: '/commands/fleet'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['commandsFleet']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['commandsFleet']>>>
}
}
'pages.tr_069_trace': {
methods: ["GET","HEAD"]
pattern: '/tr069-trace'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['tr069Trace']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['tr069Trace']>>>
}
}
'pages.store_fleet_devices': {
methods: ["POST"]
pattern: '/commands/fleet'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['storeFleetDevices']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['storeFleetDevices']>>>
}
}
'pages.store_fleet_command': {
methods: ["POST"]
pattern: '/commands/fleet/command'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['storeFleetCommand']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['storeFleetCommand']>>>
}
}
'pages.configurations_list': {
methods: ["GET","HEAD"]
pattern: '/configurations'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['configurationsList']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['configurationsList']>>>
}
}
'pages.configuration_create': {
methods: ["GET","HEAD"]
pattern: '/configurations/create'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['configurationCreate']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['configurationCreate']>>>
}
}
'pages.configuration_edit': {
methods: ["GET","HEAD"]
pattern: '/configurations/:id/edit'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['configurationEdit']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['configurationEdit']>>>
}
}
'pages.configuration_detail': {
methods: ["GET","HEAD"]
pattern: '/configurations/:id/detail'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['configurationDetail']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['configurationDetail']>>>
}
}
'pages.firmware_catalog': {
methods: ["GET","HEAD"]
pattern: '/firmware-catalog'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['firmwareCatalog']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['firmwareCatalog']>>>
}
}
'pages.firmware_catalog_create': {
methods: ["GET","HEAD"]
pattern: '/firmware-catalog/create'
types: {
body: {}
paramsTuple: []
params: {}
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['firmwareCatalogCreate']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['firmwareCatalogCreate']>>>
}
}
'pages.firmware_catalog_edit': {
methods: ["GET","HEAD"]
pattern: '/firmware-catalog/:id/edit'
types: {
body: {}
paramsTuple: [ParamValue]
params: { id: ParamValue }
query: {}
response: ExtractResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['firmwareCatalogEdit']>>>
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/pages_controller').default['firmwareCatalogEdit']>>>
}
}
}
+64 -14
View File
@@ -2,20 +2,70 @@
import type { routes } from './index.ts'
export interface ApiDefinition {
auth: {
newAccount: {
store: typeof routes['auth.new_account.store']
}
accessTokens: {
store: typeof routes['auth.access_tokens.store']
}
cwmp: typeof routes['cwmp']
health: {
checklife: typeof routes['health.checklife']
checkservice: typeof routes['health.checkservice']
}
profile: {
profile: {
show: typeof routes['profile.profile.show']
}
accessTokens: {
destroy: typeof routes['profile.access_tokens.destroy']
}
devices: {
index: typeof routes['devices.index']
criteriaValues: typeof routes['devices.criteria_values']
show: typeof routes['devices.show']
destroy: typeof routes['devices.destroy']
executeCommand: typeof routes['devices.execute_command']
refresh: typeof routes['devices.refresh']
}
commands: {
index: typeof routes['commands.index']
show: typeof routes['commands.show']
store: typeof routes['commands.store']
page: typeof routes['commands.page']
}
tr069Trace: {
index: typeof routes['tr_069_trace.index']
store: typeof routes['tr_069_trace.store']
destroy: typeof routes['tr_069_trace.destroy']
}
configurationsV1: {
index: typeof routes['configurations_v_1.index']
listTags: typeof routes['configurations_v_1.list_tags']
store: typeof routes['configurations_v_1.store']
show: typeof routes['configurations_v_1.show']
update: typeof routes['configurations_v_1.update']
destroy: typeof routes['configurations_v_1.destroy']
toggleEnabled: typeof routes['configurations_v_1.toggle_enabled']
manageTags: typeof routes['configurations_v_1.manage_tags']
history: typeof routes['configurations_v_1.history']
dryRun: typeof routes['configurations_v_1.dry_run']
compliance: typeof routes['configurations_v_1.compliance']
}
firmwareCatalog: {
index: typeof routes['firmware_catalog.index']
listEntries: typeof routes['firmware_catalog.list_entries']
showEntry: typeof routes['firmware_catalog.show_entry']
listRoutes: typeof routes['firmware_catalog.list_routes']
createEntry: typeof routes['firmware_catalog.create_entry']
updateEntry: typeof routes['firmware_catalog.update_entry']
deleteEntry: typeof routes['firmware_catalog.delete_entry']
createRoute: typeof routes['firmware_catalog.create_route']
updateRoute: typeof routes['firmware_catalog.update_route']
deleteRoute: typeof routes['firmware_catalog.delete_route']
}
pages: {
home: typeof routes['pages.home']
deviceDetail: typeof routes['pages.device_detail']
refreshDevice: typeof routes['pages.refresh_device']
getModel: typeof routes['pages.get_model']
commandsFleet: typeof routes['pages.commands_fleet']
tr069Trace: typeof routes['pages.tr_069_trace']
storeFleetDevices: typeof routes['pages.store_fleet_devices']
storeFleetCommand: typeof routes['pages.store_fleet_command']
configurationsList: typeof routes['pages.configurations_list']
configurationCreate: typeof routes['pages.configuration_create']
configurationEdit: typeof routes['pages.configuration_edit']
configurationDetail: typeof routes['pages.configuration_detail']
firmwareCatalog: typeof routes['pages.firmware_catalog']
firmwareCatalogCreate: typeof routes['pages.firmware_catalog_create']
firmwareCatalogEdit: typeof routes['pages.firmware_catalog_edit']
}
}
+1
View File
@@ -11,4 +11,5 @@ export const controllers = {
FirmwareCatalog: () => import('#controllers/firmware_catalog_controller'),
Health: () => import('#controllers/health_controller'),
Pages: () => import('#controllers/pages_controller'),
Tr069Trace: () => import('#controllers/tr069_trace_controller'),
}
+143 -13
View File
@@ -4,21 +4,151 @@ type ParamValue = string | number | bigint | boolean
export type ScannedRoutes = {
ALL: {
'auth.new_account.store': { paramsTuple?: []; params?: {} }
'auth.access_tokens.store': { paramsTuple?: []; params?: {} }
'profile.profile.show': { paramsTuple?: []; params?: {} }
'profile.access_tokens.destroy': { paramsTuple?: []; params?: {} }
}
GET: {
'profile.profile.show': { paramsTuple?: []; params?: {} }
}
HEAD: {
'profile.profile.show': { paramsTuple?: []; params?: {} }
'cwmp': { paramsTuple?: []; params?: {} }
'health.checklife': { paramsTuple?: []; params?: {} }
'health.checkservice': { paramsTuple?: []; params?: {} }
'devices.index': { paramsTuple?: []; params?: {} }
'devices.criteria_values': { paramsTuple?: []; params?: {} }
'devices.show': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'devices.destroy': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'devices.execute_command': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'devices.refresh': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'commands.index': { paramsTuple?: []; params?: {} }
'commands.show': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'commands.store': { paramsTuple?: []; params?: {} }
'tr_069_trace.index': { paramsTuple?: []; params?: {} }
'tr_069_trace.store': { paramsTuple?: []; params?: {} }
'tr_069_trace.destroy': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.index': { paramsTuple?: []; params?: {} }
'configurations_v_1.list_tags': { paramsTuple?: []; params?: {} }
'configurations_v_1.store': { paramsTuple?: []; params?: {} }
'configurations_v_1.show': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.update': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.destroy': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.toggle_enabled': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.manage_tags': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.history': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.dry_run': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.compliance': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.index': { paramsTuple?: []; params?: {} }
'firmware_catalog.list_entries': { paramsTuple?: []; params?: {} }
'firmware_catalog.show_entry': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.list_routes': { paramsTuple?: []; params?: {} }
'firmware_catalog.create_entry': { paramsTuple?: []; params?: {} }
'firmware_catalog.update_entry': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.delete_entry': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.create_route': { paramsTuple?: []; params?: {} }
'firmware_catalog.update_route': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.delete_route': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'pages.home': { paramsTuple?: []; params?: {} }
'pages.device_detail': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'pages.refresh_device': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'pages.get_model': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'commands.page': { paramsTuple?: []; params?: {} }
'pages.commands_fleet': { paramsTuple?: []; params?: {} }
'pages.tr_069_trace': { paramsTuple?: []; params?: {} }
'pages.store_fleet_devices': { paramsTuple?: []; params?: {} }
'pages.store_fleet_command': { paramsTuple?: []; params?: {} }
'pages.configurations_list': { paramsTuple?: []; params?: {} }
'pages.configuration_create': { paramsTuple?: []; params?: {} }
'pages.configuration_edit': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'pages.configuration_detail': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'pages.firmware_catalog': { paramsTuple?: []; params?: {} }
'pages.firmware_catalog_create': { paramsTuple?: []; params?: {} }
'pages.firmware_catalog_edit': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
}
POST: {
'auth.new_account.store': { paramsTuple?: []; params?: {} }
'auth.access_tokens.store': { paramsTuple?: []; params?: {} }
'profile.access_tokens.destroy': { paramsTuple?: []; params?: {} }
'cwmp': { paramsTuple?: []; params?: {} }
'devices.execute_command': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'devices.refresh': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'commands.store': { paramsTuple?: []; params?: {} }
'tr_069_trace.store': { paramsTuple?: []; params?: {} }
'configurations_v_1.store': { paramsTuple?: []; params?: {} }
'configurations_v_1.dry_run': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.create_entry': { paramsTuple?: []; params?: {} }
'firmware_catalog.create_route': { paramsTuple?: []; params?: {} }
'pages.refresh_device': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'pages.get_model': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'pages.store_fleet_devices': { paramsTuple?: []; params?: {} }
'pages.store_fleet_command': { paramsTuple?: []; params?: {} }
}
GET: {
'health.checklife': { paramsTuple?: []; params?: {} }
'health.checkservice': { paramsTuple?: []; params?: {} }
'devices.index': { paramsTuple?: []; params?: {} }
'devices.criteria_values': { paramsTuple?: []; params?: {} }
'devices.show': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'commands.index': { paramsTuple?: []; params?: {} }
'commands.show': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'tr_069_trace.index': { paramsTuple?: []; params?: {} }
'configurations_v_1.index': { paramsTuple?: []; params?: {} }
'configurations_v_1.list_tags': { paramsTuple?: []; params?: {} }
'configurations_v_1.show': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.history': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.compliance': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.index': { paramsTuple?: []; params?: {} }
'firmware_catalog.list_entries': { paramsTuple?: []; params?: {} }
'firmware_catalog.show_entry': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.list_routes': { paramsTuple?: []; params?: {} }
'pages.home': { paramsTuple?: []; params?: {} }
'pages.device_detail': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'commands.page': { paramsTuple?: []; params?: {} }
'pages.commands_fleet': { paramsTuple?: []; params?: {} }
'pages.tr_069_trace': { paramsTuple?: []; params?: {} }
'pages.configurations_list': { paramsTuple?: []; params?: {} }
'pages.configuration_create': { paramsTuple?: []; params?: {} }
'pages.configuration_edit': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'pages.configuration_detail': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'pages.firmware_catalog': { paramsTuple?: []; params?: {} }
'pages.firmware_catalog_create': { paramsTuple?: []; params?: {} }
'pages.firmware_catalog_edit': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
}
HEAD: {
'health.checklife': { paramsTuple?: []; params?: {} }
'health.checkservice': { paramsTuple?: []; params?: {} }
'devices.index': { paramsTuple?: []; params?: {} }
'devices.criteria_values': { paramsTuple?: []; params?: {} }
'devices.show': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'commands.index': { paramsTuple?: []; params?: {} }
'commands.show': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'tr_069_trace.index': { paramsTuple?: []; params?: {} }
'configurations_v_1.index': { paramsTuple?: []; params?: {} }
'configurations_v_1.list_tags': { paramsTuple?: []; params?: {} }
'configurations_v_1.show': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.history': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.compliance': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.index': { paramsTuple?: []; params?: {} }
'firmware_catalog.list_entries': { paramsTuple?: []; params?: {} }
'firmware_catalog.show_entry': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.list_routes': { paramsTuple?: []; params?: {} }
'pages.home': { paramsTuple?: []; params?: {} }
'pages.device_detail': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'commands.page': { paramsTuple?: []; params?: {} }
'pages.commands_fleet': { paramsTuple?: []; params?: {} }
'pages.tr_069_trace': { paramsTuple?: []; params?: {} }
'pages.configurations_list': { paramsTuple?: []; params?: {} }
'pages.configuration_create': { paramsTuple?: []; params?: {} }
'pages.configuration_edit': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'pages.configuration_detail': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'pages.firmware_catalog': { paramsTuple?: []; params?: {} }
'pages.firmware_catalog_create': { paramsTuple?: []; params?: {} }
'pages.firmware_catalog_edit': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
}
DELETE: {
'devices.destroy': { paramsTuple: [ParamValue]; params: {'identifier': ParamValue} }
'tr_069_trace.destroy': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.destroy': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.delete_entry': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.delete_route': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
}
PUT: {
'configurations_v_1.update': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.update_entry': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'firmware_catalog.update_route': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
}
PATCH: {
'configurations_v_1.toggle_enabled': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
'configurations_v_1.manage_tags': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
}
}
declare module '@adonisjs/core/types/http' {
+3 -1
View File
@@ -22,7 +22,9 @@ COPY --from=builder /app/build ./
RUN npm install --omit=dev
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
adduser -u 1001 -S appuser -G appgroup && \
mkdir -p /app/data /app/logs/tr069 && \
chown -R appuser:appgroup /app/data /app/logs
USER appuser
+20 -6
View File
@@ -2,7 +2,7 @@ import type { HttpContext } from '@adonisjs/core/http'
import { buildSoapEnvelope } from '../protocol/soap.js'
import { CWMPMessageBuilder } from '../protocol/common/cwmp_message_builder.js'
import { StateMachineManager } from '../services/cwmp/state_machine_manager.js'
import { Device } from '../domain/device/device.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'
@@ -122,6 +122,15 @@ export default class CwmpController {
}
device.setTmpParamValues(paramValues)
// Detect TR data model from Inform parameter names (Device.* = TR-181, InternetGatewayDevice.* = TR-098)
// Only update if currently unknown — once detected, keep it stable across sessions
if (device.dataModel === 'unknown') {
const detectedModel = detectDataModelFromParams(paramValues)
if (detectedModel !== 'unknown') {
device.setDataModel(detectedModel)
}
}
// Persist device
const hashId = getDeviceHashIdentifier({
manufacturer: deviceId.manufacturer,
@@ -135,6 +144,7 @@ export default class CwmpController {
properties: device.getProperties().map((p) => p.toSnapshot()),
parameters: device.getParameters().map((p) => p.toSnapshot()),
gpnStatus: device.gpnStatus,
dataModel: device.dataModel,
updatedAt: new Date(),
})
logger.info({ hashId }, 'Inform received')
@@ -152,16 +162,19 @@ export default class CwmpController {
await manager.startStateMachine(device, session, { events })
manager.bindRemoteToDevice(remoteKey, device.deviceId)
// After Inform for onboarding events, enqueue critical reads
// After Inform for onboarding events, enqueue critical reads using the detected data model
const onlyConnReq = events.length > 0 && events.every((e) => e === '6 CONNECTION REQUEST')
if (!onlyConnReq) {
try {
const isTr181 = device.dataModel === 'tr-181'
const root = isTr181 ? 'Device' : 'InternetGatewayDevice'
const criticalPaths = [
'InternetGatewayDevice.DeviceInfo.SpecVersion',
'InternetGatewayDevice.DeviceInfo.HardwareVersion',
'InternetGatewayDevice.DeviceInfo.SoftwareVersion',
'InternetGatewayDevice.ManagementServer.ConnectionRequestURL',
`${root}.DeviceInfo.HardwareVersion`,
`${root}.DeviceInfo.SoftwareVersion`,
`${root}.ManagementServer.ConnectionRequestURL`,
...(isTr181 ? [] : [`${root}.DeviceInfo.SpecVersion`]),
]
logger.debug({ dataModel: device.dataModel, criticalPaths }, 'Enqueueing critical reads')
await manager.enqueueImmediate(device, 'get_parameter_values', criticalPaths)
} catch (_e) {
// Non-critical
@@ -223,6 +236,7 @@ export default class CwmpController {
device.setParameter(new DeviceParameter(p.name, p.value, p.type))
)
device.setGpnStatus(record.gpnStatus ?? 'unknown')
device.setDataModel(record.dataModel ?? 'unknown')
return device
} catch (_error) {
+25
View File
@@ -2,6 +2,11 @@ import type { HttpContext } from '@adonisjs/core/http'
import logger from '@adonisjs/core/services/logger'
import DeviceModel from '#models/device'
import { Tr069TraceService } from '#services/tr069_trace_service'
import { readFileSync, existsSync } from 'node:fs'
import { join } from 'node:path'
const LOG_DIR = join(process.cwd(), 'logs', 'tr069')
const MAX_TAIL_LINES = 1000
export default class Tr069TraceController {
private get service(): Tr069TraceService {
@@ -52,4 +57,24 @@ export default class Tr069TraceController {
logger.info({ deviceId: params.id }, 'TR-069 trace disabled for device')
return response.status(204).send('')
}
async showLog({ params, request, response }: HttpContext) {
const deviceId = params.id
const lines = Math.min(Number(request.qs().lines ?? 200), MAX_TAIL_LINES)
if (!this.service.isTraced(deviceId)) {
return response.status(404).json({ error: `Device ${deviceId} is not being traced` })
}
const filePath = join(LOG_DIR, `${deviceId}.log`)
if (!existsSync(filePath)) {
return response.status(200).json({ deviceId, content: '', lines: 0 })
}
const raw = readFileSync(filePath, 'utf-8')
const allLines = raw.split('\n')
const tail = allLines.slice(-lines).join('\n')
return response.status(200).json({ deviceId, content: tail, lines: allLines.length })
}
}
+25
View File
@@ -5,6 +5,20 @@ import { createHash } from 'node:crypto'
export type GpnStatus = 'supported' | 'unsupported' | 'unknown'
/** TR-098: InternetGatewayDevice.* root — TR-181: Device.* root */
export type DataModel = 'tr-181' | 'tr-098' | 'unknown'
/** Detect which TR data model a device uses from a ParameterValueStruct list */
export function detectDataModelFromParams(paramValues: any[]): DataModel {
for (const pv of paramValues ?? []) {
const name: unknown = pv?.Name
if (typeof name !== 'string') continue
if (name.startsWith('Device.')) return 'tr-181'
if (name.startsWith('InternetGatewayDevice.')) return 'tr-098'
}
return 'unknown'
}
export enum OnboardingStatus {
PENDING = 'PENDING',
DISCOVERING = 'DISCOVERING',
@@ -22,6 +36,7 @@ export class Device {
private _lastSeen: Date = new Date()
private _onboardingStatus: OnboardingStatus = OnboardingStatus.PENDING
private _gpnStatus: GpnStatus = 'unknown'
private _dataModel: DataModel = 'unknown'
constructor(
public readonly deviceId: DeviceId,
@@ -48,6 +63,14 @@ export class Device {
this._gpnStatus = status
}
public get dataModel(): DataModel {
return this._dataModel
}
public setDataModel(model: DataModel): void {
this._dataModel = model
}
public get identifier(): string {
const raw = `${this.deviceId.manufacturer}-${this.deviceId.oui}-${this.deviceId.productClass}-${this.deviceId.serialNumber}`
return createHash('sha256').update(raw).digest('hex').substring(0, 24)
@@ -239,6 +262,7 @@ export class Device {
onboardingStatus: this._onboardingStatus,
isOnline: this.isOnline(),
gpnStatus: this._gpnStatus,
dataModel: this._dataModel,
parameters: this.getParameters().map((p) => p.toSnapshot()),
properties: this.getProperties().map((p) => p.toSnapshot()),
}
@@ -257,6 +281,7 @@ export interface DeviceSnapshot {
onboardingStatus: OnboardingStatus
isOnline: boolean
gpnStatus: GpnStatus
dataModel: DataModel
parameters: ParameterSnapshot[]
properties: PropertySnapshot[]
}
+2 -1
View File
@@ -1,7 +1,7 @@
import { DeviceId } from './device_id.js'
import { PropertySnapshot, ParameterSnapshot } from './device_parameter.js'
import { DeviceComplianceInfo } from '../configuration/compliance.js'
import { GpnStatus } from './device.js'
import { DataModel, GpnStatus } from './device.js'
export interface DeviceRecord {
identifier?: string
@@ -9,6 +9,7 @@ export interface DeviceRecord {
properties: PropertySnapshot[]
parameters: ParameterSnapshot[]
gpnStatus?: GpnStatus
dataModel?: DataModel
compliance?: DeviceComplianceInfo
provisioningCode?: string
updatedAt?: Date
+14 -30
View File
@@ -3,11 +3,13 @@ import type { NextFn } from '@adonisjs/core/types/http'
// @ts-ignore - xml-formatter types conflict with ESM interop
import formatXml from 'xml-formatter'
import { Tr069TraceService } from '#services/tr069_trace_service'
import logger from '@adonisjs/core/services/logger'
/**
* CWMP Logger Middleware
*
* - Logs all SOAP exchanges to stdout for debugging.
* Buffers SOAP request/response. After next():
* - Always logs a brief summary (INFO) to the app logger.
* - For traced devices, writes full SOAP payloads to logs/tr069/{deviceId}.log.
*/
export default class CwmpLoggerMiddleware {
@@ -23,18 +25,6 @@ export default class CwmpLoggerMiddleware {
}
}
private logToStdout(direction: string, sessionId: string, xml: string) {
const timestamp = new Date().toISOString()
const separator = '─'.repeat(80)
process.stdout.write(
`\n${separator}\n` +
`[TR-069] ${direction} | ${timestamp} | ${sessionId}\n` +
`${separator}\n` +
`${xml}\n` +
`${separator}\n\n`
)
}
async handle(ctx: HttpContext, next: NextFn) {
const { request, response } = ctx
const rawBody = request.raw() || ''
@@ -46,15 +36,10 @@ export default class CwmpLoggerMiddleware {
const localPort = socket?.localPort || 0
const sessionId = `${remoteIp}:${remotePort}->${localAddress}:${localPort}`
if (rawBody && rawBody.trim() !== '') {
this.logToStdout('CPE→ACS', sessionId, this.prettyXml(rawBody))
}
let capturedResponseXml: string | null = null
const originalSend = response.send.bind(response)
response.send = (body: any) => {
if (typeof body === 'string' && body.includes('soap-env:Envelope')) {
this.logToStdout('ACS→CPE', sessionId, this.prettyXml(body))
capturedResponseXml = body
}
return originalSend(body)
@@ -63,20 +48,19 @@ export default class CwmpLoggerMiddleware {
await next()
const deviceId = ctx.cwmpDeviceId
if (!deviceId) {
return
}
const traceService = Tr069TraceService.getInstance()
if (!traceService.isTraced(deviceId)) {
return
}
const isTraced = deviceId ? traceService.isTraced(deviceId) : false
if (rawBody && rawBody.trim() !== '') {
traceService.writeExchange(deviceId, 'CPE→ACS', this.prettyXml(rawBody))
}
if (capturedResponseXml) {
traceService.writeExchange(deviceId, 'ACS→CPE', this.prettyXml(capturedResponseXml))
if (isTraced && deviceId) {
if (rawBody && rawBody.trim() !== '') {
traceService.writeExchange(deviceId, 'CPE→ACS', sessionId, this.prettyXml(rawBody))
}
if (capturedResponseXml) {
traceService.writeExchange(deviceId, 'ACS→CPE', sessionId, this.prettyXml(capturedResponseXml))
}
} else if (rawBody && rawBody.trim() !== '') {
// Brief app-level log only (no SOAP body)
logger.debug({ sessionId, deviceId: deviceId ?? 'unknown' }, 'TR-069 exchange (not traced)')
}
}
}
+1
View File
@@ -255,6 +255,7 @@ export default class ConfigurationEngine {
* Uses glob matching for each criterion.
*/
private matchesCriteria(device: Device, config: Configuration): boolean {
if (!device?.deviceId) return false
const criteria = config.criteria
// manufacturer
+14 -5
View File
@@ -74,6 +74,7 @@ export class DeviceActionExecutor {
properties: snapshot.properties,
parameters: snapshot.parameters,
gpnStatus: snapshot.gpnStatus,
dataModel: snapshot.dataModel,
updatedAt: new Date(),
})
} catch (e) {
@@ -365,11 +366,19 @@ export class DeviceActionExecutor {
const knownPC = [...allParams, ...allProps].find((p: any) => /(^|\.)ProvisioningCode$/.test(p?.name))
const hasDeviceTree = [...allParams, ...allProps].some((p: any) => typeof p?.name === 'string' && p.name.startsWith('Device.'))
const hasIgdTree = [...allParams, ...allProps].some((p: any) => typeof p?.name === 'string' && p.name.startsWith('InternetGatewayDevice.'))
const inferredPath = hasDeviceTree ? 'Device.DeviceInfo.ProvisioningCode' : 'InternetGatewayDevice.DeviceInfo.ProvisioningCode'
const provisioningPath = knownPC?.name || (hasIgdTree && !hasDeviceTree ? 'InternetGatewayDevice.DeviceInfo.ProvisioningCode' : inferredPath)
// Use the detected data model stored on the device; fall back to scanning known params
let provisioningPath: string
if (knownPC) {
provisioningPath = knownPC.name
} else if (device.dataModel === 'tr-181') {
provisioningPath = 'Device.DeviceInfo.ProvisioningCode'
} else if (device.dataModel === 'tr-098') {
provisioningPath = 'InternetGatewayDevice.DeviceInfo.ProvisioningCode'
} else {
// Legacy fallback: scan stored params
const hasDeviceTree = [...allParams, ...allProps].some((p: any) => typeof p?.name === 'string' && p.name.startsWith('Device.'))
provisioningPath = hasDeviceTree ? 'Device.DeviceInfo.ProvisioningCode' : 'InternetGatewayDevice.DeviceInfo.ProvisioningCode'
}
const parameterKey = `SetPValues_${device.deviceId.serialNumber}_${Date.now()}`
const pvStructs = [{ Name: provisioningPath, Value: { _: device.tmpPC, $: { Type: 'xsd:string' } } }]
+1
View File
@@ -282,6 +282,7 @@ class CompletedState extends DeviceState {
properties: snapshot.properties,
parameters: snapshot.parameters,
gpnStatus: snapshot.gpnStatus,
dataModel: snapshot.dataModel,
updatedAt: new Date(),
})
} catch (e) {
+7 -7
View File
@@ -1,10 +1,10 @@
import { appendFileSync, mkdirSync, readFileSync, renameSync, statSync } from 'node:fs'
import { writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
const CONFIG_PATH = fileURLToPath(new URL('../../config/tr069-trace.json', import.meta.url))
const LOG_DIR = fileURLToPath(new URL('../../logs/tr069', import.meta.url))
const APP_ROOT = process.cwd()
const CONFIG_PATH = join(APP_ROOT, 'data', 'tr069-trace.json')
const LOG_DIR = join(APP_ROOT, 'logs', 'tr069')
const MAX_FILE_BYTES = 10 * 1024 * 1024
const MAX_ROTATED_FILES = 5
@@ -91,14 +91,14 @@ export class Tr069TraceService {
return existed
}
writeExchange(deviceId: string, direction: TraceDirection, xml: string): void {
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, 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, 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`)
@@ -106,7 +106,7 @@ export class Tr069TraceService {
const timestamp = new Date().toISOString()
const separator = '═'.repeat(80)
const entry = `\n${separator}\n[${timestamp}] ${direction}\n${separator}\n${xml}\n`
const entry = `\n${separator}\n[${timestamp}] ${direction} | ${sessionId}\n${separator}\n${xml}\n`
appendFileSync(filePath, entry, 'utf-8')
}
+4 -2
View File
@@ -25,7 +25,9 @@ services:
PROXY: "false"
BSE_PROXY: "false"
AUTO_DELETE_ENABLED: "false"
INFORM_ACCEPTED_PARAMS: "HardwareVersion,SoftwareVersion,ProvisioningCode,ConnectionRequestURL,ParameterKey,ExternalIPAddress"
# Vide = mode permissif : envoie tous les paramètres dans l'Inform (utile pour le dev/test)
# Sinon, liste les catégories sémantiques du simulateur (ex: HardwareVersion,SoftwareVersion,...)
INFORM_ACCEPTED_PARAMS: ""
ADD_IP_AS_HEADER_NAME: ""
IPV4_DENY_PREFIX: ""
IPV6_PREFIXES: ""
@@ -79,7 +81,7 @@ services:
- "3000:3000"
volumes:
# env-config.js injecté au runtime pour configurer l'URL du backend côté navigateur
- ./config/simulator-ui-env.js:/usr/share/nginx/html/env-config.js:ro
- ./public/simulator-ui-env.js:/usr/share/nginx/html/env-config.js:ro
depends_on:
- simulator
+5
View File
@@ -29,6 +29,9 @@ services:
depends_on:
mongodb:
condition: service_healthy
volumes:
- app_tr069_data:/app/data
- app_tr069_logs:/app/logs/tr069
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://localhost:8080/api/v1/checklife').then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))"]
interval: 5s
@@ -50,3 +53,5 @@ services:
volumes:
mongo_data:
app_tr069_data:
app_tr069_logs:
+120 -50
View File
@@ -31,43 +31,69 @@ const entriesError = ref('')
const actionError = ref('')
let searchTimeout: ReturnType<typeof setTimeout> | null = null
// Log viewer state
const selectedDeviceId = ref<string | null>(null)
const logContent = ref('')
const logLines = ref(0)
const loadingLog = ref(false)
const logError = ref('')
let autoRefreshTimer: ReturnType<typeof setInterval> | null = null
const tracedDeviceIds = computed(() => new Set(tracedDevices.value.map((entry) => entry.deviceId)))
const filteredSearchResults = computed(() =>
searchResults.value.filter((device) => !tracedDeviceIds.value.has(device.identifier))
)
const selectedEntry = computed(() =>
tracedDevices.value.find((e) => e.deviceId === selectedDeviceId.value) ?? null
)
onMounted(async () => {
await loadEntries()
})
watch(searchTerm, (value) => {
if (searchTimeout) {
clearTimeout(searchTimeout)
}
if (searchTimeout) clearTimeout(searchTimeout)
if (!value.trim()) {
searchResults.value = []
actionError.value = ''
return
}
searchTimeout = setTimeout(() => {
void searchDevices(value)
}, 250)
searchTimeout = setTimeout(() => { void searchDevices(value) }, 250)
})
watch(selectedDeviceId, async (id) => {
stopAutoRefresh()
logContent.value = ''
logLines.value = 0
logError.value = ''
if (id) {
await loadLog(id)
startAutoRefresh(id)
}
})
function startAutoRefresh(deviceId: string) {
autoRefreshTimer = setInterval(() => { void loadLog(deviceId) }, 3000)
}
function stopAutoRefresh() {
if (autoRefreshTimer !== null) {
clearInterval(autoRefreshTimer)
autoRefreshTimer = null
}
}
async function loadEntries() {
loadingEntries.value = true
entriesError.value = ''
try {
const response = await fetch('/api/v1/tr069-trace')
if (!response.ok) {
throw new Error('Failed to load trace entries')
}
if (!response.ok) throw new Error('Failed to load trace entries')
const data = await response.json()
tracedDevices.value = data.entries || []
if (selectedDeviceId.value && !tracedDeviceIds.value.has(selectedDeviceId.value)) {
selectedDeviceId.value = null
}
} catch (error) {
entriesError.value = error instanceof Error ? error.message : 'Failed to load trace entries'
} finally {
@@ -78,25 +104,17 @@ async function loadEntries() {
async function searchDevices(query: string) {
loadingSearch.value = true
actionError.value = ''
try {
const response = await fetch('/api/v1/devices?offset=0&limit=200')
if (!response.ok) {
throw new Error('Failed to search devices')
}
if (!response.ok) throw new Error('Failed to search devices')
const devices = (await response.json()) as DeviceRecord[]
const normalizedQuery = query.trim().toLowerCase()
searchResults.value = devices.filter((device) => {
const manufacturer = device.deviceId?.manufacturer?.toLowerCase() ?? ''
const productClass = device.deviceId?.productClass?.toLowerCase() ?? ''
const serialNumber = device.deviceId?.serialNumber?.toLowerCase() ?? ''
const identifier = device.identifier.toLowerCase()
return [manufacturer, productClass, serialNumber, identifier].some((value) =>
value.includes(normalizedQuery)
)
return [manufacturer, productClass, serialNumber, identifier].some((v) => v.includes(normalizedQuery))
})
} catch (error) {
actionError.value = error instanceof Error ? error.message : 'Failed to search devices'
@@ -109,19 +127,16 @@ async function searchDevices(query: string) {
async function addDevice(deviceId: string) {
savingDeviceId.value = deviceId
actionError.value = ''
try {
const response = await fetch('/api/v1/tr069-trace', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ deviceId }),
})
if (!response.ok) {
const payload = await response.json().catch(() => ({}))
throw new Error(payload.error || 'Failed to enable tracing')
}
searchTerm.value = ''
searchResults.value = []
await loadEntries()
@@ -135,17 +150,15 @@ async function addDevice(deviceId: string) {
async function removeDevice(deviceId: string) {
removingDeviceId.value = deviceId
actionError.value = ''
try {
const response = await fetch(`/api/v1/tr069-trace/${encodeURIComponent(deviceId)}`, {
method: 'DELETE',
})
if (!response.ok) {
const payload = await response.json().catch(() => ({}))
throw new Error(payload.error || 'Failed to disable tracing')
}
if (selectedDeviceId.value === deviceId) selectedDeviceId.value = null
await loadEntries()
} catch (error) {
actionError.value = error instanceof Error ? error.message : 'Failed to disable tracing'
@@ -153,6 +166,29 @@ async function removeDevice(deviceId: string) {
removingDeviceId.value = null
}
}
async function loadLog(deviceId: string) {
loadingLog.value = true
logError.value = ''
try {
const response = await fetch(`/api/v1/tr069-trace/${encodeURIComponent(deviceId)}/log?lines=500`)
if (!response.ok) {
const payload = await response.json().catch(() => ({}))
throw new Error(payload.error || 'Failed to load log')
}
const data = await response.json()
logContent.value = data.content ?? ''
logLines.value = data.lines ?? 0
} catch (error) {
logError.value = error instanceof Error ? error.message : 'Failed to load log'
} finally {
loadingLog.value = false
}
}
function clearLog() {
logContent.value = ''
}
</script>
<template>
@@ -160,7 +196,8 @@ async function removeDevice(deviceId: string) {
<div class="container-xxl py-4">
<div class="row g-4">
<div class="col-12 col-xl-5">
<!-- Add device -->
<div class="col-12 col-xl-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<h2 class="h4 mb-3">Add device</h2>
@@ -177,7 +214,7 @@ async function removeDevice(deviceId: string) {
placeholder="Search devices..."
/>
<p v-if="loadingSearch" class="text-muted small mt-3 mb-0">Searching devices…</p>
<p v-if="loadingSearch" class="text-muted small mt-3 mb-0">Searching devices&hellip;</p>
<p
v-else-if="searchTerm.trim() && filteredSearchResults.length === 0"
class="text-muted small mt-3 mb-0"
@@ -190,8 +227,8 @@ async function removeDevice(deviceId: string) {
<thead class="table-light">
<tr>
<th>Manufacturer</th>
<th>Product Class</th>
<th>Serial Number</th>
<th>Product</th>
<th>Serial</th>
<th class="text-end">Action</th>
</tr>
</thead>
@@ -206,7 +243,7 @@ async function removeDevice(deviceId: string) {
:disabled="savingDeviceId === device.identifier"
@click="addDevice(device.identifier)"
>
{{ savingDeviceId === device.identifier ? 'Adding…' : 'Add' }}
{{ savingDeviceId === device.identifier ? 'Adding&hellip;' : 'Add' }}
</button>
</td>
</tr>
@@ -217,27 +254,22 @@ async function removeDevice(deviceId: string) {
</div>
</div>
<div class="col-12 col-xl-7">
<div class="card border-0 shadow-sm h-100">
<!-- Traced devices + log viewer -->
<div class="col-12 col-xl-8">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h2 class="h4 mb-1">Traced devices</h2>
<p class="text-muted small mb-0">
Full SOAP traces are written to per-device files.
</p>
<p class="text-muted small mb-0">Click a row to view its live SOAP log.</p>
</div>
<span class="badge bg-dark">{{ tracedDevices.length }}</span>
</div>
<div v-if="entriesError" class="alert alert-danger" role="alert">
{{ entriesError }}
</div>
<div v-if="actionError" class="alert alert-warning" role="alert">
{{ actionError }}
</div>
<div v-if="entriesError" class="alert alert-danger" role="alert">{{ entriesError }}</div>
<div v-if="actionError" class="alert alert-warning" role="alert">{{ actionError }}</div>
<p v-if="loadingEntries" class="text-muted small mb-0">Loading traced devices…</p>
<p v-if="loadingEntries" class="text-muted small mb-0">Loading traced devices&hellip;</p>
<div v-else class="table-responsive">
<table class="table table-sm table-hover align-middle mb-0">
@@ -247,11 +279,17 @@ async function removeDevice(deviceId: string) {
<th>Product Class</th>
<th>Serial Number</th>
<th>Started</th>
<th class="text-end">Action</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in tracedDevices" :key="entry.deviceId">
<tr
v-for="entry in tracedDevices"
:key="entry.deviceId"
:class="{ 'table-active': selectedDeviceId === entry.deviceId }"
style="cursor: pointer"
@click="selectedDeviceId = entry.deviceId"
>
<td>{{ entry.manufacturer || '—' }}</td>
<td>{{ entry.productClass || '—' }}</td>
<td>{{ entry.serialNumber || '—' }}</td>
@@ -260,9 +298,9 @@ async function removeDevice(deviceId: string) {
<button
class="btn btn-sm btn-outline-danger"
:disabled="removingDeviceId === entry.deviceId"
@click="removeDevice(entry.deviceId)"
@click.stop="removeDevice(entry.deviceId)"
>
{{ removingDeviceId === entry.deviceId ? 'Removing…' : 'Remove' }}
{{ removingDeviceId === entry.deviceId ? 'Removing&hellip;' : 'Remove' }}
</button>
</td>
</tr>
@@ -276,6 +314,38 @@ async function removeDevice(deviceId: string) {
</div>
</div>
</div>
<!-- Log viewer panel -->
<div v-if="selectedDeviceId" class="card border-0 shadow-sm mt-4">
<div
class="card-header d-flex justify-content-between align-items-center bg-dark text-white py-2"
>
<div class="d-flex align-items-center gap-2">
<span class="badge bg-success">Live</span>
<span class="small font-monospace">
{{ selectedEntry?.manufacturer }} {{ selectedEntry?.productClass }} /
{{ selectedEntry?.serialNumber }}
</span>
</div>
<div class="d-flex align-items-center gap-2">
<span class="small text-white-50">{{ logLines.toLocaleString() }} lines total</span>
<button class="btn btn-sm btn-outline-light py-0" @click="clearLog">Clear view</button>
<button class="btn btn-sm btn-outline-light py-0" @click="selectedDeviceId = null">&#x2715;</button>
</div>
</div>
<div class="card-body p-0">
<div v-if="loadingLog && !logContent" class="text-muted small p-3">Loading log&hellip;</div>
<div v-else-if="logError" class="alert alert-danger m-3">{{ logError }}</div>
<div v-else-if="!logContent" class="text-muted small p-3">
No log entries yet. Waiting for SOAP exchanges&hellip;
</div>
<pre
v-else
class="mb-0 p-3 small font-monospace"
style="background:#1e1e1e;color:#d4d4d4;max-height:600px;overflow-y:auto;white-space:pre-wrap;word-break:break-all"
>{{ logContent }}</pre>
</div>
</div>
</div>
</div>
</div>
File renamed without changes.
+1
View File
@@ -37,6 +37,7 @@ router
router.get('/tr069-trace', [Tr069TraceController, 'index'])
router.post('/tr069-trace', [Tr069TraceController, 'store'])
router.delete('/tr069-trace/:id', [Tr069TraceController, 'destroy'])
router.get('/tr069-trace/:id/log', [Tr069TraceController, 'showLog'])
// Unified Configuration API (v1)
router.get('/configurations', [ConfigurationsV1Controller, 'index'])
@@ -23,6 +23,7 @@ function makeSnapshot(
onboardingStatus: OnboardingStatus.COMPLETED,
isOnline: false,
gpnStatus: 'unknown',
dataModel: 'unknown',
parameters: [],
properties: [],
...overrides,