diff --git a/pom.xml b/pom.xml index 66162ac..7045a54 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.xuxd kafka-console-ui - 1.0.3 + 1.0.4 kafka-console-ui Kafka console manage ui diff --git a/src/main/java/com/xuxd/kafka/console/beans/vo/BrokerApiVersionVO.java b/src/main/java/com/xuxd/kafka/console/beans/vo/BrokerApiVersionVO.java new file mode 100644 index 0000000..f9ac540 --- /dev/null +++ b/src/main/java/com/xuxd/kafka/console/beans/vo/BrokerApiVersionVO.java @@ -0,0 +1,24 @@ +package com.xuxd.kafka.console.beans.vo; + +import java.util.List; +import lombok.Data; + +/** + * kafka-console-ui. + * + * @author xuxd + * @date 2022-01-22 16:24:58 + **/ +@Data +public class BrokerApiVersionVO { + + private int brokerId; + + private String host; + + private int supportNums; + + private int unSupportNums; + + private List versionInfo; +} diff --git a/src/main/java/com/xuxd/kafka/console/controller/ClusterController.java b/src/main/java/com/xuxd/kafka/console/controller/ClusterController.java index 990e863..d9c67bd 100644 --- a/src/main/java/com/xuxd/kafka/console/controller/ClusterController.java +++ b/src/main/java/com/xuxd/kafka/console/controller/ClusterController.java @@ -53,4 +53,9 @@ public class ClusterController { public Object peekClusterInfo() { return clusterService.peekClusterInfo(); } + + @GetMapping("/info/api/version") + public Object getBrokerApiVersionInfo() { + return clusterService.getBrokerApiVersionInfo(); + } } diff --git a/src/main/java/com/xuxd/kafka/console/service/ClusterService.java b/src/main/java/com/xuxd/kafka/console/service/ClusterService.java index af113d2..b9472fb 100644 --- a/src/main/java/com/xuxd/kafka/console/service/ClusterService.java +++ b/src/main/java/com/xuxd/kafka/console/service/ClusterService.java @@ -21,4 +21,6 @@ public interface ClusterService { ResponseData updateClusterInfo(ClusterInfoDO infoDO); ResponseData peekClusterInfo(); + + ResponseData getBrokerApiVersionInfo(); } diff --git a/src/main/java/com/xuxd/kafka/console/service/impl/ClusterServiceImpl.java b/src/main/java/com/xuxd/kafka/console/service/impl/ClusterServiceImpl.java index 9b70555..96d97a3 100644 --- a/src/main/java/com/xuxd/kafka/console/service/impl/ClusterServiceImpl.java +++ b/src/main/java/com/xuxd/kafka/console/service/impl/ClusterServiceImpl.java @@ -3,13 +3,22 @@ package com.xuxd.kafka.console.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.xuxd.kafka.console.beans.ResponseData; import com.xuxd.kafka.console.beans.dos.ClusterInfoDO; +import com.xuxd.kafka.console.beans.vo.BrokerApiVersionVO; import com.xuxd.kafka.console.beans.vo.ClusterInfoVO; import com.xuxd.kafka.console.dao.ClusterInfoMapper; import com.xuxd.kafka.console.service.ClusterService; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; import kafka.console.ClusterConsole; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.kafka.clients.NodeApiVersions; +import org.apache.kafka.common.Node; import org.springframework.beans.factory.ObjectProvider; import org.springframework.stereotype.Service; @@ -69,4 +78,29 @@ public class ClusterServiceImpl implements ClusterService { return ResponseData.create().data(dos.stream().findFirst().map(ClusterInfoVO::from)).success(); } + @Override public ResponseData getBrokerApiVersionInfo() { + HashMap map = clusterConsole.listBrokerVersionInfo(); + List list = new ArrayList<>(map.size()); + map.forEach(((node, versions) -> { + BrokerApiVersionVO vo = new BrokerApiVersionVO(); + vo.setBrokerId(node.id()); + vo.setHost(node.host() + ":" + node.port()); + vo.setSupportNums(versions.allSupportedApiVersions().size()); + String versionInfo = versions.toString(true); + int from = 0; + int count = 0; + int index = -1; + while ((index = versionInfo.indexOf("UNSUPPORTED", from)) >= 0 && from < versionInfo.length()) { + count++; + from = index + 1; + } + vo.setUnSupportNums(count); + versionInfo = versionInfo.substring(1, versionInfo.length() - 2); + vo.setVersionInfo(Arrays.asList(StringUtils.split(versionInfo, ","))); + list.add(vo); + })); + Collections.sort(list, Comparator.comparingInt(BrokerApiVersionVO::getBrokerId)); + return ResponseData.create().data(list).success(); + } + } diff --git a/src/main/scala/kafka/console/BrokerApiVersion.scala b/src/main/scala/kafka/console/BrokerApiVersion.scala new file mode 100644 index 0000000..173b052 --- /dev/null +++ b/src/main/scala/kafka/console/BrokerApiVersion.scala @@ -0,0 +1,330 @@ +package kafka.console + +import com.xuxd.kafka.console.config.ContextConfigHolder +import kafka.utils.Implicits.MapExtensionMethods +import kafka.utils.Logging +import org.apache.kafka.clients._ +import org.apache.kafka.clients.admin.AdminClientConfig +import org.apache.kafka.clients.consumer.internals.{ConsumerNetworkClient, RequestFuture} +import org.apache.kafka.common.Node +import org.apache.kafka.common.config.ConfigDef.ValidString.in +import org.apache.kafka.common.config.ConfigDef.{Importance, Type} +import org.apache.kafka.common.config.{AbstractConfig, ConfigDef} +import org.apache.kafka.common.errors.AuthenticationException +import org.apache.kafka.common.internals.ClusterResourceListeners +import org.apache.kafka.common.message.ApiVersionsResponseData.ApiVersionCollection +import org.apache.kafka.common.metrics.Metrics +import org.apache.kafka.common.network.Selector +import org.apache.kafka.common.protocol.Errors +import org.apache.kafka.common.requests._ +import org.apache.kafka.common.utils.{KafkaThread, LogContext, Time} + +import java.io.IOException +import java.util.Properties +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.{ConcurrentLinkedQueue, TimeUnit} +import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, PropertiesHasAsScala, SetHasAsScala} +import scala.util.{Failure, Success, Try} + +/** + * kafka-console-ui. + * + * Copy from {@link kafka.admin.BrokerApiVersionsCommand}. + * + * @author xuxd + * @date 2022-01-22 15:15:57 + * */ +object BrokerApiVersion extends Logging { + + def listAllBrokerApiVersionInfo(): java.util.HashMap[Node, NodeApiVersions] = { + val res = new java.util.HashMap[Node, NodeApiVersions]() + val adminClient = createAdminClient() + try { + adminClient.awaitBrokers() + val brokerMap = adminClient.listAllBrokerVersionInfo() + brokerMap.forKeyValue { + (broker, versionInfoOrError) => + versionInfoOrError match { + case Success(v) => { + res.put(broker, v) + } + case Failure(v) => logger.error(s"${broker} -> ERROR: ${v}\n") + } + } + } finally { + adminClient.close() + } + + res + } + + private def createAdminClient(): AdminClient = { + val props = new Properties() + props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, ContextConfigHolder.CONTEXT_CONFIG.get().getBootstrapServer()) + props.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, ContextConfigHolder.CONTEXT_CONFIG.get().getRequestTimeoutMs()) + props.putAll(ContextConfigHolder.CONTEXT_CONFIG.get().getProperties()) + AdminClient.create(props) + } + + // org.apache.kafka.clients.admin.AdminClient doesn't currently expose a way to retrieve the supported api versions. + // We inline the bits we need from kafka.admin.AdminClient so that we can delete it. + private class AdminClient(val time: Time, + val client: ConsumerNetworkClient, + val bootstrapBrokers: List[Node]) extends Logging { + + @volatile var running = true + val pendingFutures = new ConcurrentLinkedQueue[RequestFuture[ClientResponse]]() + + val networkThread = new KafkaThread("admin-client-network-thread", () => { + try { + while (running) + client.poll(time.timer(Long.MaxValue)) + } catch { + case t: Throwable => + error("admin-client-network-thread exited", t) + } finally { + pendingFutures.forEach { future => + try { + future.raise(Errors.UNKNOWN_SERVER_ERROR) + } catch { + case _: IllegalStateException => // It is OK if the future has been completed + } + } + pendingFutures.clear() + } + }, true) + + networkThread.start() + + private def send(target: Node, + request: AbstractRequest.Builder[_ <: AbstractRequest]): AbstractResponse = { + val future = client.send(target, request) + pendingFutures.add(future) + future.awaitDone(Long.MaxValue, TimeUnit.MILLISECONDS) + pendingFutures.remove(future) + if (future.succeeded()) + future.value().responseBody() + else + throw future.exception() + } + + private def sendAnyNode(request: AbstractRequest.Builder[_ <: AbstractRequest]): AbstractResponse = { + bootstrapBrokers.foreach { broker => + try { + return send(broker, request) + } catch { + case e: AuthenticationException => + throw e + case e: Exception => + debug(s"Request ${request.apiKey()} failed against node $broker", e) + } + } + throw new RuntimeException(s"Request ${request.apiKey()} failed on brokers $bootstrapBrokers") + } + + private def getApiVersions(node: Node): ApiVersionCollection = { + val response = send(node, new ApiVersionsRequest.Builder()).asInstanceOf[ApiVersionsResponse] + Errors.forCode(response.data.errorCode).maybeThrow() + response.data.apiKeys + } + + /** + * Wait until there is a non-empty list of brokers in the cluster. + */ + def awaitBrokers(): Unit = { + var nodes = List[Node]() + val start = System.currentTimeMillis() + val maxWait = 30 * 1000 + do { + nodes = findAllBrokers() + if (nodes.isEmpty) { + Thread.sleep(50) + } + } + while (nodes.isEmpty && (System.currentTimeMillis() - start < maxWait)) + } + + private def findAllBrokers(): List[Node] = { + val request = MetadataRequest.Builder.allTopics() + val response = sendAnyNode(request).asInstanceOf[MetadataResponse] + val errors = response.errors + if (!errors.isEmpty) { + logger.info(s"Metadata request contained errors: $errors") + } + + // 在3.x版本中这个方法是buildCluster 代替cluster()了 + // response.buildCluster.nodes.asScala.toList + response.cluster().nodes.asScala.toList + } + + def listAllBrokerVersionInfo(): Map[Node, Try[NodeApiVersions]] = + findAllBrokers().map { broker => + broker -> Try[NodeApiVersions](new NodeApiVersions(getApiVersions(broker))) + }.toMap + + def close(): Unit = { + running = false + try { + client.close() + } catch { + case e: IOException => + error("Exception closing nioSelector:", e) + } + } + + } + + private object AdminClient { + val DefaultConnectionMaxIdleMs = 9 * 60 * 1000 + val DefaultRequestTimeoutMs = 5000 + val DefaultSocketConnectionSetupMs = CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MS_CONFIG + val DefaultSocketConnectionSetupMaxMs = CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_CONFIG + val DefaultMaxInFlightRequestsPerConnection = 100 + val DefaultReconnectBackoffMs = 50 + val DefaultReconnectBackoffMax = 50 + val DefaultSendBufferBytes = 128 * 1024 + val DefaultReceiveBufferBytes = 32 * 1024 + val DefaultRetryBackoffMs = 100 + + val AdminClientIdSequence = new AtomicInteger(1) + val AdminConfigDef = { + val config = new ConfigDef() + .define( + CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, + Type.LIST, + Importance.HIGH, + CommonClientConfigs.BOOTSTRAP_SERVERS_DOC) + .define(CommonClientConfigs.CLIENT_DNS_LOOKUP_CONFIG, + Type.STRING, + ClientDnsLookup.USE_ALL_DNS_IPS.toString, + in(ClientDnsLookup.USE_ALL_DNS_IPS.toString, + ClientDnsLookup.RESOLVE_CANONICAL_BOOTSTRAP_SERVERS_ONLY.toString), + Importance.MEDIUM, + CommonClientConfigs.CLIENT_DNS_LOOKUP_DOC) + .define( + CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, + ConfigDef.Type.STRING, + CommonClientConfigs.DEFAULT_SECURITY_PROTOCOL, + ConfigDef.Importance.MEDIUM, + CommonClientConfigs.SECURITY_PROTOCOL_DOC) + .define( + CommonClientConfigs.REQUEST_TIMEOUT_MS_CONFIG, + ConfigDef.Type.INT, + DefaultRequestTimeoutMs, + ConfigDef.Importance.MEDIUM, + CommonClientConfigs.REQUEST_TIMEOUT_MS_DOC) + .define( + CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MS_CONFIG, + ConfigDef.Type.LONG, + CommonClientConfigs.DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT_MS, + ConfigDef.Importance.MEDIUM, + CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MS_DOC) + .define( + CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_CONFIG, + ConfigDef.Type.LONG, + CommonClientConfigs.DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS, + ConfigDef.Importance.MEDIUM, + CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_DOC) + .define( + CommonClientConfigs.RETRY_BACKOFF_MS_CONFIG, + ConfigDef.Type.LONG, + DefaultRetryBackoffMs, + ConfigDef.Importance.MEDIUM, + CommonClientConfigs.RETRY_BACKOFF_MS_DOC) + .withClientSslSupport() + .withClientSaslSupport() + config + } + + class AdminConfig(originals: Map[_, _]) extends AbstractConfig(AdminConfigDef, originals.asJava, false) + + def create(props: Properties): AdminClient = { + val properties = new Properties() + val names = props.stringPropertyNames() + for (name <- names.asScala.toSet) { + properties.put(name, props.get(name).toString()) + } + create(properties.asScala.toMap) + } + + def create(props: Map[String, _]): AdminClient = create(new AdminConfig(props)) + + def create(config: AdminConfig): AdminClient = { + val clientId = "admin-" + AdminClientIdSequence.getAndIncrement() + val logContext = new LogContext(s"[LegacyAdminClient clientId=$clientId] ") + val time = Time.SYSTEM + val metrics = new Metrics(time) + val metadata = new Metadata(100L, 60 * 60 * 1000L, logContext, + new ClusterResourceListeners) + val channelBuilder = ClientUtils.createChannelBuilder(config, time, logContext) + val requestTimeoutMs = config.getInt(CommonClientConfigs.REQUEST_TIMEOUT_MS_CONFIG) + val connectionSetupTimeoutMs = config.getLong(CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MS_CONFIG) + val connectionSetupTimeoutMaxMs = config.getLong(CommonClientConfigs.SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_CONFIG) + val retryBackoffMs = config.getLong(CommonClientConfigs.RETRY_BACKOFF_MS_CONFIG) + + val brokerUrls = config.getList(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG) + val clientDnsLookup = config.getString(CommonClientConfigs.CLIENT_DNS_LOOKUP_CONFIG) + val brokerAddresses = ClientUtils.parseAndValidateAddresses(brokerUrls, clientDnsLookup) + metadata.bootstrap(brokerAddresses) + + val selector = new Selector( + DefaultConnectionMaxIdleMs, + metrics, + time, + "admin", + channelBuilder, + logContext) + + // 版本不一样,这个地方的兼容性问题也不一样了 + // 3.x版本用这个 + // val networkClient = new NetworkClient( + // selector, + // metadata, + // clientId, + // DefaultMaxInFlightRequestsPerConnection, + // DefaultReconnectBackoffMs, + // DefaultReconnectBackoffMax, + // DefaultSendBufferBytes, + // DefaultReceiveBufferBytes, + // requestTimeoutMs, + // connectionSetupTimeoutMs, + // connectionSetupTimeoutMaxMs, + // time, + // true, + // new ApiVersions, + // logContext) + + val networkClient = new NetworkClient( + selector, + metadata, + clientId, + DefaultMaxInFlightRequestsPerConnection, + DefaultReconnectBackoffMs, + DefaultReconnectBackoffMax, + DefaultSendBufferBytes, + DefaultReceiveBufferBytes, + requestTimeoutMs, + connectionSetupTimeoutMs, + connectionSetupTimeoutMaxMs, + ClientDnsLookup.USE_ALL_DNS_IPS, + time, + true, + new ApiVersions, + logContext) + + val highLevelClient = new ConsumerNetworkClient( + logContext, + networkClient, + metadata, + time, + retryBackoffMs, + requestTimeoutMs, + Integer.MAX_VALUE) + + new AdminClient( + time, + highLevelClient, + metadata.fetch.nodes.asScala.toList) + } + } +} diff --git a/src/main/scala/kafka/console/ClusterConsole.scala b/src/main/scala/kafka/console/ClusterConsole.scala index a9e3cf8..00c7593 100644 --- a/src/main/scala/kafka/console/ClusterConsole.scala +++ b/src/main/scala/kafka/console/ClusterConsole.scala @@ -1,11 +1,13 @@ package kafka.console +import com.xuxd.kafka.console.beans.{BrokerNode, ClusterInfo} +import com.xuxd.kafka.console.config.{ContextConfigHolder, KafkaConfig} +import org.apache.kafka.clients.NodeApiVersions +import org.apache.kafka.clients.admin.DescribeClusterResult +import org.apache.kafka.common.Node + import java.util.Collections import java.util.concurrent.TimeUnit -import com.xuxd.kafka.console.beans.{BrokerNode, ClusterInfo} -import com.xuxd.kafka.console.config.{ContextConfigHolder, KafkaConfig} -import org.apache.kafka.clients.admin.DescribeClusterResult - import scala.jdk.CollectionConverters.{CollectionHasAsScala, SetHasAsJava, SetHasAsScala} /** @@ -41,4 +43,8 @@ class ClusterConsole(config: KafkaConfig) extends KafkaConsole(config: KafkaConf new ClusterInfo }).asInstanceOf[ClusterInfo] } + + def listBrokerVersionInfo(): java.util.HashMap[Node, NodeApiVersions] = { + BrokerApiVersion.listAllBrokerApiVersionInfo() + } } diff --git a/ui/package-lock.json b/ui/package-lock.json index 8e05f0c..f8f268f 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1820,6 +1820,63 @@ "integrity": "sha1-/q7SVZc9LndVW4PbwIhRpsY1IPo=", "dev": true }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, + "loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "optional": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, "ssri": { "version": "8.0.1", "resolved": "https://registry.nlark.com/ssri/download/ssri-8.0.1.tgz", @@ -1828,6 +1885,28 @@ "requires": { "minipass": "^3.1.1" } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.8.3", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz", + "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==", + "dev": true, + "optional": true, + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + } } } }, @@ -12097,87 +12176,6 @@ } } }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.8.3", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz", - "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==", - "dev": true, - "optional": true, - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true - }, - "loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, - "optional": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "vue-ref": { "version": "2.0.0", "resolved": "https://registry.npm.taobao.org/vue-ref/download/vue-ref-2.0.0.tgz", diff --git a/ui/src/utils/api.js b/ui/src/utils/api.js index dfdf663..ae88c1e 100644 --- a/ui/src/utils/api.js +++ b/ui/src/utils/api.js @@ -203,6 +203,10 @@ export const KafkaClusterApi = { url: "/cluster/info/peek", method: "get", }, + getBrokerApiVersionInfo: { + url: "/cluster/info/api/version", + method: "get", + }, }; export const KafkaOpApi = { diff --git a/ui/src/views/Home.vue b/ui/src/views/Home.vue index 1cf3543..071c19b 100644 --- a/ui/src/views/Home.vue +++ b/ui/src/views/Home.vue @@ -1,25 +1,63 @@ - - + {{ k }}={{ v }} + + + kafka API 版本兼容性 + + + 详情 + + + + + + diff --git a/ui/src/views/home/VersionInfo.vue b/ui/src/views/home/VersionInfo.vue new file mode 100644 index 0000000..6cfb74f --- /dev/null +++ b/ui/src/views/home/VersionInfo.vue @@ -0,0 +1,61 @@ + + + + 格式说明 + 请求类型(1):0 to n(2) [usage: v](3) + + 表示客户端发出的请求类型 + 该请求在broker中支持的版本号区间 + + 表示当前控制台的kafka客户端使用的是v版本,如果是UNSUPPORTED,说明broker版本太老,无法处理控制台的这些请求,可能影响相关功能的使用 + + + + + + {{ info }} + + + + + + + +
{{ k }}={{ v }}
请求类型(1):0 to n(2) [usage: v](3)