diff --git a/public/docs/iot-hub/guides/solution-template/examples/fleet-tracking.zip b/public/docs/iot-hub/guides/solution-template/examples/fleet-tracking.zip index 8a8c37ba56..099ddd645a 100644 Binary files a/public/docs/iot-hub/guides/solution-template/examples/fleet-tracking.zip and b/public/docs/iot-hub/guides/solution-template/examples/fleet-tracking.zip differ diff --git a/public/docs/iot-hub/guides/solution-template/examples/smart-office.zip b/public/docs/iot-hub/guides/solution-template/examples/smart-office.zip index 8a8c37ba56..2072f4259d 100644 Binary files a/public/docs/iot-hub/guides/solution-template/examples/smart-office.zip and b/public/docs/iot-hub/guides/solution-template/examples/smart-office.zip differ diff --git a/public/docs/iot-hub/guides/solution-template/examples/swimming-pool-scada.zip b/public/docs/iot-hub/guides/solution-template/examples/swimming-pool-scada.zip index 8a8c37ba56..39ea3caa69 100644 Binary files a/public/docs/iot-hub/guides/solution-template/examples/swimming-pool-scada.zip and b/public/docs/iot-hub/guides/solution-template/examples/swimming-pool-scada.zip differ diff --git a/public/images/blog/dark-mode-for-a-dashboard/DarkMode-2-poster.jpg b/public/images/blog/dark-mode-for-a-dashboard/DarkMode-2-poster.jpg new file mode 100644 index 0000000000..22b7bc5e61 Binary files /dev/null and b/public/images/blog/dark-mode-for-a-dashboard/DarkMode-2-poster.jpg differ diff --git a/public/images/blog/dark-mode-for-a-dashboard/DarkMode-2.gif b/public/images/blog/dark-mode-for-a-dashboard/DarkMode-2.gif deleted file mode 100644 index 8438f1abdf..0000000000 Binary files a/public/images/blog/dark-mode-for-a-dashboard/DarkMode-2.gif and /dev/null differ diff --git a/public/images/blog/dark-mode-for-a-dashboard/DarkMode-2.mp4 b/public/images/blog/dark-mode-for-a-dashboard/DarkMode-2.mp4 new file mode 100644 index 0000000000..0c93d72013 Binary files /dev/null and b/public/images/blog/dark-mode-for-a-dashboard/DarkMode-2.mp4 differ diff --git a/public/images/blog/thingsboard-4-0-release/maps-4.webp b/public/images/blog/thingsboard-4-0-release/maps-4.webp index 5236cd358d..ed19216ee3 100644 Binary files a/public/images/blog/thingsboard-4-0-release/maps-4.webp and b/public/images/blog/thingsboard-4-0-release/maps-4.webp differ diff --git a/public/resources/docs/tutorial/advanced-dashboard-guide-1/district-night-background.png b/public/resources/docs/tutorial/advanced-dashboard-guide-1/district-night-background.png index abbadbdbef..7730b570b0 100644 Binary files a/public/resources/docs/tutorial/advanced-dashboard-guide-1/district-night-background.png and b/public/resources/docs/tutorial/advanced-dashboard-guide-1/district-night-background.png differ diff --git a/public/resources/docs/tutorial/advanced-dashboard-guide-2/building-background.png b/public/resources/docs/tutorial/advanced-dashboard-guide-2/building-background.png index 882cd538f2..924c26b8f8 100644 Binary files a/public/resources/docs/tutorial/advanced-dashboard-guide-2/building-background.png and b/public/resources/docs/tutorial/advanced-dashboard-guide-2/building-background.png differ diff --git a/public/resources/docs/tutorial/advanced-dashboard-guide-2/office-room.png b/public/resources/docs/tutorial/advanced-dashboard-guide-2/office-room.png index 1652499d99..2dbf594173 100644 Binary files a/public/resources/docs/tutorial/advanced-dashboard-guide-2/office-room.png and b/public/resources/docs/tutorial/advanced-dashboard-guide-2/office-room.png differ diff --git a/src/assets/devices-library/ready-to-go-devices/temco-tstat10/temco-tstat10-dashboard-ce.png b/src/assets/devices-library/ready-to-go-devices/temco-tstat10/temco-tstat10-dashboard-ce.png new file mode 100644 index 0000000000..7f1398cfed Binary files /dev/null and b/src/assets/devices-library/ready-to-go-devices/temco-tstat10/temco-tstat10-dashboard-ce.png differ diff --git a/src/components/DocImage.astro b/src/components/DocImage.astro index 94a7bf6494..402efa1f52 100644 --- a/src/components/DocImage.astro +++ b/src/components/DocImage.astro @@ -47,6 +47,7 @@ const svgRawModules = import.meta.glob( const mod = assetModules[src]; const isSvg = src.endsWith('.svg'); +const isGif = src.endsWith('.gif'); const rawSvg = isSvg ? await svgRawModules[src]?.() : undefined; let interactiveSvg = rawSvg && /]/i.test(rawSvg) ? rawSvg : null; @@ -79,8 +80,10 @@ if (interactiveSvg) { return ``; }); } +// GIFs bypass Sharp: it reads all frames (`pages: -1`) and trips +// `limitInputPixels` on large multi-frame GIFs. const optimized = - mod && !isSvg + mod && !isSvg && !isGif ? await getImage({ src: mod.default, ...(numericWidth ? { width: numericWidth } : {}), diff --git a/src/components/ImageGallery.astro b/src/components/ImageGallery.astro index fdf187f4a7..18681562bf 100644 --- a/src/components/ImageGallery.astro +++ b/src/components/ImageGallery.astro @@ -169,6 +169,7 @@ const processed = await Promise.all( } const isSvg = lightPath.endsWith('.svg'); + const isGif = lightPath.endsWith('.gif'); // SVGs skip raster processing. If they contain ``, inline them so anchors stay clickable. if (isSvg) { @@ -196,6 +197,28 @@ const processed = await Promise.all( }; } + // GIFs bypass Sharp: it reads all frames (`pages: -1`) and trips + // `limitInputPixels` on large multi-frame GIFs. + if (isGif) { + const lightSrc = lightMod.default.src; + const darkMod = hasDark ? assetModules[darkPath] : null; + const darkSrc = darkMod ? darkMod.default.src : lightSrc; + + return { + alt, + caption: captionToHtml(caption ?? ''), + thumbSrc: lightSrc, + fullSrc: lightSrc, + darkThumbSrc: darkSrc, + darkFullSrc: darkSrc, + fullWidth: lightMod.default.width, + fullHeight: lightMod.default.height, + hasDark, + interactiveSvg: undefined, + isCdn: false, + }; + } + const thumb = await getImage({ src: lightMod.default, width: 800, diff --git a/src/content/_includes/docs/mqtt-broker/user-guide/shared-subscriptions.mdx b/src/content/_includes/docs/mqtt-broker/user-guide/shared-subscriptions.mdx index 55c0830fd5..204a44e6fc 100644 --- a/src/content/_includes/docs/mqtt-broker/user-guide/shared-subscriptions.mdx +++ b/src/content/_includes/docs/mqtt-broker/user-guide/shared-subscriptions.mdx @@ -1,5 +1,6 @@ import { Aside } from '@astrojs/starlight/components'; import ImageGallery from '~/components/ImageGallery.astro'; +import DocImage from '~/components/DocImage.astro'; import DocLink from '@components/DocLink.astro'; import Tabs from '@components/Tabs.astro'; import TabItem from '@components/TabItem.astro'; @@ -121,7 +122,7 @@ unsubscribing a client removes it and the remaining members absorb its share. The following demo illustrates the shared subscription behavior end-to-end: -![Shared subscription demo](/src/assets/images/docs/mqtt-broker/user-guide/shared-subscription-demo.gif) + ## Shared subscriptions load balancing strategy diff --git a/src/content/_includes/docs/partners/hardware/seeed/samples/raspberry-pi-with-grove-base-hat.mdx b/src/content/_includes/docs/partners/hardware/seeed/samples/raspberry-pi-with-grove-base-hat.mdx index 4fcf3144b3..5a1f85a28b 100644 --- a/src/content/_includes/docs/partners/hardware/seeed/samples/raspberry-pi-with-grove-base-hat.mdx +++ b/src/content/_includes/docs/partners/hardware/seeed/samples/raspberry-pi-with-grove-base-hat.mdx @@ -1,6 +1,7 @@ import ImageGallery from '~/components/ImageGallery.astro'; import { Steps, Aside } from '@astrojs/starlight/components'; import MultiProductImageGallery from '~/components/MultiProductImageGallery.astro'; +import DocImage from '~/components/DocImage.astro'; ## Introduction @@ -8,7 +9,7 @@ This guide describes how to connect a Raspberry Pi with a Grove Base Hat to Thin The Raspberry Pi runs a Python script that reads sensor data, sends telemetry to ThingsBoard, and responds to RPC commands for servo and LED control. -Grove Base Hat dashboard demo + ## Prerequisites diff --git a/src/content/_includes/docs/partners/hardware/sodaq/samples/sodaq-universal-tracker.mdx b/src/content/_includes/docs/partners/hardware/sodaq/samples/sodaq-universal-tracker.mdx index a27a7589b5..094dab293f 100644 --- a/src/content/_includes/docs/partners/hardware/sodaq/samples/sodaq-universal-tracker.mdx +++ b/src/content/_includes/docs/partners/hardware/sodaq/samples/sodaq-universal-tracker.mdx @@ -1,11 +1,12 @@ import { Aside, Steps } from '@astrojs/starlight/components'; import ImageGallery from '~/components/ImageGallery.astro'; +import DocImage from '~/components/DocImage.astro'; ## Introduction SODAQ NB-IoT Trackers collect GPS location and sensor data and transmit it over the T-Mobile NB-IoT network. This guide walks through connecting a SODAQ NB-IoT Tracker to ThingsBoard PE using the T-Mobile IoT CDP integration, setting up a data converter, importing alarm rule chains, and visualizing data on a dashboard. -SODAQ tracker dashboard demo + ## Prerequisites diff --git a/src/content/_includes/docs/user-guide/rule-engine.mdx b/src/content/_includes/docs/user-guide/rule-engine.mdx index 4cce21dff6..ea9fa7ff75 100644 --- a/src/content/_includes/docs/user-guide/rule-engine.mdx +++ b/src/content/_includes/docs/user-guide/rule-engine.mdx @@ -185,8 +185,6 @@ configuration details, submit and retry strategies, and the three built-in queue ## Custom REST API Calls - - ThingsBoard provides an HTTP API to send custom requests directly to the Rule Engine and return the processing result in the response body — useful for extending the platform API, enriching calls with device/asset attributes, or powering custom widgets. diff --git a/src/content/blog/dark-mode-for-a-dashboard.mdx b/src/content/blog/dark-mode-for-a-dashboard.mdx index bb6b4909d9..82f27fb43b 100644 --- a/src/content/blog/dark-mode-for-a-dashboard.mdx +++ b/src/content/blog/dark-mode-for-a-dashboard.mdx @@ -20,7 +20,17 @@ Currently, the TB platform does not offer a built-in dark mode by default (thoug To see an example in action, check out our “[Smart Energy](/use-cases/smart-energy/)” use case on the website. Simply click the “Try Demo” button to explore [the dashboard](https://demo.thingsboard.io/dashboard/e8e409c0-f2b5-11e6-a6ee-bb0136cc33d0?publicId=963ab470-34c9-11e7-a7ce-bb0136cc33d0). In the top-right corner, the “sun” icon can be found, which allows to toggle between light and dark modes effortlessly. -![](/images/blog/dark-mode-for-a-dashboard/DarkMode-2.gif) + ## Custom action: The switch button for dark mode diff --git a/src/content/devices/en/raspberry-pi-cm4.mdx b/src/content/devices/en/raspberry-pi-cm4.mdx index d046a7c71d..ff74aab627 100644 --- a/src/content/devices/en/raspberry-pi-cm4.mdx +++ b/src/content/devices/en/raspberry-pi-cm4.mdx @@ -167,7 +167,7 @@ Now we have a visualizing dashboard for displaying data and controlling the DO ( ## Demonstration of the result - + ## Conclusion diff --git a/src/content/docs/docs/installation/building-from-source.mdx b/src/content/docs/docs/installation/building-from-source.mdx index 1ff6bf8b8a..a6c932b8c7 100644 --- a/src/content/docs/docs/installation/building-from-source.mdx +++ b/src/content/docs/docs/installation/building-from-source.mdx @@ -3,6 +3,7 @@ title: "Building ThingsBoard from Sources" sidebar: label: "Building from Sources" description: "Build and run ThingsBoard Community Edition from source code." +selfCanonical: true --- import DocLink from '@components/DocLink.astro'; import { Aside } from '@astrojs/starlight/components'; diff --git a/src/content/docs/docs/iot-hub/guides/solution-template.mdx b/src/content/docs/docs/iot-hub/guides/solution-template.mdx index ccde016eff..c7e51e9557 100644 --- a/src/content/docs/docs/iot-hub/guides/solution-template.mdx +++ b/src/content/docs/docs/iot-hub/guides/solution-template.mdx @@ -118,7 +118,7 @@ The ZIP archive must contain all three of these at the root. The platform reject | `asset_profiles/` | Asset profile templates referenced from `entities/assets.json` | | `rule_chains/` | Rule chain templates referenced from `entities/rule_chains.json` | | `calculated_fields/` | Calculated field templates referenced from related entities | -| `images/` | Custom images — usually not needed if you host images externally | +| `images/` | Bundled preview and gallery images referenced from `solution.json` (`previewImageUrl`, `imageUrls`) by relative path, e.g. `images/preview.png` | ## Editions @@ -153,24 +153,24 @@ If your template uses only CE features but also offers a richer PE experience (e | Field | Type | Description | |-------|------|-------------| | `title` | string | Human-readable solution name shown on the catalog card | -| `previewImageUrl` | string | Main preview image shown on browse cards and the detail page header. Can be an external URL or a path to a file in the ZIP's `images/` folder | +| `previewImageUrl` | string | Main preview image shown on browse cards and the detail page header. Accepts an external URL (e.g. `https://img.thingsboard.io/...`), a relative path to a file in the ZIP's `images/` folder (e.g. `images/hp-swimming-pool-preview.png`), or an inline data URI (`data:image/png;base64,...`) | ### Optional Fields | Field | Type | Description | |-------|------|-------------| -| `imageUrls` | string[] | Gallery images shown on the solution's detail page. Same URL/path rules as `previewImageUrl` | +| `imageUrls` | string[] | Gallery images shown on the solution's detail page. Each entry uses the same URL/path rules as `previewImageUrl` — e.g. `https://img.thingsboard.io/...` or `images/hp-swimming-pool-gallery-1.png` for files in the ZIP's `images/` folder | | `installTimeoutMs` | integer | Upper bound for how long the install may take (in ms). `0` means use the default server timeout. Set this only if your template creates many entities or heavy rule chains | -### Image URL Resolution +At install time the platform resolves every image, saves it as a ThingsBoard image resource, and rewrites dashboard and profile references to point at the new resource IDs — so the solution keeps working even if the original image host goes down later. -Both `previewImageUrl` and each entry in `imageUrls` can be: - -- An **external URL** (`https://img.thingsboard.io/...`) — downloaded at install time and cached as an image resource -- A **relative path** into the ZIP (`images/preview.png`) — read directly from the archive -- A **data URI** (`data:image/png;base64,...`) — embedded inline +### Image Best Practices -When you release the template, images are resolved, saved as image resources in ThingsBoard, and the dashboard/profile references are rewritten to point at the new resource IDs. This means your template works even if the original image host goes down later. +- **Preview image** — 16:9 aspect ratio, 1600×900 px, under 200 KB after compression. +- **Gallery images** — 16:9 or 4:3 aspect, 800×600 px minimum, showing different screens or views of the dashboard. +- Use **PNG** for screenshots with text or charts; **JPG** for photos. +- Always include at least 2–3 gallery images — users browse the gallery before clicking install. +- Crop to show dashboard content, not surrounding chrome (toolbars, navigation). ## `entities/` — The Heart of Your Template @@ -226,7 +226,7 @@ Per-device fields: | `type` | Device profile type (must match a profile in `device_profiles/` or the default `default`) | | `additionalInfo` | Arbitrary metadata (e.g. `{"gateway": true}` to mark a gateway device) | | `sharedAttributes` | Shared attributes saved to the device on creation — useful for gateway connector configs | -| `serverAttributes` | Server-side attributes saved on creation | +| `attributes` | Server-side attributes saved on creation (the field is named `attributes`, but values are stored as server-side attributes) | | `emulator` | Name of an entry from `entities/device_emulators.json` — binds this device to a telemetry emulator | | `credentials` | Optional custom credentials (access token, MQTT basic, X.509) | @@ -304,7 +304,67 @@ Wires dashboard JSON files from the `dashboards/` folder to actual dashboards. ### `entities/device_emulators.json` -Defines synthetic telemetry streams that make your solution feel alive immediately after install. See [Device Emulators](#device-emulators) below. +Device emulators are the secret sauce that makes a solution template look alive. Instead of users seeing empty dashboards, each emulated device streams realistic synthetic telemetry the moment the template is installed. + +Emulators are defined as a JSON array in `entities/device_emulators.json`: + +```json +[ + { + "name": "Tank Emulator", + "publishPeriodInDays": 7, + "publishFrequencyInSeconds": 3600, + "publishPauseInMillis": 1, + "activityPeriodInMillis": 720000, + "telemetryProfiles": [ + { + "key": "battery", + "valueStrategy": { + "type": "natural", + "precision": 1, + "minStartValue": 99, + "maxStartValue": 99, + "minLowValue": 21, + "maxLowValue": 21, + "minHighValue": 21, + "maxHighValue": 100, + "minIncrement": 100.0, + "maxIncrement": 100.0, + "minDecrement": 0.1, + "maxDecrement": 0.2, + "holidayMultiplier": 1.0, + "workHoursMultiplier": 1.0, + "nightHoursMultiplier": 1.0 + } + } + ] + } +] +``` + +#### Top-Level Emulator Fields + +| Field | Description | +|-------|-------------| +| `name` | Emulator name, referenced by devices via `"emulator": "..."` in `entities/devices.json` | +| `publishPeriodInDays` | How many historical days of data to backfill on install | +| `publishFrequencyInSeconds` | How often each telemetry point is emitted | +| `publishPauseInMillis` | Delay between individual telemetry writes | +| `activityPeriodInMillis` | Length of each simulated activity burst | +| `telemetryProfiles` | Array of per-key generation profiles | + +#### Telemetry Profile — `valueStrategy.type` + +| Strategy | Use for | +|----------|---------| +| `natural` | Smoothly drifting numeric values (temperature, humidity, battery) with min/max bounds and increment/decrement ranges | +| `enum` | Categorical values that switch randomly between a defined set | +| `constant` | Values that stay fixed | +| `step` | Values that transition between levels on a schedule | + +#### Why Emulators Matter + +Without emulators, your user installs the template, opens the dashboard, and sees empty widgets. With emulators, the user sees a realistic live system within seconds, which is the difference between a compelling demo and a confusing one. ## Dashboards @@ -392,79 +452,20 @@ If you reference a rule chain via `defaultRuleChainId`, the `id` must match the ## Rule Chains and Calculated Fields -Exported rule chain and calculated field JSONs. Rule chains are referenced by file name from `entities/rule_chains.json`; calculated fields are typically referenced from device profile configuration. - -### Rule Chain Example +Rule chains and calculated fields are exported as JSON files from ThingsBoard and dropped into `rule_chains/` and `calculated_fields/` respectively. The exports require no post-edit — wiring happens through `entities/` and device profiles. -A dedicated Rule Chain Contribution Guide will cover the full JSON structure. For now, export a working rule chain from ThingsBoard and drop it into `rule_chains/`. +### Rule Chains -### Calculated Field Example +1. In ThingsBoard, open **Rule Chains** and export the rule chain. +2. Save the JSON to `rule_chains/.json`. +3. Register it in [`entities/rule_chains.json`](#entitiesrule_chainsjson) with `name`, `file`, and a stable `jsonId`. +4. If a device profile uses this rule chain as its default, set its `defaultRuleChainId.id` to the same `jsonId`. The platform rewrites both to the real UUID at install time. -A dedicated Calculated Field Contribution Guide will cover the full JSON structure. For now, export a working calculated field from ThingsBoard and drop it into `calculated_fields/`. +### Calculated Fields -## Device Emulators - -Device emulators are the secret sauce that makes a solution template look alive. Instead of users seeing empty dashboards, each emulated device streams realistic synthetic telemetry the moment the template is installed. - -Emulators are defined in `entities/device_emulators.json`: - -```json -[ - { - "name": "Tank Emulator", - "publishPeriodInDays": 7, - "publishFrequencyInSeconds": 3600, - "publishPauseInMillis": 1, - "activityPeriodInMillis": 720000, - "telemetryProfiles": [ - { - "key": "battery", - "valueStrategy": { - "type": "natural", - "precision": 1, - "minStartValue": 99, - "maxStartValue": 99, - "minLowValue": 21, - "maxLowValue": 21, - "minHighValue": 21, - "maxHighValue": 100, - "minIncrement": 100.0, - "maxIncrement": 100.0, - "minDecrement": 0.1, - "maxDecrement": 0.2, - "holidayMultiplier": 1.0, - "workHoursMultiplier": 1.0, - "nightHoursMultiplier": 1.0 - } - } - ] - } -] -``` - -### Top-Level Emulator Fields - -| Field | Description | -|-------|-------------| -| `name` | Emulator name, referenced by devices via `"emulator": "..."` in `entities/devices.json` | -| `publishPeriodInDays` | How many historical days of data to backfill on install | -| `publishFrequencyInSeconds` | How often each telemetry point is emitted | -| `publishPauseInMillis` | Delay between individual telemetry writes | -| `activityPeriodInMillis` | Length of each simulated activity burst | -| `telemetryProfiles` | Array of per-key generation profiles | - -### Telemetry Profile — `valueStrategy.type` - -| Strategy | Use for | -|----------|---------| -| `natural` | Smoothly drifting numeric values (temperature, humidity, battery) with min/max bounds and increment/decrement ranges | -| `enum` | Categorical values that switch randomly between a defined set | -| `constant` | Values that stay fixed | -| `step` | Values that transition between levels on a schedule | - -### Why Emulators Matter - -Without emulators, your user installs the template, opens the dashboard, and sees empty widgets. With emulators, the user sees a realistic live system within seconds, which is the difference between a compelling demo and a confusing one. +1. In ThingsBoard, open the calculated field on a device or asset profile and export it. +2. Save the JSON to `calculated_fields/.json`. +3. Reference the file from the owning profile's configuration — no separate entry in `entities/` is required. ## `description.md` — Marketplace Description @@ -513,7 +514,6 @@ Keep the first paragraph under 512 characters — it's shown in a card. 4. **Why use this template** — real benefits to the user. 5. **Key features** — technical capabilities. 6. **Real-world applications** — industries and use cases that match. -7. **How to connect real devices** — bridge from the emulated demo to production. ## `instructions.md` — Post-Install Instructions @@ -570,6 +570,7 @@ These placeholders are resolved at install time: | `${MAIN_DASHBOARD_URL}` | Deep link to the solution's main dashboard | | `${DOCKER_CONFIG}` | Full `docker-compose.yml` content for the gateway, pre-filled with host, port, and token. Usually wrapped in a `bash` code block | | `${DOCS_BASE_URL}` | Base URL of the ThingsBoard documentation — use for links that should work on both ThingsBoard Cloud and on-prem | +| `${item-link:}` | Renders a link card pointing at another IoT Hub item (widget, dashboard, rule chain, etc.) the user can install in one click. Use for dependencies your template relies on | ### Code Block Tags @@ -588,7 +589,14 @@ docker run --pull always --rm -d --name tb-modbus-pool-emulator \ A good `instructions.md` follows this flow: 1. **Welcome** — one-line orientation. -2. **Prerequisites** — what the user needs (Docker, network access, accounts). +2. **Prerequisites** — what the user needs (Docker, network access, accounts). If your solution template depends on another IoT Hub item you have published (e.g. a custom widget, dashboard, or rule chain), reference it here with `${item-link:}`. The platform replaces the placeholder with a link card pointing at that IoT Hub item so users can install the dependency in one click. Example: + + ```markdown + This solution uses our custom Air Quality widget. Install it first: + + ${item-link:d93b9a1a-1c07-408c-bb86-743bbcb36832} + ``` + 3. **Launch the emulator** — copy-paste Docker commands. 4. **Launch the gateway** — use `${DOCKER_CONFIG}` to give them a ready-to-run config. 5. **Interacting with the dashboard** — link to the main dashboard via `${MAIN_DASHBOARD_URL}`. @@ -603,47 +611,6 @@ Use `edge_instructions.md` to explain edge-specific setup: how to assign the rul If you don't provide `edge_instructions.md`, the platform shows the regular `instructions.md`. -## Images - -You have two options for preview and gallery images: - -### External Hosting (recommended) - -Host images on a reliable CDN (e.g. `img.thingsboard.io`, S3, Cloudflare). Reference them by URL in `solution.json`: - -```json -{ - "previewImageUrl": "https://img.thingsboard.io/solutions/my_solution/preview.png", - "imageUrls": [ - "https://img.thingsboard.io/solutions/my_solution/gallery-1.png", - "https://img.thingsboard.io/solutions/my_solution/gallery-2.png" - ] -} -``` - -At install time the platform downloads each image, saves it as a ThingsBoard image resource, and rewrites references so the solution works even if the original URL goes down later. - -### Bundled in the ZIP - -Place images in an `images/` folder and reference by relative path: - -```json -{ - "previewImageUrl": "images/preview.png", - "imageUrls": ["images/gallery-1.png", "images/gallery-2.png"] -} -``` - -This is recommended for solutions that may ship without internet access during install. The trade-off is a larger ZIP archive. - -### Image Best Practices - -- **Preview image** — 16:9 aspect ratio, 1600×900 px, under 200 KB after compression. -- **Gallery images** — 16:9 or 4:3 aspect, 800×600 px minimum, showing different screens or views of the dashboard. -- Use **PNG** for screenshots with text or charts; **JPG** for photos. -- Always include at least 2–3 gallery images — users browse the gallery before clicking install. -- Crop to show dashboard content, not surrounding chrome (toolbars, navigation). - ## Allowed Values ### Solution Template Categories @@ -658,16 +625,6 @@ Pick one or more IoT domains the solution addresses. These are shared across the Allowed values: `Air Quality Monitoring`, `Asset Tracking`, `Cold Chain`, `Drones`, `Environment Monitoring`, `Fleet Tracking`, `Health Care`, `Industrial Automation`, `Predictive Maintenance`, `Robotics`, `SCADA`, `Smart Building`, `Smart City`, `Smart Energy`, `Smart Farming`, `Smart Home`, `Smart Metering`, `Smart Office`, `Smart Retail`, `Solar Monitoring`, `Tank Level Monitoring`, `Waste Management`. -## Variables Reference - -Variables are resolved after the solution is installed and available in `instructions.md` and `edge_instructions.md`. - -| Variable | Scope | Description | -|----------|-------|-------------| -| `${MAIN_DASHBOARD_URL}` | Dashboards | Deep link to the dashboard marked `main: true` in `entities/dashboards.json` | -| `${DOCKER_CONFIG}` | Gateway | Pre-filled `docker-compose.yml` for the ThingsBoard IoT Gateway, including host, port, and access token | -| `${DOCS_BASE_URL}` | Documentation | Base URL of the ThingsBoard docs for the running edition — resolves to `https://thingsboard.io/docs/ce` on CE and `https://thingsboard.io/docs/pe` on PE | - ## What's Auto-Derived vs What You Provide ### Auto-Derived from the Package @@ -713,7 +670,7 @@ Every upload creates a new version of your template. Bump the version in the upl - [ ] Contains a `## Solution Description` (or similar-level) heading - [ ] The first paragraph after that heading is under 512 characters (becomes the short description) -- [ ] Covers: highlights, what's inside, why use it, key features, real-world applications, how to connect real devices +- [ ] Covers: highlights, what's inside, why use it, key features, real-world applications ### `instructions.md` diff --git a/src/content/docs/docs/iot-hub/guides/widget.mdx b/src/content/docs/docs/iot-hub/guides/widget.mdx index a8b2218ec4..24cf940e3b 100644 --- a/src/content/docs/docs/iot-hub/guides/widget.mdx +++ b/src/content/docs/docs/iot-hub/guides/widget.mdx @@ -35,19 +35,34 @@ The sections below are a complete reference for each step. ## Exporting a Widget from ThingsBoard 1. Open **Resources → Widget Library** in your ThingsBoard instance. -2. Find or create your widget bundle and open the specific widget. -3. Click the **Export widget type** button (download icon in the top toolbar). +2. Locate your widget in the list. +3. In the row's action cell, click the **Export widget** button. 4. Save the resulting `.json` file — this is your upload file. The exported file is a complete widget type definition and requires no further editing to be valid. If you want to override auto-derived fields (description, image, tags), you can edit the JSON directly before uploading. +## Setting the Widget FQN + +Every widget published to IoT Hub must use a namespaced `fqn` in the form `nickname.widget_fqn`, where `nickname` is your own identifier — a personal handle (e.g. `john_doe`) or a company name (e.g. `thingsboard`). This keeps your widget unique across the marketplace and prevents collisions with other contributors. + +The ThingsBoard widget editor does **not** allow setting a custom namespace prefix in the UI — the exported JSON will contain only the plain widget name (e.g. `air_quality_card`). After exporting, open the `.json` file and prepend your nickname: + +```json +{ + "fqn": "john_doe.air_quality_card", + ... +} +``` + +Pick one nickname and reuse it for every widget you publish. + ## Widget JSON Structure A valid widget file is a JSON object with the following structure: ```json { - "fqn": "air_quality_card", + "fqn": "john_doe.air_quality_card", "name": "Air quality index card", "description": "Displays the latest air quality index telemetry in a scalable rectangle card.", "image": "tb-image;/api/images/system/air_quality_index_card_system_widget_image.png", @@ -82,10 +97,10 @@ A valid widget file is a JSON object with the following structure: | Field | Type | Description | |-------|------|-------------| -| `fqn` | string | Fully qualified name — a unique identifier across the widget library (e.g. `air_quality_card`). Lowercase and underscores recommended | +| `fqn` | string | Fully qualified name — a unique identifier across the widget library, in the form `nickname.widget_fqn` (e.g. `john_doe.air_quality_card` or `acme.air_quality_card`). Lowercase and underscores recommended. See [Setting the Widget FQN](#setting-the-widget-fqn) | | `name` | string | Human-readable widget name (e.g. `Air quality index card`) | | `descriptor` | object | Widget configuration — must include a `type` field | -| `descriptor.type` | string | One of the [Widget Types](#widget-types) | +| `descriptor.type` | string | One of `timeseries`, `latest`, `rpc`, `alarm`, `static` | ### Optional Fields @@ -95,7 +110,7 @@ A valid widget file is a JSON object with the following structure: | `image` | string | Preview image: data URI, `/api/images/...` reference (resolved from `resources`), external URL, or raw base64 | | `tags` | string[] | Free-form keywords used for search | | `deprecated` | boolean | Mark `true` to indicate the widget is deprecated | -| `resources` | array | Embedded image resources — populated automatically by ThingsBoard export | +| `resources` | array | Embedded resources — images, external CDN scripts (e.g. ECharts), or ThingsBoard extensions. Populated automatically by ThingsBoard export | ### Descriptor Fields @@ -152,28 +167,12 @@ The Listing step of the upload wizard collects the fields shown on the browse ca | Supported ThingsBoard version | Compatibility constraints | | Professional Edition | Edition targeting | -### You Provide in the Readme Step - -Long-form widget documentation — what the widget displays, data keys, configuration, value ranges. See [Readme Content](#readme-content). - ### Set Per Version Version (semver) and changelog — bumped every time you upload a changed JSON. ## Allowed Values -### Widget Types - -Every widget must declare exactly one type in `descriptor.type`. Allowed values: `timeseries`, `latest`, `rpc`, `alarm`, `static`. - -| Type | Use for | -|------|---------| -| `timeseries` | Historical data visualization — line charts, bar charts, graphs | -| `latest` | Current value display — cards, gauges, indicators | -| `rpc` | Device control — buttons, switches, sliders that issue RPC commands | -| `alarm` | Alarm display — alarm lists, counters, indicators | -| `static` | Static content — text blocks, HTML, images, labels | - ### Widget Categories Pick one or more categories that describe what your widget is. Allowed values: `Cards & Info`, `Charts & Graphs`, `Controls`, `Gauges & Indicators`, `Input Forms`, `Maps & Location`, `SCADA`, `Tables & Lists`. @@ -188,19 +187,10 @@ Allowed values: `Air Quality Monitoring`, `Asset Tracking`, `Cold Chain`, `Drone The preview image is shown on browse cards and the widget's detail page. A good preview image is critical — it's the single biggest driver of whether users click on your widget. -### How It's Resolved - -The `image` field in your widget JSON supports several formats, resolved in this order: - -1. **`tb-image;` prefix** — stripped, then the remaining path is resolved -2. **`data:image/...`** — used as-is (inline data URI) -3. **`/api/images/...`** — looked up in the `resources` array by matching the link -4. **`http://` / `https://`** — external URL -5. **Raw base64** — treated as base64-encoded image data - -For widgets exported from ThingsBoard, the image typically references `/api/images/system/...` with the actual binary embedded in the `resources` array. This works out of the box. +There are two ways to set it: -You can also replace the image directly in the Listing step of the upload wizard — dropping a new file overrides whatever the JSON resolved to. +- **From the widget JSON** — exports from ThingsBoard already embed a preview image, so it works out of the box. +- **From the upload UI** — in the Listing step of the wizard, drop a new file to override whatever came from the JSON. ### Best Practices @@ -212,7 +202,7 @@ You can also replace the image directly in the Listing step of the upload wizard ## Readme Content -The Readme step of the upload wizard collects the long-form documentation shown on your widget's detail page. It is written in standard Markdown. +The Readme step of the upload wizard collects the long-form widget documentation — what the widget displays, data keys, configuration, value ranges — shown on your widget's detail page. It is written in standard Markdown. A good readme always includes: @@ -271,7 +261,7 @@ This means: ### Widget JSON -- [ ] `fqn` is a unique, lowercase, underscore-separated identifier (e.g. `my_vendor_gauge`) +- [ ] `fqn` is namespaced as `nickname.widget_fqn` (e.g. `john_doe.air_quality_card` or `acme.air_quality_card`), lowercase with underscores - [ ] `name` is a descriptive, human-readable widget name - [ ] `descriptor` is a JSON object - [ ] `descriptor.type` is one of `timeseries`, `latest`, `rpc`, `alarm`, `static` diff --git a/src/pages/partners/hardware/apply.astro b/src/pages/partners/hardware/apply.astro index a6d5f0ccd7..c24035214e 100644 --- a/src/pages/partners/hardware/apply.astro +++ b/src/pages/partners/hardware/apply.astro @@ -6,6 +6,7 @@ import BaseLayout from '../../../layouts/BaseLayout.astro'; title="Apply for Hardware Partner Program" description="Apply to become a ThingsBoard Hardware Partner. Fill out the form to get started with the Silver, Gold, or Platinum partnership." pageId="hardware-partner-apply" + noIndex={true} >