Bring the same fetch-failure UI to every IoT Hub surface (browse,
search, home, creator profile) so all of them recover gracefully
from network / server outages instead of dead-ending on a global
error toast or a hard redirect.
- Add a tb-no-service-bg global utility in form.scss that mirrors
tb-no-data-bg but renders /assets/home/no-service.svg (copied
from thingsboard.io). Same primary-color mask treatment, so PE
builds retint without re-exporting the asset.
- Add hasError / retryTimer state to the four affected components
and switch their data calls to ignoreErrors: true so the global
toast no longer fires. hasError flips to true on error, stays
true while retries are pending, and is only cleared in the
`next` callback of the next request that actually succeeds.
- Add retryLoad* helpers (retryLoadItems / retryLoadResults /
retryLoadPopularItems / retryLoadCreator) that set isLoading=true
immediately and schedule the real load via a 350ms setTimeout so
rapid-fire retry clicks coalesce into one network round-trip.
- Search component drops the inline switchMap-on-searchSubject path
and pipes the debounced subject straight into loadResults() so
search-triggered loads go through the same error / spinner /
retry plumbing.
- Render the error block consistently across surfaces: in browse
and search inside their respective results container, on the
home page right after the category cards, on the creator profile
as the whole page body (replacing the old router.navigate to
/iot-hub). Each block uses .tb-no-service-bg + the new
iot-hub.network-server-unavailable / -text / try-again locale
keys and binds the primary button to the matching retryLoad*
method.
- Add the matching .tb-iot-hub-empty-state typography (18/500
title, 14/0.54 body, 24px button gap) to the home and creator
profile SCSS so the wrapper matches the layout already used in
browse / search.
Also pick up an unrelated dashboard-widget-select adjustment touched
in the same workspace.
- Drop the DEVICE-only `tb-iot-hub-card-preview-device` /
`dlg-preview-device` variants in the item card and item detail
dialog. The previously-special white-background-with-border treatment
is gone — device previews now reuse the same dot-pattern background
as widgets / solution templates for a consistent look.
- In the home-page iot-hub widget, fold the
`tb-iot-hub-widget-card-image-pattern` modifier into the base
`.tb-iot-hub-widget-card-image` so every preview card already paints
the dot pattern (no opt-in class required).
- Rename `.tb-search-categories` to `.tb-results-container` across
the iot-hub-search component / search page / creator profile pages
so the wrapper class better reflects what it actually contains
(browse / search results) and stays consistent between the search
component and the screens that embed it.
- Pick up incidental browse / search component html / scss tweaks
touched alongside the rename.
- 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).
Netty 4.1.133.Final introduced a regression in MqttDecoder while fixing
CVE-2026-44248: when multiple MQTT packets are present in the same
cumulation buffer, the per-message size check used the total buffer size
instead of the current packet's declared remaining length. Valid in-limit
packets get rejected with TooLongFrameException("message length exceeds
65536: <small number>"). Fixed upstream by netty/netty#16787 and ported
to 4.1 as netty/netty@30f8f284db, released in 4.1.134.Final.
- Add a public listing item-version endpoint to IotHubApiService
(GET /api/listings/public/by-slug/{slug}/item-version?ce=true&
tbVersion=N) returning MpItemVersionView, with the new
ListingItemVersionNotFound shape capturing the documented 404
bodies (noMatchingVersions / peRequired / minTbVersionRequired).
- Wire a tbVersionIntToString helper alongside the existing
tbVersionToInt so the dialogs can render the resolved versions
compactly (430 → "4.3", 421 → "4.2.1").
- Route /iot-hub/listing/:slug to TbIotHubItemResolverComponent.
Resolver picks the slug path first, dispatches the 404 bodies to
three handlers: noMatchingVersions reuses the existing
deep-link-not-found notification, peRequired opens a new dialog,
minTbVersionRequired opens another with the resolved min version.
- New TbIotHubPeRequiredDialogComponent renders the "Professional
Edition required" prompt and TbIotHubUpgradeRequiredDialogComponent
renders the "ThingsBoard upgrade required" prompt. Both reuse a
single upgrade-required.svg asset (Figma export) tinted via CSS
mask + background-color so the PE accent inherits without touching
the asset. The shared "Upgrade instance" button links to the right
upgrade doc per dialog.
- New TbIotHubAlarmRulesUnavailablePageComponent replaces
TbIotHubItemsPageComponent on /iot-hub/alarm-rules. It mirrors the
iot-hub-home glow-blob + dot-pattern background (alarm-rule
accent), renders the alarm-rules hero, a 28px title with the
primary "Thingsboard v4.3" span, the description, an Upgrade
instance link (https://thingsboard.io/docs/installation/upgrade-
instructions/), Back to IoT Hub, and the current platform version
read from env.tbVersion.
- Locale: pe-required-title / pe-required-message (bolded "Professional
Edition" / "Community Edition"), upgrade-required-title /
upgrade-required-message (bolded interpolations), and the alarm-
rules-unavailable-* keys for the new page.
- 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.
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.