diff --git a/extensions/cache/deployment/pom.xml b/extensions/cache/deployment/pom.xml
index 98136ccb95c4b..fa851fa0289a9 100644
--- a/extensions/cache/deployment/pom.xml
+++ b/extensions/cache/deployment/pom.xml
@@ -34,6 +34,15 @@
io.quarkus
quarkus-mutiny-deployment
+
+ io.quarkus
+ quarkus-vertx-http-dev-ui-spi
+
+
+ io.quarkus
+ quarkus-vertx-http-deployment
+ true
+
diff --git a/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/devconsole/CacheDevUiConsoleProcessor.java b/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/devconsole/CacheDevUiConsoleProcessor.java
new file mode 100644
index 0000000000000..b35eb2f58b73f
--- /dev/null
+++ b/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/devconsole/CacheDevUiConsoleProcessor.java
@@ -0,0 +1,28 @@
+package io.quarkus.cache.deployment.devconsole;
+
+import io.quarkus.cache.runtime.devconsole.CacheJsonRPCService;
+import io.quarkus.deployment.IsDevelopment;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
+import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
+import io.quarkus.devui.spi.page.CardPageBuildItem;
+import io.quarkus.devui.spi.page.Page;
+
+public class CacheDevUiConsoleProcessor {
+
+ @BuildStep(onlyIf = IsDevelopment.class)
+ CardPageBuildItem create(CurateOutcomeBuildItem bi) {
+ CardPageBuildItem pageBuildItem = new CardPageBuildItem("Cache");
+ pageBuildItem.addPage(Page.webComponentPageBuilder()
+ .title("Caches")
+ .componentLink("qwc-cache-caches.js")
+ .icon("font-awesome-solid:database"));
+
+ return pageBuildItem;
+ }
+
+ @BuildStep(onlyIf = IsDevelopment.class)
+ JsonRPCProvidersBuildItem createJsonRPCServiceForCache() {
+ return new JsonRPCProvidersBuildItem("Cache", CacheJsonRPCService.class);
+ }
+}
diff --git a/extensions/cache/deployment/src/main/resources/dev-ui/cache/qwc-cache-caches.js b/extensions/cache/deployment/src/main/resources/dev-ui/cache/qwc-cache-caches.js
new file mode 100644
index 0000000000000..3218fa3775036
--- /dev/null
+++ b/extensions/cache/deployment/src/main/resources/dev-ui/cache/qwc-cache-caches.js
@@ -0,0 +1,119 @@
+import { LitElement, html, css} from 'lit';
+import { JsonRpc } from 'jsonrpc';
+import '@vaadin/icon';
+import '@vaadin/button';
+import '@vaadin/text-field';
+import '@vaadin/text-area';
+import '@vaadin/form-layout';
+import '@vaadin/progress-bar';
+import '@vaadin/checkbox';
+import { until } from 'lit/directives/until.js';
+import '@vaadin/grid';
+import { columnBodyRenderer } from '@vaadin/grid/lit.js';
+import '@vaadin/grid/vaadin-grid-sort-column.js';
+
+export class QwcCacheCaches extends LitElement {
+
+ jsonRpc = new JsonRpc("Cache");
+
+ // Component style
+ static styles = css`
+ .button {
+ background-color: transparent;
+ cursor: pointer;
+ }
+ .clearIcon {
+ color: orange;
+ }
+ `;
+
+ // Component properties
+ static properties = {
+ "_caches": {state: true}
+ }
+
+ // Components callbacks
+
+ /**
+ * Called when displayed
+ */
+ connectedCallback() {
+ super.connectedCallback();
+ this.jsonRpc.getAll().then(jsonRpcResponse => {
+ this._caches = new Map();
+ jsonRpcResponse.result.forEach(c => {
+ this._caches.set(c.name, c);
+ });
+ });
+ }
+
+ /**
+ * Called when it needs to render the components
+ * @returns {*}
+ */
+ render() {
+ return html`${until(this._renderCacheTable(), html`Loading caches...`)}`;
+ }
+
+ // View / Templates
+
+ _renderCacheTable() {
+ if (this._caches) {
+ let caches = [...this._caches.values()];
+ return html`
+
+
+
+
+
+
+
+
+
+ `;
+ }
+ }
+
+ _actionRenderer(cache) {
+ return html`
+ this._clear(cache.name)} class="button">
+ Clear
+ `;
+ }
+
+ _nameRenderer(cache) {
+ return html`
+ this._refresh(cache.name)} class="button">
+
+
+ ${cache.name}`;
+ }
+
+ _clear(name) {
+ this.jsonRpc.clear({name: name}).then(jsonRpcResponse => {
+ this._updateCache(jsonRpcResponse.result)
+ });
+ }
+
+ _refresh(name) {
+ this.jsonRpc.refresh({name: name}).then(jsonRpcResponse => {
+ this._updateCache(jsonRpcResponse.result)
+ });
+ }
+
+ _updateCache(cache){
+ if (this._caches.has(cache.name) && cache.size !== -1) {
+ this._caches.set(cache.name, cache);
+ this.requestUpdate();
+ }
+ }
+
+}
+customElements.define('qwc-cache-caches', QwcCacheCaches);
diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/devconsole/CacheJsonRPCService.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/devconsole/CacheJsonRPCService.java
new file mode 100644
index 0000000000000..3bf4cfcc74384
--- /dev/null
+++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/devconsole/CacheJsonRPCService.java
@@ -0,0 +1,72 @@
+package io.quarkus.cache.runtime.devconsole;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import org.jboss.logging.Logger;
+
+import io.quarkus.cache.Cache;
+import io.quarkus.cache.CacheManager;
+import io.quarkus.cache.CaffeineCache;
+import io.quarkus.cache.runtime.caffeine.CaffeineCacheImpl;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+
+@ApplicationScoped
+public class CacheJsonRPCService {
+
+ @Inject
+ CacheManager manager;
+
+ @Inject
+ Logger logger;
+
+ public JsonArray getAll() {
+ Collection names = manager.getCacheNames();
+ List allCaches = new ArrayList<>(names.size());
+ for (String name : names) {
+ Optional cache = manager.getCache(name);
+ if (cache.isPresent() && cache.get() instanceof CaffeineCache) {
+ allCaches.add((CaffeineCache) cache.get());
+ }
+ }
+ allCaches.sort(Comparator.comparing(CaffeineCache::getName));
+
+ var array = new JsonArray();
+ for (CaffeineCache cc : allCaches) {
+ array.add(getJsonRepresentationForCache(cc));
+ }
+ return array;
+ }
+
+ private JsonObject getJsonRepresentationForCache(Cache cc) {
+ return new JsonObject().put("name", cc.getName()).put("size", ((CaffeineCacheImpl) cc).getSize());
+ }
+
+ public JsonObject clear(String name) {
+ Optional cache = manager.getCache(name);
+ if (cache.isPresent()) {
+ cache.get().invalidateAll().subscribe().asCompletionStage()
+ .thenAccept(x -> logger.infof("Cache %s cleared", name));
+ return getJsonRepresentationForCache(cache.get());
+ } else {
+ return new JsonObject().put("name", name).put("size", -1);
+ }
+ }
+
+ public JsonObject refresh(String name) {
+ Optional cache = manager.getCache(name);
+ if (cache.isPresent()) {
+ return getJsonRepresentationForCache(cache.get());
+ } else {
+ return new JsonObject().put("name", name).put("size", -1);
+ }
+ }
+
+}