From 31578e368f95da7b41f37a8d5c5420ef684434f3 Mon Sep 17 00:00:00 2001 From: Kevin L'Hereec Date: Tue, 2 Jun 2026 17:56:17 +0200 Subject: [PATCH] feat: estimated next connection + fix build errors - Store lastPeriodicInformAt on device when PERIODIC Inform received - Display 'Prochaine connexion' in DeviceDetail identity card - Fix tsconfig exclude (tests, inertia, coverage) for Docker build - Fix TS errors in tests (CommandResult type, unused imports) - Fix distinctCriteriaValues typing in controller --- app/controllers/cwmp_controller.ts | 6 ++++ app/controllers/devices_controller.ts | 2 +- app/domain/device/device_record.ts | 1 + inertia/pages/DeviceDetail.vue | 29 +++++++++++++++++++ tests/unit/domain/command.spec.ts | 10 +++---- .../configuration_evaluation_state.spec.ts | 2 +- tsconfig.json | 15 ++++++++-- 7 files changed, 55 insertions(+), 10 deletions(-) diff --git a/app/controllers/cwmp_controller.ts b/app/controllers/cwmp_controller.ts index 3faf291..bd3f78d 100644 --- a/app/controllers/cwmp_controller.ts +++ b/app/controllers/cwmp_controller.ts @@ -172,6 +172,12 @@ export default class CwmpController { logger.info({ events }, 'CWMP Inform events extracted') + // Track last periodic inform timestamp + const isPeriodic = events.some((e) => String(e).toUpperCase().includes('PERIODIC') || String(e) === '2') + if (isPeriodic) { + await DeviceModel.updateOne({ identifier: hashId }, { $set: { lastPeriodicInformAt: new Date() } }).catch(() => {}) + } + // Start state machine await manager.startStateMachine(device, session, { events }) manager.bindRemoteToDevice(remoteKey, device.deviceId) diff --git a/app/controllers/devices_controller.ts b/app/controllers/devices_controller.ts index 84a75e1..a64c2f0 100644 --- a/app/controllers/devices_controller.ts +++ b/app/controllers/devices_controller.ts @@ -8,7 +8,7 @@ export default class DevicesController { private deviceService = new DeviceService() async criteriaValues({ response }: HttpContext) { - const values = await DeviceModel.distinctCriteriaValues() + const values = await (DeviceModel as any).distinctCriteriaValues() return response.json(values) } diff --git a/app/domain/device/device_record.ts b/app/domain/device/device_record.ts index f0eceb6..bdf9336 100644 --- a/app/domain/device/device_record.ts +++ b/app/domain/device/device_record.ts @@ -14,6 +14,7 @@ export interface DeviceRecord { compliance?: DeviceComplianceInfo provisioningCode?: string references?: DeviceReference[] + lastPeriodicInformAt?: Date updatedAt?: Date createdAt?: Date } diff --git a/inertia/pages/DeviceDetail.vue b/inertia/pages/DeviceDetail.vue index 8deaee5..b1206c4 100644 --- a/inertia/pages/DeviceDetail.vue +++ b/inertia/pages/DeviceDetail.vue @@ -126,6 +126,18 @@ function submitGetModel() { }) } +// Estimated next connection = lastPeriodicInformAt + PeriodicInformInterval +const estimatedNextConnection = (() => { + const lastPeriodic = device.lastPeriodicInformAt + if (!lastPeriodic) return null + const allParams = [...(device.properties || []), ...(device.parameters || [])] + const intervalParam = allParams.find((p: any) => p.name?.includes('PeriodicInformInterval')) + const intervalSeconds = intervalParam?.value ? parseInt(intervalParam.value, 10) : 0 + if (!intervalSeconds || intervalSeconds <= 0) return null + const next = new Date(new Date(lastPeriodic).getTime() + intervalSeconds * 1000) + return next.toLocaleString() +})() + // Check if the device has a ConnectionRequestURL (needed for immediate mode / Refresh) const hasConnectionRequestUrl = (() => { const allParams = [...(device.properties || []), ...(device.parameters || [])] @@ -333,6 +345,23 @@ async function resolveAllProviders() { + +
+
+
+ + + + +
+
+
Prochaine connexion
+
{{ estimatedNextConnection }}
+
+
+
+ diff --git a/tests/unit/domain/command.spec.ts b/tests/unit/domain/command.spec.ts index 44e768b..68fdd89 100644 --- a/tests/unit/domain/command.spec.ts +++ b/tests/unit/domain/command.spec.ts @@ -41,15 +41,15 @@ describe('Command', () => { it('complete transitions SENT → COMPLETED', () => { const cmd = makeCommand() cmd.markAsSent() - cmd.complete({ success: true }) + cmd.complete({ success: true, timestamp: new Date() }) expect(cmd.status).toBe(CommandStatus.COMPLETED) - expect(cmd.result).toEqual({ success: true }) + expect(cmd.result?.success).toBe(true) expect(cmd.isCompleted()).toBe(true) }) it('complete throws if PENDING', () => { const cmd = makeCommand() - expect(() => cmd.complete({})).toThrow('Cannot complete command') + expect(() => cmd.complete({ success: true, timestamp: new Date() } as any)).toThrow('Cannot complete command') }) it('fail transitions to FAILED', () => { @@ -64,7 +64,7 @@ describe('Command', () => { it('fail throws if already COMPLETED', () => { const cmd = makeCommand() cmd.markAsSent() - cmd.complete({}) + cmd.complete({ success: true, timestamp: new Date() } as any) expect(() => cmd.fail('oops')).toThrow('Cannot fail a successful command') }) @@ -78,7 +78,7 @@ describe('Command', () => { it('cancel throws if COMPLETED', () => { const cmd = makeCommand() cmd.markAsSent() - cmd.complete({}) + cmd.complete({ success: true, timestamp: new Date() } as any) expect(() => cmd.cancel()).toThrow('Cannot cancel command') }) diff --git a/tests/unit/services/cwmp/configuration_evaluation_state.spec.ts b/tests/unit/services/cwmp/configuration_evaluation_state.spec.ts index 2849719..31447cf 100644 --- a/tests/unit/services/cwmp/configuration_evaluation_state.spec.ts +++ b/tests/unit/services/cwmp/configuration_evaluation_state.spec.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { describe, it, expect, vi } from 'vitest' // Mock logger + DeviceModel vi.mock('@adonisjs/core/services/logger', () => ({ diff --git a/tsconfig.json b/tsconfig.json index 8c9803b..bf5ae99 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,16 @@ "extends": "@adonisjs/tsconfig/tsconfig.app.json", "compilerOptions": { "rootDir": "./", - "jsx": "react", "outDir": "./build" - } -} + }, + "vueCompilerOptions": { + "strictTemplates": true, + "target": 3.3 + }, + "exclude": [ + "node_modules", + "tests", + "coverage", + "inertia" + ] +} \ No newline at end of file