Route all parse-failure throws through a single parseFailure(action, itemType[, section], cause) helper so the user-facing wording lives in one place and update paths share the template with install paths.
- IotHubRestClient.getVersionFileData now streams the response and
enforces a configurable size cap (iot-hub.max-file-data-size-bytes,
env IOT_HUB_MAX_FILE_DATA_SIZE_BYTES, default 100 MiB). Oversized
payloads are rejected up front via Content-Length and mid-stream
for chunked transfers, preventing OOMs from unknown bodies.
- DefaultIotHubService guards every iotHubRestClient.getVersionInfo
call against a null response, and rejects update attempts where
the installed item's type does not match the new version's type
("Installed item type does not match the new version's item type.").
Solution-template descriptor is also populated with
tenantTelemetryKeys / tenantAttributeKeys captured during install.
Drops the empty updateDeviceProfile stub.
- Add a tenant-scoped findByTenantIdAndId + deleteByTenantIdAndId to
IotHubInstalledItemDao (and the bulk deleteByTenantIdAndIdIn JPA
query). IotHubInstalledItemServiceImpl now uses these so reads
and deletes always honour the tenant boundary.
- Database: add lts schema_update.sql + schema-entities-idx.sql
indexes on iot_hub_installed_item(tenant_id),
(tenant_id, item_type), and (tenant_id, item_id) for the new
tenant-scoped lookups.
- DefaultSolutionService: minor cleanup touched alongside.
- .gitignore trailing-newline fix; remove stale superpowers
plan/spec docs that have been folded into the implementation.
- Provide a scoped Angular ErrorHandler on the dynamic component's
injector in HtmlContainerWidgetComponent so template-runtime
exceptions raised inside user-authored Angular templates flow
through handleWidgetException instead of crashing the global
Angular ErrorHandler.
- Add `ctx.invokeAction($event, actionName, additionalParams?)` to
WidgetActionsApi / WidgetComponent and a `WidgetDestroyCallback` +
ctx.registerDestroyCallback() API on WidgetContext (callbacks run
in registration order on destroy, errors per-callback are caught
so one bad cleanup doesn't break the rest).
- Surface widget actions on the HTML Container basic config: render
tb-widget-actions-panel with the new strokedPanel input below
the html-container settings, and register a default "JavaScript"
multi-action source on the html_container widget JSON.
- Expand the widget-completion docs for `registerDestroyCallback`
(when it fires, what to use it for, lifecycle semantics, exact
callback signature `() => void`) and for `invokeAction`'s
`additionalParams` arg (forwarded to the configured JS action
handler, common payload examples).
- Move the widget-types table add-action descriptors from the
constructor into resolve() and gate "Add from IoT Hub" so it
only appears for TENANT_ADMIN; sys-admins keep just the
create-new / import options.
- Swap the iot_hub system widget JSON to its own dedicated image
resource (iot_hub_system_widget_image.png) instead of reusing the
dashboards placeholder, with the matching link / title /
fileName / publicResourceKey / base64 data and reformatted tags
block.
Extend the IoT Hub install dialog so installing a rule chain can also set
it as the Default rule chain on a Device or Asset profile in one step.
The confirm step shows three actions: Cancel, Install (creates the chain
without targeting any profile), and Set for profile (opens a picker step).
The picker step has Cancel, Back, and Install — confirming with a profile
that already has a non-null defaultRuleChainId routes through a
confirm-overwrite step before replacing it. CALCULATED_FIELD keeps its
existing single-button flow unchanged.
Backend:
* RuleChainInstalledItemDescriptor gains a nullable EntityId
targetProfileId field, persisted in the existing descriptor JSON column
(no schema migration; @JsonIgnoreProperties(ignoreUnknown=true) handles
pre-existing rows).
* DefaultIotHubService.installRuleChain() now accepts SecurityUser and
JsonNode data; a new setAsDefaultRuleChain() helper applies the chain
as Default rule chain on the selected DEVICE_PROFILE or ASSET_PROFILE
via the existing tbDeviceProfileService / tbAssetProfileService save
paths (so the change shows up in the tenant audit log as a normal
profile update).
Frontend:
* iot-hub-install-dialog.component grows a per-ItemType selectEntityConfig
map, a 'confirm-overwrite' state, and the methods
installAsEntityProfileDefault, selectEntityBack, resolveOverwrite,
confirmOverwriteReplace, confirmOverwriteCancel.
* tb-entity-select gets a 512px min-width above the gt-sm breakpoint so
the picker renders at a consistent width regardless of the prompt text
length (mirrors the pattern in recipient-notification-dialog).
* New i18n keys: rule-chain-install-desc, rule-chain-install-as-default,
select-profile-for-rule-chain, rule-chain-overwrite-title,
rule-chain-overwrite-body, rule-chain-overwrite-replace.
Two test layers covering the controller surface that the JVN PoC uses:
Java unit (Spring MockMvc):
RuleChainControllerTest#testScriptForbiddenForCustomer asserts a
customer JWT against POST /api/ruleChain/testScript returns 403,
locking in the existing @PreAuthorize('TENANT_ADMIN') guard.
Black-box (live docker-compose):
JsExecutorSandboxIsolationTest#testRuleChainScriptCannotReachHostProcess
posts the JVN exploit payload as a tenant admin and asserts the
response carries error='process is not defined'. End-to-end through
tb-node -> Kafka -> tb-js-executor with use_sandbox=true.
Registered the new org.thingsboard.server.msa.security package in
the connectivity TestNG suite so the black-box runner picks it up.
Added a thin TestRestClient.testRuleChainScript() helper.
- Split the settings dialog into a resizable two-pane layout (left:
resources/HTML/CSS/JS tabs; right: live preview) using split.js,
with a fullscreen toggle that resets the tab animation duration to
avoid jank during expand.
- Split ContainerFunctionEditorCompleter into HTML- and Angular-mode
variants so the autocomplete suggests `container` only in HTML
mode (Angular mode has no container argument).
- Mark the widget with previewWidth/previewHeight 100% and
overflowVisible: true in its controllerScript typeParameters so
the basic config preview fills its slot.
ImageService.replaceBase64WithImageUrl rewrites base64 data URIs into
system-image URLs at save time. After install, the DB image is a
'tb-image;...' URL while the JSON file still carries a base64 data URI —
naive string compare always reported a diff and caused every system
bundle to be re-saved on every patch run.
SystemPatchApplier now creates widget types missing from the DB
instead of throwing, and merges new fqns into existing system bundles.
Bundle creation and inline widgetTypes in bundle JSONs are explicitly
rejected as out-of-scope for patch upgrades.
- Restructure HTML Container settings into a mat-tab-group (Resources /
HTML / CSS / JavaScript) instead of a single resources expansion panel
followed by stacked editor blocks. Each editor tab uses [fillHeight]
so the editors fill the panel.
- Wire fill-height plumbing: tb-widget-settings host h-full, basic and
advanced settings @HostBinding('style.height')='100%', advanced panel
switched from inline height:100% to flex-1, mat-content height:100%.
- Register html_container in widget_bundles/html_widgets.json so the
widget appears in the HTML Widgets bundle.
- Replace the placeholder html-card image reference with a dedicated
html-container.png asset and embedded data.
- Add 'JavaScript' translation key for the new tab label.
- Fill description and tags for the HTML Container widget type JSON.
- Add basic config component (plain HTML / Angular mode editor).
- Add advanced settings component and shared common settings.
New static widget that replaces the dashboard layout with configurable
HTML, CSS, and JavaScript and exposes the WidgetContext to the user
script. Use for custom complex visualizations or actions when system
widgets are not enough.
* feat(iot-hub): scaffold ALARM_RULE item type and CREATOR_VISIBLE_ITEM_TYPES
* feat(iot-hub): wire ALARM_RULE through browse, item card, detail dialog, installed items, and install dialog
* feat(iot-hub): reorder home cards, hide Dashboards, add Alarm Rules
* feat(iot-hub): scaffold ALARM_RULE descriptor and reject install with v4.3 upgrade hint
Registers AlarmRuleInstalledItemDescriptor in the Jackson polymorphism so
4.2 can browse Alarm Rule items from the marketplace without descriptor
deserialization failures.
The install handler explicitly rejects ALARM_RULE with a friendly message
asking the user to upgrade to v4.3+. The full install/update/delete
implementation depends on CalculatedFieldType.ALARM (added in v4.3) and
will land once master is merged into this branch.
* feat(iot-hub): hide redundant Type filter on Alarm Rules browse
* feat(iot-hub): show v4.3 update info dialog directly when installing an Alarm Rule
Relocate the iot_hub_installed_item CREATE TABLE statement from the
basic schema_update.sql to the LTS cumulative schema_update.sql, where
it belongs for this LTS line.
The guard around iterating createdEntityIds was checking isEmpty()
instead of !isEmpty(), so entity deletion was skipped whenever there
was actually data to clean up.
SolutionTemplateInstalledItemDescriptor and SolutionInstallResponse now
carry tenantTelemetryKeys and tenantAttributeKeys lists. SolutionService
.deleteSolution takes the full descriptor (instead of just the
created-entity list) so uninstall can also clean up tenant-scoped
telemetry/attributes. Update IotHub install descriptor population and
delete call sites; force-update path logs and continues if delete throws.
Propagate User through SolutionInstallContext, DefaultSolutionService,
and TbCalculatedFieldService.delete so non-security-context callers can
drive solution installs and calculated-field cleanup. Pass
TenantSolutionTemplateInstructions into the context constructor so the
caller controls instruction state.
Add CreatedRuleChainInfo extending CreatedEntityInfo that overrides
getEntityPageLink to produce /edgeManagement/ruleChains/<id> for EDGE
rule chains and /ruleChains/<id> otherwise. SolutionInstallContext now
registers rule chains with this type-aware info.
Creates missing system images from application/src/main/data/resources/images
during LTS patch startup, mirroring the upgrade-path loadSystemResources logic.
Existing system images in the DB are left untouched.
Several testSaveProtoDeviceProfileWithInvalidRpcRequestSchema* tests
intermittently fail with:
org.thingsboard.server.dao.exception.TenantNotFoundException: Tenant
with id <fresh-tenant-uuid> not found
when the tenant created in @Before has not yet been populated in the
tenant profile cache by the time the request hits the partition-lookup
path (DefaultTenantRoutingInfoService -> TbTenantProfileCache ->
TenantService#findTenantById). The underlying request is idempotent
(the schema is invalid so it is rejected with 400 regardless of
retries), so wrap the doPost + status assertion in Awaitility with
Mockito.reset inside the retry block: only the last attempt's
invocations are visible to the subsequent verify* assertions.
Applies to all testSaveDeviceProfileWithInvalidRpcRequestProtoSchema
callers, including the currently-muted
testSaveProtoDeviceProfileWithInvalidRpcRequestSchemaRequestIdDateType.
The test asserts exactly 2 UserCredentialsUpdateMsg after creating a new
tenant-admin user, but the user activation flow can emit either 2 or 3
depending on timing:
- activateUserCredentials publishes CREDENTIALS_UPDATED (msg #1)
- setUserCredentialsEnabled publishes CREDENTIALS_UPDATED (msg #2)
- the initial USER ADDED edge event is processed asynchronously in
UserEdgeProcessor and bundles an extra UserCredentialsUpdateMsg when
it finds userCredentials.isEnabled() == true (i.e. activation
already raced past the ADDED event)
When the race goes the second way we end up with 1 UserUpdateMsg plus
3 UserCredentialsUpdateMsg, which currently fails the hard-coded
assertEquals(2, ...) assertion.
Accept both 2 and 3 UserCredentialsUpdateMsg instead of asserting an
exact count, matching the reality of the asynchronous edge event
pipeline.
Await cached resource data to become available after save eviction
before asserting, and await null after deletion. Prevents Mockito
verifyNoMoreInteractions(resourceService) failure caused by racing
background cache-load invocations.
Backport of 99334ba7fe from master.
- New TbIotHubInstalledItemsDialogComponent opened from detail dialog Manage section
- Extract TbIotHubInstalledItemsTableComponent with itemVersion mode for single-item view
- Add installed item counts API (GET /installedItems/counts) and itemId filter to installed items list
- Wire installed counts into home, browse, search, and detail dialog (replaces installedDevices list)
- Propagate ChipOverflowDirective with minChips input; use on detail dialog meta chips
- openEntity opens in new tab; "View item details" swaps to "Review device instructions" for DEVICE items
Redesign item detail dialog: move install/add/update/open buttons to footer,
move creator to sticky meta bar with avatar and verified badge, add manage
section with solution instructions, remove, and installed items count buttons,
update subtitle with grouped icon+text pairs matching design.
Add getInstalledItemCounts API (backend + frontend) returning item counts
by itemId per type. Replace installedDevices list with count-based tracking
in home, browse, and search components. Add installedItemsCount to item card
and detail dialog data flow.
Relax version check to allow maintenance digit increases within the same
LTS family (e.g. 4.3.0 -> 4.3.1), not just patch digit increases.
Add LTS SQL schema patch execution from upgrade/lts/schema_update.sql,
running before views and widget updates so schema changes are in place
for dependent objects.
The /api/ai/model/chat endpoint returns DeferredResult (async), so
MockMvc needs asyncDispatch() to capture the resolved response body.
Without it the test sees an empty body and fails on deserialization.
The runtime SSRF test called /api/ai/chat, but the actual endpoint is
/api/ai/model/chat (mapped via @RequestMapping("/api/ai/model") +
@PostMapping("/chat")).
Wrap configure() call in AiChatModelServiceImpl.sendChatRequestAsync
so a synchronous exception (e.g. runtime SSRF block) is converted to
a failed future and caught by the controller's .catching() chain,
returning TbChatResponse.Failure instead of a raw 500.
Apply SsrfProtectionValidator.validateUri() to AI providers with
user-supplied URLs (OpenAI baseUrl, Azure OpenAI endpoint, Ollama
baseUrl). Validation at two layers:
- execution time in Langchain4jChatModelConfigurerImpl
- save time in AiModelDataValidator
Controlled by the existing SSRF_PROTECTION_ENABLED flag.
Replace the blocking semaphore guard with a non-blocking bounded FIFO queue
+ semaphore pattern:
- No semaphore/queue when maxParallelRequestsCount=0 (default): direct doHttpCall,
identical to the old behavior.
- When a concurrency limit is set, incoming messages are enqueued via non-blocking
offer(); a full queue triggers onFailure immediately.
- tryProcess() acquires one semaphore slot and dispatches the next valid queued task.
Stale tasks (batch deadline expired) are dropped and the slot reused in the same pass.
- doFinally hook releases the semaphore and calls tryProcess() exactly once after any
terminal signal (success, error, cancel), preventing double-release and permit leaks.
- publishOn(externalCallExecutor) moves callbacks off reactor-netty I/O threads.
System-level safety caps are wired through thingsboard.yml → ActorSystemContext →
TbContext → TbHttpClient, scoped to rule-engine services only via @TbRuleEngineComponent:
actors.rule.external.http_client.max_parallel_requests (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_MAX_PARALLEL_REQUESTS)
actors.rule.external.http_client.max_pending_requests (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_MAX_PENDING_REQUESTS)
actors.rule.external.http_client.pool_max_connections (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_POOL_MAX_CONNECTIONS)
Backward compat: TB_RE_HTTP_CLIENT_POOL_MAX_CONNECTIONS still honored via yaml fallback.
Observability: five AtomicLong counters (dispatched, success, failure, droppedQueueFull,
droppedStale) with periodic WARN anomaly logging including semaphorePermits for leak detection.
No configuration changes or upgrade scripts required — docker image update is sufficient.
Rename RestApiCallNodeSettings to TbHttpClientSettings
The settings are about HTTP client transport concerns (connection pool,
concurrency, queue depth), not REST API Call node business logic.
The new name matches the consumer (TbHttpClient) and the YAML path
(actors.rule.external.http_client.*).
The admin settings endpoint requires SYS_ADMIN authority but the device
install wizard runs as TENANT_ADMIN.
Fix: add getConnectivityInfo(baseUrl) to DeviceConnectivityService which
resolves host/port with baseUrl fallback (reusing existing getHost/getPort
logic). Expose via new TENANT_ADMIN endpoint GET /api/iot-hub/connectivity.
Remove AdminService dependency from the dialog component.