ThingsBoard Monitoring Integration — Revival Phase 0: Rebase & Reconciliation

Context

Epic #104 and its sub-issues #105–#110 define a ThingsBoard integration to power a realtime
“Quan trắc” (monitoring) screen inside “Tổng quan” (dashboard overview). Two incompatible
implementations exist:

  • master: a “phase 0” model where the frontend authenticates directly to ThingsBoard using
    per-user credentials fetched from the backend (GET /thingsboard/credentials, which returns
    the user’s ThingsBoard password in plaintext — a security defect, and a violation of the
    documented backend rule “KHÔNG trả về password hash trong response”). Real, shipped work also
    exists here: ThingsboardClientService (shared-tenant REST client used by devices,
    telemetry, health) and gateway RPC command dispatch/persistence fixes.
  • issue-104 (unmerged, last commit 2026-05-22, diverged 29 commits from master, which has
    since advanced 15 commits): a backend-proxy model matching the epic’s original architecture
    decision (“ThingsBoard là lớp xử lý chính… Hệ thống hiện tại là lớp sản phẩm, cung cấp UI duy
    nhất”). This branch already implements normalized telemetry contracts, an org-scoped resolver,
    a realtime WebSocket gateway (/monitoring), snapshot/history query APIs, an alert-ack alias,
    and a frontend monitoring page + hooks — but it predates 15 commits’ worth of master changes
    and has at least one direct schema conflict with it.

Decision: adopt the backend-proxy model (issue-104’s architecture) as the accepted design,
and treat the frontend-direct-to-ThingsBoard flow on master as tech debt to remove. This is
Phase 0 of a multi-phase effort. It only covers reviving issue-104’s code onto current
master and reconciling conflicts — it does not cover closing the feature gaps identified
in the epic audit (error model/idempotency, RBAC matrix documentation, full observability,
audit trail for RPC commands, or moving the MVP screen into “Tổng quan”). Those are separate,
later specs, tracked as GitHub sub-issues #105–#110 under epic #104.

Goal

Produce a new branch, revive/thingsboard-monitoring, based on current master, containing
the issue-104 monitoring functionality, with all conflicts resolved, dead/insecure code
removed, and tests/build green — ready for its own PR review. issue-104 itself is left
untouched (reference only, not force-pushed, not deleted).

Out of scope (deferred to later specs, tracked under #105–#110)

  • Error model / retryable-vs-non-retryable codes / idempotency keys (#105 gap)
  • RBAC matrix documentation for monitoring:* permissions, service-to-service auth (#106 gap)
  • Message-ordering guarantees, latency SLA definition/measurement (#107 gap)
  • Correlation-id logging, throughput/error-rate metrics, tracing, alerting, dashboards (#108)
  • Moving the monitoring page from a standalone /monitoring route into “Tổng quan”, nav wiring
    (#109 gap)
  • Audit trail for RPC commands, RBAC-scoped command bridge, retry/backoff policy (#110 gap)

Branch strategy

  • Create revive/thingsboard-monitoring from master (currently at f5f08199f).
  • Do not rebase or force-push issue-104. It stays as historical reference until the revival
    branch is merged, then can be deleted.
  • Port issue-104’s 29 commits by squashing into logical groups and re-applying on top of
    current master, skipping commits that are either unrelated (accidentally carried topnav
    spacing changes) or superseded by independent master work (see “Commits dropped” below).

Commit groups to port

  1. feat(monitoring): define normalized telemetry contracts + harden validation
    (squash of f974b989b, 7d02d1062)
  2. feat(monitoring): add snapshot and history api surface + tenant contract hardening
    (squash of 8641ef03e, 7e2bfb188, 9d56135de, 569ff61af)
  3. feat(monitoring): implement ThingsBoard adapter core + realtime stream proxy
    (squash of 8a67aba3b, aced14be0, 5e0615a9e, b6e1c7d3e, 7c40877ae, a9b390320,
    0a386ee21)
  4. feat(monitoring): add reconnect/observability baseline + org-scoped rpc proxy
    (squash of 5acf4e5eb, bf3ac2008)
  5. feat(alerts): expose ack alias endpoint for monitoring contract (6d4a9b56d)
  6. feat(frontend): add monitoring dashboard page, data service and hooks
    (squash of e49b0b134, 54d8cf6ba)
  7. test(monitoring): enforce API and tenant-scope contracts end-to-end
    (squash of 5419e38bc, 012f08a57)

Commits dropped (not ported)

  • 929c60133, bee413a15 (topnav spacing) — unrelated, accidentally on this branch.
  • 59c281377 (JWT for WebSocket auth) — superseded; master’s
    backend/src/websocket/websocket.gateway.ts already verifies JWT via
    handshake.auth.token. The new monitoring-stream.gateway.ts (namespace /monitoring)
    will follow the same pattern directly rather than porting this commit.
  • 4104e0aae, 242dcca74, 69755fad8 (compose/EMQX/install-gate changes) — superseded;
    master’s docker-compose.yml already defines THINGSBOARD_BASE_URL,
    THINGSBOARD_USERNAME, THINGSBOARD_PASSWORD, THINGSBOARD_INGEST_TOKEN for the backend
    service (consumed by the existing ThingsboardClientService). The EMQX/MQTT broker changes
    in this group are unrelated to the monitoring feature and out of scope here.

Schema reconciliation

master and issue-104 each independently added a ThingsBoard device-id column to devices,
with incompatible names/types, and neither has production data yet:

master issue-104
Migration 1778500000000-AddTbDeviceIdToDevices.ts 1779362177924-AddThingsboardDeviceIdToDevices.ts
Column tb_device_id thingsboard_device_id
Type varchar(100) uuid
Index none unique

Resolution:

  • Drop master’s AddTbDeviceIdToDevices migration and its down-equivalent effect (no
    production data exists, confirmed by user).
  • Add a new migration (fresh timestamp, after master’s current head) implementing
    issue-104’s version: thingsboard_device_id uuid with a unique index.
  • Rename Device.tbDeviceIdDevice.thingsboardDeviceId in the entity and everywhere it’s
    referenced on master: thingsboard-client.service.ts (sendRpc call site),
    devices.service.ts, device-command-dispatch.service.ts, and any specs.

Dead/insecure code removal

The GET /thingsboard/credentials flow is confirmed unused by any frontend page or component
(verified via full-repo search) and returns a plaintext password — remove entirely:

  • backend/src/thingsboard/thingsboard.controller.ts (the whole controller — it has no other
    routes)
  • ThingsboardService.getCredentials in thingsboard.service.ts (delete the service if nothing
    else uses it after this removal)
  • TbCredentialsDto
  • frontend/src/stores/thingsboard.store.ts
  • User.thingsboardUsername / User.thingsboardPassword columns — add a new migration to drop
    both columns (no production data, confirmed by user) and remove the fields from the User
    entity. The original 1745200000000-AddThingsboardCredentialsToUsers migration file is left
    in place (historical record), reverted by the new drop migration going forward.

ThingsboardClientService, thingsboard.module.ts’s existing provider wiring for that service,
and all of devices/telemetry/health usage of it are untouched — that is real, working
backend-to-ThingsBoard integration unrelated to this cleanup.

Verification plan

  • Run bun run test (Jest, never bun test) for affected specs after each commit group is
    ported: ThingsBoard adapter service, monitoring scope resolver, monitoring stream gateway,
    monitoring controller (end-to-end tenant-scope contract test from group 7).
  • Run both new migrations up then down (migration:run / revert) on a dev database to confirm
    symmetry: device id column swap, and the users TB-credential column drop.
  • Run bun run check (Biome) across changed workspaces.
  • Manual smoke test: call /api/v1/monitoring/snapshot with a valid JWT and confirm org-scoped
    results; load MonitoringPage.tsx at its current standalone /monitoring route (relocation
    into “Tổng quan” is a later spec) and confirm the realtime connection indicator and snapshot
    table render.

Testing

Existing Jest specs ported in commit group 7 (and any spec files bundled with groups 1–4) must
pass unmodified in intent — only import paths / renamed fields (thingsboardDeviceId) change.
No new test scenarios are introduced in this phase; new test coverage for the deferred gaps
(#105–#110) belongs to their respective specs.