The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.
このページを編集

Dev UI

General overview

Quarkus Dev UI is a developer-friendly user interface that comes to life when you run your application in development mode (./mvnw quarkus:dev). It serves as a powerful portal for exploring, debugging, and interacting with your application - all while it’s running - with zero code changes or restarts.

What is Dev UI?

The Dev UI is designed to boost developer joy by making Quarkus' inner workings observable and extensible. It offers:

  • Insight into available extensions and their capabilities

  • Access to runtime metadata and tools

  • A responsive frontend that integrates tightly with hot reload

  • A flexible way for extensions to expose configuration, documentation, and actions

Whether you’re a developer exploring your project or an extension author enhancing the experience, Dev UI is your entry point.

Layout at a Glance

When running in dev mode, Quarkus provides a dynamic, modular web interface accessible at http://localhost:8080/q/dev-ui. You can also click d in the console log, that will open your default browser at Dev UI.

The basic layout has the following parts:

  • Menu

  • Page

  • Footer

  • Card

Below is a visual overview of the layout:

Dev UI Layout

The menu on the left-hand side provides structured access to built-in pages:

Menu Item 説明

Extensions

Displays all Quarkus extensions (used by the current application) as cards. Each card may provide configuration, documentation, or tools.

Configuration

Shows all configuration options (with current, default, and documentation info). Includes a config editor (text & form modes).

Workspace

A project file browser. Files can be opened and edited inline. Extensions may add workspace actions.

Endpoints

Lists all HTTP endpoints (REST, GraphQL, internal, etc.). Helps you inspect available routes in your app.

Continuous Testing

Monitors and controls continuous testing. View test results, rerun tests, or toggle testing state.

Dev Services

Displays information about automatically started services (e.g., databases, Kafka brokers).

Build Metrics

Provides performance metrics from the last build and reload cycle.

Readme

Renders your project’s README.md, if available. Useful for onboarding or project context.

Dependencies

Shows all runtime and deployment dependencies with dependency path exploration and search functionality.

You can drag a page from an extension card into the menu to bookmark it for quick access.

Page Area (Main Content)

The main panel displays content for the selected menu item or extension. Depending on the selected page, it may render:

  • Extension-specific tools and UIs (e.g., GraphQL UI, Swagger UI)

  • JSON viewers, code editors, or metrics charts

  • Interactive actions (e.g., buttons, toggles)

  • Documentation and links

Pages are modular and dynamically loaded. Extensions can contribute custom pages written using Web Components and interact with the backend via JSON-RPC.

The footer at the bottom is primarily used to display logs while the application runs in dev mode.

By default, the Server log (standard Quarkus log output) is selected, but additional log tabs may appear depending on which extensions are present. For example:

  • HTTP, GraphQL,Scheduler, etc., may appear when relevant extensions are in use

  • Dev Services log output from started services like databases, Kafka brokers, etc.

This live log view helps developers track behavior, debug issues, and monitor Dev Services without switching to the terminal.

Cards

Each Quarkus extension that contributes to Dev UI is represented as a card in the Extensions page. These cards provide quick access to features, documentation, configuration, and runtime tools offered by the extension.

Extension Card Anatomy

Each card may include the following elements:

Element 説明

Logo (optional)

An optional logo representing the extension or its domain

Title

The name of the extension as recognized in the Dev UI.

Favorite

Marks the card as a favorite. Favorited cards are always displayed first in the Extensions view for easier access.

Guide (optional)

Links to the online Quarkus guide for the extension (if available).

Description

A short explanation of what the extension does or enables.

Page Links (optional)

Interactive entries that navigate to custom pages contributed by the extension. A page link can also optionally contain a label that we discuss later.

Configuration

A shortcut to the configuration editor filtered to settings relevant to this extension.

Underlying Library (optional)

Shows the version of the main library powering the extension (if any).

More Details

Opens a dialog with all information

Clicking a page link inside a card navigates to a dedicated UI page for that feature. These pages can be bookmarked into the menu for quick access.

Extensions are free to customize their cards and add interactive behaviors based on what they provide at runtime.

Guide for Extension Developers

Quarkus extensions can enhance the developer experience by contributing to the Dev UI. The rest of this guide outlines how to integrate your extension into the Dev UI, covering metadata configuration, adding pages to the card, menu, and footer, and best practices for dynamic content.

quarkus-extension.yaml

To have your extension appear in the Dev UI, ensure the quarkus-extension.yaml file is present in the src/main/resources/META-INF directory of your extension’s runtime module. This file provides metadata that the Dev UI uses to generate the extension card.

例:

name: "Hibernate ORM"
description: "Define your persistent model with Hibernate ORM and Jakarta Persistence"
guide: "https://quarkus.io/guides/hibernate-orm"
metadata:
  categories:
    - "data"
  config:
    - "quarkus.hibernate-orm"

Key fields:

  • name: Displayed as the extension title in the Dev UI card.

  • description: Shown as the card’s summary.

  • guide: URL to the extension’s guide; used to render the guide icon on the card.

  • metadata.config: Filters configuration keys shown when clicking "Configuration" on the card.

Adding Pages to the Dev UI

Your extension needs to have the following dependency in it’s deployment module:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-vertx-http-dev-ui-spi</artifactId>
</dependency>

This can be transitivly or directly and is only needed in deployment module. This gives your processor access to the relative Build Items.

Extensions can contribute interactive pages to the Dev UI in the following areas:

  • Card: Add links to pages directly on the extension card.

  • Footer: Add tabs to the footer for logs or other runtime information.

  • Menu: Add pages to the Dev UI’s left-hand menu.

Card

The most common way to contribute to Dev UI is via Pages from your extension card in the Extension page.

To add links to your extension’s card, produce a CardPageBuildItem in a @BuildStep:

    @BuildStep(onlyIf = IsLocalDevelopment.class) (1)
    void createJokesPageOnCard(BuildProducer<CardPageBuildItem> cardsProducer) {

        CardPageBuildItem cardPageBuildItem = new CardPageBuildItem(); (2)
        cardPageBuildItem.setLogo("clown.svg", "clown.svg"); (3)

        cardPageBuildItem.addPage(Page.webComponentPageBuilder() (4)
                .title("Joke List") (5)
                .icon("font-awesome-solid:cubes") (6)
                .componentLink("qwc-jokes-list.js")); (7)

        cardsProducer.produce(cardPageBuildItem);
    }
1 Always make sure that this build step is only run when in local dev mode.
2 カードに何かを追加するには、 CardPageBuildItem を返す/生成する必要があります。
3 You can optionally add a logo (dark and light mode) in deployment/src/main/resources/dev-ui/.
4 To add a link on the card, you can use the addPage method, as all links go to a "page". Page has some builders to assist with building a page. The most common one is the webComponentPageBuilder - but we will discuss some others later.
5 You can (optionally) add the title, else it will be derived from the component link.
6 You can add an icon. All free font-awesome icons are available.
7 Add the link to the web component source (js) file in deployment/src/main/resources/dev-ui/. This has to take the format of qwc-extensionname-pagename.js

Later we will discuss how to create this web component page.

アイコンに関する注意事項
Font awesome でアイコンを見つけた場合は、次のようにマッピングできます。たとえば、 <i class="fa-solid fa-house"></i>font-awesome-solid:house にマッピングされるため、 fafont-awesome になり、アイコン名から fa- が削除されます。
Optional: Library Version

You can add a underlying library badge on the card. This is usefull for cases where the extension is exposing a well known library. As example, the quarkus-hibernate-orm extension provides access to Hibernate. Showing the underlying Hibernate details could be useful information for the user:

Dev UI Layout

This can be done using the addLibraryVersion method:

cardPageBuildItem.addLibraryVersion("org.hibernate.orm", "hibernate-core", "Hibernate", "https://hibernate.org/orm/");

You provide the groupId and artifactId (so that we can look up the version) and a Name and optionally a url.

Optional: Build Time Data

You can pass data gathered during build to the Page (js):

cardPageBuildItem.addBuildTimeData("jokes", jokesBuildItem.getJokes());

ページに必要なことがビルド時にわかっているすべてのデータに対して、これらのキーと値のペアを複数追加できます。

The key will be scoped automatically, so you do not have to prefix any namespace. Later we will discuss how to access this data in the webcomponent (page).

Optional: Labels

You can add an optional label to the link in the card using one of the builder methods on the page builder. These labels can be

  • 静的 (ビルド時に既知のラベル) .staticLabel("staticLabelValue")

  • 動的 (実行時にロード) .dynamicLabelJsonRPCMethodName("yourJsonRPCMethodName")

  • ストリーミング (実行時に更新される値の継続的なストリーミング)値を継続的にストリーミング .streamingLabelJsonRPCMethodName("yourJsonRPCMethodName")

For dynamic and streaming labels, the method name is the name of the JsonRPC method, that we will discuss later.

Dev UI card labels

Apart from adding a card and a page, extensions can add a tab to the footer. This is useful for things that are happening continuously. A page will disconnect from the DOM (and maybe the backend) when navigating away from that page (or webcomponent), and a log in the footer will be permanently connected to the DOM, as it’s always part of the view (or app)

There are 2 ways to add things to the footer. There easiest is to just expose the log in a BuildItem. Here we expect either a supplier (function) that return a Flow.Publisher<String> or a RuntimeValue<SubmissionPublisher<String>>:

@BuildStep(onlyIf = { IsLocalDevelopment.class }) (1)
public void createFooterLog(BuildProducer<FooterLogBuildItem> footerLogProducer){
    footerLogProducer.produce(new FooterLogBuildItem("My Extension Log", () -> {(2)
        return createLogPublisher();(3)
    }));
}
1 Always make sure that this build step is only run when in local dev mode.
2 You must return/produce a FooterLogBuildItem.
3 Here return a Flow.Publisher<String> that will stream the log

The RuntimeValue is useful if the log is only available at runtime. If you can get the log in the deployment module you can use the supplier.

Or you can take full control of the UI and produce a FooterPageBuildItem and provide a custom web component in a js file:

    @BuildStep(onlyIf = IsLocalDevelopment.class)(1)
    void createJokesLog(BuildProducer<FooterPageBuildItem> footerProducer) {

            FooterPageBuildItem footerPageBuildItem = new FooterPageBuildItem();(2)

            footerPageBuildItem.addPage(Page.webComponentPageBuilder()(3)
                    .title("Joke Log")(4)
                    .icon("font-awesome-regular:face-grin-tongue-wink")(5)
                    .componentLink("qwc-jokes-log.js"));(6)

            footerProducer.produce(footerPageBuildItem);
    }
1 Always make sure that this build step is only run when in local dev mode.
2 To add anything on the footer, you must return/produce a FooterPageBuildItem.
3 To add a tab in the footer, you can use the addPage method, as all tabs renders a "page". Page has some builders to assist with building a page. The most common one is the webComponentPageBuilder - but we will discuss some others later.
4 You can (optionally) add the title, else it will be derived from the component link.
5 アイコンを追加できます。無料の font-awesome アイコンが利用可能です。
6 Add the link to the web component source (js) file in deployment/src/main/resources/dev-ui/. This has to take the format of qwc-extensionname-pagename.js
Optional: Build Time Data

You can pass data gathered during build to the Page (js):

footerPageBuildItem.addBuildTimeData("jokes", jokesBuildItem.getJokes());

ページに必要なことがビルド時にわかっているすべてのデータに対して、これらのキーと値のペアを複数追加できます。

The key will be scoped automatically, so you do not have to prefix any namespace. Later we will discuss how to access this data in the webcomponent (page).

To add a page to the Dev UI menu, produce a MenuPageBuildItem:

    @BuildStep(onlyIf = IsLocalDevelopment.class) (1)
    void createJokesMenu(BuildProducer<MenuPageBuildItem> menuProducer) {

        MenuPageBuildItem menuPageBuildItem = new MenuPageBuildItem();  (2)

        menuPageBuildItem.addPage(Page.webComponentPageBuilder() (3)
                .title("One Joke") (4)
                .icon("font-awesome-regular:face-grin-tongue-wink") (5)
                .componentLink("qwc-jokes-menu.js")); (6)

        menuProducer.produce(menuPageBuildItem);
    }
}
1 Always make sure that this build step is only run when in local dev mode.
2 To add anything on the menu, you must return/produce a MenuPageBuildItem.
3 To add a link in the menu, you can use the addPage method, as all links go to a "page". Page has some builders to assist with building a page. The most common one is the webComponentPageBuilder - but we will discuss some others later.
4 You can (optionally) add the title, else it will be derived from the component link.
5 アイコンを追加できます。無料の font-awesome アイコンが利用可能です。
6 Add the link to the web component source (js) file in deployment/src/main/resources/dev-ui/. This has to take the format of qwc-extensionname-pagename.js

This page will appear alongside built-in menu items like "Configuration" or "Workspace."

Optional: Build Time Data

You can pass data gathered during build to the Page (js):

menuPageBuildItem.addBuildTimeData("jokes", jokesBuildItem.getJokes());

ページに必要なことがビルド時にわかっているすべてのデータに対して、これらのキーと値のペアを複数追加できます。

The key will be scoped automatically, so you do not have to prefix any namespace. Later we will discuss how to access this data in the webcomponent (page).

Dev UI Jokes

Building Web Components

Dev UI は、これらの Web コンポーネントの構築を容易にするために Lit を使用します。Web コンポーネントと Lit の詳細は、以下を参照してください。

Web コンポーネントページの基本構造

Web コンポーネントページは、新しい HTML 要素を作成する JavaScript クラスです。

import { LitElement, html, css} from 'lit'; (1)
import { jokes } from 'build-time-data'; (2)

export class QwcJokesList extends LitElement { (3)

    static styles = css` (4)
        .buttonBar {
            display: flex;
            justify-content: space-between;
            gap: 10px;
            align-items: center;
            width: 90%;
            color: var(--lumo-primary-text-color); (5)
        }

        .buttonBar .button {
            width: 100%;
        }
        `;

    static properties = {
        _jokes: {state: true},
        _numberOfJokes: {state: true},
        _message: {state: true},
        _isStreaming: {state: true} (6)
    };

    constructor() { (7)
        super();
        this._jokes = [];
        this._numberOfJokes = 0;
        this._isStreaming = false;
    }

    connectedCallback() { (8)
        super.connectedCallback();
        jokes.forEach((joke) =>{
            var item = this._toJokeItem(joke);
            this._jokes.push(item);
        });
        this._numberOfJokes = this._jokes.length;
    }

    disconnectedCallback() { (9)
        if(this._isStreaming){
            this._observer.cancel();
        }
        super.disconnectedCallback()
    }

    render() { (10)
         return html`<h3>Here are ${this._numberOfJokes} jokes</h3> (11)
            <vaadin-message-list .items="${this._jokes}"></vaadin-message-list>

            ${this._renderLoadingMessage()}
            <div class="buttonBar">
                <vaadin-button class="button" theme="success" @click=${() => this._fetchMoreJokes()}>
                    <vaadin-icon icon="font-awesome-solid:comment"></vaadin-icon> Tell me more jokes
                </vaadin-button>
                <vaadin-checkbox class="button" label="Stream new jokes continuously" @input=${(e) =>this._startStopStreaming(e)}></vaadin-checkbox>
            </div>
            `;
    }

    // ... more private methods
}
customElements.define('qwc-jokes-list', QwcJokesList); (12)
1 他のライブラリーからクラスや関数をインポートできます。 その場合、 LitLitElement クラスと html および css 関数を使用します。
2 ビルド・ステップで定義されたビルド・タイム・データは、キーを使用して build-time-data から常にインポートすることができます。ビルド・ステップで追加されたすべてのキーが使用可能になります。
3 コンポーネントの名前は、Qwc (Quarkus Web Component の略)、エクステンション名、ページタイトルの順に、すべて Camel Case で連結した形式にする必要があります。これは、前述のファイル名の形式とも一致します。コンポーネントは LitComponent を拡張する必要もあります。
4 CSS スタイルは css 関数を使用して追加でき、これらのスタイルはコンポーネントにのみ適用されます。
5 スタイルは、グローバルに定義された CSS 変数を参照して、特にライトモードとダークモードの切り替え時にページが正しくレンダリングされるようにすることができます。すべての CSS 変数は、Vaadin のドキュメント ((カラー・サイズと間隔など) で確認できます。
6 Properties can be added. Use _ in front of a property if that property is private. Properties are usually injected in the HTML template, and can be defined as having state, meaning that if that property changes, the component (or part of it) should re-render. In this case, the jokes are Build time data that we gathered while building.
7 コンストラクター (オプション) は必ず最初に super を呼び出し、次にそのプロパティーのデフォルト値を設定する必要があります。
8 connectedCallback is a method from Lit that gets called when this component are connected to the DOM. This is a good place to do 'page load' type things, like fetching data from the backend (we will discuss this later)
9 disconnectedCallback us a method from Lit that gets called when this component are disconnected from the DOM. This is a good place to do any clean up.
10 render is a method from Lit that will be called to render the page. In this method, you return the markup of the page you want.
11 You can use the html function from Lit, which gives you a template language to output the HTML you want. Once the template is created, you must only set or change the properties to re-render the page content. Read more about Lit html
12 Web コンポーネントは、必ず一意のタグを持つカスタム要素として登録する必要があります。その場合のタグは、ファイル名と同じ形式 (qwc ダッシュ エクステンション名 ダッシュ ページタイトル) である必要があります。
ホットリロード

ホットリロードの発生時に、画面を自動的に更新できます。そのためには、Webcomponent が拡張する LitElementQwcHotReloadElement に置き換えます。

QwcHotReloadElementLitElement を拡張するため、コンポーネントは変わらず Lit Element です。

QwcHotReloadElement を拡張する場合は、 hotReload メソッドを使用する必要があります (Lit の render メソッドも提供する必要があります)。

import { QwcHotReloadElement, html, css} from 'qwc-hot-reload-element';

// ...

export class QwcMyExtensionPage extends QwcHotReloadElement {

    render(){
        // ...
    }

    hotReload(){
        // ..
    }

}
UI Components
Vaadin Web Components

Vaadin Web Components: Dev UI makes extensive usage of Vaadin web components as UI Building blocks.

Qomponent

Qomponent: A few custom build compoments that can be used.

Currently the following UI component are available:

  • qui-dot - Render dot files.

  • qui-code-block - Render code. (See below Code block section)

  • qui-directory-tree - Render a directory tree (like in the workspace).

  • qui-alert - Show an alert.

  • qui-card - Card component.

  • qui-switch - Switch button.

  • qui-badge - Badge component.

Code block

Creates a code block (containing marked up code). This could also be made editable. This component use the above mentioned code block from qomponent, that is build with code-mirror, but adds the automatic theme state when switching themes.

Code can be provided remotely (src) or as a property (content) or as a slotted value (example below).

import 'qui-themed-code-block';
<qui-themed-code-block mode="properties">
      <slot>
foo = bar
      </slot>
</qui-themed-code-block>

Currently the following modes are supported:

  • xml

  • javascript

  • php

  • cpp

  • go

  • rust

  • python

  • json

  • java

  • sql

  • yaml

  • html

  • css

  • sass

  • less

  • markdown

  • asciidoc

  • properties

  • asciiArmor

  • powerShell

  • shell

  • protobuf

  • dockerFile

  • diff

See the @qomponent/qui-code-block for more details.

ユーザーの IDE (IDE を検出できる場合) で開くことができるリソース (Java ソースファイルなど) へのリンクを作成します。

import 'qui-ide-link';
<qui-ide-link title='Source full class name'
                        class='text-source'
                        fileName='${sourceClassNameFull}'
                        lineNumber='${sourceLineNumber}'>[${sourceClassNameFull}]</qui-ide-link>;
内部コントローラーの使用

特定のことを簡単にするために、いくつかの 内部コントローラー が用意されている:

  • Notifier

  • Storage

  • Log

  • Router

Notifier

これは容易にトーストメッセージを表示できる方法です。トーストは画面上に配置でき (デフォルトでは左下)、レベル (Info、Success、Warning、Error) を設定できます。いずれかのレベルを primary にすることで、目を引くトーストメッセージを作成できます。

このコントローラーのソースについては、 こちら を参照してください。

使用例:

Dev UI Notifier
import { notifier } from 'notifier';
<a @click=${() => this._info()}>Info</a>;
_info(position = null){
    notifier.showInfoMessage("This is an information message", position);
}

有効な位置はすべて こちら で確認できます。

Storage

安全にローカルストレージにアクセスできる方法です。これにより、エクステンションをスコープとするローカルストレージに値が保存されます。この方法では、別のエクステンションと競合する心配はありません。

ローカルストレージは、ユーザーの好みや状態を記憶するのに便利です。例えば、フッターは一番下の引き出しの状態(開閉)と開いたときのサイズを記憶しています。

import { StorageController } from 'storage-controller';

// ...

storageControl = new StorageController(this); // Passing in this will scope the storage to your extension

// ...

const storedHeight = this.storageControl.get("height"); // Get some value

// ...

this.storageControl.set('height', 123); // Set some val
Log

The log controller is used to add control buttons to a (footer) log. See Footer.

Dev UI Log control
import { LogController } from 'log-controller';

// ...

logControl = new LogController(this); // Passing in this will scope the control to your extension

// ...
this.logControl
                .addToggle("On/off switch", true, (e) => {
                    this._toggleOnOffClicked(e);
                }).addItem("Log levels", "font-awesome-solid:layer-group", "var(--lumo-tertiary-text-color)", (e) => {
                    this._logLevels();
                }).addItem("Columns", "font-awesome-solid:table-columns", "var(--lumo-tertiary-text-color)", (e) => {
                    this._columns();
                }).addItem("Zoom out", "font-awesome-solid:magnifying-glass-minus", "var(--lumo-tertiary-text-color)", (e) => {
                    this._zoomOut();
                }).addItem("Zoom in", "font-awesome-solid:magnifying-glass-plus", "var(--lumo-tertiary-text-color)", (e) => {
                    this._zoomIn();
                }).addItem("Clear", "font-awesome-solid:trash-can", "var(--lumo-error-color)", (e) => {
                    this._clearLog();
                }).addFollow("Follow log", true , (e) => {
                    this._toggleFollowLog(e);
                }).done();
Router

ルーターは主に内部で使用されます。これは、SPA内の正しいページ/セクションにURLをルーティングするために、隠れて Vaadin Router を使用しています。これはナビゲーションを更新し、履歴(戻るボタン)を許可します。これはまた、複数のページを持つエクステンションで利用可能なサブメニューを作成します。

controller には、役立ちそうなメソッドが記載されています。

Global State

The properties on a Web component page is scope for that page/webcomponent. There is also a few global states available that might be useful in your component. Dev UI uses LitState for this. LitState automatically re-renders your LitElement components, when a shared app state variable they use changes. It’s like LitElement’s properties, but then shared over multiple components.

Dev UI has the following build-in state:

  • Connection

  • テーマ

  • Assistant

  • Dev UI

Connection State

This will give you the connection state to the backend. Dev UI is connected to the backend with a Web Socket. The UI might loose connection to the backend in certain cases, example a Hot reload is in progress or the user actually stopped the server.

To use this state in your page:

import { observeState } from 'lit-element-state'; (1)
import { connectionState } from 'connection-state'; (2)

export class QwcExtentionPage extends observeState(LitElement) {  (3)
1 import observeState from the LitState library.
2 import the state you are interested in, in this case connection state.
3 Wrap the LitElement in observerState

Now you can access the connection state anywhere in your page, and when that state changes it will act exactly the same as a local state - re-render the relevant parts of the page:

render() {
    return html`<vaadin-icon title="${connectionState.current.message}" style="color:${connectionState.current.color}" icon="font-awesome-solid:${connectionState.current.icon}"></vaadin-icon>`;
    }

You can see all the properties of connection state here

Theme State

This will give you access to the current theme, that the user can change at any time.

To use this state in your page:

import { observeState } from 'lit-element-state'; (1)
import { themeState } from 'theme-state'; (2)

export class QwcExtentionPage extends observeState(LitElement) {  (3)
1 import observeState from the LitState library.
2 import the state you are interested in, in this case theme state.
3 Wrap the LitElement in observerState

Now you can access the theme state anywhere in your page, and when that state changes it will act exactly the same as a local state - re-render the relevant parts of the page:

render() {
    return html`<div class="codeBlock">
                        <qui-code-block
                            mode='json'
                            content='${json}'
                            theme='${themeState.theme.name}'
                            showLineNumbers>
                        </qui-code-block>`;
    }

You can see all the properties of theme state here

Assistant State

This state contains information on the Quarkus Assistant, if it’s available and if it’s configured and ready to be used. This is useful if you extension provide assistant features and you need to know the state of the assistant.

To use this state in your page:

import { observeState } from 'lit-element-state'; (1)
import { assistantState } from 'assistant-state'; (2)

export class QwcExtentionPage extends observeState(LitElement) {  (3)
1 import observeState from the LitState library.
2 import the state you are interested in, in this case assistant state.
3 Wrap the LitElement in observerState

Now you can access the assistant state anywhere in your page, and when that state changes it will act exactly the same as a local state - re-render the relevant parts of the page:

render() {
    if(assistantState.current.isConfigured){
        return html`<div class="assistantfeature">
                        <span> Magic happends here</span>
                    </div>`;
    }
}

You can see all the properties of assistant state here

Dev UI State

This state is a general state that contains global properties used in Dev UI, and it’s mostly use internally. It works exactly the same as any of the states discussed before.

A lot of the properties in this state is really build time data that auto reload when a hot-reload happens.

You can see all the properties of dev ui state here

Other ways to create pages

As mentioned, there are some other ways to create pages in Dev UI (other than using Web Components).

これらは、他の (Dev UI の外の) データを参照するリンクで、HTML ページ、テキスト、その他のデータがあります。

その良い例としては、JSON と YAML の両方のフォーマットで生成された OpenAPI スキーマへのリンクと、Swagger UI へのリンクが含まれる SmallRye OpenAPI エクステンションが挙げられます。

Dev UI extension card

外部参照へのリンクはビルド時に判明するため、このようなリンクをカードに表示するには、エクステンションに以下のビルドステップを追加します。

@BuildStep(onlyIf = IsLocalDevelopment.class)(1)
public CardPageBuildItem pages(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {

    CardPageBuildItem cardPageBuildItem = new CardPageBuildItem(); (2)

    cardPageBuildItem.addPage(Page.externalPageBuilder("Schema yaml") (3)
            .url(nonApplicationRootPathBuildItem.resolvePath("openapi")) (4)
            .isYamlContent() (5)
            .icon("font-awesome-solid:file-lines")); (6)

    cardPageBuildItem.addPage(Page.externalPageBuilder("Schema json")
            .url(nonApplicationRootPathBuildItem.resolvePath("openapi") + "?format=json")
            .isJsonContent()
            .icon("font-awesome-solid:file-code"));

    cardPageBuildItem.addPage(Page.externalPageBuilder("Swagger UI")
            .url(nonApplicationRootPathBuildItem.resolvePath("swagger-ui"))
            .isHtmlContent()
            .icon("font-awesome-solid:signs-post"));

    return cardPageBuildItem;
}
1 Always make sure that this build step is only run when in local dev mode
2 カードに何かを追加するには、 CardPageBuildItem を返す/生成する必要があります。
3 リンクを追加するには addPage メソッドを使用します。すべてのリンクが "page" に移動します。 Page には、ページの構築を支援するビルダーがいくつかあります。 external リンクの場合は、 externalPageBuilder を使用します。
4 外部リンクの URL を追加します。ここでは、リンクが設定可能な非アプリケーションパス (デフォルトは /q) の下にあるため、 NonApplicationRootPathBuildItem を使用してリンクを作成します。リンクが /q の下にある場合は、必ず NonApplicationRootPathBuildItem を使用します。
5 移動先のコンテンツのコンテンツタイプを (オプションで) ヒントとして表示できます。ヒントがない場合、 MediaType を決定するためにヘッダー呼び出しが実行されます。
6 アイコンを追加できます。無料の font-awesome アイコンが利用可能です。
外部コンテンツの埋め込み

デフォルトでは、外部リンクも Dev UI の内部 (埋め込み) でレンダリングされます。HTML の場合、ページがレンダリングされ、その他のコンテンツはメディアタイプをマークアップするために code-mirror を使用して表示されます。たとえば yaml 形式のOpenAPI スキーマドキュメントは、次のようになります。

Dev UI embedded page

コンテンツを埋め込みたくない場合は、ページビルダーの .doNotEmbed()、リンクを新しいタブで開くことができます。

The example above assumes you know the link to use at build time. There might be cases where you only know this at runtime. In that case, you can use a JsonRPC Method (discussed later) that returns the link to add, and use that when creating the link. Rather than using the .url method on the page builder, use the .dynamicUrlJsonRPCMethodName("yourJsonRPCMethodName").

Raw data pages

If you have some data that is known at build time (build time data) that you want to display, you can use one of the following builders in Page:

Marked-up data

データは、生の (シリアル化された) JSON 値で表示されます。

cardPageBuildItem.addPage(Page.rawDataPageBuilder("Raw data") (1)
                .icon("font-awesome-brands:js")
                .buildTimeDataKey("someKey")); (2)
1 rawDataPageBuilder を使用します。
2 Page BuildItem の addBuildTimeData でビルド時データを追加したときに使用したキーにリンクします。

これにより、生データを JSON でレンダリングするページへのリンクが作成されます。

Dev UI raw page
テーブルデータ

構造上可能な場合は、ビルド時データをテーブルに表示することもできます。

cardPageBuildItem.addPage(Page.tableDataPageBuilder("Table data") (1)
                .icon("font-awesome-solid:table")
                .showColumn("timestamp") (2)
                .showColumn("user") (2)
                .showColumn("fullJoke") (2)
                .buildTimeDataKey("someKey")); (3)
1 tableDataPageBuilder を使用します。
2 オプションで特定のフィールドのみを表示します。
3 Page BuildItem の addBuildTimeData でビルド時データを追加したときに使用したキーにリンクします。

これにより、テーブル内のデータをレンダリングするページへのリンクが作成されます。

Dev UI table page
Qute データ

qute テンプレートを使用して、ビルド時データを表示することもできます。すべてのビルド時データキーは、テンプレートで使用できます。

cardPageBuildItem.addPage(Page.quteDataPageBuilder("Qute data") (1)
                .icon("font-awesome-solid:q")
                .templateLink("qute-jokes-template.html")); (2)
1 quteDataPageBuilder を使用します。
2 /deployment/src/main/resources/dev-ui/ の Qute テンプレートにリンクします。

データを表示するには、任意の Qute テンプレートを使用します (例: qute-jokes-template.html)。

<table>
    <thead>
        <tr>
            <th>Timestamp</th>
            <th>User</th>
            <th>Joke</th>
        </tr>
    </thead>
    <tbody>
        {#for joke in jokes} (1)
        <tr>
            <td>{joke.timestamp}</td>
            <td><span><img src="{joke.profilePic}" height="30px"></img> {joke.user}</span></td>
            <td>{joke.fullJoke}</td>
        </tr>
        {/for}
    </tbody>
</table>
1 Page Build Item のビルド時データキーとして jokes が追加されました。

バックエンドとの通信

All communication to the backend happend with JsonRPC over web socket. Dev UI makes this easy to use for extension developers, and you do not really need to get into the details of either JsonRPC or Web Sockets.

There are 3 stages to communicate to a backend during runtime (when the user is running their app in dev mode):

  • Executing some method against the runtime classpath

  • Executing some method against the deployment classpath

  • Returning data from some recorded value

ランタイムクラスパスに対する JsonRPC

You can fetch or stream runtime data (rather than Build time data discussed earlier) or execute methods against the runtime classpath (as opposed to deployment classpath). There are two parts to getting data during runtime. The Java side in the runtime or runtime-dev module, and then the usage in the web component (that we will discuss later).

In your Runtime or Runtime-dev module, create the JsonRPC Service. This class will default to an application-scoped bean, except if you explicitly scope the bean. All public methods that return something will be made available to call from the Web component Javascript.

これらのメソッドで返されるオブジェクトは次のとおりです。

  • プリミティブまたは String

  • io.vertx.core.json.JsonArray

  • io.vertx.core.json.JsonObject

  • JSON にシリアル化できるその他の POJO

上記のすべては、ブロッキング (POJO) または非ブロッキング (@NonBlocking または Uni) にできます。また、 Multi を使用してデータをストリーミングすることもできます。

(1)
public class JokesJsonRPCService {

    private final BroadcastProcessor<Joke> jokeStream = BroadcastProcessor.create();
    private final BroadcastProcessor<Joke> jokeLog = BroadcastProcessor.create();
    private static int numberOfJokesTold = 10;

    @PostConstruct
    void init() {
        Multi.createFrom().ticks().every(Duration.ofMinutes(1)).subscribe().with((item) -> {
            jokeStream.onNext(getJoke());
        });
    }

    public Multi<Joke> streamJokes() { (2)
        return jokeStream;
    }

    @NonBlocking (3)
    public Joke getJoke() {
        numberOfJokesTold++;
        Joke joke = fetchRandomJoke();
        jokeLog.onNext(joke);
        return joke;
    }

    public Multi<Joke> jokeLog() {
        return jokeLog;
    }

    // Some more private methods
}
1 Non-scoped class will default to Application Scope
2 Data can be streamed with Multi
3 This example runs nonblocking. We could also return Uni<Joke>

このコードは、データを UI に表示できるようにする役割を担います。

デプロイメントモジュールのプロセッサーに JsonPRCService を登録する必要があります。

@BuildStep
JsonRPCProvidersBuildItem createJokesJsonRPCService() {(1)
    return new JsonRPCProvidersBuildItem(JokesJsonRPCService.class);(2)
}
1 JsonRPCProvidersBuildItem を生成または返します。
2 Define the class in your runtime or runtime-dev module that will contain methods that make data available in the UI

JsonRPC against the Deployment classpath

場合によっては、デプロイメントクラスパスに対してメソッドを実行したり、データを取得したりする必要があるかもしれません。同様の状況は JsonRPC 通信でも発生しますが、その場合はランタイムモジュールに JsonRPC サービスは作成せず、 デプロイメントモジュールのサプライヤーで実行されるコードを提供します。これを行うには、以下のように BuildTimeActionBuildItem を作成します。

    @BuildStep(onlyIf = IsLocalDevelopment.class)
    BuildTimeActionBuildItem createBuildTimeActions() { (1)
        BuildTimeActionBuildItem generateManifestActions = new BuildTimeActionBuildItem();(2)
        generateManifestActions.addAction("generateManifests", params -> { (3)
            try {
                List<Manifest> manifests = holder.getManifests();
                // Avoid relying on databind.
                Map<String, String> map = new LinkedHashMap<>();
                for (Manifest manifest : manifests) {
                    map.put(manifest.getName(), manifest.getContent());
                }
                return map;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });

        return generateManifestActions;
    }
1 BuildProducer を返す、または使用して、 BuildTimeActionBuildItem を作成します
2 BuildTimeActionBuildItem は、エクステンションの namespace で自動的にスコープ指定されます。
3 Here we add an action, that is the same as a request-response method. The method name (that can be called from js in the same way as any json-rpc service) is generateManifests. If there are any parameters, those will be available in a map (params)

You can also return a CompletableFuture/CompletionStage as an action, and if you want to stream data you need to use addSubscription (rather than addAction) and return a Flow.Publisher. Here you can not use Uni and Multi as we need to pass data between the deployment and runtime classpaths, so sticking to JDK classes is the safe option.

JsonRPC against a recorded value

Passing recorded data to the UI work the same as the above deployment classpath, except rather than a function, you pass the RuntimeValue (returned from your recorder)

@BuildStep(onlyIf = IsLocalDevelopment.class)
BuildTimeActionBuildItem createBuildTimeActions() {
    BuildTimeActionBuildItem actionBuildItem = new BuildTimeActionBuildItem();
    actionBuildItem.addAction("getMyRecordedValue", runtimeValue); (1)

    return actionBuildItem;
}
1 Set the RuntimeValue as returned from your recorder.

JsonRPC in the Webcomponent (Page)

You can use the built-in JsonRPC controller to access the any of the methods you defined. Runtime, Deployment and Recorded all works the same way in the WebComponent.

import { JsonRpc } from 'jsonrpc';

// ...

jsonRpc = new JsonRpc(this); // Passing in this will scope the RPC calls to your extension

// ...

connectedCallback() {
    super.connectedCallback();
    this.jsonRpc.getJoke().then(jsonRpcResponse => { (1)
        this._addToJokes(jsonRpcResponse.result); (2)
    });
}
1 Note the method getJoke corresponds to the method in your Java Service. This method returns a Promise with the JsonRPC result.
2 In this case, the result is an object, so we just add it to our list of jokes. This could also be an array if the server returned some collection.

JsonArray (または任意の Java コレクション) は、ブロッキングまたは非ブロッキングであれば配列を返します。それ以外の場合は、JsonObject が返されます。

呼び出されるメソッドにパラメーターを渡すこともできます。以下はその例です (ランタイム Java コード内)。

public Uni<JsonObject> clear(String name) { (1)
    Optional<Cache> cache = manager.getCache(name);
    if (cache.isPresent()) {
        return cache.get().invalidateAll().map((t) -> getJsonRepresentationForCache(cache.get()));
    } else {
        return Uni.createFrom().item(new JsonObject().put("name", name).put("size", -1));
    }
}
1 clear メソッドは name と呼ばれるパラメーターを 1 つ受け取ります。

Webcomponent (Javascript) の場合:

_clear(name) {
    this.jsonRpc.clear({name: name}).then(jsonRpcResponse => { (1)
        this._updateCache(jsonRpcResponse.result)
    });
}
1 name パラメーターが渡されます。

ストリーミングデータ

データを継続的に画面にストリーミングすることで、UI 画面を最新のデータで更新することができます。これは、 Multi (Java 側) と Observer (Javascript 側) で実行できます。

ストリーミングデータの Java 側:

public class JokesJsonRPCService {

    private final BroadcastProcessor<Joke> jokeStream = BroadcastProcessor.create();

    @PostConstruct
    void init() {
        Multi.createFrom().ticks().every(Duration.ofHours(4)).subscribe().with((item) -> {
            jokeStream.onNext(getJoke());
        });
    }

    public Multi<Joke> streamJokes() { (1)
        return jokeStream;
    }

    // ...
}
1 jokes をストリーミングする Multi を返します。

ストリーミングデータの Javascript 側:

this._observer = this.jsonRpc.streamJokes().onNext(jsonRpcResponse => { (1)
    this._addToJokes(jsonRpcResponse.result);
    this._numberOfJokes = this._numberOfJokes++;
});

// ...

this._observer.cancel(); (2)
1 メソッドを呼び出し (オプションでパラメーターを渡す)、次のイベントで呼び出されるコードを提供できます。
2 必要に応じて後でキャンセルできるように、オブザーバーのインスタンスを保管してください。

Workspace

Extensions can contribute actions to items in the workspace.

Dev UI workspace

Actions are really a JsonRPC Method that takes a workspace item as input.

To do this you can return/produce a WorkspaceActionBuildItem in your processor:

    @BuildStep(onlyIf = IsLocalDevelopment.class)
    WorkspaceActionBuildItem createWorkspaceActions() {
        ActionBuilder actionBuilder = Action.actionBuilder() (1)
                .label("Joke") (2)
                .function((t) -> { (3)
                    // Here do something with the input and return something
                    String content = t.content;
                    // ....
                    return t;
                })
                .display(Display.split) (4)
                .displayType(DisplayType.markdown) (5)
                .filter(Patterns.ANY_JAVA); (6)

        return new WorkspaceActionBuildItem(actionBuilder);
    }
1 Use the actionBuilder to create a new Action.
2 The label is what will be displayed in the action drop down in thw Workspace Page
3 Here the code that will execute if the user selects this action. You will receive some input (see the Input section below)
4 How the result should be displayed (see the Display section below)
5 What the result type would be (see the DisplayType section below)
6 Optional filter if this action only applies to certain items. Takes a regex as input, and some predefined regexes exisits in the the Patterns class

Input

The input your fuction receive is:

  • actionId: The unique (autoscoped) action id

  • name: The item (or file) name

  • path: The full path to that item (or file)

  • content: The content of this item (or file)

  • type: the type (example text/plain)

Display

Here you can set how the response should display in the Workspace page. Options are:

  • nothing: Nothing will be displayed

  • dialog: Content will be displayed in a dialog popup

  • replace: Content will replace the original (input) content

  • split: Content will display in a split screen (left/right)

  • notification: Content will in a notification

DisplayType

Depending on what your action does with the content input, your output might produce the following types:

  • raw: This be used as is (text)

  • code: This will be rendered in a code editor

  • markdown: This will display interperated markdown

  • html: This will display interperated html

  • image: This will display an image

Advanced: Custom cards

デフォルトの内蔵カードを使用したくない場合は、拡張ページに表示されるカードをカスタマイズすることができます。

これを行うには、提供されたカードの代わりに読み込まれる Web コンポーネントを提供し、これを Java プロセッサーに登録する必要があります。

cardPageBuildItem.setCustomCard("qwc-mycustom-card.js");

Javascript 側では、すべてのページにアクセスできます (リンクを作成する必要がある場合)。

import { pages } from 'build-time-data';

次に、以下のプロパティが渡されます。

  • extensionName

  • description

  • guide

  • namespace

  • logoUrl

static properties = {
    extensionName: {type: String},
    description: {type: String},
    guide: {type: String},
    namespace: {type: String},
    logoUrl: {type: String}
}

Dev UI Log

999-SNAPSHOT バージョンを使用してローカルアプリケーションを実行すると、Dev UI のフッターに Dev UI Log が表示されます。これは、ブラウザーと Quarkus アプリ間でやり取りされるすべての JSON RPC メッセージをデバッグする場合に役立ちます。

There might be cases where you develop an extension outside of Quarkus core (like Quarkiverse), so your quarkus version is not 999-SNAPSHOT. In that case you can still enable the Dev UI log with this application property: quarkus.dev-ui.show-json-rpc-log=true

Dev UI Json RPC Log

テスト

エクステンションには、以下をテストするテストを追加できます。

  • ビルド時データ

  • JsonRPC経由の実行時データ

これを pom に追加する必要があります。

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-vertx-http-dev-ui-tests</artifactId>
    <scope>test</scope>
</dependency>

そうすることで、テストを作成するための 2 つのベースクラスにアクセスできるようになります。

ビルド時データのテスト

ビルド時データを追加した場合の例:

cardPageBuildItem.addBuildTimeData("somekey", somevalue);

ビルド時データが正しく生成されていることをテストするには、 DevUIBuildTimeDataTest を拡張するテストを追加できます。

public class SomeTest extends DevUIBuildTimeDataTest {

    @RegisterExtension
    static final QuarkusDevModeTest config = new QuarkusDevModeTest().withEmptyApplication();

    public SomeTest() {
        super("io.quarkus.my-extension");
    }

    @Test
    public void testSomekey() throws Exception {
        JsonNode somekeyResponse = super.getBuildTimeData("somekey");
        Assertions.assertNotNull(somekeyResponse);

        // Check more values on somekeyResponse
    }

}

実行時データのテスト

実行時データレスポンスが含まれる JsonRPC サービスを追加した場合の例:

public boolean updateProperties(String content, String type) {
    // ...
}

updateProperties が JsonRPC 経由で正しく実行されることをテストするには、 DevUIJsonRPCTest を拡張するテストを追加できます。

The following dependency might also be required to be added to your pom, if it’s not yet added by other dependencies, otherwise Dev UI will not start during testing:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-vertx-http-deployment</artifactId>
    <scope>test</scope>
</dependency>
public class SomeTest extends DevUIJsonRPCTest {

    @RegisterExtension
    static final QuarkusDevModeTest config = new QuarkusDevModeTest().withEmptyApplication();

    public SomeTest() {
        super("io.quarkus.my-extension");
    }

    @Test
    public void testUpdateProperties() throws Exception {

        JsonNode updatePropertyResponse = super.executeJsonRPCMethod("updateProperty",
                Map.of(
                        "name", "quarkus.application.name",
                        "value", "changedByTest"));
        Assertions.assertTrue(updatePropertyResponse.asBoolean());

        // Get the properties to make sure it is changed
        JsonNode allPropertiesResponse = super.executeJsonRPCMethod("getAllValues");
        String applicationName = allPropertiesResponse.get("quarkus.application.name").asText();
        Assertions.assertEquals("changedByTest", applicationName);
    }
}

関連コンテンツ