trace + backup propertie
This commit is contained in:
1 parent
b3b1edec17
commit
ea73e91aa4
8 files changed
+164
-37
No files matched your search
@@ -90,12 +90,30 @@ const routes = {
|
||||
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.raw_config': {
|
||||
methods: ["GET","HEAD"],
|
||||
pattern: '/api/v1/tr069-trace/config',
|
||||
tokens: [{"old":"/api/v1/tr069-trace/config","type":0,"val":"api","end":""},{"old":"/api/v1/tr069-trace/config","type":0,"val":"v1","end":""},{"old":"/api/v1/tr069-trace/config","type":0,"val":"tr069-trace","end":""},{"old":"/api/v1/tr069-trace/config","type":0,"val":"config","end":""}],
|
||||
types: placeholder as Registry['tr_069_trace.raw_config']['types'],
|
||||
},
|
||||
'tr_069_trace.save_raw_config': {
|
||||
methods: ["PUT"],
|
||||
pattern: '/api/v1/tr069-trace/config',
|
||||
tokens: [{"old":"/api/v1/tr069-trace/config","type":0,"val":"api","end":""},{"old":"/api/v1/tr069-trace/config","type":0,"val":"v1","end":""},{"old":"/api/v1/tr069-trace/config","type":0,"val":"tr069-trace","end":""},{"old":"/api/v1/tr069-trace/config","type":0,"val":"config","end":""}],
|
||||
types: placeholder as Registry['tr_069_trace.save_raw_config']['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'],
|
||||
},
|
||||
'tr_069_trace.show_log': {
|
||||
methods: ["GET","HEAD"],
|
||||
pattern: '/api/v1/tr069-trace/:id/log',
|
||||
tokens: [{"old":"/api/v1/tr069-trace/:id/log","type":0,"val":"api","end":""},{"old":"/api/v1/tr069-trace/:id/log","type":0,"val":"v1","end":""},{"old":"/api/v1/tr069-trace/:id/log","type":0,"val":"tr069-trace","end":""},{"old":"/api/v1/tr069-trace/:id/log","type":1,"val":"id","end":""},{"old":"/api/v1/tr069-trace/:id/log","type":0,"val":"log","end":""}],
|
||||
types: placeholder as Registry['tr_069_trace.show_log']['types'],
|
||||
},
|
||||
'configurations_v_1.index': {
|
||||
methods: ["GET","HEAD"],
|
||||
pattern: '/api/v1/configurations',
|
||||
|
||||
+36
@@ -175,6 +175,30 @@ export interface Registry {
|
||||
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['store']>>>
|
||||
}
|
||||
}
|
||||
'tr_069_trace.raw_config': {
|
||||
methods: ["GET","HEAD"]
|
||||
pattern: '/api/v1/tr069-trace/config'
|
||||
types: {
|
||||
body: {}
|
||||
paramsTuple: []
|
||||
params: {}
|
||||
query: {}
|
||||
response: ExtractResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['rawConfig']>>>
|
||||
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['rawConfig']>>>
|
||||
}
|
||||
}
|
||||
'tr_069_trace.save_raw_config': {
|
||||
methods: ["PUT"]
|
||||
pattern: '/api/v1/tr069-trace/config'
|
||||
types: {
|
||||
body: {}
|
||||
paramsTuple: []
|
||||
params: {}
|
||||
query: {}
|
||||
response: ExtractResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['saveRawConfig']>>>
|
||||
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['saveRawConfig']>>>
|
||||
}
|
||||
}
|
||||
'tr_069_trace.destroy': {
|
||||
methods: ["DELETE"]
|
||||
pattern: '/api/v1/tr069-trace/:id'
|
||||
@@ -187,6 +211,18 @@ export interface Registry {
|
||||
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['destroy']>>>
|
||||
}
|
||||
}
|
||||
'tr_069_trace.show_log': {
|
||||
methods: ["GET","HEAD"]
|
||||
pattern: '/api/v1/tr069-trace/:id/log'
|
||||
types: {
|
||||
body: {}
|
||||
paramsTuple: [ParamValue]
|
||||
params: { id: ParamValue }
|
||||
query: {}
|
||||
response: ExtractResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['showLog']>>>
|
||||
errorResponse: ExtractErrorResponse<Awaited<ReturnType<import('#controllers/tr069_trace_controller').default['showLog']>>>
|
||||
}
|
||||
}
|
||||
'configurations_v_1.index': {
|
||||
methods: ["GET","HEAD"]
|
||||
pattern: '/api/v1/configurations'
|
||||
|
||||
Vendored
+3
@@ -24,7 +24,10 @@ export interface ApiDefinition {
|
||||
tr069Trace: {
|
||||
index: typeof routes['tr_069_trace.index']
|
||||
store: typeof routes['tr_069_trace.store']
|
||||
rawConfig: typeof routes['tr_069_trace.raw_config']
|
||||
saveRawConfig: typeof routes['tr_069_trace.save_raw_config']
|
||||
destroy: typeof routes['tr_069_trace.destroy']
|
||||
showLog: typeof routes['tr_069_trace.show_log']
|
||||
}
|
||||
configurationsV1: {
|
||||
index: typeof routes['configurations_v_1.index']
|
||||
|
||||
Vendored
+8
@@ -18,7 +18,10 @@ export type ScannedRoutes = {
|
||||
'commands.store': { paramsTuple?: []; params?: {} }
|
||||
'tr_069_trace.index': { paramsTuple?: []; params?: {} }
|
||||
'tr_069_trace.store': { paramsTuple?: []; params?: {} }
|
||||
'tr_069_trace.raw_config': { paramsTuple?: []; params?: {} }
|
||||
'tr_069_trace.save_raw_config': { paramsTuple?: []; params?: {} }
|
||||
'tr_069_trace.destroy': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
|
||||
'tr_069_trace.show_log': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
|
||||
'configurations_v_1.index': { paramsTuple?: []; params?: {} }
|
||||
'configurations_v_1.list_tags': { paramsTuple?: []; params?: {} }
|
||||
'configurations_v_1.store': { paramsTuple?: []; params?: {} }
|
||||
@@ -81,6 +84,8 @@ export type ScannedRoutes = {
|
||||
'commands.index': { paramsTuple?: []; params?: {} }
|
||||
'commands.show': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
|
||||
'tr_069_trace.index': { paramsTuple?: []; params?: {} }
|
||||
'tr_069_trace.raw_config': { paramsTuple?: []; params?: {} }
|
||||
'tr_069_trace.show_log': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
|
||||
'configurations_v_1.index': { paramsTuple?: []; params?: {} }
|
||||
'configurations_v_1.list_tags': { paramsTuple?: []; params?: {} }
|
||||
'configurations_v_1.show': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
|
||||
@@ -112,6 +117,8 @@ export type ScannedRoutes = {
|
||||
'commands.index': { paramsTuple?: []; params?: {} }
|
||||
'commands.show': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
|
||||
'tr_069_trace.index': { paramsTuple?: []; params?: {} }
|
||||
'tr_069_trace.raw_config': { paramsTuple?: []; params?: {} }
|
||||
'tr_069_trace.show_log': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
|
||||
'configurations_v_1.index': { paramsTuple?: []; params?: {} }
|
||||
'configurations_v_1.list_tags': { paramsTuple?: []; params?: {} }
|
||||
'configurations_v_1.show': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
|
||||
@@ -142,6 +149,7 @@ export type ScannedRoutes = {
|
||||
'firmware_catalog.delete_route': { paramsTuple: [ParamValue]; params: {'id': ParamValue} }
|
||||
}
|
||||
PUT: {
|
||||
'tr_069_trace.save_raw_config': { paramsTuple?: []; params?: {} }
|
||||
'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} }
|
||||
|
||||
@@ -3,6 +3,7 @@ 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, detectDataModelFromParams } from '../domain/device/device.js'
|
||||
import { reclassifyDeviceFromCatalog } from '../services/parameter_writability.js'
|
||||
import { DeviceId } from '../domain/device/device_id.js'
|
||||
import { DeviceParameter, DeviceProperty } from '../domain/device/device_parameter.js'
|
||||
import DeviceModel from '../models/device.js'
|
||||
@@ -120,6 +121,12 @@ export default class CwmpController {
|
||||
if (paramValues.length > 0) {
|
||||
device.updateFromValues(paramValues)
|
||||
}
|
||||
|
||||
// Reclassify properties → parameters using catalog when GPN is unsupported
|
||||
if (device.gpnStatus === 'unsupported') {
|
||||
reclassifyDeviceFromCatalog(device)
|
||||
}
|
||||
|
||||
device.setTmpParamValues(paramValues)
|
||||
|
||||
// Detect TR data model from Inform parameter names (Device.* = TR-181, InternetGatewayDevice.* = TR-098)
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import { Device } from '../../domain/device/device.js'
|
||||
import { DeviceParameter } from '../../domain/device/device_parameter.js'
|
||||
import { ParameterType } from '../../domain/shared/types.js'
|
||||
import { CWMPTaskQueue, ResultStatus, TaskResult } from './task_queue.js'
|
||||
import { normalizePath } from '../parameter_writability.js'
|
||||
import { reclassifyDeviceFromCatalog } from '../parameter_writability.js'
|
||||
import DeviceModel from '../../models/device.js'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const tr181Catalog: Array<{ path: string; writable: boolean; deviceClass: string }> =
|
||||
require('../../../config/tr181-catalog.json')
|
||||
const catalogWritableIndex = new Set(
|
||||
tr181Catalog.filter((e) => e.writable).map((e) => e.path)
|
||||
)
|
||||
|
||||
// Parameter types (simplified from legacy provisioning domain)
|
||||
interface SetParameterValue {
|
||||
@@ -93,33 +84,8 @@ export class DeviceActionExecutor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reclassify device properties as parameters using the TR-181 catalog.
|
||||
* Called when GPN is unsupported — properties that are writable per the catalog
|
||||
* should be moved to parameters so the configuration engine can write to them.
|
||||
* Get parameter values from device.
|
||||
*/
|
||||
private reclassifyFromCatalog(device: Device): void {
|
||||
const properties = device.getProperties()
|
||||
let reclassified = 0
|
||||
|
||||
for (const prop of properties) {
|
||||
const normalized = normalizePath(prop.name)
|
||||
if (catalogWritableIndex.has(normalized)) {
|
||||
// Move from properties to parameters (writable)
|
||||
const snap = prop.toSnapshot()
|
||||
device.setParameter(
|
||||
new DeviceParameter(snap.name, snap.value, snap.type, snap.createdAt, snap.updatedAt, snap.notification, snap.accessList)
|
||||
)
|
||||
// Remove from properties map
|
||||
;(device as any)._properties.delete(prop.name)
|
||||
reclassified++
|
||||
}
|
||||
}
|
||||
|
||||
if (reclassified > 0) {
|
||||
logger.debug({ identifier: device.identifier, reclassified }, '[DeviceAction] Reclassified properties → parameters via catalog')
|
||||
}
|
||||
}
|
||||
|
||||
async getParameterValues(
|
||||
device: Device,
|
||||
parameterPaths: string[],
|
||||
@@ -208,7 +174,7 @@ export class DeviceActionExecutor {
|
||||
if (code === 9000 || code === 9001) {
|
||||
device.setGpnStatus('unsupported')
|
||||
// Reclassify parameters using TR-181 catalog writability
|
||||
this.reclassifyFromCatalog(device)
|
||||
reclassifyDeviceFromCatalog(device)
|
||||
await this.persistDevice(device)
|
||||
}
|
||||
// Autres erreurs (réseau, timeout, 9002+) → gpnStatus inchangé
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createRequire } from 'node:module'
|
||||
import type { DeviceSnapshot } from '../domain/device/device.js'
|
||||
import type { Device } from '../domain/device/device.js'
|
||||
import { DeviceParameter } from '../domain/device/device_parameter.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const tr181Catalog: Array<{ path: string; writable: boolean; deviceClass: string }> =
|
||||
@@ -7,6 +9,9 @@ const tr181Catalog: Array<{ path: string; writable: boolean; deviceClass: string
|
||||
|
||||
// Index chargé une fois au démarrage — O(1) lookup
|
||||
const catalogIndex = new Map(tr181Catalog.map((e) => [e.path, e.writable]))
|
||||
const catalogWritableIndex = new Set(
|
||||
tr181Catalog.filter((e) => e.writable).map((e) => e.path)
|
||||
)
|
||||
|
||||
/** Remplace les segments numériques par {i} pour matcher les entrées du catalogue */
|
||||
export function normalizePath(path: string): string {
|
||||
@@ -46,3 +51,27 @@ export function resolveWritability(snapshot: DeviceSnapshot, paramPath: string):
|
||||
if (gpnStatus === 'unsupported') return { writable: true, source: 'default' }
|
||||
return { writable: null, source: 'unknown' }
|
||||
}
|
||||
|
||||
/**
|
||||
* Reclassify device properties as parameters using the TR-181 catalog.
|
||||
* Properties that are writable per the catalog are moved to parameters.
|
||||
* Should be called after updateFromValues when gpnStatus is 'unsupported'.
|
||||
*/
|
||||
export function reclassifyDeviceFromCatalog(device: Device): number {
|
||||
const properties = device.getProperties()
|
||||
let reclassified = 0
|
||||
|
||||
for (const prop of properties) {
|
||||
const normalized = normalizePath(prop.name)
|
||||
if (catalogWritableIndex.has(normalized)) {
|
||||
const snap = prop.toSnapshot()
|
||||
device.setParameter(
|
||||
new DeviceParameter(snap.name, snap.value, snap.type, snap.createdAt, snap.updatedAt, snap.notification, snap.accessList)
|
||||
)
|
||||
;(device as any)._properties.delete(prop.name)
|
||||
reclassified++
|
||||
}
|
||||
}
|
||||
|
||||
return reclassified
|
||||
}
|
||||
@@ -14,6 +14,51 @@ const { device, modelPaths, commands } = defineProps<{
|
||||
|
||||
const isLoading = ref(false)
|
||||
const showDeleteConfirm = ref(false)
|
||||
const isTraced = ref(false)
|
||||
const tracingAction = ref(false)
|
||||
|
||||
// Check if device is already traced
|
||||
async function checkTraceStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/v1/tr069-trace')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
const entries = data.entries || []
|
||||
isTraced.value = entries.some((e: any) => e.deviceId === device.identifier)
|
||||
}
|
||||
} catch {
|
||||
// Silently ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleTrace() {
|
||||
tracingAction.value = true
|
||||
try {
|
||||
if (isTraced.value) {
|
||||
const response = await fetch(`/api/v1/tr069-trace/${encodeURIComponent(device.identifier)}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
if (response.ok || response.status === 204) {
|
||||
isTraced.value = false
|
||||
}
|
||||
} else {
|
||||
const response = await fetch('/api/v1/tr069-trace', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ deviceId: device.identifier }),
|
||||
})
|
||||
if (response.ok) {
|
||||
isTraced.value = true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Silently ignore
|
||||
} finally {
|
||||
tracingAction.value = false
|
||||
}
|
||||
}
|
||||
|
||||
checkTraceStatus()
|
||||
|
||||
function deleteDevice() {
|
||||
router.delete(`/devices/${encodeURIComponent(device.identifier)}`, {
|
||||
@@ -328,6 +373,21 @@ function getPropertyValue(name: string): string {
|
||||
<button class="btn btn-outline-dark btn-sm" @click="openGetModelPopup" :disabled="isLoading">
|
||||
{{ t('button.getModel') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="isTraced ? 'btn-warning' : 'btn-outline-primary'"
|
||||
:disabled="tracingAction"
|
||||
@click="toggleTrace"
|
||||
>
|
||||
<svg v-if="!isTraced" xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
|
||||
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/>
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293z"/>
|
||||
</svg>
|
||||
{{ tracingAction ? '…' : (isTraced ? 'Remove TR-069 monitoring' : 'Add to TR-069 monitoring') }}
|
||||
</button>
|
||||
<button class="btn btn-outline-danger btn-sm ms-auto" @click="showDeleteConfirm = true">
|
||||
Supprimer l'équipement
|
||||
</button>
|
||||
|
||||
Reference in new issue
Block a user