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.
- 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
- Add 'add' mode to item card: shows "Add" button, emits addClick
- Add 'add' mode and addItem output to browse component
- Browse passes mode to cards and detail dialog, propagates add results
- New TbIotHubAddItemDialogComponent: fullscreen dialog with embedded browse
in add mode, installs item via API and returns descriptor
- Add "Add from IoT Hub" menu item to dashboards table config
- On add: installs dashboard, navigates to newly created dashboard
- Add HomeComponentsModule import to IotHubModule
- Move iot-hub-browse.component to home/components/iot-hub/
- Move iot-hub-item-card.component to home/components/iot-hub/
- Register both in HomeComponentsModule (declarations + exports)
- Remove from IotHubModule declarations
- Update relative imports to use @home/pages/iot-hub/ for dialog dependencies
- Add All/Installed toggle in IoT Hub mode header
- Installed mode fetches published versions for installed widgets with in-memory filtering
- Filter panel with Type, Category, Use Case sections via tb-popover
- Filter badge count on filter button
- Installed badge (check + label) on widget cards in All mode
- Widget type badge from dataDescriptor on IoT Hub widget cards
- Filters apply to both All (server-side) and Installed (in-memory) modes
- Load installed widgets on entering IoT Hub mode
- Skip detail dialog for already-installed widgets, emit directly
- Move item detail dialog to home/components/iot-hub for shared access
- Register in HomeComponentsModule (declarations + exports)
- Add 'add' mode to item detail dialog showing "Add" button instead of install
- Add 'iotHub' mode to dashboard-widget-select with IoT Hub widget grid
- IoT Hub cards show author + install count footer
- Clicking card opens detail dialog in 'add' mode
- Add installs widget via IoT Hub API then emits widgetSelected with full type info
- Add "IoT Hub" option to mode toggle in dashboard page header
- Hide filter/deprecated controls in IoT Hub mode
- Hero keywords clickable to navigate to items pages
- Use subscriptSizing="dynamic" on hero search and search page inputs
- Replace native select with mat-select for items-per-page in search page
- Propagate search text to type pages via ?search= query param
- Remove back button negative margin
- New TbIotHubSearchComponent at /iot-hub/search with ?search= query param
- Results grouped by item type with section headers linking to type pages
- 5-column grid for widgets/dashboards, 3-column for calculated fields/rule chains
- Search input with clear button, sort dropdown, pagination with page numbers
- Empty state with search_off icon when no results found
- Reuses item card component with install/update/delete dialog handling
- Search popup using mat-autocomplete with results grouped by item type
- Debounced search with distinctUntilChanged, page size 10, most installed sort
- Loading spinner shown immediately on first focus while fetching
- No results state with search_off icon and contextual message
- Clear button in search input to reset text
- "See all results" footer navigates to /iot-hub/search page
- Search button performs same navigation as "See all results"
- Popup styled to match design: 424px max-height, 8px radius, layered shadow
- Update table type chips, update column, and filter panel to match design
- Add afterClosed handling to viewItemDetails and viewUpdateDetails dialogs
- Reload table data when items are updated or deleted from detail dialog
- Back link + "Installed Items" title + "Check for updates" primary button
- Filter button (stroked primary) with dropdown checkbox panel for item types
- Filter chips (mat-chip-set) with "Clear all" button when active
- Search field matching browse page design (320px, rounded-8, outlined)
- Mat-table with columns: Name, Type (colored icon+text), Version,
Installed date, Updates (up-to-date/available+update btn), Actions
- Actions: info, open-in-new, remove (mdi:trash-can-outline) icon buttons
- Updates column: green "Up to date" or "vX.X.X Available" + Update stroked warn
- Item type filter passed as List<String> to API
- Use TbIotHubDeleteDialogComponent for remove confirmation
- Item card: installed chip (non-clickable pill, green) + separate remove icon
button (mdi:trash-can-outline) for both same-version and update-available states
- Detail dialog: info section matches design exactly (badge, links, actions layout),
solution template links on separate row, remove button with warn outline matching
label text color, matButtonIcon + tb-mat-24 on remove icon
- Rename all Delete labels/keys to Remove across locale and components
- Solution template remove dialog: specific description about multiple entities
- getInstalledItems API: change itemType to List<String> itemTypes through full stack
(controller, service, DAO, repository JPQL uses IN clause, UI accepts string|string[])
* Add device-package.models.ts with install wizard types
Defines ConnectivityType, InstallStepType, ENTITY_STEP_TYPES,
stepTypeAliasMap, DevicePackageInfo, DeviceInstallStep, form field
interfaces, and EntityStepProgress types for the device install wizard.
* Add device install wizard dialog component for IoT Hub
Multi-step dialog that provisions TB entities from a device package ZIP:
- Connectivity type selector, instruction/markdown views, dynamic forms
- Entity creation with progress tracking (device profile, device, dashboard, rule chain)
- Variable resolution across steps (form values, entity outputs, positional refs)
* fix(iot-hub): fix device install dialog template and translations
- Use correct tb-markdown inputs ([data] and [usePlainMarkdown])
- Fix translation key format (hyphens, not dots)
- Remove unused ConnectivityType import and resolveImageUrl method
- Auto-advance to first step when single connectivity type
- Use inline data URIs for helpImage directly
* feat(iot-hub): wire device install dialog into all install entry points
Branch DEVICE item type to fetch ZIP and open TbDeviceInstallDialogComponent
instead of the simple install dialog. Applied to:
- Item detail dialog (install button)
- Browse component (card install button)
- Home component (card install button)
* fix(iot-hub): fix build errors in device install dialog
- Fix tb-markdown input: [markdownText] → [data] in done view
- Fix JSZip entry type annotation for strict TS compilation
* fix(iot-hub): reuse existing device profiles and add missing retry translation
- Find device profile by name before creating; reuse if exists
- Add action.retry translation key (was showing raw key)
* feat(iot-hub): add find-or-create for rule chains in device install
Reuse existing rule chains by name (same pattern as device profiles).
Shared entities (device profile, rule chain) are looked up before creation.
Per-install entities (device, dashboard) are always created fresh.
* feat(iot-hub): refactor device install dialog to use mat-stepper
- Replace single-panel view switching with mat-horizontal-stepper
- Group consecutive entity steps into a single "Provisioning" stepper step
- Each SHOW_INSTRUCTION and SHOW_FORM is its own stepper step
- Add 2s minimum display time per entity creation step
- Connectivity selector shown before stepper (not a stepper step)
- Progress step auto-advances to next step after completion
- Stepper shows completed checkmarks for finished steps
* fix(iot-hub): fix stepper navigation by tracking step completion
Linear mat-stepper requires steps to be marked completed before
advancing. Added completed flag to WizardStep, set it when user
clicks Next or when entity steps finish.
* feat(iot-hub): add device package install tracking with entity cleanup
Backend:
- Expand DeviceInstalledItemDescriptor with createdEntityIds and dashboardId
- Add POST /api/iot-hub/device/register endpoint to save installed item
- Implement reverse-order entity deletion for DEVICE uninstall
- Update DEVICE case in updateItemVersion to clean old entities
Frontend:
- Expand DeviceInstalledItemDescriptor TS model
- Add registerDeviceInstall API method
- Wire wizard to collect entity IDs and register after completion
- Enable dashboard navigation for installed device packages
* fix(iot-hub): add type discriminator to device register request body
Jackson requires the 'type' field for @JsonTypeInfo polymorphic
deserialization of DeviceInstalledItemDescriptor.
* feat(iot-hub): show entity type in provisioning progress rows
Each progress row now displays "Device Profile — ESP32 Dev Kit" instead
of just "ESP32 Dev Kit", making it clear what type of entity is being
provisioned.
* fix(iot-hub): suppress global error toast and add Back button on provisioning error
- Add ignoreErrors: true to all entity creation API calls so errors
are only shown in the dialog, not as global toast notifications
- Add Back button on provisioning error to navigate back to the last
form step, allowing users to fix parameters (e.g. device name)
- Make form steps editable so stepper allows backward navigation
- Reset progress step state when going back so it re-runs with
updated form values
* fix(iot-hub): resolve ExpressionChangedAfterItHasBeenCheckedError in device install dialog
Inject ChangeDetectorRef and call detectChanges() after async state
mutations (entity step status changes, stepper auto-advance, ngOnInit
completion). The NG0100 error occurred because async operations
(setTimeout, HTTP calls) mutated component state outside Angular's
change detection cycle.
* fix(iot-hub): fix device register endpoint JSON deserialization
Accept JsonNode and use JacksonUtil.treeToValue() instead of direct
@RequestBody DeviceInstalledItemDescriptor. The @JsonTypeInfo on the
parent interface caused Spring's message converter to fail when
deserializing the concrete class directly.
* fix(iot-hub): fix dashboardId serialization in device register request
DashboardId implements EntityId which uses EntityIdDeserializer requiring
both entityType and id fields. Frontend was sending {"id":"..."} without
entityType, causing "Missing entityType or id!" 400 error.
Fix: include entityType: "DASHBOARD" in the dashboardId payload.
Add deserialization unit test to verify the exact frontend payload format.
- Create TbIotHubItemsPageComponent with hero section (title, description,
illustration, installed items button, contribute link) per item type
- Add separate routes: /iot-hub/widgets, /iot-hub/dashboards,
/iot-hub/solution-templates, /iot-hub/calculated-fields,
/iot-hub/rule-chains, /iot-hub/devices
- Remove old /iot-hub/browse route, update all navigation links
- Browse component: add @Input activeType and hideTabs for embedded use
- Home component: navigateToBrowse now routes to type-specific pages
- Add item type page description translations