diff --git a/astro.sidebar.ts b/astro.sidebar.ts index 1d3764e4a..00adaccdb 100644 --- a/astro.sidebar.ts +++ b/astro.sidebar.ts @@ -597,6 +597,17 @@ const apisAndSdksItems = (prefix: string) => [ `${prefix}/reference/python-client`, ], }, + { + label: 'Extensions', + collapsed: true, + items: [ + `${prefix}/reference/extensions/overview`, + `${prefix}/reference/extensions/quickstart`, + `${prefix}/reference/extensions/development-workflow`, + `${prefix}/reference/extensions/cli-deployment`, + `${prefix}/reference/extensions/deployment-api`, + ], + }, { label: 'Mobile', collapsed: true, diff --git a/src/assets/schemas/extension-deployment-options-dark.svg b/src/assets/schemas/extension-deployment-options-dark.svg new file mode 100644 index 000000000..8d70e506d --- /dev/null +++ b/src/assets/schemas/extension-deployment-options-dark.svg @@ -0,0 +1 @@ +Public cloudThingsBoard Cloudthingsboard.cloudruns & scales itExtension containerport 8090deploy: CLI or REST APIPrivate cloudThingsBoard PEyour own infrastructureruns & scales itExtension containerport 8090deploy: CLI or REST APISelf-hostedDocker Composeany host you manageyou run itExtension containerport 8090you route /api/extension/* diff --git a/src/assets/schemas/extension-deployment-options.svg b/src/assets/schemas/extension-deployment-options.svg new file mode 100644 index 000000000..c0ee92f8b --- /dev/null +++ b/src/assets/schemas/extension-deployment-options.svg @@ -0,0 +1 @@ +Public cloudThingsBoard Cloudthingsboard.cloudruns & scales itExtension containerport 8090deploy: CLI or REST APIPrivate cloudThingsBoard PEyour own infrastructureruns & scales itExtension containerport 8090deploy: CLI or REST APISelf-hostedDocker Composeany host you manageyou run itExtension containerport 8090you route /api/extension/* diff --git a/src/assets/schemas/extension-dev-workflow-dark.svg b/src/assets/schemas/extension-dev-workflow-dark.svg new file mode 100644 index 000000000..3fa1be48d --- /dev/null +++ b/src/assets/schemas/extension-dev-workflow-dark.svg @@ -0,0 +1 @@ +Your machine — the local dev loopThingsBoard Cloud / PECreate the projecttb extension newWrite the codeendpoints · scheduled tasksBuild & testbuild · run · logs · stopPush the imagetb extension pushDeploytb extension deployExtension is Activestatus · logs · Swagger UIfix · rebuild · run againthe image is readyiterate: change the code,deploy a new image tag diff --git a/src/assets/schemas/extension-dev-workflow.svg b/src/assets/schemas/extension-dev-workflow.svg new file mode 100644 index 000000000..be922e2d7 --- /dev/null +++ b/src/assets/schemas/extension-dev-workflow.svg @@ -0,0 +1 @@ +Your machine — the local dev loopThingsBoard Cloud / PECreate the projecttb extension newWrite the codeendpoints · scheduled tasksBuild & testbuild · run · logs · stopPush the imagetb extension pushDeploytb extension deployExtension is Activestatus · logs · Swagger UIfix · rebuild · run againthe image is readyiterate: change the code,deploy a new image tag diff --git a/src/assets/schemas/extension-lifecycle-dark.svg b/src/assets/schemas/extension-lifecycle-dark.svg new file mode 100644 index 000000000..da8468e61 --- /dev/null +++ b/src/assets/schemas/extension-lifecycle-dark.svg @@ -0,0 +1 @@ +ProvisioningActiveUpdatingFailedStoppeddeployreadynew imagereadystopstarterrorerrorredeployDegradedreplicas not readyrecovered diff --git a/src/assets/schemas/extension-lifecycle.svg b/src/assets/schemas/extension-lifecycle.svg new file mode 100644 index 000000000..6eb538fa6 --- /dev/null +++ b/src/assets/schemas/extension-lifecycle.svg @@ -0,0 +1 @@ +ProvisioningActiveUpdatingFailedStoppeddeployreadynew imagereadystopstarterrorerrorredeployDegradedreplicas not readyrecovered diff --git a/src/assets/schemas/extension-mode-private-cloud-dark.svg b/src/assets/schemas/extension-mode-private-cloud-dark.svg new file mode 100644 index 000000000..47c10dfb2 --- /dev/null +++ b/src/assets/schemas/extension-mode-private-cloud-dark.svg @@ -0,0 +1 @@ +Your networkDeveloper / CIsame CLI, own profileThingsBoard PE4.3.1.3 or laterInternal systemsAPIs · databasesExtensioncontainer · :8090Registryinternal works toodeploytb extension pushruns & scalespull — imagestays insidereads internalsystems diff --git a/src/assets/schemas/extension-mode-private-cloud.svg b/src/assets/schemas/extension-mode-private-cloud.svg new file mode 100644 index 000000000..8bae13efc --- /dev/null +++ b/src/assets/schemas/extension-mode-private-cloud.svg @@ -0,0 +1 @@ +Your networkDeveloper / CIsame CLI, own profileThingsBoard PE4.3.1.3 or laterInternal systemsAPIs · databasesExtensioncontainer · :8090Registryinternal works toodeploytb extension pushruns & scalespull — imagestays insidereads internalsystems diff --git a/src/assets/schemas/extension-mode-public-cloud-dark.svg b/src/assets/schemas/extension-mode-public-cloud-dark.svg new file mode 100644 index 000000000..2724db24d --- /dev/null +++ b/src/assets/schemas/extension-mode-public-cloud-dark.svg @@ -0,0 +1 @@ +ThingsBoard Cloud — thingsboard.cloudDeveloper / CIThingsBoard CLI or REST APIContainer registryDocker Hub · GHCR · privateThingsBoardrouter + REST APIExtension containerresource size limits · replicastb extension pushimage goes to the registrydeployruns & scales,restarts on failurepulled overthe internet diff --git a/src/assets/schemas/extension-mode-public-cloud.svg b/src/assets/schemas/extension-mode-public-cloud.svg new file mode 100644 index 000000000..061495490 --- /dev/null +++ b/src/assets/schemas/extension-mode-public-cloud.svg @@ -0,0 +1 @@ +ThingsBoard Cloud — thingsboard.cloudDeveloper / CIThingsBoard CLI or REST APIContainer registryDocker Hub · GHCR · privateThingsBoardrouter + REST APIExtension containerresource size limits · replicastb extension pushimage goes to the registrydeployruns & scales,restarts on failurepulled overthe internet diff --git a/src/assets/schemas/extension-mode-self-hosted-dark.svg b/src/assets/schemas/extension-mode-self-hosted-dark.svg new file mode 100644 index 000000000..f286c9956 --- /dev/null +++ b/src/assets/schemas/extension-mode-self-hosted-dark.svg @@ -0,0 +1 @@ +Your host — Docker ComposeClientrule chain · widget · curlThingsBoardPE or CloudReverse proxyroutes /api/extension/*Extension containerport 8090HTTPforwardsauth + datayou start, update, and scale this yourself diff --git a/src/assets/schemas/extension-mode-self-hosted.svg b/src/assets/schemas/extension-mode-self-hosted.svg new file mode 100644 index 000000000..1c2b520a4 --- /dev/null +++ b/src/assets/schemas/extension-mode-self-hosted.svg @@ -0,0 +1 @@ +Your host — Docker ComposeClientrule chain · widget · curlThingsBoardPE or CloudReverse proxyroutes /api/extension/*Extension containerport 8090HTTPforwardsauth + datayou start, update, and scale this yourself diff --git a/src/assets/schemas/extension-request-flow-dark.svg b/src/assets/schemas/extension-request-flow-dark.svg new file mode 100644 index 000000000..962d319ac --- /dev/null +++ b/src/assets/schemas/extension-request-flow-dark.svg @@ -0,0 +1 @@ +Clientrule chain · widget · curlThingsBoard routerchecks auth, forwardsThingsBoard REST APIauth & platform dataExtension containeryour Java / Python app · :8090GET/POST{BASE_URL}/api/ext/{slug}/…is the caller allowed?yes / nostrips the prefix, forwards/api/extension/…reads / writes datawith the caller's credential diff --git a/src/assets/schemas/extension-request-flow.svg b/src/assets/schemas/extension-request-flow.svg new file mode 100644 index 000000000..401a883d1 --- /dev/null +++ b/src/assets/schemas/extension-request-flow.svg @@ -0,0 +1 @@ +Clientrule chain · widget · curlThingsBoard routerchecks auth, forwardsThingsBoard REST APIauth & platform dataExtension containeryour Java / Python app · :8090GET/POST{BASE_URL}/api/ext/{slug}/…is the caller allowed?yes / nostrips the prefix, forwards/api/extension/…reads / writes datawith the caller's credential diff --git a/src/content/_includes/docs/reference/apis-and-sdks.mdx b/src/content/_includes/docs/reference/apis-and-sdks.mdx index c528a526a..2002e7f67 100644 --- a/src/content/_includes/docs/reference/apis-and-sdks.mdx +++ b/src/content/_includes/docs/reference/apis-and-sdks.mdx @@ -48,6 +48,14 @@ import DocLink from '@components/DocLink.astro'; - Java REST Client - Python REST Client + + + - Overview + - Quickstart + - Development workflow + - Deploy with the CLI + - Deployment API + ## Mobile apps diff --git a/src/content/_includes/docs/reference/extensions/cli-deployment.mdx b/src/content/_includes/docs/reference/extensions/cli-deployment.mdx new file mode 100644 index 000000000..6d3fbdde2 --- /dev/null +++ b/src/content/_includes/docs/reference/extensions/cli-deployment.mdx @@ -0,0 +1,189 @@ +import { Aside } from '@astrojs/starlight/components'; +import DocLink from '@components/DocLink.astro'; +import Banner from '~/components/Banner.astro'; +import { Products } from '~/models/site.models'; + +Extensions work only with ThingsBoard Professional and ThingsBoard Cloud version **4.3.1.3** or later. + +{props.product !== Products.CE && ( + Available from ThingsBoard PE/Cloud 4.3.1.3 and later. +)} + +The ThingsBoard CLI is the easiest way to run an extension inside ThingsBoard Cloud or PE. This page is the reference for the `tb extension deploy` command and the `tb extension deployments` control commands. For a full walkthrough, see the Quickstart. + + + +## Before you deploy + +`deploy` only provisions an image the registry already holds. Push it first: + +```bash +tb extension push --image-name myorg/acme-billing +``` + +When you push a version tag (`--image-tag 1.1.0`), the CLI also pushes the same build as `:latest`, replacing the registry's current `latest`. Pass `--no-latest` to skip that — for example, for release candidates. + +Authentication uses your active profile. Override it per command with `--profile `, or with `--url` and `--api-key`. The profile also decides **where** the extension runs — ThingsBoard Cloud or your own PE installation (see Where extensions run). + +The registry must be reachable from the ThingsBoard instance that pulls the image: the public internet for ThingsBoard Cloud, or your own network for a private PE installation. For a private registry, ThingsBoard needs pull credentials — pass them with the registry flags of `deploy` (see [Private registry](#private-registry)). + +## Commands at a glance + +| Command | What it does | +|---------|--------------| +| `tb extension deploy` | Create or update the deployment from a pushed image | +| `tb extension undeploy [ref]` | Tear down the deployment and its entity | +| `tb extension sizes` | List the available resource sizes | +| `tb extension deployments list` | List this tenant's deployed extensions | +| `tb extension deployments status ` | Show live state, replicas, and URL | +| `tb extension deployments logs ` | Tail the container logs | +| `tb extension deployments restart ` | Roll out a restart | +| `tb extension deployments stop ` | Scale to zero (keep the entity) | +| `tb extension deployments start ` | Resume to the desired replicas | + +`` is the extension **slug** or **UUID**. When a flag is omitted, the CLI reads its value (name, image, slug, size) from the project's `thingsboard.json`. + +## `tb extension deploy` + +Create or update the deployment. The CLI shows a confirmation panel, sends the request, and by default waits until the extension is `ACTIVE`. + +```bash +tb extension deploy --image-name myorg/acme-billing --image-tag 1.0 --replicas 2 --size medium +``` + +```text +╭─ tb extension deploy ─────────────────────────────╮ +│ Deploy 'acme-billing' │ +│ image: myorg/acme-billing:1.0 │ +│ replicas: 2 port: 8090 │ +│ size: medium │ +│ target: https://thingsboard.cloud │ +╰────────────────────────────────────────────────────╯ +Proceed? [y/N]: y +OK Deployed 'acme-billing' → acme-billing-9f3a (2/2 replicas ready) + URL: https://thingsboard.cloud/api/ext/acme-billing-9f3a +``` + +### Flags + +| Flag | Default | Description | +|------|---------|-------------| +| `--image-name` | saved image | Registry image repository, **without a tag** | +| `--image-tag` | `latest` | Image tag | +| `--name` | saved extension name | Human-readable extension name (from `thingsboard.json`, set by `tb extension new` or `rename`) | +| `--slug-base` | from name | Base for the generated slug | +| `--replicas` | `1` | Number of replicas (1–20) | +| `--port` | `8090` | Port the container listens on | +| `--env KEY=VALUE` | — | Environment variable; repeat for more than one. Names starting with `TB_EXT_`, and the key `THINGSBOARD_URL`, are reserved | +| `--size` | `small` | Resource size — run `tb extension sizes` to list the options | +| `--registry` | from image | Private registry host. When omitted, ThingsBoard derives it from the image reference | +| `--registry-user` | — | Username (or robot account) for a private registry — see [Private registry](#private-registry) | +| `--registry-password-secret` | — | Name of a ThingsBoard Secret that holds the registry password — see [Private registry](#private-registry) | +| `--clear-registry-auth` | — | On an update, remove the stored registry credentials instead of keeping them | +| `--clear-env` | — | On an update, remove all environment variables instead of keeping them | +| `--wait` / `--no-wait` | `--wait` | Wait until `ACTIVE`/`FAILED`, or return at once | +| `--timeout` | `180` | Wait timeout, in seconds | +| `-y` / `--yes` | — | Skip the confirmation prompt | + + + +The `size:` line is always shown — it takes its value from `--size`, the saved deployment, or the default. Optional flags you set add extra rows. For example, `--env LOG_LEVEL=debug --size medium` shows: + +```text +│ size: medium │ +│ env: LOG_LEVEL=debug │ +``` + +On an update, when you do not pass `--env` or the registry flags, the CLI keeps the current values from the server. The panel then shows `env: (kept from current deployment)` or `registry-auth: (kept from current deployment)`. + +With `--no-wait`, the command returns immediately and the state updates in the background: + +```text +OK Provisioned 'acme-billing' → acme-billing-9f3a (state will converge; use deployments status) +``` + +### Private registry + +ThingsBoard pulls the image on the server side, so it needs its own pull credentials — your local `docker login` does not help here. The CLI never sends the password itself. Instead, it sends a reference to a ThingsBoard Secret: + +1. Create a Secret with the registry password under your tenant — use the **Secrets** page in the ThingsBoard UI, or `POST /api/secret`. +2. Deploy with the username and the secret name: + +```bash +tb extension deploy --registry-user robot-acme --registry-password-secret acme-registry +``` + +`--registry-user` and `--registry-password-secret` are required together. `--registry` is optional: when omitted, ThingsBoard takes the registry host from the image reference. Under the hood, the CLI fills the `registryAuth` field of the deployment API and sends the password as a `${secret:acme-registry;type:TEXT}` reference. You can also pass a full `${secret:…}` reference as the flag value — the CLI keeps it as is. + +You set the registry flags once. On later updates, the CLI keeps the credentials from the current deployment — you do not repeat the flags. Pass new values to replace them, or `--clear-registry-auth` to remove them. + +## Resource sizes + +Run `tb extension sizes` to list the resource tiers and the CPU and memory each one maps to. Pass the chosen tier to `deploy` with `--size` (for example `xsmall`, `small`, `medium`, `large`). Start small and move up only if the extension needs more. + +## `tb extension undeploy [ref]` + +Delete the deployment and its entity. With no `ref`, the CLI uses the saved deployment from `thingsboard.json`. + +```bash +tb extension undeploy acme-billing-9f3a +``` + +## Inspecting and controlling a deployment + +```bash +tb extension deployments list # all extensions in the tenant +tb extension deployments status acme-billing-9f3a # live state, replicas, URL +tb extension deployments logs acme-billing-9f3a -n 200 # tail the last 200 log lines +tb extension deployments restart acme-billing-9f3a # roll out a restart +tb extension deployments stop acme-billing-9f3a # scale to zero, keep the entity +tb extension deployments start acme-billing-9f3a # resume to the desired replicas +``` + +`list` prints one row per extension: name, slug, state, resource size, image, and the Swagger path. + +`status` shows the live state, ready and desired replicas, the public URL, and the Swagger path. It adds a **Last error** row only when the extension reports one. + +## Deployment state in the project + +After a successful deploy, the CLI saves the deployment details in the project's `thingsboard.json`: + +```json +{ + "extension": { + "name": "acme-billing", + "language": "java", + "deployment": { + "id": "…", "slug": "acme-billing-9f3a", + "lastImage": "myorg/acme-billing", "lastTag": "1.0", "size": "medium" + } + } +} +``` + +Because of this, a later `tb extension deploy` with no flags **updates** the same extension using the saved image, slug, and size — you do not have to repeat them. The environment variables and the private-registry credentials are also kept on an update: the CLI copies them from the current deployment on the server. Pass `--env` or the registry flags to replace them, or `--clear-env` / `--clear-registry-auth` to remove them. Note that `--env` replaces the whole set — repeat every variable you want to keep. Only the image tag is **not** reused: `lastTag` is informational, and without `--image-tag` the CLI deploys `latest`. So pass `--image-tag` on every update. + +## Deploy from CI + +Environment variables let you deploy without an interactive profile — useful in a pipeline: + +```bash +export TB_URL=https://thingsboard.cloud +export TB_API_KEY=tb_XXXX +tb extension deploy --image-name myorg/acme-billing --image-tag "$CI_COMMIT_TAG" -y --no-wait +``` + +`TB_URL` sets the instance, `TB_API_KEY` supplies the key, `-y` skips the prompt, and `--no-wait` returns at once. + + + +## Next steps + +- Deployment REST API — the endpoints the CLI calls, for use without the CLI. +- Quickstart — the full build-and-deploy walkthrough. diff --git a/src/content/_includes/docs/reference/extensions/deployment-api.mdx b/src/content/_includes/docs/reference/extensions/deployment-api.mdx new file mode 100644 index 000000000..4b414b90b --- /dev/null +++ b/src/content/_includes/docs/reference/extensions/deployment-api.mdx @@ -0,0 +1,137 @@ +import { Aside } from '@astrojs/starlight/components'; +import DocLink from '@components/DocLink.astro'; +import HostCode from '~/components/HostCode.astro'; +import Banner from '~/components/Banner.astro'; +import { Products } from '~/models/site.models'; + +Extensions work only with ThingsBoard Professional and ThingsBoard Cloud version **4.3.1.3** or later. + +{props.product !== Products.CE && ( + Available from ThingsBoard PE/Cloud 4.3.1.3 and later. +)} + +The deployment API lets you provision and control extensions with plain REST calls. The ThingsBoard CLI uses these same endpoints under the hood. Use the API directly when you want to deploy from CI, a script, or your own tool without the CLI. + +A typical script does three steps: + +1. `POST /api/extension` — create the extension. Save the `id` and the `slug` from the response. +2. Poll `GET /api/extension/{extensionId}/status` until the `state` is `ACTIVE`. +3. Call the extension at `{BASE_URL}/api/ext/{slug}/...`. + +To update the extension later, send `POST /api/extension` again with the same `id` — this is what a repeated `tb extension deploy` does. + + + +## Endpoints + +| Method | Path | Purpose | +|--------|------|---------| +| `POST` | `/api/extension` | Create or update an extension | +| `GET` | `/api/extension/{extensionId}` | Get one extension, including its `env` | +| `GET` | `/api/tenant/extensions` | List the tenant's extensions (paged) | +| `GET` | `/api/extension/resourceSizes` | List the available resource sizes | +| `GET` | `/api/extension/{extensionId}/status` | Live state, replicas, and URL | +| `GET` | `/api/extension/{extensionId}/logs?lines=200` | Tail the container logs | +| `POST` | `/api/extension/{extensionId}/restart` | Roll out a restart | +| `POST` | `/api/extension/{extensionId}/stop` | Scale to zero, keep the entity | +| `POST` | `/api/extension/{extensionId}/start` | Resume to the desired replicas | +| `DELETE` | `/api/extension/{extensionId}` | Tear down the extension | + +`{extensionId}` is the extension's UUID. In the create response and in the list results, the `id` field is an object (`{"entityType": "EXTENSION", "id": ""}`) — use the nested `id.id` value. + +Write actions (`POST`, `DELETE`, `restart`, `stop`, `start`) return **202 Accepted**; the create or update response also contains the saved extension. The work runs in the background — check `status` until the state stops changing. + + + +## Create or update an extension + +`POST /api/extension` both creates and updates. Send a body **without** an `id` to create a new extension; include the existing `id` to update one. + +An update **replaces** the extension — the fields are not merged. The new `containerSpec` fully overwrites the old one. To change only the image tag from a script, do a read-modify-write: `GET /api/extension/{extensionId}` to read the current entity (this response includes `env`), change `containerSpec.tag`, and send the whole entity back. If your update body has no `env`, every environment variable is removed from the running extension. Do not build the body from the list endpoint — the list omits `env` for safety. The CLI does this read-modify-write for you: on an update, `tb extension deploy` keeps the server's `env` and `registryAuth` unless you replace or clear them. + + + +The response is the saved extension. Its `state` starts at `PROVISIONING`, and its read-only `slug` is set — for example `acme-billing-9f3a`. + +Two top-level fields describe the extension itself: `name` is the human-readable name, and `slugBase` is the base for the generated slug (when omitted, it is derived from the name). + +### Container spec fields + +| Field | Default | Description | +|-------|---------|-------------| +| `image` | — | Registry image repository, **without a tag** | +| `tag` | — | Image tag | +| `replicas` | `1` | Number of replicas (1–20) | +| `port` | `8090` | Port the container listens on | +| `env` | — | Map of environment variables | +| `resources.size` | `SMALL` | Resource size — the same tiers as the CLI's `--size`, written in upper case; see `GET /api/extension/resourceSizes` | +| `registryAuth` | — | Credentials for a private registry — see below | + +For a **private registry**, set `registryAuth` with three fields: `registry` (optional — derived from the image reference when omitted), `username`, and `password`. The password must be a reference to a ThingsBoard Secret, for example `"${secret:acme-registry;type:TEXT}"` — a plain-text password is rejected. Create the secret under your tenant first (the **Secrets** page in the ThingsBoard UI, or `POST /api/secret`). ThingsBoard resolves the reference at deploy time and creates the runtime pull secret itself; the value is never returned by the API. The CLI builds this field for you from its registry flags — see Private registry. + + + +## Read status + +Poll the live status after a create, update, or control action: + + + +The response reports the `state` (`PROVISIONING`, `ACTIVE`, `UPDATING`, `DEGRADED`, `STOPPED`, or `FAILED`), the ready and desired replicas, the public `url`, and a `lastError` with the last failure reason (cleared while the state is `ACTIVE`). + +The state comes from a background health check, so a change can take a short time to appear — keep polling until it settles. + +## List extensions + +`GET /api/tenant/extensions` returns a paged list. It omits `env` for safety; fetch a single extension by id when you need its full spec. + + + +## Control and remove + + + +## Routing and access + +Once an extension is `ACTIVE`, clients reach it at `{BASE_URL}/api/ext/{slug}/...`. ThingsBoard checks every call against the caller's `X-Authorization` header before forwarding it — see the overview for the full request flow. + +## Next steps + +- Deploy with the CLI — the same actions from the command line. +- REST API — authentication and the rest of the ThingsBoard API. diff --git a/src/content/_includes/docs/reference/extensions/development-workflow.mdx b/src/content/_includes/docs/reference/extensions/development-workflow.mdx new file mode 100644 index 000000000..700ba817d --- /dev/null +++ b/src/content/_includes/docs/reference/extensions/development-workflow.mdx @@ -0,0 +1,289 @@ +import { Aside, Steps } from '@astrojs/starlight/components'; +import Tabs from '@components/Tabs.astro'; +import TabItem from '@components/TabItem.astro'; +import DocLink from '@components/DocLink.astro'; +import ImageGallery from '@components/ImageGallery.astro'; +import Banner from '~/components/Banner.astro'; +import { Products } from '~/models/site.models'; + +Extensions work only with ThingsBoard Professional and ThingsBoard Cloud version **4.3.1.3** or later. + +{props.product !== Products.CE && ( + Available from ThingsBoard PE/Cloud 4.3.1.3 and later. +)} + +The Quickstart deploys the starter project as it is. This page shows the full path for a real feature: how to plan it, write the code, test it on your machine, deploy it, and ship updates after the first release. + +One example runs through the whole page: a **weather extension**. It reads a device's `latitude`/`longitude` attributes, calls a public weather API ([Open-Meteo](https://open-meteo.com/)), and writes the outside temperature back as telemetry. It has REST endpoints and a scheduled task — the two most common building blocks. + + + +## Plan the feature first + +Write a short plan before you write code. Five lines are enough. For the weather extension: + +- **Endpoints:** `GET /api/extension/weather/{deviceId}` returns the current outside temperature; `POST /api/extension/weather/refresh/{deviceId}` also writes it as telemetry. +- **Scheduled task:** every 60 seconds, refresh every device that has coordinates. +- **External service:** Open-Meteo (no API key needed). +- **Data:** read the `latitude`/`longitude` server attributes; write `outsideTemp` telemetry. +- **Callers:** a dashboard button (JWT) and the scheduled task (configured API key). + +Decide the endpoint paths now. Dashboards and rule chains will call these URLs, so renaming them later means updating every caller. Also keep external API limits in mind: the scheduled task calls the weather API once per device per cycle, so a short interval with many devices can exhaust a free quota. + +## Create the project + +Run this inside a ThingsBoard project — `tb init` in an empty folder creates one (`mkdir weather && cd weather && tb init`). One command then unpacks a ready-to-run service with Docker files, authentication, and worked examples into an `extension/` folder. All file paths on this page are relative to that folder: + + + + ```bash + tb extension new --lang java --name weather + ``` + + A Spring Boot project. Your code goes into `extension/src/main/java/` as `@RestController` classes and `@Scheduled` components. + + + ```bash + tb extension new --lang python --name weather + ``` + + A FastAPI project. Your code goes into `app/extension/` as routers; background loops start from the app lifespan. + + + +Before you start your own feature, read the `examples/` folder. It contains small, complete patterns, including a no-auth transformer, an API-key billing hook, a JWT report endpoint, and a scheduled health check. Copy the pattern that matches your feature, then remove the examples — in the Java starter, follow the **Removing Examples** section of the starter README (deleting the folder alone breaks the Maven build); in the Python starter, deleting the `examples/` folder is enough. + +## Write the code + +Every endpoint lives under `/api/extension/...`. The starter already handles authentication: your handler receives a ThingsBoard client that acts **as the caller**, so tenant isolation and permissions work without extra code. + +The core of the weather feature is one endpoint that reads attributes, calls the external API, and writes telemetry: + + + + ```java + // extension/src/main/java/org/thingsboard/extension/weather/WeatherController.java + package org.thingsboard.extension.weather; + + import com.fasterxml.jackson.databind.JsonNode; + import org.springframework.web.bind.annotation.PathVariable; + import org.springframework.web.bind.annotation.PostMapping; + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RestController; + import org.springframework.web.client.RestClient; + import org.thingsboard.client.ThingsboardClient; + import org.thingsboard.client.model.AttributeData; + + import java.util.HashMap; + import java.util.Map; + + @RestController + @RequestMapping("/api/extension/weather") + public class WeatherController { + + @PostMapping("/refresh/{deviceId}") + public Map refresh(@PathVariable String deviceId, + ThingsboardClient tb) throws Exception { + Map attrs = new HashMap<>(); + for (AttributeData a : tb.getAttributesByScope( + "DEVICE", deviceId, "SERVER_SCOPE", "latitude,longitude", null)) { + attrs.put(a.getKey(), a.getValue()); + } + double outsideTemp = fetchOutsideTemp(attrs.get("latitude"), attrs.get("longitude")); + tb.saveEntityTelemetry("DEVICE", deviceId, "ANY", + "{\"outsideTemp\": " + outsideTemp + "}"); + return Map.of("deviceId", deviceId, "outsideTemp", outsideTemp, "written", true); + } + + private double fetchOutsideTemp(Object lat, Object lon) { + JsonNode json = RestClient.create().get() + .uri("https://api.open-meteo.com/v1/forecast" + + "?latitude={lat}&longitude={lon}¤t=temperature_2m", lat, lon) + .retrieve().body(JsonNode.class); + return json.get("current").get("temperature_2m").asDouble(); + } + } + ``` + + The `ThingsboardClient tb` argument is resolved per request from the caller's `X-Authorization` header. + + + ```python + # app/extension/weather.py + import json + from typing import Annotated + + import httpx + from fastapi import APIRouter, Depends + from tb_pe_client.client import ThingsboardClient + + from app.security import tb_client_dep + + router = APIRouter() + + + def fetch_outside_temp(lat: float, lon: float) -> float: + r = httpx.get( + "https://api.open-meteo.com/v1/forecast", + params={"latitude": lat, "longitude": lon, "current": "temperature_2m"}, + timeout=10, + ) + r.raise_for_status() + return r.json()["current"]["temperature_2m"] + + + @router.post("/weather/refresh/{device_id}") + def refresh( + device_id: str, + client: Annotated[ThingsboardClient, Depends(tb_client_dep)], + ) -> dict: + attrs = { + a.key: a.value + for a in client.telemetry_controller.get_attributes_by_scope( + "DEVICE", device_id, "SERVER_SCOPE", keys="latitude,longitude" + ) + } + outside_temp = fetch_outside_temp(attrs["latitude"], attrs["longitude"]) + client.telemetry_controller.save_entity_telemetry( + "DEVICE", device_id, "ANY", json.dumps({"outsideTemp": outside_temp}) + ) + return {"deviceId": device_id, "outsideTemp": outside_temp, "written": True} + ``` + + Register the router in `app/extension/__init__.py`: + + ```python + from app.extension import weather + router.include_router(weather.router) + ``` + + Also add `httpx` to the `dependencies` list in `pyproject.toml`. The starter ships it only in the `dev` group for tests, so the image built by `tb extension build` does not include it — without this change the container fails at startup with `ModuleNotFoundError`. + + + +**The scheduled task** repeats the same work on a timer, without a caller. It uses a service credential from an environment variable (`TB_AUTH_API_KEY`, or a username and password) instead of a request header. Follow the health-check example in the starter: `DeviceHealthCheckTask.java` (Java) or `examples/tasks/device_health_check.py` (Python). Read the interval from an environment variable — you can then tune it at deploy time without a rebuild. The examples hardcode 60 seconds, so change them: in Java, use `@Scheduled(fixedRateString = "${WEATHER_ENRICH_INTERVAL_SECONDS:60}", timeUnit = TimeUnit.SECONDS)`; in Python, read `int(os.environ.get("WEATHER_ENRICH_INTERVAL_SECONDS", "60"))` and pass it to the loop. + +## Test on your machine + +This is the fast inner loop from the diagram. One thing to set up first: the local container checks credentials and reads data from a **real ThingsBoard instance**. Copy `.env.example` to `.env` in the `extension/` folder and set `THINGSBOARD_URL` (for example `https://thingsboard.cloud`). The default, `http://host.docker.internal:8080`, works only when ThingsBoard runs on your own machine. + +Then repeat the loop until the feature works: + + + +1. **Build and run the container locally.** + + ```bash + tb extension build + tb extension run + ``` + +2. **Call your endpoint.** + + ```bash + curl -X POST http://localhost:8090/api/extension/weather/refresh/ \ + -H 'X-Authorization: Bearer ' + ``` + + To get a JWT, call `POST {BASE_URL}/api/auth/login` with your username and password. An API key works too: `-H 'X-Authorization: ApiKey '`. + +3. **Read the logs, fix the code, and go back to step 1.** + + ```bash + tb extension logs + ``` + + + +Stop the local container with `tb extension stop` when you are done. The starter also ships unit tests (`mvn test` / `pytest`) — keep them green while you work; they catch broken authentication and routing early. + +## Deploy for the first time + +When the feature works locally, release it. Give the image a real version tag from day one — updates will be much easier to track than with `latest`: + + + +1. **Push the image to your registry.** + + ```bash + tb extension build --image-tag 1.0.0 + tb extension push --image-name myorg/weather --image-tag 1.0.0 + ``` + +2. **Deploy it.** + + The scheduled task needs a service credential, so pass an API key as an environment variable: + + ```bash + tb extension deploy --image-name myorg/weather --image-tag 1.0.0 \ + --env TB_AUTH_API_KEY= \ + --env WEATHER_ENRICH_INTERVAL_SECONDS=60 + ``` + + The CLI waits until the extension is `ACTIVE` and prints its URL and slug (for example `weather-9f3a`). + +3. **Verify.** + + ```bash + tb extension deployments status weather-9f3a + tb extension deployments logs weather-9f3a + ``` + + Open the API docs at `{BASE_URL}/api/ext/weather-9f3a/swagger-ui/index.html` (Java) or `{BASE_URL}/api/ext/weather-9f3a/docs` (Python) and try the endpoints. + + Then connect the real callers. In a rule chain, add a **REST API call** node with the URL `{BASE_URL}/api/ext/weather-9f3a/api/extension/weather/refresh/{deviceId}` and the header `X-Authorization: ApiKey `. For a dashboard button, call the same URL with the current user's session token: `X-Authorization: Bearer `. Every call must carry this header — the router rejects requests without a valid credential. + + + +## Ship updates + +An extension is not finished after the first deploy. The outer loop from the diagram is the normal life of the project: change the code, release a new image tag, deploy it to the same extension. + + + +1. **Change the code and pass the local loop again** (build → run → test). + +2. **Build and push a new tag.** + + ```bash + tb extension build --image-tag 1.1.0 + tb extension push --image-name myorg/weather --image-tag 1.1.0 + ``` + + When `--image-tag` is set, `push` also updates `:latest` in the registry to the same build. Pass `--no-latest` to skip that — for example, for a release candidate that should not become the new `latest`. + +3. **Deploy the new tag to the same extension.** + + ```bash + tb extension deploy --image-name myorg/weather --image-tag 1.1.0 + ``` + + The environment variables and the registry credentials are kept from the current deployment, so you do not repeat the `--env` flags — only the new tag is needed. The running extension is not stopped. Its state shows **Updating** while the new version rolls out; replicas that are already ready keep serving traffic. The slug and the URL never change, so dashboards and rule chains keep working. + + + +4. **Watch the rollout.** + + ```bash + tb extension deployments status weather-9f3a + ``` + + + +If the new version fails to start, the extension moves to **Failed** and the last error is shown in the status. The entity and the slug survive — fix the code and deploy again. To roll back fast, deploy the previous tag: + +```bash +tb extension deploy --image-name myorg/weather --image-tag 1.0.0 +``` + + + +## Next steps + +- Deploy with the CLI — every flag of `tb extension deploy`, resource sizes, and the `deployments` commands. +- Deployment REST API — run the same release cycle from CI, without the CLI. +- Extensions overview — architecture, routing, authentication, and the lifecycle states. diff --git a/src/content/_includes/docs/reference/extensions/overview.mdx b/src/content/_includes/docs/reference/extensions/overview.mdx new file mode 100644 index 000000000..c70459716 --- /dev/null +++ b/src/content/_includes/docs/reference/extensions/overview.mdx @@ -0,0 +1,138 @@ +import { Aside } from '@astrojs/starlight/components'; +import DocLink from '@components/DocLink.astro'; +import ImageGallery from '@components/ImageGallery.astro'; +import Banner from '~/components/Banner.astro'; +import { Products } from '~/models/site.models'; + +Extensions work only with ThingsBoard Professional and ThingsBoard Cloud version **4.3.1.3** or later. + +{props.product !== Products.CE && ( + Available from ThingsBoard PE/Cloud 4.3.1.3 and later. +)} + +A **ThingsBoard extension** is a small service that adds your own REST API to a tenant. You write it in **Java** or **Python**, package it as a Docker image, and ThingsBoard runs it next to the platform. Clients reach it through ThingsBoard at a fixed path, and every call is checked with the same login your users already have. + +Use an extension when the built-in features are not enough — for example: + +- **Rule chain callbacks** — react to device telemetry, entity changes, or alarms from a rule chain. +- **Dashboard widget backends** — run custom logic when a user clicks a button on a dashboard. +- **Scheduled jobs** — run a task on a timer that reads or writes ThingsBoard data. +- **Custom integrations** — talk to a third-party system and expose the result as an API. + + + +## How it works + +An extension is a normal web service. It listens on port **8090** (the default) and serves every endpoint under `/api/extension/...`. In the managed mode you do not run or scale it yourself: you give ThingsBoard the image, and ThingsBoard creates the container, keeps it running, and routes traffic to it. (You can also run the container yourself — see [Where extensions run](#where-extensions-run).) + + + +Three parts work together: + +1. **The extension entity.** Each extension is a tenant entity in ThingsBoard, like a device or an asset. It holds the container spec — the image, tag, number of replicas, port, environment variables, and resource size. When you save it, ThingsBoard pulls the image and starts the container. ThingsBoard watches the container and restarts it if it fails. +2. **The router.** Clients never call the container directly. They call ThingsBoard at `{BASE_URL}/api/ext/{slug}/...`. The router removes the `/api/ext/{slug}` prefix and forwards the rest to your service as `/api/extension/...`. +3. **The auth gateway.** Before forwarding, the router asks ThingsBoard whether the caller is allowed, using the caller's `X-Authorization` header. The check passes only when the caller belongs to the tenant that **owns** the extension; anything else is rejected. In the managed mode, your service does not have to check tokens itself. + +### The slug + +Every extension gets a **slug** — a short, unique name used in its URL. You choose a **slug base** (like `acme-billing`). On ThingsBoard Cloud, ThingsBoard adds a random suffix (for example `acme-billing-9f3a`) so the slug stays unique across all tenants. On a private installation there is no suffix — the slug is the base itself. The slug never changes after the extension is created. + +### Calling ThingsBoard back + +Your extension often needs to read or write ThingsBoard data. It does this with the **ThingsBoard REST client** for your language — Python (`tb-pe-client`) or Java (`thingsboard-pe-client`); each page shows the exact package for your edition. The client uses the **caller's own credential**, so every call respects that user's tenant and permissions. You do not store an admin password inside the extension. + +## Authentication + +An incoming request carries an `X-Authorization` header. There are three ways an extension is used: + +| Mode | Header | Typical use | +|------|--------|-------------| +| **API key** | `X-Authorization: ApiKey ` | Rule chain callbacks | +| **JWT** | `X-Authorization: Bearer ` | Dashboard widget buttons (the user's session token) | +| **Configured** | *(none — set at startup)* | Scheduled background jobs | + +For API key and JWT, the extension acts as the caller. For scheduled jobs there is no caller, so you set a service credential (`TB_AUTH_API_KEY`, or a username and password) as an environment variable. + + + + + +## Lifecycle + +After you deploy an extension, it moves through a few states: + + + +- **Provisioning** — ThingsBoard is pulling the image and starting the container. +- **Active** — the container is running and ready to serve requests. +- **Updating** — a new image or spec is rolling out; the replicas that are already ready keep serving. +- **Degraded** — the extension runs, but some replicas are not ready. +- **Stopped** — you stopped it, and ThingsBoard scaled it to zero replicas; the entity stays, but nothing runs. +- **Failed** — the container could not start, or it crashed while running (for example, out of memory or a crash loop); the reason is shown as the last error. + +You can **stop**, **start**, and **restart** an extension at any time, and read its logs — from the CLI or the deployment API. A **Failed** extension keeps its entity and its slug — fix the image or the spec and deploy again. Deploying a new image to a running extension does not stop it: the state shows **Updating** while the new version rolls out. Watch the rollout with the `status` command or endpoint. + +## Where extensions run + +The same extension image can run in three environments. In the first two, ThingsBoard is **managed** — it runs and scales the container for you. In the third, you run the container yourself. + + + +| | Public cloud | Private cloud | Self-hosted | +|---|---|---|---| +| **Where** | [ThingsBoard Cloud](/installations/?product=thingsboard-cloud) | Your own ThingsBoard PE | Any host you manage | +| **Who runs the container** | ThingsBoard | ThingsBoard | You | +| **How to deploy** | CLI or REST API | CLI or REST API | Docker Compose (`deploy/` folder) | +| **Routing and auth** | Built in | Built in | You set it up | +| **Scaling and restarts** | Automatic | Automatic | Manual | + +### Public cloud — ThingsBoard Cloud + +ThingsBoard Cloud runs the container on shared cloud infrastructure. This is the fastest way to get an extension online: you push the image and deploy — there is nothing to install or operate. + + + +Points to know: + +- The slug always ends with a random suffix (for example `acme-billing-9f3a`), so it is unique across all tenants. +- The image registry must be reachable from the internet. For a private registry, add pull credentials — the registry flags of the CLI, or `registryAuth` in the deployment API. +- CPU and memory are limited by the **resource size** you pick (`tb extension sizes` lists the options). + +### Private cloud — your own ThingsBoard PE + +A ThingsBoard PE installation (version **4.3.1.3** or later) offers the same managed experience on your own infrastructure. The workflow does not change: the same CLI commands and the same REST API — only your profile points at your own instance instead of `thingsboard.cloud`. One small difference: the slug gets no random suffix here — it is exactly your slug base. + + + +Use this mode when your data must stay inside your network, or when the extension needs to reach internal systems that are not visible from the public cloud. The image registry only has to be reachable from your PE installation, so an internal registry works. + +One setup step is needed first: on a PE installation, managed extensions are **off by default**. A system administrator turns them on with the `TB_EXTENSIONS_MODE` environment variable (`extensions.mode` in `thingsboard.yml`): `docker-compose` runs each extension as a container on a single Docker host; `private-cloud` runs them in the same Kubernetes cluster as ThingsBoard. While the mode is `disabled`, the `/api/extension` endpoints do not exist, and the CLI reports that the deployment API is not available. ThingsBoard Cloud has the feature enabled already. + +### Self-hosted — Docker Compose + +You can also run the extension container yourself, next to your ThingsBoard instance (PE or Cloud). The starter project that `tb extension new` creates includes a `deploy/` folder with a ready Docker Compose setup. + + + +In this mode there is no extension entity in ThingsBoard, so nothing is managed for you: + +- You start, update, and restart the container yourself. +- You route `/api/extension/*` to the container — for example with a reverse proxy. +- The service still checks callers against your ThingsBoard instance, so authentication keeps working the same way. + +Choose this mode for local development, or when you want full control over how and where the container runs. For the details, follow the `deploy/` guide inside the starter project. + +The other pages in this section — the Quickstart, the CLI reference, and the deployment API — describe the **managed** path. + +## Next steps + +- Quickstart — build and deploy your first extension in Java or Python. +- Development workflow — the full development loop: plan, code, test, deploy, and ship updates. +- Deploy with the CLI — the `tb extension deploy` command reference. +- Deployment REST API — provision extensions from CI without the CLI. diff --git a/src/content/_includes/docs/reference/extensions/quickstart.mdx b/src/content/_includes/docs/reference/extensions/quickstart.mdx new file mode 100644 index 000000000..67e3a7b91 --- /dev/null +++ b/src/content/_includes/docs/reference/extensions/quickstart.mdx @@ -0,0 +1,160 @@ +import { Aside, Steps } from '@astrojs/starlight/components'; +import Tabs from '@components/Tabs.astro'; +import TabItem from '@components/TabItem.astro'; +import DocLink from '@components/DocLink.astro'; +import Banner from '~/components/Banner.astro'; +import { Products } from '~/models/site.models'; + +Extensions work only with ThingsBoard Professional and ThingsBoard Cloud version **4.3.1.3** or later. + +{props.product !== Products.CE && ( + Available from ThingsBoard PE/Cloud 4.3.1.3 and later. +)} + +This guide builds and deploys a first extension end to end. You will create a project, build the Docker image, push it to a registry, and let ThingsBoard run it. The same steps work for **Java** and **Python** — only the first command changes. They also work the same on **ThingsBoard Cloud** and on **your own ThingsBoard PE** — the CLI profile decides where the extension is deployed. + +New here? Read the Extensions overview first to see how the pieces fit together. + +## Prerequisites + +- The ThingsBoard CLI is installed and a profile points at your ThingsBoard Cloud or PE instance (`tb config`). +- **Docker** is installed and running — the CLI builds the image with Docker. +- A **container registry** you can push to (Docker Hub, GitHub Container Registry, or a private registry). ThingsBoard must be able to pull from it — for a private registry, pass pull credentials at deploy time (see Private registry). Log in on your machine first (for example `docker login`) — `tb extension push` uses your local Docker credentials and does not log in for you. +- An API key for your tenant (the CLI uses it to talk to ThingsBoard). + +## Build and deploy + + + +1. **Create the project.** + + `tb extension` commands run inside a ThingsBoard project — a folder with a `thingsboard.json` file. If you do not have one yet, create it first: + + ```bash + mkdir acme-billing && cd acme-billing + tb init + ``` + + Then pick the language. The command unpacks a ready-to-run starter into an `extension/` folder, including a set of small, complete examples. + + + + ```bash + tb extension new --lang java --name acme-billing + ``` + + A Spring Boot service. You add endpoints as `@RestController` classes under `/api/extension/...`. + + + ```bash + tb extension new --lang python --name acme-billing + ``` + + A FastAPI service. You add endpoints as an `APIRouter` in `app/extension/`, kept under `/api/extension/...`. + + + + `--name` sets the local image name. You can change it later with `tb extension rename`. + +2. **Look at the examples (optional).** + + The starter ships complete examples that show the common patterns — a no-auth unit converter (the endpoint ignores the caller's identity; when deployed, the router still checks the credential), an API-key billing hook, a JWT tenant report, and a scheduled health check. Read them, keep what you need, and remove them when you start your own code. In the Java starter, follow the **Removing Examples** section of the starter README — deleting the folder alone breaks the Maven build. In the Python starter, deleting the `examples/` folder is enough. + +3. **Build the image.** + + ```bash + tb extension build + ``` + + This produces a local Docker image tagged with your extension name. The build targets `linux/amd64` by default, so the image runs on common Linux hosts. + +4. **Run it locally to test (optional).** + + ```bash + tb extension run + ``` + + In another terminal, check that the service is up: + + + + The Java starter serves the examples out of the box. Call the sample unit-conversion endpoint: + + ```bash + curl -X POST http://localhost:8090/api/extension/transform/telemetry \ + -H 'Content-Type: application/json' \ + -d '{"temperature_f": 77.0, "pressure_psi": 14.7}' + # {"temperature_c":25.0,"pressure_bar":1.01} + ``` + + + The Python starter does not mount the examples by default, so call the health endpoint: + + ```bash + curl http://localhost:8090/api/health + ``` + + To try an example endpoint, copy it into your app first — for instance, copy `examples/routers/telemetry_unit_conversion.py` into `app/extension/`, register its router in `app/extension/__init__.py` (each example's docstring shows the two lines to add), and rebuild. + + + + Stop it with `tb extension stop` when you are done (this stops only the local container, not a deployed one). + +5. **Push the image to a registry.** + + ThingsBoard runs the image from a registry, not from your machine. Push it first: + + ```bash + tb extension push --image-name myorg/acme-billing + ``` + + Use the full publish target, including any registry or organization prefix (for example `myorg/acme-billing` or `registry.example.com/team/acme-billing`). + +6. **Deploy it to ThingsBoard.** + + ```bash + tb extension deploy --image-name myorg/acme-billing --image-tag latest + ``` + + `latest` is fine for this first test. For a real project, use a version tag such as `1.0.0` — see the Development workflow. + + The CLI shows a confirmation panel, sends the deployment to ThingsBoard, and waits until the extension is `ACTIVE`: + + ```text + OK Deployed 'acme-billing' → acme-billing-9f3a (1/1 replicas ready) + URL: https://thingsboard.cloud/api/ext/acme-billing-9f3a + ``` + +7. **Verify.** + + ```bash + tb extension deployments status acme-billing-9f3a + ``` + + Open the interactive API docs in a browser to try the endpoints. The path depends on the language: + + - Java: `https://thingsboard.cloud/api/ext/acme-billing-9f3a/swagger-ui/index.html` + - Python: `https://thingsboard.cloud/api/ext/acme-billing-9f3a/docs` + + The docs page itself opens without a token — it is public on purpose. Calling any endpoint from it still needs a valid credential in the `X-Authorization` header. + +8. **Clean up (optional).** + + Remove the deployment and its entity when you no longer need it: + + ```bash + tb extension undeploy acme-billing-9f3a + ``` + + + + + +## What to read next + +- Development workflow — the full path of a real project: plan, code, test, deploy, and ship updates. +- Deploy with the CLI — every flag of `tb extension deploy`, resource sizes, environment variables, and the `deployments` commands. +- Deployment REST API — do the same from CI or a script, without the CLI. +- Extensions overview — architecture, routing, and authentication. diff --git a/src/content/docs/docs/paas/eu/reference/extensions/cli-deployment.mdx b/src/content/docs/docs/paas/eu/reference/extensions/cli-deployment.mdx new file mode 100644 index 000000000..953ef3830 --- /dev/null +++ b/src/content/docs/docs/paas/eu/reference/extensions/cli-deployment.mdx @@ -0,0 +1,8 @@ +--- +title: Deploy with the CLI +description: "Deploy and control ThingsBoard extensions from the command line with tb extension deploy." +--- +import CliDeployment from '@includes/docs/reference/extensions/cli-deployment.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/paas/eu/reference/extensions/deployment-api.mdx b/src/content/docs/docs/paas/eu/reference/extensions/deployment-api.mdx new file mode 100644 index 000000000..a5cdc0c5b --- /dev/null +++ b/src/content/docs/docs/paas/eu/reference/extensions/deployment-api.mdx @@ -0,0 +1,8 @@ +--- +title: Deployment API +description: "Provision and control ThingsBoard extensions with the REST deployment API." +--- +import DeploymentApi from '@includes/docs/reference/extensions/deployment-api.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/paas/eu/reference/extensions/development-workflow.mdx b/src/content/docs/docs/paas/eu/reference/extensions/development-workflow.mdx new file mode 100644 index 000000000..ff8a49938 --- /dev/null +++ b/src/content/docs/docs/paas/eu/reference/extensions/development-workflow.mdx @@ -0,0 +1,8 @@ +--- +title: Development workflow +description: "Plan, code, test, deploy, and update a ThingsBoard extension — the full development loop shown on a weather example." +--- +import DevelopmentWorkflow from '@includes/docs/reference/extensions/development-workflow.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/paas/eu/reference/extensions/overview.mdx b/src/content/docs/docs/paas/eu/reference/extensions/overview.mdx new file mode 100644 index 000000000..9b2461e49 --- /dev/null +++ b/src/content/docs/docs/paas/eu/reference/extensions/overview.mdx @@ -0,0 +1,8 @@ +--- +title: Extensions overview +description: "What ThingsBoard extensions are, how the architecture and routing work, and how they are deployed." +--- +import Overview from '@includes/docs/reference/extensions/overview.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/paas/eu/reference/extensions/quickstart.mdx b/src/content/docs/docs/paas/eu/reference/extensions/quickstart.mdx new file mode 100644 index 000000000..085438da2 --- /dev/null +++ b/src/content/docs/docs/paas/eu/reference/extensions/quickstart.mdx @@ -0,0 +1,8 @@ +--- +title: Quickstart +description: "Build, push, and deploy your first ThingsBoard extension in Java or Python using the CLI." +--- +import Quickstart from '@includes/docs/reference/extensions/quickstart.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/paas/reference/extensions/cli-deployment.mdx b/src/content/docs/docs/paas/reference/extensions/cli-deployment.mdx new file mode 100644 index 000000000..277dda78c --- /dev/null +++ b/src/content/docs/docs/paas/reference/extensions/cli-deployment.mdx @@ -0,0 +1,8 @@ +--- +title: Deploy with the CLI +description: "Deploy and control ThingsBoard extensions from the command line with tb extension deploy." +--- +import CliDeployment from '@includes/docs/reference/extensions/cli-deployment.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/paas/reference/extensions/deployment-api.mdx b/src/content/docs/docs/paas/reference/extensions/deployment-api.mdx new file mode 100644 index 000000000..225c46d32 --- /dev/null +++ b/src/content/docs/docs/paas/reference/extensions/deployment-api.mdx @@ -0,0 +1,8 @@ +--- +title: Deployment API +description: "Provision and control ThingsBoard extensions with the REST deployment API." +--- +import DeploymentApi from '@includes/docs/reference/extensions/deployment-api.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/paas/reference/extensions/development-workflow.mdx b/src/content/docs/docs/paas/reference/extensions/development-workflow.mdx new file mode 100644 index 000000000..b022941da --- /dev/null +++ b/src/content/docs/docs/paas/reference/extensions/development-workflow.mdx @@ -0,0 +1,8 @@ +--- +title: Development workflow +description: "Plan, code, test, deploy, and update a ThingsBoard extension — the full development loop shown on a weather example." +--- +import DevelopmentWorkflow from '@includes/docs/reference/extensions/development-workflow.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/paas/reference/extensions/overview.mdx b/src/content/docs/docs/paas/reference/extensions/overview.mdx new file mode 100644 index 000000000..8e5f2dd9a --- /dev/null +++ b/src/content/docs/docs/paas/reference/extensions/overview.mdx @@ -0,0 +1,8 @@ +--- +title: Extensions overview +description: "What ThingsBoard extensions are, how the architecture and routing work, and how they are deployed." +--- +import Overview from '@includes/docs/reference/extensions/overview.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/paas/reference/extensions/quickstart.mdx b/src/content/docs/docs/paas/reference/extensions/quickstart.mdx new file mode 100644 index 000000000..b45e64ebd --- /dev/null +++ b/src/content/docs/docs/paas/reference/extensions/quickstart.mdx @@ -0,0 +1,8 @@ +--- +title: Quickstart +description: "Build, push, and deploy your first ThingsBoard extension in Java or Python using the CLI." +--- +import Quickstart from '@includes/docs/reference/extensions/quickstart.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/pe/reference/extensions/cli-deployment.mdx b/src/content/docs/docs/pe/reference/extensions/cli-deployment.mdx new file mode 100644 index 000000000..e72d69e4d --- /dev/null +++ b/src/content/docs/docs/pe/reference/extensions/cli-deployment.mdx @@ -0,0 +1,8 @@ +--- +title: Deploy with the CLI +description: "Deploy and control ThingsBoard extensions from the command line with tb extension deploy." +--- +import CliDeployment from '@includes/docs/reference/extensions/cli-deployment.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/pe/reference/extensions/deployment-api.mdx b/src/content/docs/docs/pe/reference/extensions/deployment-api.mdx new file mode 100644 index 000000000..4b85a348a --- /dev/null +++ b/src/content/docs/docs/pe/reference/extensions/deployment-api.mdx @@ -0,0 +1,8 @@ +--- +title: Deployment API +description: "Provision and control ThingsBoard extensions with the REST deployment API." +--- +import DeploymentApi from '@includes/docs/reference/extensions/deployment-api.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/pe/reference/extensions/development-workflow.mdx b/src/content/docs/docs/pe/reference/extensions/development-workflow.mdx new file mode 100644 index 000000000..ffa6c0284 --- /dev/null +++ b/src/content/docs/docs/pe/reference/extensions/development-workflow.mdx @@ -0,0 +1,8 @@ +--- +title: Development workflow +description: "Plan, code, test, deploy, and update a ThingsBoard extension — the full development loop shown on a weather example." +--- +import DevelopmentWorkflow from '@includes/docs/reference/extensions/development-workflow.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/pe/reference/extensions/overview.mdx b/src/content/docs/docs/pe/reference/extensions/overview.mdx new file mode 100644 index 000000000..7db396d6a --- /dev/null +++ b/src/content/docs/docs/pe/reference/extensions/overview.mdx @@ -0,0 +1,8 @@ +--- +title: Extensions overview +description: "What ThingsBoard extensions are, how the architecture and routing work, and how they are deployed." +--- +import Overview from '@includes/docs/reference/extensions/overview.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/pe/reference/extensions/quickstart.mdx b/src/content/docs/docs/pe/reference/extensions/quickstart.mdx new file mode 100644 index 000000000..07f6d4a3b --- /dev/null +++ b/src/content/docs/docs/pe/reference/extensions/quickstart.mdx @@ -0,0 +1,8 @@ +--- +title: Quickstart +description: "Build, push, and deploy your first ThingsBoard extension in Java or Python using the CLI." +--- +import Quickstart from '@includes/docs/reference/extensions/quickstart.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/reference/extensions/cli-deployment.mdx b/src/content/docs/docs/reference/extensions/cli-deployment.mdx new file mode 100644 index 000000000..bc5f69fc7 --- /dev/null +++ b/src/content/docs/docs/reference/extensions/cli-deployment.mdx @@ -0,0 +1,8 @@ +--- +title: Deploy with the CLI +description: "Deploy and control ThingsBoard extensions from the command line with tb extension deploy." +--- +import CliDeployment from '@includes/docs/reference/extensions/cli-deployment.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/reference/extensions/deployment-api.mdx b/src/content/docs/docs/reference/extensions/deployment-api.mdx new file mode 100644 index 000000000..3ec9cb89f --- /dev/null +++ b/src/content/docs/docs/reference/extensions/deployment-api.mdx @@ -0,0 +1,8 @@ +--- +title: Deployment API +description: "Provision and control ThingsBoard extensions with the REST deployment API." +--- +import DeploymentApi from '@includes/docs/reference/extensions/deployment-api.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/reference/extensions/development-workflow.mdx b/src/content/docs/docs/reference/extensions/development-workflow.mdx new file mode 100644 index 000000000..aca6e5b61 --- /dev/null +++ b/src/content/docs/docs/reference/extensions/development-workflow.mdx @@ -0,0 +1,8 @@ +--- +title: Development workflow +description: "Plan, code, test, deploy, and update a ThingsBoard extension — the full development loop shown on a weather example." +--- +import DevelopmentWorkflow from '@includes/docs/reference/extensions/development-workflow.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/reference/extensions/overview.mdx b/src/content/docs/docs/reference/extensions/overview.mdx new file mode 100644 index 000000000..286edc1aa --- /dev/null +++ b/src/content/docs/docs/reference/extensions/overview.mdx @@ -0,0 +1,8 @@ +--- +title: Extensions overview +description: "What ThingsBoard extensions are, how the architecture and routing work, and how they are deployed." +--- +import Overview from '@includes/docs/reference/extensions/overview.mdx' +import { Products } from '~/models/site.models' + + diff --git a/src/content/docs/docs/reference/extensions/quickstart.mdx b/src/content/docs/docs/reference/extensions/quickstart.mdx new file mode 100644 index 000000000..97f2a58f3 --- /dev/null +++ b/src/content/docs/docs/reference/extensions/quickstart.mdx @@ -0,0 +1,8 @@ +--- +title: Quickstart +description: "Build, push, and deploy your first ThingsBoard extension in Java or Python using the CLI." +--- +import Quickstart from '@includes/docs/reference/extensions/quickstart.mdx' +import { Products } from '~/models/site.models' + +