From 17505543c19c3799a1433e2b47160b86691536d5 Mon Sep 17 00:00:00 2001 From: Ozan Gunalp Date: Tue, 23 Aug 2022 19:37:47 +0300 Subject: [PATCH] Kafka dev ui as a dev console route minor fixes for UI --- bom/application/pom.xml | 5 - build-parent/pom.xml | 12 + extensions/kafka-client/deployment/pom.xml | 6 +- .../deployment/KafkaBuildTimeConfig.java | 7 - .../deployment/KafkaBuildTimeUiConfig.java | 28 - .../client/deployment/KafkaProcessor.java | 207 +------ .../main/resources/dev-static/js/config.js | 2 + .../main/resources/dev-static/js}/kafka_ui.js | 2 - .../js}/pages/accessControlListPage.js | 14 +- .../js}/pages/consumerGroupDetailsPage.js | 19 +- .../dev-static/js}/pages/consumerGroupPage.js | 3 +- .../dev-static/js}/pages/messagesPage.js | 0 .../dev-static/js}/pages/navigator.js | 0 .../dev-static/js}/pages/nodesPage.js | 7 +- .../dev-static/js}/pages/schemaPage.js | 0 .../dev-static/js}/pages/topicsPage.js | 0 .../dev-static/js}/util/contentManagement.js | 0 .../dev-static/js}/util/datetimeUtil.js | 0 .../resources/dev-static/js}/util/spinner.js | 0 .../main/resources/dev-static/js}/web/web.js | 0 .../resources/dev-templates/embedded.html | 2 +- .../resources/dev-templates/kafka-dev-ui.html | 513 +++++++++++++++++ extensions/kafka-client/pom.xml | 1 - extensions/kafka-client/runtime/pom.xml | 2 +- .../ui/AbstractHttpRequestHandler.java | 29 +- .../client/runtime/ui/KafkaUiHandler.java | 6 - .../client/runtime/ui/KafkaUiRecorder.java | 34 +- extensions/kafka-client/ui/pom.xml | 212 ------- .../kafka-client/ui/src/main/webapp/config.js | 4 - .../ui/src/main/webapp/favicon.ico | Bin 4286 -> 0 bytes .../ui/src/main/webapp/index.html | 532 ------------------ .../kafka-client/ui/src/main/webapp/logo.png | Bin 31710 -> 0 bytes .../main/webapp/quarkus_icon_rgb_reverse.svg | 1 - .../ui/src/main/webapp/util/logo.js | 8 - extensions/vertx-http/deployment/pom.xml | 59 ++ .../devmode/console/DevConsoleProcessor.java | 48 +- .../spi/DevConsoleWebjarBuildItem.java | 105 ++++ .../devmode/RuntimeDevConsoleRoute.java | 18 +- 38 files changed, 807 insertions(+), 1079 deletions(-) delete mode 100644 extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeUiConfig.java create mode 100644 extensions/kafka-client/deployment/src/main/resources/dev-static/js/config.js rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/kafka_ui.js (73%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/pages/accessControlListPage.js (84%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/pages/consumerGroupDetailsPage.js (85%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/pages/consumerGroupPage.js (94%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/pages/messagesPage.js (100%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/pages/navigator.js (100%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/pages/nodesPage.js (90%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/pages/schemaPage.js (100%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/pages/topicsPage.js (100%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/util/contentManagement.js (100%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/util/datetimeUtil.js (100%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/util/spinner.js (100%) rename extensions/kafka-client/{ui/src/main/webapp => deployment/src/main/resources/dev-static/js}/web/web.js (100%) create mode 100644 extensions/kafka-client/deployment/src/main/resources/dev-templates/kafka-dev-ui.html delete mode 100644 extensions/kafka-client/ui/pom.xml delete mode 100644 extensions/kafka-client/ui/src/main/webapp/config.js delete mode 100644 extensions/kafka-client/ui/src/main/webapp/favicon.ico delete mode 100644 extensions/kafka-client/ui/src/main/webapp/index.html delete mode 100644 extensions/kafka-client/ui/src/main/webapp/logo.png delete mode 100644 extensions/kafka-client/ui/src/main/webapp/quarkus_icon_rgb_reverse.svg delete mode 100644 extensions/kafka-client/ui/src/main/webapp/util/logo.js create mode 100644 extensions/vertx-http/dev-console-spi/src/main/java/io/quarkus/devconsole/spi/DevConsoleWebjarBuildItem.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index d3902993aa765..83a9594b3528c 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -1265,11 +1265,6 @@ quarkus-kafka-client-deployment ${project.version} - - io.quarkus - quarkus-kafka-client-ui - ${project.version} - io.quarkus quarkus-kafka-streams diff --git a/build-parent/pom.xml b/build-parent/pom.xml index e2d6e9c1b05eb..2a9d7f36089c1 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -134,6 +134,8 @@ 4.6.1 + 0.9.15 + 1.9.1 6.1.2 3.6.1 5.62.2 @@ -309,6 +311,16 @@ bootstrap ${webjar.bootstrap.version} + + org.webjars + bootstrap-multiselect + ${webjar.bootstrap-multiselect.version} + + + org.webjars.npm + bootstrap-icons + ${webjar.bootstrap-icons.version} + org.webjars font-awesome diff --git a/extensions/kafka-client/deployment/pom.xml b/extensions/kafka-client/deployment/pom.xml index 78ae0ae217423..a7da8dfa0277f 100644 --- a/extensions/kafka-client/deployment/pom.xml +++ b/extensions/kafka-client/deployment/pom.xml @@ -46,12 +46,8 @@ io.quarkus - quarkus-vertx-http-deployment + quarkus-vertx-http-dev-console-spi - - io.quarkus - quarkus-kafka-client-ui - org.testcontainers testcontainers diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java index ab52f4b2dc7a1..420d8bd85a453 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java @@ -1,6 +1,5 @@ package io.quarkus.kafka.client.deployment; -import io.quarkus.runtime.annotations.ConfigDocSection; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; @@ -30,10 +29,4 @@ public class KafkaBuildTimeConfig { @ConfigItem public KafkaDevServicesBuildTimeConfig devservices; - /** - * Kafka UI configuration - */ - @ConfigItem - @ConfigDocSection - public KafkaBuildTimeUiConfig ui; } diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeUiConfig.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeUiConfig.java deleted file mode 100644 index d8dfd5706a5d9..0000000000000 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeUiConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.quarkus.kafka.client.deployment; - -import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.ConfigItem; - -@ConfigGroup -public class KafkaBuildTimeUiConfig { - - /** - * The path where Kafka UI is available. - * The value `/` is not allowed as it blocks the application from serving anything else. - * By default, this URL will be resolved as a path relative to `${quarkus.http.non-application-root-path}`. - */ - @ConfigItem(defaultValue = "kafka-ui") - public String rootPath; - /** - * Whether or not to enable Kafka Dev UI in non-development native mode. - */ - @ConfigItem(name = "handlerpath", defaultValue = "kafka-admin") - public String handlerRootPath; - /** - * Always include the UI. By default, this will only be included in dev and test. - * Setting this to true will also include the UI in Prod - */ - @ConfigItem(defaultValue = "false") - public boolean alwaysInclude; - -} diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java index 1557b9430add2..c29791d421569 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java @@ -1,21 +1,14 @@ package io.quarkus.kafka.client.deployment; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Scanner; import java.util.Set; import java.util.function.Consumer; import java.util.logging.Level; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.security.auth.spi.LoginModule; @@ -67,6 +60,7 @@ import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; +import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -77,13 +71,11 @@ import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.IndexDependencyBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.LogCategoryBuildItem; import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem; -import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSecurityProviderBuildItem; @@ -92,6 +84,9 @@ import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; import io.quarkus.deployment.pkg.NativeConfig; +import io.quarkus.dev.spi.DevModeType; +import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; +import io.quarkus.devconsole.spi.DevConsoleWebjarBuildItem; import io.quarkus.kafka.client.runtime.*; import io.quarkus.kafka.client.runtime.KafkaRuntimeConfigProducer; import io.quarkus.kafka.client.runtime.ui.KafkaTopicClient; @@ -108,18 +103,7 @@ import io.quarkus.kafka.client.serialization.ObjectMapperDeserializer; import io.quarkus.kafka.client.serialization.ObjectMapperSerializer; import io.quarkus.maven.dependency.GACT; -import io.quarkus.runtime.LaunchMode; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; -import io.quarkus.vertx.http.deployment.BodyHandlerBuildItem; -import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; -import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; -import io.quarkus.vertx.http.deployment.RouteBuildItem; -import io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem; -import io.quarkus.vertx.http.deployment.webjar.WebJarResourcesFilter; -import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem; -import io.vertx.core.Handler; -import io.vertx.ext.web.RoutingContext; public class KafkaProcessor { @@ -168,25 +152,11 @@ public class KafkaProcessor { static final DotName PARTITION_ASSIGNER = DotName .createSimple("org.apache.kafka.clients.consumer.internals.PartitionAssignor"); - // For the UI - private static final GACT KAFKA_UI_WEBJAR_ARTIFACT_KEY = new GACT("io.quarkus", "quarkus-kafka-client-ui", null, "jar"); - private static final String KAFKA_UI_WEBJAR_STATIC_RESOURCES_PATH = "META-INF/resources/kafka-ui/"; - private static final String FILE_TO_UPDATE = "config.js"; - private static final String LINE_TO_UPDATE = "export const api = '"; - private static final String LINE_FORMAT = LINE_TO_UPDATE + "%s';"; - private static final String UI_LINE_TO_UPDATE = "export const ui = '"; - private static final String UI_LINE_FORMAT = UI_LINE_TO_UPDATE + "%s';"; - private static final String LOGO_LINE_TO_UPDATE = "export const logo = '"; - private static final String LOGO_LINE_FORMAT = LOGO_LINE_TO_UPDATE + "%s';"; - private static final String UI_LOGO_PATH = "logo.png"; - // UI brandibg - private static final String BRANDING_DIR = "META-INF/branding/"; - private static final String BRANDING_LOGO_GENERAL = BRANDING_DIR + "logo.png"; - private static final String BRANDING_LOGO_MODULE = BRANDING_DIR + "quarkus-kafka-client-ui.png"; - private static final String BRANDING_STYLE_GENERAL = BRANDING_DIR + "style.css"; - private static final String BRANDING_STYLE_MODULE = BRANDING_DIR + "quarkus-kafka-client-ui.css"; - private static final String BRANDING_FAVICON_GENERAL = BRANDING_DIR + "favicon.ico"; - private static final String BRANDING_FAVICON_MODULE = BRANDING_DIR + "quarkus-kafka-client-ui.ico"; + private static final GACT DEVCONSOLE_WEBJAR_ARTIFACT_KEY = new GACT("io.quarkus", + "quarkus-kafka-client-deployment", null, "jar"); + private static final String DEVCONSOLE_WEBJAR_STATIC_RESOURCES_PATH = "dev-static/"; + public static final String KAFKA_ADMIN_PATH = "kafka-admin"; + public static final String KAFKA_RESOURCES_ROOT_PATH = "kafka-ui"; @BuildStep FeatureBuildItem feature() { @@ -535,153 +505,28 @@ public AdditionalBeanBuildItem kafkaClientBeans() { .build(); } - @BuildStep + @BuildStep(onlyIf = IsDevelopment.class) @Record(ExecutionTime.RUNTIME_INIT) public void registerKafkaUiExecHandler( - BuildProducer routeProducer, - KafkaUiRecorder recorder, - LaunchModeBuildItem launchMode, - HttpRootPathBuildItem httpRootPathBuildItem, - NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, - KafkaBuildTimeConfig buildConfig, - BodyHandlerBuildItem bodyHandlerBuildItem, - ShutdownContextBuildItem shutdownContext) { - - if (shouldIncludeUi(launchMode, buildConfig)) { - String handlerPath = nonApplicationRootPathBuildItem.resolvePath(buildConfig.ui.handlerRootPath); - Handler executionHandler = recorder.kafkaControlHandler(); - HttpRootPathBuildItem.Builder requestBuilder = httpRootPathBuildItem.routeBuilder() - .routeFunction(handlerPath, recorder.routeFunction(bodyHandlerBuildItem.getHandler())) - .handler(executionHandler) - .routeConfigKey("quarkus.kafka-client-ui.root-path") - .displayOnNotFoundPage("Kafka UI Endpoint"); - - routeProducer.produce(requestBuilder.build()); - } - } - - @BuildStep - List uiBrandingFiles() { - return Stream.of(BRANDING_LOGO_GENERAL, - BRANDING_STYLE_GENERAL, - BRANDING_FAVICON_GENERAL, - BRANDING_LOGO_MODULE, - BRANDING_STYLE_MODULE, - BRANDING_FAVICON_MODULE).map(HotDeploymentWatchedFileBuildItem::new) - .collect(Collectors.toList()); + BuildProducer routeProducer, + KafkaUiRecorder recorder) { + routeProducer.produce(DevConsoleRouteBuildItem.builder() + .method("POST") + .handler(recorder.kafkaControlHandler()) + .path(KAFKA_ADMIN_PATH) + .bodyHandlerRequired() + .build()); } - @BuildStep - void getKafkaUiFinalDestination( - HttpRootPathBuildItem httpRootPath, - NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, - LaunchModeBuildItem launchMode, - KafkaBuildTimeConfig buildConfig, - BuildProducer webJarBuildProducer) { - - if (shouldIncludeUi(launchMode, buildConfig)) { - - if ("/".equals(buildConfig.ui.rootPath)) { - throw new ConfigurationException( - "quarkus.kafka-client-ui.root-path was set to \"/\", this is not allowed as it blocks the application from serving anything else.", - Collections.singleton("quarkus.kafka-client-ui.root-path")); - } - - String devUiPath = nonApplicationRootPathBuildItem.resolvePath("dev"); - String kafkaUiPath = nonApplicationRootPathBuildItem.resolvePath(buildConfig.ui.rootPath); - String kafkaHandlerPath = nonApplicationRootPathBuildItem.resolvePath(buildConfig.ui.handlerRootPath); - webJarBuildProducer.produce( - WebJarBuildItem.builder().artifactKey(KAFKA_UI_WEBJAR_ARTIFACT_KEY) - .root(KAFKA_UI_WEBJAR_STATIC_RESOURCES_PATH) - .filter(new WebJarResourcesFilter() { - @Override - public WebJarResourcesFilter.FilterResult apply(String fileName, InputStream file) - throws IOException { - if (fileName.endsWith(FILE_TO_UPDATE)) { - String content = new String(file.readAllBytes(), StandardCharsets.UTF_8); - content = updateUrl(content, kafkaHandlerPath, - LINE_TO_UPDATE, - LINE_FORMAT); - content = updateUrl(content, kafkaUiPath, - UI_LINE_TO_UPDATE, - UI_LINE_FORMAT); - content = updateUrl(content, - getLogoUrl(launchMode, kafkaUiPath + "/" + UI_LOGO_PATH, - kafkaUiPath + "/" + UI_LOGO_PATH), - LOGO_LINE_TO_UPDATE, - LOGO_LINE_FORMAT); - - return new WebJarResourcesFilter.FilterResult( - new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), true); - } - - return new WebJarResourcesFilter.FilterResult(file, false); - } - }) - .build()); + @BuildStep(onlyIf = IsDevelopment.class) + public DevConsoleWebjarBuildItem setupWebJar(LaunchModeBuildItem launchModeBuildItem) { + if (launchModeBuildItem.getDevModeType().orElse(null) != DevModeType.LOCAL) { + return null; } + return DevConsoleWebjarBuildItem.builder().artifactKey(DEVCONSOLE_WEBJAR_ARTIFACT_KEY) + .root(DEVCONSOLE_WEBJAR_STATIC_RESOURCES_PATH) + .routeRoot(KAFKA_RESOURCES_ROOT_PATH) + .build(); } - @BuildStep - @Record(ExecutionTime.RUNTIME_INIT) - void registerKafkaUiHandler( - BuildProducer routeProducer, - KafkaUiRecorder recorder, - LaunchModeBuildItem launchMode, - NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, - KafkaBuildTimeConfig buildConfig, - WebJarResultsBuildItem webJarResultsBuildItem, - ShutdownContextBuildItem shutdownContext) { - - WebJarResultsBuildItem.WebJarResult result = webJarResultsBuildItem.byArtifactKey(KAFKA_UI_WEBJAR_ARTIFACT_KEY); - if (result == null) { - return; - } - - if (shouldIncludeUi(launchMode, buildConfig)) { - String kafkaUiPath = nonApplicationRootPathBuildItem.resolvePath(buildConfig.ui.rootPath); - String finalDestination = result.getFinalDestination(); - - Handler handler = recorder.uiHandler(finalDestination, - kafkaUiPath, result.getWebRootConfigurations(), shutdownContext); - routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() - .route(buildConfig.ui.rootPath) - .displayOnNotFoundPage("Kafka UI") - .routeConfigKey("quarkus.kafka-client.ui.root-path") - .handler(handler) - .build()); - - routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() - .route(buildConfig.ui.rootPath + "*") - .handler(handler) - .build()); - - } - } - - // In dev mode, when you click on the logo, you should go to Dev UI - private String getLogoUrl(LaunchModeBuildItem launchMode, String devUIValue, String defaultValue) { - if (launchMode.getLaunchMode().equals(LaunchMode.DEVELOPMENT)) { - return devUIValue; - } - return defaultValue; - } - - private String updateUrl(String original, String path, String lineStartsWith, String format) { - try (Scanner scanner = new Scanner(original)) { - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - if (line.trim().startsWith(lineStartsWith)) { - String newLine = String.format(format, path); - return original.replace(line.trim(), newLine); - } - } - } - - return original; - } - - private static boolean shouldIncludeUi(LaunchModeBuildItem launchMode, KafkaBuildTimeConfig config) { - return launchMode.getLaunchMode().isDevOrTest() || config.ui.alwaysInclude; - } } diff --git a/extensions/kafka-client/deployment/src/main/resources/dev-static/js/config.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/config.js new file mode 100644 index 0000000000000..130b130828fbe --- /dev/null +++ b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/config.js @@ -0,0 +1,2 @@ +export const api = '/q/dev/io.quarkus.quarkus-kafka-client/kafka-admin'; +export const ui = 'kafka-ui'; \ No newline at end of file diff --git a/extensions/kafka-client/ui/src/main/webapp/kafka_ui.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/kafka_ui.js similarity index 73% rename from extensions/kafka-client/ui/src/main/webapp/kafka_ui.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/kafka_ui.js index c07fdfd378217..36667854c1944 100644 --- a/extensions/kafka-client/ui/src/main/webapp/kafka_ui.js +++ b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/kafka_ui.js @@ -1,10 +1,8 @@ import Navigator from './pages/navigator.js' -import {setLogo} from "./util/logo.js"; const navigator = new Navigator(); $(document).ready( () => { - setLogo(); navigator.navigateToDefaultPage(); } ); diff --git a/extensions/kafka-client/ui/src/main/webapp/pages/accessControlListPage.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/accessControlListPage.js similarity index 84% rename from extensions/kafka-client/ui/src/main/webapp/pages/accessControlListPage.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/accessControlListPage.js index 2e396ec39874a..bf10bd05b5e66 100644 --- a/extensions/kafka-client/ui/src/main/webapp/pages/accessControlListPage.js +++ b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/accessControlListPage.js @@ -2,7 +2,7 @@ import {doPost, errorPopUp} from "../web/web.js"; import {createTableItem} from "../util/contentManagement.js"; import {toggleSpinner} from "../util/spinner.js"; -export default class AccessControlListPage{ +export default class AccessControlListPage { constructor(containerId) { this.containerId = containerId; Object.getOwnPropertyNames(AccessControlListPage.prototype).forEach((key) => { @@ -17,15 +17,15 @@ export default class AccessControlListPage{ const req = { action: "getAclInfo" }; + toggleSpinner(this.containerId); doPost(req, (data) => { - let that = this; - setTimeout(function () { - that.updateInfo(data); - toggleSpinner(that.containerId); + setTimeout(() => { + this.updateInfo(data); + toggleSpinner(this.containerId); }, 2000); }, data => { errorPopUp("Error getting Kafka ACL info: ", data); - }); + }); } updateInfo(data) { @@ -33,7 +33,7 @@ export default class AccessControlListPage{ $('#acluster-controller').html(data.broker); $('#acluster-acl').html(data.aclOperations); - const acls = data.entires; + const acls = data.entries; let aclTable = $('#acl-table tbody'); aclTable.empty(); for (let i = 0; i < acls.length; i++) { diff --git a/extensions/kafka-client/ui/src/main/webapp/pages/consumerGroupDetailsPage.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/consumerGroupDetailsPage.js similarity index 85% rename from extensions/kafka-client/ui/src/main/webapp/pages/consumerGroupDetailsPage.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/consumerGroupDetailsPage.js index cfd39d4d8be2d..3766588ce9292 100644 --- a/extensions/kafka-client/ui/src/main/webapp/pages/consumerGroupDetailsPage.js +++ b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/consumerGroupDetailsPage.js @@ -16,7 +16,7 @@ export default class ConsumerGroupDetailsPage { consumerGroupsTable.empty(); for (let i = 0; i < membersData.length; i++) { const d = membersData[i]; - const groupId = "group-" + window.crypto.randomUUID(); + const groupId = "group-" + d.memberId; let tableRow = $(""); let collapseRow; @@ -37,18 +37,17 @@ export default class ConsumerGroupDetailsPage { tableRow.append(createTableItem(d.host)); tableRow.append(createTableItem("" + new Set(d.partitions.map(x => x.partition)).size)); tableRow.append(createTableItem("" + d.partitions.map(x => x.lag).reduce((l, r) => l + r, 0))); - consumerGroupsTable.append(tableRow); if (d.partitions.length > 0) { const content = this.createConsumerGroupCollapseInfo(d); - tableRow - .addClass("pointer") - .click(collapseRow.collapse); - consumerGroupsTable.append( - collapseRow - .getCollapseContent(tableRow.children().length, content) - .addClass("no-hover") - ); + tableRow.addClass("pointer") + tableRow.click(() => collapseRow.collapse()); + consumerGroupsTable.append(tableRow); + consumerGroupsTable.append(collapseRow + .getCollapseContent(tableRow.children().length, content) + .addClass("no-hover")); + } else { + consumerGroupsTable.append(tableRow); } } } diff --git a/extensions/kafka-client/ui/src/main/webapp/pages/consumerGroupPage.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/consumerGroupPage.js similarity index 94% rename from extensions/kafka-client/ui/src/main/webapp/pages/consumerGroupPage.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/consumerGroupPage.js index 4a8500fab0c0e..51928af67a7e3 100644 --- a/extensions/kafka-client/ui/src/main/webapp/pages/consumerGroupPage.js +++ b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/consumerGroupPage.js @@ -40,8 +40,7 @@ export default class ConsumerGroupPage { tableRow.append(createTableItem(d.protocol)); tableRow.append(createTableItem(d.members.length)); tableRow.append(createTableItem(d.lag)); - const self = this; - tableRow.click(() => self.navigator.navigateTo(pages.CONSUMER_GROUPS_DETAILS, [d.name, d.members])); + tableRow.click(() => this.navigator.navigateTo(pages.CONSUMER_GROUPS_DETAILS, [d.name, d.members])); consumerGroupsTable.append(tableRow); } } diff --git a/extensions/kafka-client/ui/src/main/webapp/pages/messagesPage.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/messagesPage.js similarity index 100% rename from extensions/kafka-client/ui/src/main/webapp/pages/messagesPage.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/messagesPage.js diff --git a/extensions/kafka-client/ui/src/main/webapp/pages/navigator.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/navigator.js similarity index 100% rename from extensions/kafka-client/ui/src/main/webapp/pages/navigator.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/navigator.js diff --git a/extensions/kafka-client/ui/src/main/webapp/pages/nodesPage.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/nodesPage.js similarity index 90% rename from extensions/kafka-client/ui/src/main/webapp/pages/nodesPage.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/nodesPage.js index ad8675344e22b..94b2b1e6a270d 100644 --- a/extensions/kafka-client/ui/src/main/webapp/pages/nodesPage.js +++ b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/nodesPage.js @@ -17,10 +17,9 @@ export default class NodesPage { action: "getInfo" }; doPost(req, (data) => { - let that = this; - setTimeout(function () { - that.updateInfo(data); - toggleSpinner(that.containerId); + setTimeout(() => { + this.updateInfo(data); + toggleSpinner(this.containerId); }, 2000); }, data => { errorPopUp("Error getting Kafka info: ", data); diff --git a/extensions/kafka-client/ui/src/main/webapp/pages/schemaPage.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/schemaPage.js similarity index 100% rename from extensions/kafka-client/ui/src/main/webapp/pages/schemaPage.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/schemaPage.js diff --git a/extensions/kafka-client/ui/src/main/webapp/pages/topicsPage.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/topicsPage.js similarity index 100% rename from extensions/kafka-client/ui/src/main/webapp/pages/topicsPage.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/pages/topicsPage.js diff --git a/extensions/kafka-client/ui/src/main/webapp/util/contentManagement.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/util/contentManagement.js similarity index 100% rename from extensions/kafka-client/ui/src/main/webapp/util/contentManagement.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/util/contentManagement.js diff --git a/extensions/kafka-client/ui/src/main/webapp/util/datetimeUtil.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/util/datetimeUtil.js similarity index 100% rename from extensions/kafka-client/ui/src/main/webapp/util/datetimeUtil.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/util/datetimeUtil.js diff --git a/extensions/kafka-client/ui/src/main/webapp/util/spinner.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/util/spinner.js similarity index 100% rename from extensions/kafka-client/ui/src/main/webapp/util/spinner.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/util/spinner.js diff --git a/extensions/kafka-client/ui/src/main/webapp/web/web.js b/extensions/kafka-client/deployment/src/main/resources/dev-static/js/web/web.js similarity index 100% rename from extensions/kafka-client/ui/src/main/webapp/web/web.js rename to extensions/kafka-client/deployment/src/main/resources/dev-static/js/web/web.js diff --git a/extensions/kafka-client/deployment/src/main/resources/dev-templates/embedded.html b/extensions/kafka-client/deployment/src/main/resources/dev-templates/embedded.html index 312a6c5268e02..dac4cf81f4150 100644 --- a/extensions/kafka-client/deployment/src/main/resources/dev-templates/embedded.html +++ b/extensions/kafka-client/deployment/src/main/resources/dev-templates/embedded.html @@ -1,3 +1,3 @@ - + Kafka UI diff --git a/extensions/kafka-client/deployment/src/main/resources/dev-templates/kafka-dev-ui.html b/extensions/kafka-client/deployment/src/main/resources/dev-templates/kafka-dev-ui.html new file mode 100644 index 0000000000000..f07f33984441f --- /dev/null +++ b/extensions/kafka-client/deployment/src/main/resources/dev-templates/kafka-dev-ui.html @@ -0,0 +1,513 @@ +{#include main fluid=true} +{#style} +html { +min-height: 90vh; +min-width: 100vh; +} + +body { +min-height: 90vh; +min-width: 100vh; +} + +.row-holder { +padding: 0; +margin: 0; + +} + +.row:after { +content: ""; +display: table; +clear: both; +} + +.content-holder { +height: auto; +min-height: 90vh; +} + +.link { +background: none; +border: none; +} + +.top-margin { +margin-top: 1em; +} + +.left-margin { +margin-left: 1em; +} + +.left-padding { +padding-left: 1em; +} + +.shown { +display: flex; +height: auto; +min-width: 100%; +} + +.text-shown { +display: inline; +} + +.hidden { +display: none +} + +.nav-item:hover > .nav-row > a { +background-color: #005fff; +color: #e9ecef; +} + +.nav-item:hover > .nav-row > i { +background-color: #005fff; +color: #e9ecef; +} + +#navbar-list > .nav-item:hover { +background-color: #005fff; +color: #e9ecef; +} + +.table-hover:hover { +cursor: pointer; +} + +.multiselect-container > li > a > label { +padding-left: 15px !important; +} + +.page { +min-height: calc(100vh - 135px); +} + +.table-hover:hover { +cursor: pointer; +} + +.pointer { +cursor: pointer; +} + +.no-hover { +background-color: white; +cursor: default; +} + +.no-hover:hover { +background-color: white !important; +cursor: default; +} + +.icon-rotated { +transform: rotate(90deg); +} + +.navbar-brand img { +border-right: 1px solid darkgrey; +padding-right: 10px; +margin-right: 5px; +} + +.navbar-brand { +padding: 0; +margin: 0; +} + +#nav-menu-panel { +padding: 0px; +} + +.float-plus-btn { +position: fixed; +bottom: 60px; +right: 60px; +border-radius: 100%; +height: 50px; +width: 50px; +} + +.breadcrumb-item::before { +float: left; +padding-right: 0.5rem; +color: #007bff; +content: "〉"; +} + +.breadcrumb-item + .breadcrumb-item::before { +float: left; +padding-right: 0.5rem; +color: #007bff; +content: "〉"; +} + +.breadcrumb { +background-color: #343a40; +margin-bottom: 0; +padding: 0 0 0 5px; +} + +.bi-trash-fill:hover { +color: #007bff; +} + +.collapse-content { +max-width: 1200px; +} + +.thead-multiselect { +background-color: #343a40; +color: white; +border: 0px; +font-weight: bold; +} + +.thead-text { +color: white; +} + +#msg-table-holder { +min-width: 100%; +} +{/style} +{#styleref} + + +{/styleref} +{#scriptref} + + +{/scriptref} +{#title}Kafka Dev UI{/title} +{#body} +
+ + + + + + +
+
+
+ + + + + + + + + + + + +
Topic NameIdPartitions countNumber of msg
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + +
Offset + + Timestamp KeyValue
+
+ +
+
+ + +
+
+
+ + + + + + + + + + + + + +
StateIdCoordinatorProtocolMembersLag(Sum)
+
+
+
+
+ + + + + + + + + + + + +
Member IDHostPartitionsLag(Sum)
+
+
+
+
+
+ Kafka cluster id: 
+ Controller node (broker): 
+ ACL operations: 
+
+
+

Cluster nodes

+
+ + + + + + + + + + +
IdHostPort
+
+
+ +
+
+{/body} +{/include} \ No newline at end of file diff --git a/extensions/kafka-client/pom.xml b/extensions/kafka-client/pom.xml index dd7448eb06106..a0e0885d2b170 100644 --- a/extensions/kafka-client/pom.xml +++ b/extensions/kafka-client/pom.xml @@ -15,7 +15,6 @@ pom - ui deployment runtime diff --git a/extensions/kafka-client/runtime/pom.xml b/extensions/kafka-client/runtime/pom.xml index 0e037445a128e..1d237acadf213 100644 --- a/extensions/kafka-client/runtime/pom.xml +++ b/extensions/kafka-client/runtime/pom.xml @@ -61,7 +61,7 @@ io.quarkus - quarkus-vertx-http + quarkus-vertx-http-dev-console-runtime-spi diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/AbstractHttpRequestHandler.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/AbstractHttpRequestHandler.java index bc83093041b33..ede6bc54f21f8 100644 --- a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/AbstractHttpRequestHandler.java +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/AbstractHttpRequestHandler.java @@ -2,24 +2,15 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ManagedContext; -import io.quarkus.security.identity.CurrentIdentityAssociation; -import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.vertx.http.runtime.CurrentVertxRequest; -import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; import io.vertx.core.Handler; import io.vertx.core.http.HttpServerRequest; import io.vertx.ext.web.RoutingContext; public abstract class AbstractHttpRequestHandler implements Handler { - private final CurrentIdentityAssociation currentIdentityAssociation; - private final CurrentVertxRequest currentVertxRequest; private final ManagedContext currentManagedContext; private final Handler currentManagedContextTerminationHandler; - public AbstractHttpRequestHandler(CurrentIdentityAssociation currentIdentityAssociation, - CurrentVertxRequest currentVertxRequest) { - this.currentIdentityAssociation = currentIdentityAssociation; - this.currentVertxRequest = currentVertxRequest; + public AbstractHttpRequestHandler() { this.currentManagedContext = Arc.container().requestContext(); this.currentManagedContextTerminationHandler = e -> currentManagedContext.terminate(); } @@ -29,7 +20,7 @@ public AbstractHttpRequestHandler(CurrentIdentityAssociation currentIdentityAsso public void handle(final RoutingContext ctx) { if (currentManagedContext.isActive()) { - handleWithIdentity(ctx); + doHandle(ctx); } else { currentManagedContext.activate(); @@ -39,7 +30,7 @@ public void handle(final RoutingContext ctx) { .closeHandler(currentManagedContextTerminationHandler); try { - handleWithIdentity(ctx); + doHandle(ctx); } catch (Throwable t) { currentManagedContext.terminate(); throw t; @@ -70,20 +61,6 @@ public void doHandle(RoutingContext ctx) { } } - private void handleWithIdentity(final RoutingContext ctx) { - if (currentIdentityAssociation != null) { - QuarkusHttpUser existing = (QuarkusHttpUser) ctx.user(); - if (existing != null) { - SecurityIdentity identity = existing.getSecurityIdentity(); - currentIdentityAssociation.setIdentity(identity); - } else { - currentIdentityAssociation.setIdentity(QuarkusHttpUser.getSecurityIdentity(ctx, null)); - } - } - currentVertxRequest.setCurrent(ctx); - doHandle(ctx); - } - public abstract void handlePost(RoutingContext event); public abstract void handleGet(RoutingContext event); diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaUiHandler.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaUiHandler.java index 99d5b9d550813..8b7d916de765b 100644 --- a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaUiHandler.java +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaUiHandler.java @@ -13,17 +13,11 @@ import io.quarkus.kafka.client.runtime.ui.model.request.KafkaMessageCreateRequest; import io.quarkus.kafka.client.runtime.ui.model.request.KafkaMessagesRequest; import io.quarkus.kafka.client.runtime.ui.model.request.KafkaOffsetRequest; -import io.quarkus.security.identity.CurrentIdentityAssociation; -import io.quarkus.vertx.http.runtime.CurrentVertxRequest; import io.vertx.core.http.HttpServerRequest; import io.vertx.ext.web.RoutingContext; public class KafkaUiHandler extends AbstractHttpRequestHandler { - public KafkaUiHandler(CurrentIdentityAssociation currentIdentityAssociation, CurrentVertxRequest currentVertxRequest) { - super(currentIdentityAssociation, currentVertxRequest); - } - @Override public void handlePost(RoutingContext event) { if (event.body() == null) { diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaUiRecorder.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaUiRecorder.java index 21d89717ffcbc..90afe8521cc11 100644 --- a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaUiRecorder.java +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaUiRecorder.java @@ -1,18 +1,7 @@ package io.quarkus.kafka.client.runtime.ui; -import java.util.List; -import java.util.function.Consumer; - -import io.quarkus.arc.Arc; -import io.quarkus.arc.InstanceHandle; -import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.security.identity.CurrentIdentityAssociation; -import io.quarkus.vertx.http.runtime.CurrentVertxRequest; -import io.quarkus.vertx.http.runtime.devmode.FileSystemStaticHandler; -import io.quarkus.vertx.http.runtime.webjar.WebJarStaticHandler; import io.vertx.core.Handler; -import io.vertx.ext.web.Route; import io.vertx.ext.web.RoutingContext; /** @@ -22,28 +11,7 @@ public class KafkaUiRecorder { public Handler kafkaControlHandler() { - return new KafkaUiHandler(getCurrentIdentityAssociation(), - Arc.container().instance(CurrentVertxRequest.class).get()); - } - - public Consumer routeFunction(Handler bodyHandler) { - return route -> route.handler(bodyHandler); + return new KafkaUiHandler(); } - public Handler uiHandler(String finalDestination, String uiPath, - List webRootConfigurations, - ShutdownContext shutdownContext) { - WebJarStaticHandler handler = new WebJarStaticHandler(finalDestination, uiPath, webRootConfigurations); - shutdownContext.addShutdownTask(new ShutdownContext.CloseRunnable(handler)); - return handler; - } - - private CurrentIdentityAssociation getCurrentIdentityAssociation() { - InstanceHandle identityAssociations = Arc.container() - .instance(CurrentIdentityAssociation.class); - if (identityAssociations.isAvailable()) { - return identityAssociations.get(); - } - return null; - } } diff --git a/extensions/kafka-client/ui/pom.xml b/extensions/kafka-client/ui/pom.xml deleted file mode 100644 index 31960a9ee918e..0000000000000 --- a/extensions/kafka-client/ui/pom.xml +++ /dev/null @@ -1,212 +0,0 @@ - - - 4.0.0 - - - io.quarkus - quarkus-kafka-client-parent - 999-SNAPSHOT - - - quarkus-kafka-client-ui - jar - Quarkus - Kafka - Client - UI - - - kafka-ui - 0.9.15 - 1.9.1 - - - - - - org.webjars - bootstrap - provided - - - org.webjars - font-awesome - provided - - - org.webjars - jquery - provided - - - org.webjars - bootstrap-multiselect - ${bootstrap-mutiliselect.version} - - - org.webjars.npm - bootstrap-icons - ${bootstrap-icons.version} - - - - - - - - maven-resources-plugin - - - copy-web - generate-sources - - copy-resources - - - ${project.build.directory}/classes/META-INF/resources/${path.kafkaui} - - - ${basedir}/src/main/webapp - - **/*.* - - - - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.3.0 - - - install-js - generate-sources - - unpack - - - - - - org.webjars - bootstrap - ${webjar.bootstrap.version} - jar - true - ${project.build.directory}/classes/META-INF/resources/${path.kafkaui}/css/ - **/bootstrap.min.css, **/bootstrap.min.css.map - - - - - - org.webjars - bootstrap - ${webjar.bootstrap.version} - jar - true - ${project.build.directory}/classes/META-INF/resources/${path.kafkaui}/js/ - **/bootstrap.bundle.min.js, **/bootstrap.bundle.min.js.map - - - - - - org.webjars - bootstrap-multiselect - ${bootstrap-mutiliselect.version} - jar - true - ${project.build.directory}/classes/META-INF/resources/${path.kafkaui}/js/ - **/bootstrap-multiselect.js - - - - - - org.webjars - bootstrap-multiselect - ${bootstrap-mutiliselect.version} - jar - true - ${project.build.directory}/classes/META-INF/resources/${path.kafkaui}/css/ - **/bootstrap-multiselect.css - - - - - - org.webjars.npm - bootstrap-icons - ${bootstrap-icons.version} - jar - true - ${project.build.directory}/classes/META-INF/resources/${path.kafkaui}/css/ - **/font/bootstrap-icons.css - - - - - - org.webjars.npm - bootstrap-icons - ${bootstrap-icons.version} - jar - true - ${project.build.directory}/classes/META-INF/resources/${path.kafkaui}/css/fonts/ - **/font/fonts/ - - - - - - - - org.webjars - jquery - ${webjar.jquery.version} - jar - true - ${project.build.directory}/classes/META-INF/resources/${path.kafkaui}/js/ - **/jquery.min.js, **/jquery.min.js.map - - - - - - - org.webjars - font-awesome - ${webjar.font-awesome.version} - jar - true - ${project.build.directory}/classes/META-INF/resources/${path.kafkaui}/fontawesome/css - **/css/all.min.css - - - - - - org.webjars - font-awesome - ${webjar.font-awesome.version} - jar - true - ${project.build.directory}/classes/META-INF/resources/${path.kafkaui}/fontawesome/webfonts - **/webfonts/**.* - - - - - - - - - - - - - - - diff --git a/extensions/kafka-client/ui/src/main/webapp/config.js b/extensions/kafka-client/ui/src/main/webapp/config.js deleted file mode 100644 index 1c141c4c1e97c..0000000000000 --- a/extensions/kafka-client/ui/src/main/webapp/config.js +++ /dev/null @@ -1,4 +0,0 @@ -export const api = '/kafka-admin'; -export const logo = 'quarkus_icon_rgb_reverse.svg'; -export const faviconLogo = 'favicon.ico'; -export const ui = ''; \ No newline at end of file diff --git a/extensions/kafka-client/ui/src/main/webapp/favicon.ico b/extensions/kafka-client/ui/src/main/webapp/favicon.ico deleted file mode 100644 index b4ef4208a6f489de1c17dd3791a3691ae77dee0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmeH~OGs2v7{|XECtu~LmF8KXiW zL}V0H3yD@?4`w0gq6cmw(o~dL)FP#^`FFlMoI7`%nH=YHqh?{~h(y>l*+ zPW&Y&6aS}&QABY>lrBW5C|?Ncl_uu?H}4Uc?kqBVyIIiq{z{&8;#MKV^`fN26boTp zk>1)Y^Li!iWlT+Uuz@{dGOtV4Kpu=xrYO>OA}HiL2FB=Q(4LlNP<;dbX+I z^Lm%2o%4ksx=N3c);)N0E@wfu04e#{5t>xyIhyTW%=0VKN%wl+WSdrP;8N7Sai79j0 zviYym5vs~sF*Z60*(6_HPmYhFx%QOEzvDd|sQhOp8PR`s2VOtDCs9g2pU=wPb_;gV zTSU@Umpb&P{`sBSQMn0Iw;jKOzVf*~O34lr-9-q+EBo+=e|&cWK40If)|Qq(Jw1)4 zrY1#rEF1gp-~&bU9f?%qH=pyjR>a`Ou?!^!%(vNWFq_Tj>gvMW+?*mjW*yHt_;zfe zZUj@Dngf6Q*VfPGga}-1s6|Ul3#?YFbXVD{>69P?PObAR`;-iPy-&*x9B<;94P=g%dbzqRKlAI6@}-%I-b@PR3x6OZ2? vB1e1% - - - - kafka-devUI - - - - - - - - - - -
- - - - - - -
-
-
- - - - - - - - - - - - -
Topic NameIdPartitions countNumber of msg
-
- -
-
-
-
-
- - - - - - - - - - - - - - -
Offset - - Timestamp KeyValue
-
- -
-
- - -
-
-
- - - - - - - - - - - - - -
StateIdCoordinatorProtocolMembersLag(Sum)
-
-
-
-
- - - - - - - - - - - - -
Member IDHostPartitionsLag(Sum)
-
-
-
-
-
- Kafka cluster id: 
- Controller node (broker): 
- ACL operations: 
-
-
-

Cluster nodes

-
- - - - - - - - - - -
IdHostPort
-
-
- -
-
- - - - - - - - - - diff --git a/extensions/kafka-client/ui/src/main/webapp/logo.png b/extensions/kafka-client/ui/src/main/webapp/logo.png deleted file mode 100644 index 6a1626104eb98f63a7dd134135784fbb0eca1d0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31710 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 diff --git a/extensions/kafka-client/ui/src/main/webapp/quarkus_icon_rgb_reverse.svg b/extensions/kafka-client/ui/src/main/webapp/quarkus_icon_rgb_reverse.svg deleted file mode 100644 index 1969e1e886af3..0000000000000 --- a/extensions/kafka-client/ui/src/main/webapp/quarkus_icon_rgb_reverse.svg +++ /dev/null @@ -1 +0,0 @@ -quarkus_icon_rgb_1024px_reverse \ No newline at end of file diff --git a/extensions/kafka-client/ui/src/main/webapp/util/logo.js b/extensions/kafka-client/ui/src/main/webapp/util/logo.js deleted file mode 100644 index 2f40efa91fb37..0000000000000 --- a/extensions/kafka-client/ui/src/main/webapp/util/logo.js +++ /dev/null @@ -1,8 +0,0 @@ -import {faviconLogo, logo} from "../config.js" - -export function setLogo(){ - $("#navbar-logo") - .attr("src", logo); - $("#favicon") - .attr("href", faviconLogo); -} \ No newline at end of file diff --git a/extensions/vertx-http/deployment/pom.xml b/extensions/vertx-http/deployment/pom.xml index f73b67dd3c39e..a5154ddd7b939 100644 --- a/extensions/vertx-http/deployment/pom.xml +++ b/extensions/vertx-http/deployment/pom.xml @@ -52,6 +52,16 @@ bootstrap provided
+ + org.webjars + bootstrap-multiselect + provided + + + org.webjars.npm + bootstrap-icons + provided + org.webjars font-awesome @@ -196,6 +206,55 @@ + + + org.webjars + bootstrap-multiselect + ${webjar.bootstrap-multiselect.version} + jar + true + ${project.build.directory}/classes/dev-static/js/ + **/bootstrap-multiselect.js + + + + + + org.webjars + bootstrap-multiselect + ${webjar.bootstrap-multiselect.version} + jar + true + ${project.build.directory}/classes/dev-static/css/ + **/bootstrap-multiselect.css + + + + + + org.webjars.npm + bootstrap-icons + ${webjar.bootstrap-icons.version} + jar + true + ${project.build.directory}/classes/dev-static/css/ + **/font/bootstrap-icons.css + + + + + + org.webjars.npm + bootstrap-icons + ${webjar.bootstrap-icons.version} + jar + true + ${project.build.directory}/classes/dev-static/css/fonts/ + **/font/fonts/ + + + + org.webjars diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java index 0943aeda594d1..6c34e448daabe 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java @@ -64,6 +64,7 @@ import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; +import io.quarkus.devconsole.spi.DevConsoleWebjarBuildItem; import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.maven.dependency.GACT; import io.quarkus.netty.runtime.virtual.VirtualChannel; @@ -88,6 +89,7 @@ import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.TemplateHtmlBuilder; import io.quarkus.utilities.OS; +import io.quarkus.vertx.http.deployment.BodyHandlerBuildItem; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; @@ -387,6 +389,46 @@ public WebJarBuildItem setupWebJar( .build(); } + @BuildStep(onlyIf = IsDevelopment.class) + public void setupDevConsoleWebjar( + List devConsoleWebjarBuildItems, + BuildProducer webJarBuildItemBuildProducer, + LaunchModeBuildItem launchModeBuildItem) { + if (launchModeBuildItem.getDevModeType().orElse(null) != DevModeType.LOCAL) { + return; + } + for (DevConsoleWebjarBuildItem devConsoleWebjar : devConsoleWebjarBuildItems) { + webJarBuildItemBuildProducer.produce(WebJarBuildItem.builder() + .artifactKey(devConsoleWebjar.getArtifactKey()) + .root(devConsoleWebjar.getRoot()) + .useDefaultQuarkusBranding(devConsoleWebjar.getUseDefaultQuarkusBranding()) + .onlyCopyNonArtifactFiles(devConsoleWebjar.getOnlyCopyNonArtifactFiles()) + .build()); + } + } + + @Record(ExecutionTime.RUNTIME_INIT) + @BuildStep(onlyIf = IsDevelopment.class) + public void setupDevConsoleRoutes( + List devConsoleWebjarBuildItems, + DevConsoleRecorder recorder, + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, + ShutdownContextBuildItem shutdownContext, + BuildProducer routeBuildItemBuildProducer, + WebJarResultsBuildItem webJarResultsBuildItem) { + + for (DevConsoleWebjarBuildItem webjarBuildItem : devConsoleWebjarBuildItems) { + WebJarResultsBuildItem.WebJarResult result = webJarResultsBuildItem.byArtifactKey(webjarBuildItem.getArtifactKey()); + if (result == null) { + continue; + } + routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() + .route("dev/" + webjarBuildItem.getRouteRoot() + "/*") + .handler(recorder.fileSystemStaticHandler(result.getWebRootConfigurations(), shutdownContext)) + .build()); + } + } + @BuildStep(onlyIf = { IsDevelopment.class }) public DevConsoleTemplateInfoBuildItem config(List serviceDescriptions) { return new DevConsoleTemplateInfoBuildItem("devServices", serviceDescriptions); @@ -404,7 +446,8 @@ public void setupDevConsoleRoutes( ShutdownContextBuildItem shutdownContext, BuildProducer routeBuildItemBuildProducer, WebJarResultsBuildItem webJarResultsBuildItem, - CurateOutcomeBuildItem curateOutcomeBuildItem) { + CurateOutcomeBuildItem curateOutcomeBuildItem, + BodyHandlerBuildItem bodyHandlerBuildItem) { WebJarResultsBuildItem.WebJarResult result = webJarResultsBuildItem.byArtifactKey(DEVCONSOLE_WEBJAR_ARTIFACT_KEY); @@ -432,7 +475,8 @@ public void setupDevConsoleRoutes( NonApplicationRootPathBuildItem.Builder builder = nonApplicationRootPathBuildItem.routeBuilder() .routeFunction( "dev/" + groupAndArtifact.getKey() + "." + groupAndArtifact.getValue() + "/" + i.getPath(), - new RuntimeDevConsoleRoute(i.getMethod())); + new RuntimeDevConsoleRoute(i.getMethod(), + i.isBodyHandlerRequired() ? bodyHandlerBuildItem.getHandler() : null)); if (i.isBlockingHandler()) { builder.blockingRoute(); } diff --git a/extensions/vertx-http/dev-console-spi/src/main/java/io/quarkus/devconsole/spi/DevConsoleWebjarBuildItem.java b/extensions/vertx-http/dev-console-spi/src/main/java/io/quarkus/devconsole/spi/DevConsoleWebjarBuildItem.java new file mode 100644 index 0000000000000..01d16ceea3e3c --- /dev/null +++ b/extensions/vertx-http/dev-console-spi/src/main/java/io/quarkus/devconsole/spi/DevConsoleWebjarBuildItem.java @@ -0,0 +1,105 @@ +package io.quarkus.devconsole.spi; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.maven.dependency.GACT; + +public final class DevConsoleWebjarBuildItem extends MultiBuildItem { + /** + * ArtifactKey pointing to the web jar. Has to be one of the applications dependencies. + */ + private final GACT artifactKey; + + /** + * Root inside the webJar starting from which resources are unpacked. + */ + private final String root; + + /** + * Only copy resources of the webjar which are either user overridden, or contain variables. + */ + private final boolean onlyCopyNonArtifactFiles; + + /** + * Defines whether Quarkus can override resources of the webjar with Quarkus internal files. + */ + private final boolean useDefaultQuarkusBranding; + + /** + * The root of the route to expose resources of the webjar + */ + private final String routeRoot; + + private DevConsoleWebjarBuildItem(Builder builder) { + this.artifactKey = builder.artifactKey; + this.root = builder.root; + this.useDefaultQuarkusBranding = builder.useDefaultQuarkusBranding; + this.onlyCopyNonArtifactFiles = builder.onlyCopyNonArtifactFiles; + this.routeRoot = builder.routeRoot; + } + + public GACT getArtifactKey() { + return artifactKey; + } + + public String getRoot() { + return root; + } + + public boolean getUseDefaultQuarkusBranding() { + return useDefaultQuarkusBranding; + } + + public boolean getOnlyCopyNonArtifactFiles() { + return onlyCopyNonArtifactFiles; + } + + public String getRouteRoot() { + return routeRoot; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private GACT artifactKey; + private String root; + private boolean useDefaultQuarkusBranding = true; + private boolean onlyCopyNonArtifactFiles = true; + private String routeRoot; + + public Builder artifactKey(GACT artifactKey) { + this.artifactKey = artifactKey; + return this; + } + + public Builder root(String root) { + this.root = root; + + if (this.root != null && this.root.startsWith("/")) { + this.root = this.root.substring(1); + } + + return this; + } + + public Builder routeRoot(String route) { + this.routeRoot = route; + return this; + } + + public Builder useDefaultQuarkusBranding(boolean useDefaultQuarkusBranding) { + this.useDefaultQuarkusBranding = useDefaultQuarkusBranding; + return this; + } + + public Builder onlyCopyNonArtifactFiles(boolean onlyCopyNonArtifactFiles) { + this.onlyCopyNonArtifactFiles = onlyCopyNonArtifactFiles; + return this; + } + + public DevConsoleWebjarBuildItem build() { + return new DevConsoleWebjarBuildItem(this); + } + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/RuntimeDevConsoleRoute.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/RuntimeDevConsoleRoute.java index ff5a05a705691..d3adff64c5e0a 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/RuntimeDevConsoleRoute.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/RuntimeDevConsoleRoute.java @@ -2,24 +2,37 @@ import java.util.function.Consumer; +import io.vertx.core.Handler; import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.Route; +import io.vertx.ext.web.RoutingContext; public class RuntimeDevConsoleRoute implements Consumer { private String method; + private Handler bodyHandler; + public RuntimeDevConsoleRoute() { } - public RuntimeDevConsoleRoute(String method) { + public RuntimeDevConsoleRoute(String method, Handler hasBodyHandler) { this.method = method; + this.bodyHandler = hasBodyHandler; } public String getMethod() { return method; } + public Handler getBodyHandler() { + return bodyHandler; + } + + public void setBodyHandler(Handler bodyHandler) { + this.bodyHandler = bodyHandler; + } + public RuntimeDevConsoleRoute setMethod(String method) { this.method = method; return this; @@ -29,5 +42,8 @@ public RuntimeDevConsoleRoute setMethod(String method) { public void accept(Route route) { route.method(HttpMethod.valueOf(method)) .order(-100); + if (bodyHandler != null) { + route.handler(bodyHandler); + } } }