Skip to main content

writing-extension-devui

How to add a Dev UI page to a Quarkus extension: deployment processors, runtime-dev JSON-RPC services, and Lit web components.

Informations de source

Dépôt
quarkusio/quarkus
Dernière activité de la source
6 octobre 2026 à 20:37
Langue détectée de SKILL.md
anglais
Étoiles
15 942
Forks
3 337

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
writing-extension-devui
description
How to add a Dev UI page to a Quarkus extension: deployment processors, runtime-dev JSON-RPC services, and Lit web components.
# Writing a Dev UI for a Quarkus Extension Dev UI is the interactive dashboard at `/q/dev-ui` during `quarkus:dev`. Extensions add pages via build items, runtime JSON-RPC services, and Lit web components. See the [Dev UI guide](https://quarkus.io/guides/dev-ui) for full documentation. ## Directory Layout ``` my-extension/ deployment/src/main/java/.../deployment/devui/ MyFeatureDevUIProcessor.java # Build steps deployment/src/main/resources/dev-ui/ qwc-myfeature-dashboard.js # Lit web components runtime-dev/src/main/java/.../runtime/dev/ui/ MyFeatureJsonRpcService.java # JSON-RPC service ``` - **JS naming:** `qwc-<extensionname>-<pagename>.js` - **JSON-RPC services go in `runtime-dev/`**, not `runtime/`. Register as a conditional dev dependency — see the `classloading-and-runtime-dev` skill. ## Deployment Processor Gate all Dev UI build steps with `@BuildStep(onlyIf = IsDevelopment.class)` or use `@BuildSteps(onlyIf = IsLocalDevelopment.class)` at the class level. ```java import io.quarkus.devui.spi.page.CardPageBuildItem; import io.quarkus.devui.spi.page.Page; @BuildStep(onlyIf = IsDevelopment.class) CardPageBuildItem devUI() { CardPageBuildItem card = new CardPageBuildItem(); card.addPage(Page.webComponentPageBuilder() .title("Dashboard") .componentLink("qwc-myfeature-dashboard.js") .icon("font-awesome-solid:robot")); // Build-time data — available in JS via: import { items } from 'build-time-data'; card.addBuildTimeData("items", someList); return card; } ``` Register the JSON-RPC provider in a separate build step. This one must **not** be gated: it is also used to discover valid usages of execution model affecting annotations, which happens outside dev mode. ```java import io.quarkus.devjsonrpc.spi.JsonRPCProvidersBuildItem; @BuildStep JsonRPCProvidersBuildItem jsonRpcProvider() { return new JsonRPCProvidersBuildItem(MyFeatureJsonRpcService.class); } ``` **Maven dependency** for the deployment module: ```xml <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-devui-deployment-spi</artifactId> </dependency> ``` That brings in `quarkus-devjsonrpc-deployment-spi` transitively, which is where `JsonRPCProvidersBuildItem` lives. ## Runtime JSON-RPC Service A plain class in `runtime-dev`. Every public method becomes a JSON-RPC endpoint automatically — registration happens via `JsonRPCProvidersBuildItem`. ```java public class MyFeatureJsonRpcService { @Inject SomeBean bean; public List<Item> getItems() { return bean.listAll(); } public boolean doAction(String id) { return bean.execute(id); } } ``` - Use `@Inject` for CDI; `@PostConstruct` for initialization. - Return JSON-serializable data, **not** HTML. - For streaming, return `Multi<JsonObject>` (Smallrye Mutiny). ## Frontend Web Components Components extend `QwcHotReloadElement` (not `LitElement` directly) and use Vaadin Web Components for consistent styling. ```javascript import { QwcHotReloadElement, html, css } from 'qwc-hot-reload-element'; import { JsonRpc } from 'jsonrpc'; import { items } from 'build-time-data'; export class QwcMyfeatureDashboard extends QwcHotReloadElement { jsonRpc = new JsonRpc(this); static properties = { _items: { state: true } }; constructor() { super(); this._items = items; } connectedCallback() { super.connectedCallback(); this.hotReload(); } hotReload() { this.jsonRpc.getItems().then(r => { this._items = r.result; }); } render() { if (!this._items) return html`<vaadin-progress-bar indeterminate></vaadin-progress-bar>`; return html`<vaadin-grid .items="${this._items}" theme="row-stripes"> <vaadin-grid-column path="name" header="Name"></vaadin-grid-column> </vaadin-grid>`; } } customElements.define('qwc-myfeature-dashboard', QwcMyfeatureDashboard); ``` - `build-time-data` keys must match what was passed to `card.addBuildTimeData(key, value)`. - `JsonRpc` method names must match the Java service method names exactly. - Access results via `response.result`. - For state updates, use spread: `this._items = [...this._items, newItem]`. - Unsubscribe streaming observers in `disconnectedCallback()`. ## Observability Dashboard An extension that captures a telemetry signal (traces, logs, events) can offer its page as a card on the core **Observability** dashboard, on top of its own extension card. Produce an `ObservabilitySignalBuildItem` (`io.quarkus.devui.spi.observability`, in `quarkus-devui-deployment-spi`) next to the page it refers to: ```java signals.produce(new ObservabilitySignalBuildItem( "traces", // unique key, identifies the stored card "OpenTelemetry Traces", // title (name the backend, not just the signal) "font-awesome-solid:diagram-project", // icon "quarkus-opentelemetry/traces", // page id: <namespace>/<dashed-title>, or null "spanCount")); // JSON-RPC live count, or null ``` The dashboard imports that page's web component and renders it inline in a card, so size the component against its host (`height: 100%` or a flex column), not against the viewport. A null page id advertises the signal without contributing a card, which is what metrics does - meters are picked individually instead. Meters need no build item: everything registered with Micrometer or the OpenTelemetry SDK is offered in the dashboard's picker automatically. Only a new metrics *backend* (one that samples into `MetricsTimeSeriesStore`) produces a `MetricsBackendBuildItem`. Full documentation: `docs/src/main/asciidoc/dev-ui.adoc`, "Observability dashboard". ## Testing Extend `DevUIJsonRPCTest` (`io.quarkus.devui.tests`). Pass the extension namespace to the super constructor, then call `executeJsonRPCMethod()`: ```java public class MyFeatureDevUITest extends DevUIJsonRPCTest { @RegisterExtension static final QuarkusDevModeTest config = new QuarkusDevModeTest() .withApplicationRoot((jar) -> jar.addClass(MyBean.class)); public MyFeatureDevUITest() { super("quarkus-myfeature"); } @Test public void testGetItems() throws Exception { JsonNode result = super.executeJsonRPCMethod("getItems"); assertNotNull(result); } } ``` ## Key Rules - **Correct imports:** `CardPageBuildItem` is in `io.quarkus.devui.spi.page` (`quarkus-devui-deployment-spi`), `JsonRPCProvidersBuildItem` is in `io.quarkus.devjsonrpc.spi` (`quarkus-devjsonrpc-deployment-spi`). - **JSON-RPC services belong in `runtime-dev/`**, never in `runtime/`. - **JS files go in `deployment/src/main/resources/dev-ui/`**. - **Extend `QwcHotReloadElement`**, not `LitElement` — it provides the `hotReload()` hook that re-runs on dev-mode restarts. - **Return JSON from services**, not HTML. Use `Page.externalPageBuilder()` for external content like Swagger UI.
Voir sur GitHub