From 6082a0e6102f3e060001739866c6c748dacdc339 Mon Sep 17 00:00:00 2001 From: Kevin L'Hereec Date: Mon, 1 Jun 2026 14:33:44 +0200 Subject: [PATCH] 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> --- .adonisjs/client/data.d.ts | 5 - .adonisjs/client/manifest.d.ts | 1 - .adonisjs/client/registry/index.ts | 324 ++++++++- .adonisjs/client/registry/schema.d.ts | 630 +++++++++++++++++- .adonisjs/client/registry/tree.d.ts | 78 ++- .adonisjs/server/controllers.ts | 1 + .adonisjs/server/routes.d.ts | 156 ++++- Dockerfile | 4 +- app/controllers/cwmp_controller.ts | 26 +- app/controllers/tr069_trace_controller.ts | 25 + app/domain/device/device.ts | 25 + app/domain/device/device_record.ts | 3 +- app/middleware/cwmp_logger_middleware.ts | 44 +- app/services/configuration_engine.ts | 1 + app/services/cwmp/device_action_executor.ts | 19 +- app/services/cwmp/main_state_machine.ts | 1 + app/services/tr069_trace_service.ts | 14 +- docker-compose.simulator.yml | 6 +- docker-compose.yml | 5 + inertia/pages/Tr069Trace.vue | 170 +++-- {config => public}/simulator-ui-env.js | 0 start/routes.ts | 1 + .../services/parameter_writability.spec.ts | 1 + 23 files changed, 1360 insertions(+), 180 deletions(-) rename {config => public}/simulator-ui-env.js (100%) diff --git a/.adonisjs/client/data.d.ts b/.adonisjs/client/data.d.ts index ee014a4..1513a7a 100644 --- a/.adonisjs/client/data.d.ts +++ b/.adonisjs/client/data.d.ts @@ -5,11 +5,6 @@ /// import type { InferData, InferVariants } from '@adonisjs/core/types/transformers' -import type UserTransformer from '#transformers/user_transformer' export namespace Data { - export type User = InferData - export namespace User { - export type Variants = InferVariants - } } diff --git a/.adonisjs/client/manifest.d.ts b/.adonisjs/client/manifest.d.ts index 9fc3356..9120583 100644 --- a/.adonisjs/client/manifest.d.ts +++ b/.adonisjs/client/manifest.d.ts @@ -4,6 +4,5 @@ */ /// -/// /// /// diff --git a/.adonisjs/client/registry/index.ts b/.adonisjs/client/registry/index.ts index 298b217..0bb6e4b 100644 --- a/.adonisjs/client/registry/index.ts +++ b/.adonisjs/client/registry/index.ts @@ -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 diff --git a/.adonisjs/client/registry/schema.d.ts b/.adonisjs/client/registry/schema.d.ts index 82c02fd..75de136 100644 --- a/.adonisjs/client/registry/schema.d.ts +++ b/.adonisjs/client/registry/schema.d.ts @@ -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> + body: {} paramsTuple: [] params: {} - query: ExtractQuery> - response: ExtractResponse>> - errorResponse: ExtractErrorResponse>> | { status: 422; response: { errors: SimpleError[] } } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> } } - 'auth.access_tokens.store': { - methods: ["POST"] - pattern: '/api/v1/auth/login' - types: { - body: ExtractBody> - paramsTuple: [] - params: {} - query: ExtractQuery> - response: ExtractResponse>> - errorResponse: ExtractErrorResponse>> | { 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>> - errorResponse: ExtractErrorResponse>> + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> } } - '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>> - errorResponse: ExtractErrorResponse>> + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'devices.index': { + methods: ["GET","HEAD"] + pattern: '/api/v1/devices' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'devices.criteria_values': { + methods: ["GET","HEAD"] + pattern: '/api/v1/devices/criteria-values' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'devices.show': { + methods: ["GET","HEAD"] + pattern: '/api/v1/devices/:identifier' + types: { + body: {} + paramsTuple: [ParamValue] + params: { identifier: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'devices.destroy': { + methods: ["DELETE"] + pattern: '/api/v1/devices/:identifier' + types: { + body: {} + paramsTuple: [ParamValue] + params: { identifier: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'devices.execute_command': { + methods: ["POST"] + pattern: '/api/v1/devices/:identifier/command' + types: { + body: {} + paramsTuple: [ParamValue] + params: { identifier: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'devices.refresh': { + methods: ["POST"] + pattern: '/api/v1/devices/:identifier/refresh' + types: { + body: {} + paramsTuple: [ParamValue] + params: { identifier: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'commands.index': { + methods: ["GET","HEAD"] + pattern: '/api/v1/commands' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'commands.show': { + methods: ["GET","HEAD"] + pattern: '/api/v1/commands/:id' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'commands.store': { + methods: ["POST"] + pattern: '/api/v1/commands' + types: { + body: ExtractBody> + paramsTuple: [] + params: {} + query: ExtractQuery> + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> | { status: 422; response: { errors: SimpleError[] } } + } + } + 'tr_069_trace.index': { + methods: ["GET","HEAD"] + pattern: '/api/v1/tr069-trace' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'tr_069_trace.store': { + methods: ["POST"] + pattern: '/api/v1/tr069-trace' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'tr_069_trace.destroy': { + methods: ["DELETE"] + pattern: '/api/v1/tr069-trace/:id' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.index': { + methods: ["GET","HEAD"] + pattern: '/api/v1/configurations' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.list_tags': { + methods: ["GET","HEAD"] + pattern: '/api/v1/configurations/tags' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.store': { + methods: ["POST"] + pattern: '/api/v1/configurations' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.show': { + methods: ["GET","HEAD"] + pattern: '/api/v1/configurations/:id' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.update': { + methods: ["PUT"] + pattern: '/api/v1/configurations/:id' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.destroy': { + methods: ["DELETE"] + pattern: '/api/v1/configurations/:id' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.toggle_enabled': { + methods: ["PATCH"] + pattern: '/api/v1/configurations/:id/toggle' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.manage_tags': { + methods: ["PATCH"] + pattern: '/api/v1/configurations/:id/tags' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.history': { + methods: ["GET","HEAD"] + pattern: '/api/v1/configurations/:id/history' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.dry_run': { + methods: ["POST"] + pattern: '/api/v1/configurations/:id/dry-run' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'configurations_v_1.compliance': { + methods: ["GET","HEAD"] + pattern: '/api/v1/configurations/:id/compliance' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'firmware_catalog.index': { + methods: ["GET","HEAD"] + pattern: '/api/v1/firmware-catalog' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'firmware_catalog.list_entries': { + methods: ["GET","HEAD"] + pattern: '/api/v1/firmware-catalog/entries' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'firmware_catalog.show_entry': { + methods: ["GET","HEAD"] + pattern: '/api/v1/firmware-catalog/entries/:id' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'firmware_catalog.list_routes': { + methods: ["GET","HEAD"] + pattern: '/api/v1/firmware-catalog/routes' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'firmware_catalog.create_entry': { + methods: ["POST"] + pattern: '/api/v1/firmware-catalog/entries' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'firmware_catalog.update_entry': { + methods: ["PUT"] + pattern: '/api/v1/firmware-catalog/entries/:id' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'firmware_catalog.delete_entry': { + methods: ["DELETE"] + pattern: '/api/v1/firmware-catalog/entries/:id' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'firmware_catalog.create_route': { + methods: ["POST"] + pattern: '/api/v1/firmware-catalog/routes' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'firmware_catalog.update_route': { + methods: ["PUT"] + pattern: '/api/v1/firmware-catalog/routes/:id' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'firmware_catalog.delete_route': { + methods: ["DELETE"] + pattern: '/api/v1/firmware-catalog/routes/:id' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.home': { + methods: ["GET","HEAD"] + pattern: '/' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.device_detail': { + methods: ["GET","HEAD"] + pattern: '/devices/:identifier' + types: { + body: {} + paramsTuple: [ParamValue] + params: { identifier: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.refresh_device': { + methods: ["POST"] + pattern: '/devices/:identifier/refresh' + types: { + body: {} + paramsTuple: [ParamValue] + params: { identifier: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.get_model': { + methods: ["POST"] + pattern: '/devices/:identifier/get-model' + types: { + body: {} + paramsTuple: [ParamValue] + params: { identifier: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'commands.page': { + methods: ["GET","HEAD"] + pattern: '/commands' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.commands_fleet': { + methods: ["GET","HEAD"] + pattern: '/commands/fleet' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.tr_069_trace': { + methods: ["GET","HEAD"] + pattern: '/tr069-trace' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.store_fleet_devices': { + methods: ["POST"] + pattern: '/commands/fleet' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.store_fleet_command': { + methods: ["POST"] + pattern: '/commands/fleet/command' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.configurations_list': { + methods: ["GET","HEAD"] + pattern: '/configurations' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.configuration_create': { + methods: ["GET","HEAD"] + pattern: '/configurations/create' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.configuration_edit': { + methods: ["GET","HEAD"] + pattern: '/configurations/:id/edit' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.configuration_detail': { + methods: ["GET","HEAD"] + pattern: '/configurations/:id/detail' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.firmware_catalog': { + methods: ["GET","HEAD"] + pattern: '/firmware-catalog' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.firmware_catalog_create': { + methods: ["GET","HEAD"] + pattern: '/firmware-catalog/create' + types: { + body: {} + paramsTuple: [] + params: {} + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> + } + } + 'pages.firmware_catalog_edit': { + methods: ["GET","HEAD"] + pattern: '/firmware-catalog/:id/edit' + types: { + body: {} + paramsTuple: [ParamValue] + params: { id: ParamValue } + query: {} + response: ExtractResponse>> + errorResponse: ExtractErrorResponse>> } } } diff --git a/.adonisjs/client/registry/tree.d.ts b/.adonisjs/client/registry/tree.d.ts index 3964805..af290a0 100644 --- a/.adonisjs/client/registry/tree.d.ts +++ b/.adonisjs/client/registry/tree.d.ts @@ -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'] } } diff --git a/.adonisjs/server/controllers.ts b/.adonisjs/server/controllers.ts index ec42cd6..f74b65d 100644 --- a/.adonisjs/server/controllers.ts +++ b/.adonisjs/server/controllers.ts @@ -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'), } diff --git a/.adonisjs/server/routes.d.ts b/.adonisjs/server/routes.d.ts index 9aa155f..e7e32bb 100644 --- a/.adonisjs/server/routes.d.ts +++ b/.adonisjs/server/routes.d.ts @@ -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' { diff --git a/Dockerfile b/Dockerfile index 5401dab..4477dd8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/app/controllers/cwmp_controller.ts b/app/controllers/cwmp_controller.ts index 576b7d6..54c98e9 100644 --- a/app/controllers/cwmp_controller.ts +++ b/app/controllers/cwmp_controller.ts @@ -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) { diff --git a/app/controllers/tr069_trace_controller.ts b/app/controllers/tr069_trace_controller.ts index aaa8835..2f5652e 100644 --- a/app/controllers/tr069_trace_controller.ts +++ b/app/controllers/tr069_trace_controller.ts @@ -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 }) + } } diff --git a/app/domain/device/device.ts b/app/domain/device/device.ts index 3e1fca0..9fd6989 100644 --- a/app/domain/device/device.ts +++ b/app/domain/device/device.ts @@ -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[] } diff --git a/app/domain/device/device_record.ts b/app/domain/device/device_record.ts index 30d6a4d..6cfbeb0 100644 --- a/app/domain/device/device_record.ts +++ b/app/domain/device/device_record.ts @@ -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 diff --git a/app/middleware/cwmp_logger_middleware.ts b/app/middleware/cwmp_logger_middleware.ts index 62c40e1..ae8399f 100644 --- a/app/middleware/cwmp_logger_middleware.ts +++ b/app/middleware/cwmp_logger_middleware.ts @@ -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)') } } } diff --git a/app/services/configuration_engine.ts b/app/services/configuration_engine.ts index 0e3905e..30583e2 100644 --- a/app/services/configuration_engine.ts +++ b/app/services/configuration_engine.ts @@ -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 diff --git a/app/services/cwmp/device_action_executor.ts b/app/services/cwmp/device_action_executor.ts index e09b9e6..9bb7722 100644 --- a/app/services/cwmp/device_action_executor.ts +++ b/app/services/cwmp/device_action_executor.ts @@ -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' } } }] diff --git a/app/services/cwmp/main_state_machine.ts b/app/services/cwmp/main_state_machine.ts index f80abc3..b46a42e 100644 --- a/app/services/cwmp/main_state_machine.ts +++ b/app/services/cwmp/main_state_machine.ts @@ -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) { diff --git a/app/services/tr069_trace_service.ts b/app/services/tr069_trace_service.ts index 1998d8b..1a729a4 100644 --- a/app/services/tr069_trace_service.ts +++ b/app/services/tr069_trace_service.ts @@ -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 { + private async doWrite(deviceId: string, direction: TraceDirection, sessionId: string, xml: string): Promise { 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') } diff --git a/docker-compose.simulator.yml b/docker-compose.simulator.yml index 3d0717f..fd7370a 100644 --- a/docker-compose.simulator.yml +++ b/docker-compose.simulator.yml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 962b69c..97a5b0c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/inertia/pages/Tr069Trace.vue b/inertia/pages/Tr069Trace.vue index 18ccbc4..1ae9e38 100644 --- a/inertia/pages/Tr069Trace.vue +++ b/inertia/pages/Tr069Trace.vue @@ -31,43 +31,69 @@ const entriesError = ref('') const actionError = ref('') let searchTimeout: ReturnType | null = null +// Log viewer state +const selectedDeviceId = ref(null) +const logContent = ref('') +const logLines = ref(0) +const loadingLog = ref(false) +const logError = ref('') +let autoRefreshTimer: ReturnType | 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 = '' +}