- Group the slide-toggle and the conditionally rendered profile
pickers in a single bordered card via tb-form-panel.stroked,
matching the 4.3 entity-aggregation 'Apply await timeout' pattern.
- Set the toggle's class to 'mat-slide flex' to match the canonical
TB idiom; switch the tooltip label to plain interpolation.
- Restore the trailing '?' in install-confirm-title — the confirm
step is still a real confirmation question for item types that
don't go through the select-entity flow.
CF and Rule Chain installs now open directly into the selection form,
skipping the prior "Install?" confirmation step. Rule Chain uses a
single "Set as profile default rule chain" toggle that reveals the
profile pickers when enabled, replacing the previous two-button
design. The overwrite-confirmation step is preserved.
Follow-up to #15645.
- 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.
* `selectEntityConfig` did not have an `ALARM_RULE` entry. The install
dialog routes both `CALCULATED_FIELD` and `ALARM_RULE` to the
`select-entity` step, but the per-`ItemType` config map (introduced
by the CE 4.2 rule chain work) only had `CALCULATED_FIELD` and
`RULE_CHAIN` entries. The alarm rule prompt paragraph silently
dropped and the picker bindings had no config to read from. Add an
`ALARM_RULE` entry with the same allow-list as CF and the new i18n
key `iot-hub.select-entity-for-alarm-rule`.
* `resolveEntityDetailsUrl` returned `/calculatedFields/{id}` for
ALARM_RULE installs because the `ITEM_TYPE_TO_ENTITY_TYPE` map sent
`ALARM_RULE` through `EntityType.CALCULATED_FIELD`. Alarm rules
share the CalculatedField data structure on the backend but live at
`/alarms/alarm-rules/{id}` in the UI. Resolve the URL directly for
ALARM_RULE before falling through to the entity-type map, and drop
the misleading `'ALARM_RULE' → CALCULATED_FIELD` entry / switch case.
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.
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.