diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.html b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.html
index ad0ef8fd1e..22705663c8 100644
--- a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.html
+++ b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.html
@@ -62,6 +62,14 @@
(click)="passwordVisible[field.key] = !passwordVisible[field.key]">
{{ passwordVisible[field.key] ? 'visibility_off' : 'visibility' }}
+ @if (field.randomGenerator) {
+
+ }
@if (ws.formGroup.controls[field.key]?.hasError('required')) {
{{ field.label + ' is required' }}
}
@@ -86,6 +94,14 @@
{{ field.label }}
+ @if (field.randomGenerator) {
+
+ }
@if (ws.formGroup.controls[field.key]?.hasError('required')) {
{{ field.label + ' is required' }}
}
diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.scss b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.scss
index 192d569f56..ebdf5da07c 100644
--- a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.scss
+++ b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.scss
@@ -210,6 +210,34 @@
}
}
+ .tb-doc-link-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 16px;
+ margin: 4px 8px 4px 0;
+ border: 1px solid rgba(0, 0, 0, 0.12);
+ border-radius: 6px;
+ background: #fff;
+ color: rgba(0, 0, 0, 0.76);
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 20px;
+ text-decoration: none;
+ cursor: pointer;
+
+ i.material-icons {
+ font-size: 18px;
+ line-height: 18px;
+ color: rgba(0, 0, 0, 0.54);
+ }
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.04);
+ border-color: rgba(0, 0, 0, 0.24);
+ }
+ }
+
.tb-callout {
display: flex;
align-items: flex-start;
diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts
index 3356683244..98d5542d64 100644
--- a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts
+++ b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts
@@ -33,6 +33,7 @@ import { RuleChainService } from '@core/http/rule-chain.service';
import { AttributeService } from '@core/http/attribute.service';
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { EntityId } from '@shared/models/id/entity-id';
+import { generateSecret } from '@core/utils';
import {
installMethodLabels as INSTALL_METHOD_LABELS,
peOnlyInstallMethods,
@@ -44,6 +45,7 @@ import {
FormFieldDefinition,
FormFieldType,
InstallStepType,
+ resolveDocLinkPlaceholders,
stepTypeAliasMap
} from '@shared/models/iot-hub/device-package.models';
@@ -74,6 +76,7 @@ export interface WizardStep {
}
const ENTITY_STEP_MIN_DELAY = 2000;
+const DEFAULT_RANDOM_SIZE = 20;
@Component({
selector: 'tb-device-install-dialog',
@@ -422,6 +425,14 @@ export class TbDeviceInstallDialogComponent extends DialogComponent {
if (key in this.formValues) {
return String(this.formValues[key]);
@@ -591,7 +602,7 @@ export class TbDeviceInstallDialogComponent extends DialogComponent 0) {
validators.push(Validators.pattern(field.validators[0].pattern));
}
- const initialValue = storedValues?.[field.key] ?? field.defaultValue ?? (field.type === FormFieldType.BOOLEAN ? false : '');
+ const initialValue = this.resolveInitialFieldValue(field, storedValues?.[field.key]);
controls[field.key] = new UntypedFormControl(initialValue, validators);
if (field.type === FormFieldType.PASSWORD) {
this.passwordVisible[field.key] = true; // Show passwords in review mode
@@ -604,6 +615,26 @@ export class TbDeviceInstallDialogComponent extends DialogComponent this.readmeContent = this.prefixResourceUrls(content || '')
+ content => this.readmeContent = this.resolveDocLinks(this.prefixResourceUrls(content || ''))
);
}
@@ -307,4 +308,14 @@ export class TbIotHubItemDetailDialogComponent extends DialogComponent;
+ productURL?: string;
+ datasheetURL?: string;
}
export enum FormFieldType {
@@ -241,6 +243,9 @@ export interface FormFieldDefinition {
helpImage?: string;
validators?: FormFieldValidator[];
options?: FormFieldOption[];
+ randomGenerator?: boolean;
+ randomSize?: number;
+ randomByDefault?: boolean;
}
export interface EntityStepOutput {
@@ -265,3 +270,49 @@ export interface EntityStepProgress {
conflictType?: ConflictType;
resolution?: string;
}
+
+export interface DocLinks {
+ productURL?: string;
+ datasheetURL?: string;
+}
+
+export interface DocLinkLabels {
+ productPage: string;
+ datasheet: string;
+}
+
+export function resolveDocLinkPlaceholders(
+ markdown: string,
+ name: string,
+ links: DocLinks,
+ labels: DocLinkLabels
+): string {
+ return markdown
+ .replace(/\$\{product\.button}/g, () =>
+ links.productURL ? buildDocLinkButton(links.productURL, `${name} ${labels.productPage}`, 'open_in_new') : '')
+ .replace(/\$\{datasheet\.button}/g, () =>
+ links.datasheetURL ? buildDocLinkButton(links.datasheetURL, `${name} ${labels.datasheet}`, 'description') : '');
+}
+
+function buildDocLinkButton(url: string, text: string, icon: string): string {
+ const safeUrl = escapeHtmlAttr(url);
+ const safeText = escapeHtml(text);
+ return `` +
+ `${icon}${safeText}`;
+}
+
+function escapeHtml(value: string): string {
+ return value.replace(/[&<>]/g, ch => ch === '&' ? '&' : ch === '<' ? '<' : '>');
+}
+
+function escapeHtmlAttr(value: string): string {
+ return value.replace(/[&<>"']/g, ch => {
+ switch (ch) {
+ case '&': return '&';
+ case '<': return '<';
+ case '>': return '>';
+ case '"': return '"';
+ default: return ''';
+ }
+ });
+}
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json
index 3977bcbd93..639fcc9194 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -4190,6 +4190,7 @@
},
"iot-hub": {
"iot-hub": "IoT Hub",
+ "generate-random-value": "Generate random value",
"home-title": "ThingsBoard IoT Hub",
"home-subtitle-prefix": "Discover ready-to-use",
"and-devices": "& Devices",