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.
SimpleCalculatedFieldState.formatResult cast the double result down to
int via TbUtils.toInt when "Decimals by default" was 0 (the UI default).
BigDecimal.intValue() returns only the low-order 32 bits, so a sum
above ~2.1B wrapped to a negative number (e.g. 3,980,173,734 ->
-314,793,562).
Add TbUtils.toLong(double) alongside toInt (toInt is left untouched to
preserve TBEL script behavior), switch the CF precision=0 path to it,
and add a Long branch in createResultJson so the JSON node is emitted
as a numeric long
The form rendering for the SHOW_FORM step was inline in the dialog template.
Extract it into a presentation-only InstallFormRendererComponent next to the
dialog. The dialog now binds [fields], [formGroup], [resolveImagePath], and
[reviewMode] inputs.
[reviewMode] consolidates the previous dialog-side passwordVisible[key]=true
assignment into the renderer's own state — the dialog no longer needs to
track per-field password visibility.
Layout mirrors thingsboard-pe so changes propagate cleanly between branches.
The form rendering for the SHOW_FORM step was inline in the dialog template.
Extract it into a presentation-only InstallFormRendererComponent next to the
dialog. The dialog now binds [fields], [formGroup], [resolveImagePath], and
[reviewMode] inputs.
[reviewMode] consolidates the previous dialog-side passwordVisible[key]=true
assignment into the renderer's own state — the dialog no longer needs to
track per-field password visibility.
Layout mirrors thingsboard-pe so changes propagate cleanly between branches.
- ${images.gallery(...)} accepts JS-style image objects
({src, alt?, caption?}) that may span multiple lines and contain
whitespace inside the gallery brackets. Captions go through a
whitelist sanitiser (b/strong/i/em/u/s/mark/small/sub/sup/br/code/
span with class+style only — style values containing
expression()/javascript:/url() are dropped) so authors can use
inline markup without exposing XSS.
- Promote escapeHtml / escapeHtmlAttr to exported helpers in
iot-hub-markdown.utils, add the new sanitizeInlineHtml. Drop the
duplicate copies (and the now-unused DocLinks helpers + buildDocLinkButton)
from shared/models/iot-hub/device-package.models.
- Move the PhotoSwipe wiring out of TbIotHubMarkdownComponent into a
reusable tbPhotoSwipeGallery shared directive, declared/exported
by SharedModule. The component now just renders the
data-attributed wrapper and the directive lazy-binds PhotoSwipe.
- Tighten the gallery layout: 200px-min auto-fill grid, 8px vertical
margins, hover border swapped to the shared #2a7dec accent token.
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.
Four test cases under describe('js-executor'):
- sandbox isolates args from host realm (JVN#16937365 — regression guard)
- sandbox passes string args through unchanged
- non-sandbox path does not isolate from host realm (documented contract)
- non-sandbox path passes string args through unchanged
Tests use Node's built-in node:test + node:assert (zero new devDependencies;
ts-node was already there). Two npm scripts:
test — spec output for local dev
test:ci — spec to stdout + Node's built-in junit reporter to
target/surefire-reports/TEST-js-executor.xml
Wired 'yarn test:ci' into the Maven 'test' phase via frontend-maven-plugin,
so 'mvn test -pl=msa/js-executor' produces JUnit XML that TeamCity's
Maven runner auto-discovers under the 'js-executor' suite name.
TEST_FAST.md picks up the same step.
tsconfig excludes test/ from the production pkg bundle.
The args array passed into the sandbox carried the host realm prototype
chain, so a script could reach the host Function constructor via
args.constructor.constructor and execute arbitrary code in the host
process (read files, run shell commands, dump env vars).
Construct args inside the sandbox context using vm.runInContext('[]'),
then populate with string primitives. The resulting array's prototype
chain belongs to the sandbox realm, so constructor traversal cannot
escape. Strings are primitives and safe to cross the realm boundary.
Affects use_sandbox=true path only. The use_sandbox=false path
(invokeFunction) is intentionally left as-is and explicitly marked as
dangerous-by-design — it compiles and runs user-supplied scripts in
the host realm via vm.compileFunction (parsingContext only isolates
parsing, not execution). It remains as a documented performance
trade-off for trusted, non-public clusters; a startup WARN is logged
when script.use_sandbox=false, and an operator-facing yaml comment
sits next to the setting in config/default.yml.
Reported by Hiroki Imai, LAC Co., Ltd.
- Wire photoswipe (5.4.4) into ui-ngx and load its CSS via angular.json.
- Replace the in-place ".tb-gallery-img-expanded" toggle in
TbIotHubMarkdownComponent.onReady with a PhotoSwipe lightbox bound
to each ".tb-gallery-images" container, picking up
".tb-gallery-image" anchors. Items are sized from the underlying
image's naturalWidth/Height; the lightbox slide uses contained
rendering with a max viewport sized 90vw / 78vh and a 0-opacity
backdrop.
- Style the gallery: 240px-min auto-fill grid with 12px gap, 115px
thumbnail strip with object-fit contained images, hover lift
(border + translate + shadow) and a fade-in caption tooltip
bottom-pinned over the thumbnail.
- Move solution-install-dialog.component.{ts,html,scss} from
home/components/solution/ into home/components/iot-hub/, drop the
now-empty solution folder.
- Register SolutionInstallDialogComponent in IotHubComponentsModule
(declaration + export) and remove its declaration / export /
import from HomeComponentsModule. HomeComponentsModule already
imports IotHubComponentsModule, so consumers continue to resolve
the component without changes.
- Update import paths in iot-hub-install-dialog,
iot-hub-update-dialog, iot-hub-item-detail-dialog (and the
modified home-components module) to the new
@home/components/iot-hub/solution-install-dialog.component path.
- Pick up incidental work-in-progress edits in the iot-hub markdown
utils / template / dialogs touched alongside this move.
- Add TbIotHubMarkdownComponent (home/components/iot-hub) wrapping
tb-markdown with applyDefaultMarkdownStyle=false, the IoT Hub item
link compile module, and forwarded lineNumbers /
fallbackToPlainMarkdown coerced-boolean inputs.
- Carry the readme/description typography (h1..h6, p, ul/ol, li, a,
img, code, code-wrapper / clipboard-btn, pre[language] with Prism
tokens and line-numbers, tables) so callers don't have to override
the default markdown styles. Tunes ported from the design:
list margins, 6px heading bottom padding, table widths and
padding, clipboard offsets, etc.
- Replace tb-markdown with tb-iot-hub-markdown in the item detail
dialog (description + readme + changelog), the install / update
dialog error blocks, and the solution install dialog. Drop the
inline .dlg-readme markdown overrides and the
.dlg-description ::ng-deep block, leaving only the dlg-readme
layout tweak and a padding:0 reset for .dlg-description.
- Solution install dialog drops the now-redundant
itemLinkCompileModules field + IotHubItemLinkModule import and the
.table-wrapper ::ng-deep override; the iot-hub-markdown component
carries those styles itself.
--no-bytecode is only required for the Windows target; running pkg as a
single multi-target invocation applied it to Linux too, dropping V8
bytecode from the Linux exe. Split into two pkg invocations so Linux
keeps bytecode and only the Windows build relaxes it.
- Selected card inverts the icon container: primary-coloured
background with the masked glyph painted white, on top of the
existing primary border + inset shadow.
- Default (idle, non-hovered) cards use a transparent border so the
outline appears only on hover or when selected — the 1px layout
space stays intact to avoid jitter.
Apply a tb-connectivity-cards-pair modifier to the card grid when
availableInstallMethods.length === 2 and switch its tracks to
repeat(auto-fill, minmax(300px, 1fr)) so the two cards spread to
sensible widths instead of squeezing into the 240px minimum.
- Add installMethodIcons map covering integrations (existing
assets/integration-icon files), direct-connect transports (new
assets/direct-connect-icon: http/mqtt/coap/lwm2m/snmp), and gateway
connectors (new assets/gateway-connect-icon: mqtt/modbus/opc-ua/
bacnet/ble/can/ftp/ocpp/odbc/request/rest/snmp/socket/xmpp).
Direct/gateway icons are exported from the design via the Figma
REST API. The PE-only illustration is also exported.
- Replace the row of stroked buttons in the device install
connectivity selector with a card grid that mirrors the design
(276x116, 6px radius, primary border on hover/select, mask-image
driven icon rendered in primary, primary-tinted 4% surface,
inner-shadowed 48x48 white icon container, "PE Only" pill).
- Restructure the wizard so that the connection method is the first
wizard step ("Connection method"). Three placeholder steps
(Prerequisites / Configuration / Provisioning) preview the rest of
the flow until a method is picked. On selection, placeholders are
swapped for the real install steps and the stepper auto-advances;
re-clicking the already selected card is a no-op.
- Replace the inline PE-only panel with a dedicated dialog
(TbPeConnectivityMethodPromptComponent) matching the design
(500x364 white card, 140 illustration, 24px title with primary
connector name, "Try Professional Edition" link to
https://thingsboard.io/installations/, Close, top-right X). PE-only
cards open this prompt instead of advancing the wizard.
* docs(iot-hub): spec for ${item-link:uuid} markdown component
Design for a new markdown placeholder that renders an item card
(thumbnail + name + creator) inline in IoT Hub readmes and install
instructions, opening the linked item in a new tab.
* docs(iot-hub): correct branch name in item-link spec
* docs(iot-hub): implementation plan for ${item-link:uuid} markdown component
Step-by-step task plan covering the placeholder utility, the link card
component, module wiring, three integration sites (item readme, device
install instructions, solution install instructions), translations, and
manual visual QA.
* feat(iot-hub): add item-link placeholder utility for markdown
* feat(iot-hub): add TbIotHubItemLinkCardComponent for markdown item links
* refactor(iot-hub): remove impossible-state guards in item-link card
* feat(iot-hub): wrap item-link card in IotHubItemLinkModule
* feat(iot-hub): add item-link-unavailable translation key
* feat(iot-hub): render ${item-link} cards in item readme
* feat(iot-hub): render ${item-link} cards in device install instructions
* feat(iot-hub): render ${item-link} cards in solution install instructions
* fix(iot-hub): match item-link card thumbnail to search popup (99x56, contain)
- 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.