- Remove stale iotHubApiService from dialog data (now directly injected)
- Fall back to regular detail dialog for items without installState
(installed before the review mode feature)
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.*).
Search component:
- New TbIotHubSearchComponent in home/components/iot-hub/ with search toolbar,
pagination, grouped results, sort, loading/empty states
- Inputs: searchText (two-way), creatorId, showCreator; ng-content for title
- Custom search input (320x40px, border, search icon, clear button)
- Sort icon-only button on lt-sm, mobile results count in title row
- Divider before categories section
Search page:
- Renamed to TbIotHubSearchPageComponent, embeds TbIotHubSearchComponent
- Title projected via ng-content, responsive inset padding per breakpoint
- Full-height layout with flex
Creator profile:
- Two-column layout: creator info (360px) + vertical divider + published items
- Uses TbIotHubSearchComponent with creatorId and showCreator=false
- 140px avatar, 28px name, description, link rows with hover styles
- Responsive: column layout on lt-lg, adjustable padding per breakpoint
- Back button inside creator info column
Item detail dialog:
- showCreator parameter (default true), hides author section when false
- openItemDetail service method accepts showCreator argument
Item card:
- Respects showCreator input, hides author in both compact and big layouts
Template syntax:
${note(This is an informational note.)}
${warn(All code examples require ThingsBoard Library version **0.10.2**.)}
${error(This device is not compatible with ThingsBoard CE.)}
Renders as styled callout boxes with Material Icons:
- note: blue background, info icon
- warn: amber background, warning icon
- error: red/pink background, error icon
Track which resolution was used per entity step (created, use-existing,
overwrite, create-copy). Store in installState for review mode.
Provisioning step now shows green resolution label on the right:
"Created", "Used existing", "Overwritten", or "Created copy".
Visible in both live install and read-only review dialog.
When an entity with the same name already exists, show amber conflict
UI with resolution options instead of a red error:
- Device Profile / Rule Chain / Device / Gateway:
"Use existing" (reuse as-is) or "Overwrite" (update with template)
- Dashboard: "Overwrite" or "Create copy" (duplicate names allowed)
Pre-checks by name before each entity creation. If conflict found,
pauses provisioning and shows resolution buttons. After user chooses,
resumes with remaining steps.
New status: 'conflict' with amber styling, warning icon, and action
buttons on the progress row.
Store install state (form values, entity outputs, selected connectivity)
in DeviceInstalledItemDescriptor during registration. Installed Items
info icon for DEVICE type re-fetches the ZIP and opens the wizard in
read-only review mode:
- All stepper steps clickable (jump to any step)
- Form fields populated with stored values and disabled
- Entity progress shows all items as completed
- Markdown instructions rendered with resolved variables
- Download buttons and image galleries still work
- Single "Close" button in footer
Backend: add selectedConnectivity and installState fields to descriptor
Test: verify installState deserialization round-trip
Replace Done button on the last step with:
- Close (text button) — closes dialog
- Open Dashboard / Open Device / Open Gateway (primary button) —
closes dialog and navigates to the entity
Priority: dashboard > device > gateway > integration.
If no entity was created, only Close is shown.
Add ${images.gallery(path1,path2,path3)} template syntax that renders
multiple ZIP images as a side-by-side gallery in instruction steps.
Images are resolved from zipImages (base64 data URIs). Click to expand
individual images. Styled as a flex row with thumbnails.
The helpImage field in form definitions was rendered with the raw path
from the ZIP. Now resolved through zipImages map to get the base64 data
URI, same as markdown images.
Add optional 'dockerCompose' field to GATEWAY step definition pointing
to a YAML template in the ZIP. Variables (${mqtt.host}, ${gateway.token},
form values) are resolved after gateway creation. The download button
serves the custom template as a Blob download instead of calling the
standard backend endpoint.
Falls back to the standard API-generated docker-compose.yml when no
custom template is provided.
Add optional 'credentials' field to DeviceInstallStep pointing to a
JSON file in the ZIP. After device creation, fetches existing credentials,
applies overrides (credentialsType, credentialsValue, credentialsId),
and saves. Supports MQTT_BASIC with clientId/userName/password.
Example credentials.json:
{
"credentialsType": "MQTT_BASIC",
"credentialsValue": {
"clientId": "${deviceName}",
"userName": "${mqttUsername}",
"password": "${mqttPassword}"
}
}
Load image files (png, jpg, gif, svg) from ZIP as base64 data URIs
during parsing. Resolve markdown image references  to
inline data URIs before rendering, so images bundled in the device
package display in instruction steps without a server endpoint.
Instead of a fixed download bar above markdown, the creator writes
${gateway.downloadButton} anywhere in their markdown. It resolves to
an HTML button with data-action attribute. After markdown renders,
onMarkdownReady() attaches a click handler that calls the authenticated
DeviceService.downloadGatewayDockerComposeFile().
Usage in post-install.md:
${gateway.downloadButton}
Markdown links can't pass JWT auth headers, so instead of relying on
${gateway.dockerComposeUrl} in markdown, add a download bar above the
instruction content when a gateway was created. Uses the existing
DeviceService.downloadGatewayDockerComposeFile() which handles
authenticated download via ResourcesService (JWT injected by interceptor,
Blob created, browser download triggered).
Each entity output now includes a url field:
- deviceProfile: /profiles/deviceProfiles/{id}
- device: /entities/devices/{id}
- gateway: /entities/gateways (generic, no ID)
- dashboard: /dashboards/{id}
- ruleChain: /ruleChains/{id}
Available in templates as ${device.url}, ${dashboard.url}, etc.
- Add GATEWAY step: creates gateway device, outputs dockerComposeUrl
- Add GATEWAY_CONNECTOR step: saves connector config as shared attributes,
appends to active_connectors array on the gateway
- Add optional serverAttributes/sharedAttributes fields to all entity steps
(DEVICE, GATEWAY, DEVICE_PROFILE, DASHBOARD, RULE_CHAIN)
- Attribute files are read from ZIP, variable-resolved, and saved via
AttributeService after entity creation
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.
Fetch connectivity settings from admin API on wizard init and make
transport variables available in all templates and instructions:
${http.host}, ${mqtt.port}, ${coap.host}, etc.
Resolution priority: form values > transport vars > entity outputs.
Drop ${step1.id}, ${step2.name} etc. positional variable resolution.
Only named entity outputs (${device.id}, ${dashboard.name}) are supported.
Updated spec to reflect the simplified resolution priority.
Removed overflow:hidden from DynamicFormSelectItemsComponent that was hiding the Value/Label table headers and "No options configured" message. Added overflow-visible to nested dynamic-form-properties to prevent clipping in expansion panels.
- Extract filter panel content into ng-template for reuse
- Desktop sidebar (tb-iot-hub-filters-desktop): hidden on lt-lg
- Filter toggle button: shown on lt-lg with active filter count
- mat-drawer (right-side overlay): 60% width on lt-lg, 100% on lt-md
- Drawer has header with close button, active filter chips, and filter body
- mat-drawer-container wraps content area for proper drawer integration
- Add activeFilterCount getter and filterDrawerOpened property
- New addItem(itemType, options?) method opens add item dialog via service
- Options: optional itemSubType and entityId
- Refactored dashboards, rule chains, devices, widget types, calculated fields
to use iotHubActions.addItem() instead of opening dialog directly
- Removed direct TbIotHubAddItemDialogComponent imports from all table configs
- All table configs now inject IotHubActionsService
- New iot-hub-components.models.ts with shared ITEM_TYPE_TO_ENTITY_TYPE
mapping and resolveEntityDetailsUrl function
- Remove duplicated ITEM_TYPE_TO_ENTITY_TYPE from install and update dialogs
- Remove duplicated resolveEntityDetailsUrl from install and update dialogs
- Both dialogs now use shared function from models file
- Make installDevice public in IotHubActionsService
- Add item dialog: for DEVICE type, invoke iotHubActions.installDevice
instead of standard install flow
- Add "Add from IoT Hub" action to devices table (tenant scope)
- Update IotHubModule to import IotHubComponentsModule instead of HomeComponentsModule
- Update CLAUDE.md with IotHubActionsService and IotHubComponentsModule docs
- New IotHubActionsService with openItemDetail, installItem, updateItem,
deleteItem methods returning Observables for dialog results
- Service handles all dialog opening, device install flow (fetch zip + dialog),
and delete confirmation + API call
- Registered as provider in IotHubComponentsModule
- Refactored browse component: removed all dialog imports, uses service
- Refactored home component: removed dialog imports, uses service
- Refactored search component: removed dialog imports, uses service
- Removed ~150 lines of duplicated dialog code across 3 components
- Move iot-hub-install-dialog to home/components/iot-hub/
- Move iot-hub-update-dialog to home/components/iot-hub/
- Move iot-hub-delete-dialog to home/components/iot-hub/ (with new separate HTML/SCSS)
- Move device-install-dialog to home/components/iot-hub/device-install-dialog/
- Register all in HomeComponentsModule (declarations + exports)
- Remove from IotHubModule declarations
- Update all imports across home/browse/search/installed/detail/widget-select
- Remove iotHubApiService from IotHubItemDetailDialogData, inject in constructor
- Remove iotHubApiService from IotHubInstallDialogData, inject in constructor
- Remove iotHubApiService from IotHubUpdateDialogData, inject in constructor
- Remove iotHubApiService from DeviceInstallDialogData, inject in constructor
- Update all callers (home, browse, search, installed items, widget select,
item detail dialog) to remove iotHubApiService from dialog data
- Fix device install dialog data (item + zipData) after bulk removal
- IotHubApiService injected in dialog constructor, removed from data interface
- Add optional entityId to dialog data, passed to installItemVersion for CF
- Add optional itemSubType to dialog data and fixedSubType to browse component
- fixedSubType: applies subtype filter, hides subtype filter section/chips,
excluded from active filters and clear all, hides card subtype badge
- Add "Add from IoT Hub" to calculated fields table with entityId
- Rule chains: pass CORE/EDGE subType based on ruleChainScope, fix navigation
- Show alert dialog on install failure with error details
- Remove IotHubApiService from all dialog data callers