commit 5b36126ea4611c6da36fab9e17306d4091eb76e8
Author: 许晓东 <763795151@qq.com>
Date: Mon Aug 30 20:24:24 2021 +0800
kafka console initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 0000000..e76d1f3
--- /dev/null
+++ b/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.6";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..2cc7d4a
Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..abd303b
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5aeb3ff
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# kafka可视化管理平台
+目前支持部分acl功能管理操作
\ No newline at end of file
diff --git a/mvnw b/mvnw
new file mode 100755
index 0000000..a16b543
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 0000000..c8d4337
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/package.sh b/package.sh
new file mode 100644
index 0000000..8d0a950
--- /dev/null
+++ b/package.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+mvn clean scala:compile compile package -Dmaven.test.skip=true
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..87b795b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,128 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.10
+
+
+ com.xuxd
+ kafka-console-ui
+ 0.0.1-SNAPSHOT
+ kafka-console-ui
+ Kafka console manage ui
+
+ 1.8
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+ org.scala-lang
+ scala-library
+ 2.13.6
+
+
+ org.scala-lang
+ scala-compiler
+ 2.13.6
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.apache.kafka
+ kafka-clients
+ 2.8.0
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.12.0
+
+
+
+ com.google.guava
+ guava
+ 23.0
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.20
+ provided
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.scala-tools
+ maven-scala-plugin
+ 2.15.2
+
+
+ scala-compile-first
+
+ compile
+
+
+
+ **/*.scala
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.2.0
+
+
+ add-source
+ generate-sources
+
+ add-source
+
+
+
+ src/main/java
+ src/main/scala
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/xuxd/kafka/console/KafkaConsoleUiApplication.java b/src/main/java/com/xuxd/kafka/console/KafkaConsoleUiApplication.java
new file mode 100644
index 0000000..57c4199
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/KafkaConsoleUiApplication.java
@@ -0,0 +1,13 @@
+package com.xuxd.kafka.console;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class KafkaConsoleUiApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(KafkaConsoleUiApplication.class, args);
+ }
+
+}
diff --git a/src/main/java/com/xuxd/kafka/console/beans/AclEntry.java b/src/main/java/com/xuxd/kafka/console/beans/AclEntry.java
new file mode 100644
index 0000000..248132e
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/beans/AclEntry.java
@@ -0,0 +1,97 @@
+package com.xuxd.kafka.console.beans;
+
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kafka.common.acl.AccessControlEntry;
+import org.apache.kafka.common.acl.AccessControlEntryFilter;
+import org.apache.kafka.common.acl.AclBinding;
+import org.apache.kafka.common.acl.AclBindingFilter;
+import org.apache.kafka.common.acl.AclOperation;
+import org.apache.kafka.common.acl.AclPermissionType;
+import org.apache.kafka.common.resource.PatternType;
+import org.apache.kafka.common.resource.ResourcePattern;
+import org.apache.kafka.common.resource.ResourcePatternFilter;
+import org.apache.kafka.common.resource.ResourceType;
+import org.apache.kafka.common.security.auth.KafkaPrincipal;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 20:17:27
+ **/
+@Data
+public class AclEntry {
+
+ private String resourceType;
+
+ private String name = null;
+
+ private String patternType;
+
+ private String principal = null;
+
+ private String host;
+
+ private String operation;
+
+ private String permissionType;
+
+ public static AclEntry valueOf(AclBinding binding) {
+ AclEntry entry = new AclEntry();
+ entry.setResourceType(binding.pattern().resourceType().name());
+ entry.setName(binding.pattern().name());
+ entry.setPatternType(binding.pattern().patternType().name());
+ entry.setPrincipal(KafkaPrincipal.fromString(binding.entry().principal()).getName());
+ entry.setHost(binding.entry().host());
+ entry.setOperation(binding.entry().operation().name());
+ entry.setPermissionType(binding.entry().permissionType().name());
+ return entry;
+ }
+
+ public AclBinding toAclBinding() {
+ ResourceType resourceType = StringUtils.isBlank(this.resourceType) ? ResourceType.UNKNOWN : ResourceType.valueOf(this.resourceType);
+ String resourceName = StringUtils.isBlank(this.name) ? ResourcePattern.WILDCARD_RESOURCE : this.name;
+ PatternType patternType = StringUtils.isBlank(this.patternType) ? PatternType.LITERAL : PatternType.valueOf(this.patternType);
+ String principal = StringUtils.isNotBlank(this.principal) ? new KafkaPrincipal(KafkaPrincipal.USER_TYPE, this.principal).toString() : KafkaPrincipal.ANONYMOUS.toString();
+ String host = StringUtils.isBlank(this.host) ? ResourcePattern.WILDCARD_RESOURCE : this.host;
+ AclOperation operation = StringUtils.isBlank(this.operation) ? AclOperation.UNKNOWN : AclOperation.valueOf(this.operation);
+ AclPermissionType permissionType = StringUtils.isBlank(this.permissionType) ? AclPermissionType.ALLOW : AclPermissionType.valueOf(this.permissionType);
+ return new AclBinding(new ResourcePattern(resourceType, resourceName, patternType),
+ new AccessControlEntry(principal, host, operation, permissionType));
+ }
+
+ public AclBindingFilter toAclBindingFilter() {
+ ResourceType resourceType = StringUtils.isBlank(this.resourceType) ? ResourceType.UNKNOWN : ResourceType.valueOf(this.resourceType);
+ String resourceName = StringUtils.isBlank(this.name) ? ResourcePattern.WILDCARD_RESOURCE : this.name;
+ PatternType patternType = StringUtils.isBlank(this.patternType) ? PatternType.LITERAL : PatternType.valueOf(this.patternType);
+ String principal = StringUtils.isNotBlank(this.principal) ? new KafkaPrincipal(KafkaPrincipal.USER_TYPE, this.principal).toString() : KafkaPrincipal.ANONYMOUS.toString();
+ String host = StringUtils.isBlank(this.host) ? ResourcePattern.WILDCARD_RESOURCE : this.host;
+ AclOperation operation = StringUtils.isBlank(this.operation) ? AclOperation.UNKNOWN : AclOperation.valueOf(this.operation);
+ AclPermissionType permissionType = StringUtils.isBlank(this.permissionType) ? AclPermissionType.ALLOW : AclPermissionType.valueOf(this.permissionType);
+
+ AclBindingFilter filter = new AclBindingFilter(new ResourcePatternFilter(resourceType, resourceName, patternType),
+ new AccessControlEntryFilter(principal, host, operation, permissionType));
+ return filter;
+ }
+
+ public AclBindingFilter toAclBindingFilter(boolean allResource, boolean allPrincipal, boolean allOperation) {
+ AclEntry entry = deepClone();
+ AclBindingFilter filter = new AclBindingFilter(new ResourcePatternFilter(allResource ? ResourceType.ANY : ResourceType.valueOf(entry.resourceType), entry.name, PatternType.LITERAL),
+ new AccessControlEntryFilter(allPrincipal ? null : entry.principal, entry.host, allOperation ? AclOperation.ALL : AclOperation.valueOf(entry.operation), AclPermissionType.ANY));
+ System.out.println(filter);
+ return filter;
+ }
+
+ public AclEntry deepClone() {
+ AclEntry entry = new AclEntry();
+ entry.setResourceType(this.resourceType);
+ entry.setName(this.name);
+ entry.setPatternType(this.patternType);
+ entry.setPrincipal(this.principal);
+ entry.setHost(this.host);
+ entry.setOperation(this.operation);
+ entry.setPermissionType(this.permissionType);
+ return entry;
+ }
+}
diff --git a/src/main/java/com/xuxd/kafka/console/beans/AclUser.java b/src/main/java/com/xuxd/kafka/console/beans/AclUser.java
new file mode 100644
index 0000000..b91abfb
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/beans/AclUser.java
@@ -0,0 +1,17 @@
+package com.xuxd.kafka.console.beans;
+
+import lombok.Data;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 19:43:26
+ **/
+@Data
+public class AclUser {
+
+ private String username;
+
+ private String password;
+}
diff --git a/src/main/java/com/xuxd/kafka/console/beans/CounterList.java b/src/main/java/com/xuxd/kafka/console/beans/CounterList.java
new file mode 100644
index 0000000..68374da
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/beans/CounterList.java
@@ -0,0 +1,26 @@
+package com.xuxd.kafka.console.beans;
+
+import java.util.List;
+import lombok.Getter;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-30 20:10:07
+ **/
+public class CounterList {
+
+ @Getter
+ private List list;
+
+ private int total;
+
+ public CounterList(List list) {
+ this.list = list;
+ }
+
+ public int getTotal() {
+ return list != null ? list.size() : 0;
+ }
+}
diff --git a/src/main/java/com/xuxd/kafka/console/beans/ResponseData.java b/src/main/java/com/xuxd/kafka/console/beans/ResponseData.java
new file mode 100644
index 0000000..835c732
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/beans/ResponseData.java
@@ -0,0 +1,74 @@
+package com.xuxd.kafka.console.beans;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 19:29:11
+ **/
+public class ResponseData {
+
+ public static final int SUCCESS_CODE = 0, FAILED_CODE = -9999;
+
+ public static final String SUCCESS_MSG = "success", FAILED_MSG = "failed";
+
+ @Getter
+ @Setter
+ private int code = SUCCESS_CODE;
+
+ @Getter
+ @Setter
+ private String msg = SUCCESS_MSG;
+
+ @Getter
+ @Setter
+ private T data;
+
+ public static ResponseData create() {
+ return new ResponseData();
+ }
+
+ public static ResponseData create(Class cls) {
+ return new ResponseData();
+ }
+
+ public ResponseData data(T t) {
+ this.data = t;
+ return this;
+ }
+
+ public ResponseData success() {
+ this.code = SUCCESS_CODE;
+ this.msg = SUCCESS_MSG;
+ return this;
+ }
+
+ public ResponseData success(String msg) {
+ this.code = SUCCESS_CODE;
+ this.msg = msg;
+ return this;
+ }
+
+ public ResponseData failed() {
+ this.code = FAILED_CODE;
+ this.msg = FAILED_MSG;
+ return this;
+ }
+
+ public ResponseData failed(String msg) {
+ this.code = FAILED_CODE;
+ this.msg = msg;
+ return this;
+ }
+
+ @Override public String toString() {
+ return "ResponseData{" +
+ "code=" + code +
+ ", msg='" + msg + '\'' +
+ ", data=" + data +
+ '}';
+ }
+}
diff --git a/src/main/java/com/xuxd/kafka/console/beans/dto/ConsumerAuthDTO.java b/src/main/java/com/xuxd/kafka/console/beans/dto/ConsumerAuthDTO.java
new file mode 100644
index 0000000..d058e66
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/beans/dto/ConsumerAuthDTO.java
@@ -0,0 +1,37 @@
+package com.xuxd.kafka.console.beans.dto;
+
+import com.xuxd.kafka.console.beans.AclEntry;
+import lombok.Data;
+import org.apache.kafka.common.resource.ResourceType;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-30 16:28:47
+ **/
+@Data
+public class ConsumerAuthDTO {
+
+ private String topic;
+
+ private String groupId;
+
+ private String username;
+
+ public AclEntry toTopicEntry() {
+ AclEntry entry = new AclEntry();
+ entry.setPrincipal(username);
+ entry.setName(topic);
+ entry.setResourceType(ResourceType.TOPIC.name());
+ return entry;
+ }
+
+ public AclEntry toGroupEntry() {
+ AclEntry entry = new AclEntry();
+ entry.setPrincipal(username);
+ entry.setName(groupId);
+ entry.setResourceType(ResourceType.GROUP.name());
+ return entry;
+ }
+}
diff --git a/src/main/java/com/xuxd/kafka/console/beans/dto/DeleteAclDTO.java b/src/main/java/com/xuxd/kafka/console/beans/dto/DeleteAclDTO.java
new file mode 100644
index 0000000..7cea14b
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/beans/dto/DeleteAclDTO.java
@@ -0,0 +1,23 @@
+package com.xuxd.kafka.console.beans.dto;
+
+import com.xuxd.kafka.console.beans.AclEntry;
+import lombok.Data;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-30 19:37:47
+ **/
+@Data
+public class DeleteAclDTO {
+
+ private String username;
+
+ public AclEntry toUserEntry() {
+ AclEntry entry = new AclEntry();
+ entry.setPrincipal(username);
+
+ return entry;
+ }
+}
diff --git a/src/main/java/com/xuxd/kafka/console/beans/dto/ProducerAuthDTO.java b/src/main/java/com/xuxd/kafka/console/beans/dto/ProducerAuthDTO.java
new file mode 100644
index 0000000..c23c68f
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/beans/dto/ProducerAuthDTO.java
@@ -0,0 +1,27 @@
+package com.xuxd.kafka.console.beans.dto;
+
+import com.xuxd.kafka.console.beans.AclEntry;
+import lombok.Data;
+import org.apache.kafka.common.resource.ResourceType;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-30 16:28:23
+ **/
+@Data
+public class ProducerAuthDTO {
+
+ private String topic;
+
+ private String username;
+
+ public AclEntry toEntry() {
+ AclEntry entry = new AclEntry();
+ entry.setPrincipal(username);
+ entry.setName(topic);
+ entry.setResourceType(ResourceType.TOPIC.name());
+ return entry;
+ }
+}
diff --git a/src/main/java/com/xuxd/kafka/console/config/KafkaConfig.java b/src/main/java/com/xuxd/kafka/console/config/KafkaConfig.java
new file mode 100644
index 0000000..0404d9b
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/config/KafkaConfig.java
@@ -0,0 +1,65 @@
+package com.xuxd.kafka.console.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 11:21:12
+ **/
+@Configuration
+@ConfigurationProperties(prefix = "kafka.config")
+public class KafkaConfig {
+
+ private String bootstrapServer;
+
+ private int requestTimeoutMs;
+
+ private String securityProtocol;
+
+ private String saslMechanism;
+
+ private String saslJaasConfig;
+
+ public String getBootstrapServer() {
+ return bootstrapServer;
+ }
+
+ public void setBootstrapServer(String bootstrapServer) {
+ this.bootstrapServer = bootstrapServer;
+ }
+
+ public int getRequestTimeoutMs() {
+ return requestTimeoutMs;
+ }
+
+ public void setRequestTimeoutMs(int requestTimeoutMs) {
+ this.requestTimeoutMs = requestTimeoutMs;
+ }
+
+ public String getSecurityProtocol() {
+ return securityProtocol;
+ }
+
+ public void setSecurityProtocol(String securityProtocol) {
+ this.securityProtocol = securityProtocol;
+ }
+
+ public String getSaslMechanism() {
+ return saslMechanism;
+ }
+
+ public void setSaslMechanism(String saslMechanism) {
+ this.saslMechanism = saslMechanism;
+ }
+
+ public String getSaslJaasConfig() {
+ return saslJaasConfig;
+ }
+
+ public void setSaslJaasConfig(String saslJaasConfig) {
+ this.saslJaasConfig = saslJaasConfig;
+ }
+}
diff --git a/src/main/java/com/xuxd/kafka/console/config/KafkaConfiguration.java b/src/main/java/com/xuxd/kafka/console/config/KafkaConfiguration.java
new file mode 100644
index 0000000..47d7c94
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/config/KafkaConfiguration.java
@@ -0,0 +1,26 @@
+package com.xuxd.kafka.console.config;
+
+import kafka.console.KafkaAclConsole;
+import kafka.console.KafkaConfigConsole;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 11:45:26
+ **/
+@Configuration
+public class KafkaConfiguration {
+
+ @Bean
+ public KafkaConfigConsole kafkaConfigConsole(KafkaConfig config) {
+ return new KafkaConfigConsole(config);
+ }
+
+ @Bean
+ public KafkaAclConsole kafkaAclConsole(KafkaConfig config) {
+ return new KafkaAclConsole(config);
+ }
+}
diff --git a/src/main/java/com/xuxd/kafka/console/controller/AclAuthController.java b/src/main/java/com/xuxd/kafka/console/controller/AclAuthController.java
new file mode 100644
index 0000000..2832200
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/controller/AclAuthController.java
@@ -0,0 +1,98 @@
+package com.xuxd.kafka.console.controller;
+
+import com.xuxd.kafka.console.beans.AclEntry;
+import com.xuxd.kafka.console.beans.dto.ConsumerAuthDTO;
+import com.xuxd.kafka.console.beans.dto.DeleteAclDTO;
+import com.xuxd.kafka.console.beans.dto.ProducerAuthDTO;
+import com.xuxd.kafka.console.service.AclService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 11:47:48
+ **/
+@RestController
+@RequestMapping("/acl")
+public class AclAuthController {
+
+ @Autowired
+ private AclService aclService;
+
+ @GetMapping
+ public Object getAclList() {
+ return aclService.getAclList();
+ }
+
+ @PostMapping
+ public Object addAcl(@RequestBody AclEntry entry) {
+ return aclService.addAcl(entry);
+ }
+
+ /**
+ * add producer acl.
+ *
+ * @param param entry.topic && entry.username must.
+ * @return
+ */
+ @PostMapping("/producer")
+ public Object addProducerAcl(@RequestBody ProducerAuthDTO param) {
+
+ return aclService.addProducerAcl(param.toEntry());
+ }
+
+ /**
+ * add consumer acl.
+ *
+ * @param param entry.topic && entry.groupId entry.username must.
+ * @return
+ */
+ @PostMapping("/consumer")
+ public Object addConsumerAcl(@RequestBody ConsumerAuthDTO param) {
+
+ return aclService.addConsumerAcl(param.toTopicEntry(), param.toGroupEntry());
+ }
+
+ /**
+ * delete user acl .
+ *
+ * @param param entry.username
+ * @return
+ */
+ @DeleteMapping("/user")
+ public Object deleteAclByUser(@RequestBody DeleteAclDTO param) {
+ return aclService.deleteUserAcl(param.toUserEntry());
+ }
+
+ /**
+ * add producer acl.
+ *
+ * @param param entry.topic && entry.username must.
+ * @return
+ */
+ @DeleteMapping("/producer")
+ public Object deleteProducerAcl(@RequestBody ProducerAuthDTO param) {
+
+ return aclService.deleteProducerAcl(param.toEntry());
+ }
+
+ /**
+ * add consumer acl.
+ *
+ * @param param entry.topic && entry.groupId entry.username must.
+ * @return
+ */
+ @DeleteMapping("/consumer")
+ public Object deleteConsumerAcl(@RequestBody ConsumerAuthDTO param) {
+
+ return aclService.deleteConsumerAcl(param.toTopicEntry(), param.toGroupEntry());
+ }
+
+}
diff --git a/src/main/java/com/xuxd/kafka/console/controller/AclUserController.java b/src/main/java/com/xuxd/kafka/console/controller/AclUserController.java
new file mode 100644
index 0000000..16bce79
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/controller/AclUserController.java
@@ -0,0 +1,40 @@
+package com.xuxd.kafka.console.controller;
+
+import com.xuxd.kafka.console.beans.AclUser;
+import com.xuxd.kafka.console.service.AclService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 21:13:05
+ **/
+@RestController
+@RequestMapping("/user")
+public class AclUserController {
+
+ @Autowired
+ private AclService aclService;
+
+ @GetMapping
+ public Object getUserList() {
+ return aclService.getUserList();
+ }
+
+ @PostMapping
+ public Object addOrUpdateUser(@RequestBody AclUser user) {
+ return aclService.addOrUpdateUser(user.getUsername(), user.getPassword());
+ }
+
+ @DeleteMapping
+ public Object deleteUser(@RequestBody AclUser user) {
+ return aclService.deleteUser(user.getUsername());
+ }
+}
diff --git a/src/main/java/com/xuxd/kafka/console/controller/IndexController.java b/src/main/java/com/xuxd/kafka/console/controller/IndexController.java
new file mode 100644
index 0000000..ddfb0bc
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/controller/IndexController.java
@@ -0,0 +1,19 @@
+package com.xuxd.kafka.console.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 10:54:58
+ **/
+@RestController
+public class IndexController {
+
+ @RequestMapping("/")
+ public String index() {
+ return "hello world";
+ }
+}
diff --git a/src/main/java/com/xuxd/kafka/console/service/AclService.java b/src/main/java/com/xuxd/kafka/console/service/AclService.java
new file mode 100644
index 0000000..e62b3f5
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/service/AclService.java
@@ -0,0 +1,37 @@
+package com.xuxd.kafka.console.service;
+
+import com.xuxd.kafka.console.beans.AclEntry;
+import com.xuxd.kafka.console.beans.ResponseData;
+import java.util.Set;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 11:44:06
+ **/
+public interface AclService {
+
+ ResponseData> getUserList();
+
+ ResponseData addOrUpdateUser(String name, String pass);
+
+ ResponseData deleteUser(String name);
+
+ ResponseData getAclList();
+
+ ResponseData deleteAcl(AclEntry entry);
+
+ ResponseData addAcl(AclEntry entry);
+
+ ResponseData addProducerAcl(AclEntry entry);
+
+ ResponseData addConsumerAcl(AclEntry topic, AclEntry group);
+
+ ResponseData deleteProducerAcl(AclEntry entry);
+
+ ResponseData deleteConsumerAcl(AclEntry topic, AclEntry group);
+
+ ResponseData deleteUserAcl(AclEntry entry);
+
+}
diff --git a/src/main/java/com/xuxd/kafka/console/service/impl/AclServiceImpl.java b/src/main/java/com/xuxd/kafka/console/service/impl/AclServiceImpl.java
new file mode 100644
index 0000000..d20fa34
--- /dev/null
+++ b/src/main/java/com/xuxd/kafka/console/service/impl/AclServiceImpl.java
@@ -0,0 +1,84 @@
+package com.xuxd.kafka.console.service.impl;
+
+import com.xuxd.kafka.console.beans.AclEntry;
+import com.xuxd.kafka.console.beans.CounterList;
+import com.xuxd.kafka.console.beans.ResponseData;
+import com.xuxd.kafka.console.service.AclService;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import kafka.console.KafkaAclConsole;
+import kafka.console.KafkaConfigConsole;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.common.acl.AclBinding;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 11:44:40
+ **/
+@Slf4j
+@Service
+public class AclServiceImpl implements AclService {
+
+ @Autowired
+ private KafkaConfigConsole configConsole;
+
+ @Autowired
+ private KafkaAclConsole aclConsole;
+
+ @Override public ResponseData> getUserList() {
+ try {
+ return ResponseData.create(Set.class).data(configConsole.getUserList()).success();
+ } catch (Exception e) {
+ log.error("getUserList error.", e);
+ return ResponseData.create().failed();
+ }
+ }
+
+ @Override public ResponseData addOrUpdateUser(String name, String pass) {
+ return configConsole.addOrUpdateUser(name, pass) ? ResponseData.create().success() : ResponseData.create().failed();
+ }
+
+ @Override public ResponseData deleteUser(String name) {
+ return configConsole.deleteUser(name) ? ResponseData.create().success() : ResponseData.create().failed();
+ }
+
+ @Override public ResponseData getAclList() {
+ List aclBindingList = aclConsole.getAclList();
+
+ return ResponseData.create().data(new CounterList<>(aclBindingList.stream().map(x -> AclEntry.valueOf(x)).collect(Collectors.toList()))).success();
+ }
+
+ @Override public ResponseData deleteAcl(AclEntry entry) {
+ return aclConsole.deleteAcl(entry, false, false, false) ? ResponseData.create().success() : ResponseData.create().failed();
+ }
+
+ @Override public ResponseData addAcl(AclEntry entry) {
+ return aclConsole.addAcl(Collections.singletonList(entry.toAclBinding())) ? ResponseData.create().success() : ResponseData.create().failed();
+ }
+
+ @Override public ResponseData addProducerAcl(AclEntry entry) {
+ return aclConsole.addProducerAcl(entry) ? ResponseData.create().success() : ResponseData.create().failed();
+ }
+
+ @Override public ResponseData addConsumerAcl(AclEntry topic, AclEntry group) {
+ return aclConsole.addConsumerAcl(topic, group) ? ResponseData.create().success() : ResponseData.create().failed();
+ }
+
+ @Override public ResponseData deleteProducerAcl(AclEntry entry) {
+ return aclConsole.deleteProducerAcl(entry) ? ResponseData.create().success() : ResponseData.create().failed();
+ }
+
+ @Override public ResponseData deleteConsumerAcl(AclEntry topic, AclEntry group) {
+ return aclConsole.deleteConsumerAcl(topic, group) ? ResponseData.create().success() : ResponseData.create().failed();
+ }
+
+ @Override public ResponseData deleteUserAcl(AclEntry entry) {
+ return aclConsole.deleteUserAcl(entry) ? ResponseData.create().success() : ResponseData.create().failed();
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..03b586e
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,11 @@
+server:
+ port: 7766
+
+kafka:
+ config:
+ bootstrap-server: 'localhost:9092'
+ request-timeout-ms: 60000
+ security-protocol: SASL_PLAINTEXT
+ sasl-mechanism: SCRAM-SHA-256
+ sasl-jaas-config: org.apache.kafka.common.security.scram.ScramLoginModule required username="admin" password="admin";
+
diff --git a/src/main/scala/kafka/console/KafkaAclConsole.scala b/src/main/scala/kafka/console/KafkaAclConsole.scala
new file mode 100644
index 0000000..a06f38b
--- /dev/null
+++ b/src/main/scala/kafka/console/KafkaAclConsole.scala
@@ -0,0 +1,135 @@
+package kafka.console
+
+import java.util
+import java.util.concurrent.TimeUnit
+import java.util.{Collections, List}
+
+import com.xuxd.kafka.console.beans.AclEntry
+import com.xuxd.kafka.console.config.KafkaConfig
+import org.apache.kafka.common.acl._
+import org.apache.kafka.common.resource.{ResourcePattern, ResourcePatternFilter, ResourceType}
+
+import scala.jdk.CollectionConverters.SetHasAsJava
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 19:53:06
+ * */
+class KafkaAclConsole(config: KafkaConfig) extends KafkaConsole(config: KafkaConfig) with Logging {
+
+ def getAclList(): List[AclBinding] = {
+ withAdminClient(adminClient => adminClient.describeAcls(AclBindingFilter.ANY).values().get()).asInstanceOf[List[AclBinding]]
+ }
+
+ def addAcl(acls: List[AclBinding]): Boolean = {
+ withAdminClient(adminClient => {
+ try {
+ adminClient.createAcls(acls).all().get(3000, TimeUnit.MILLISECONDS)
+ true
+ } catch {
+ case e: Exception => log.error("addAcl error", e)
+ false
+ }
+ }).asInstanceOf[Boolean]
+ }
+
+ // must param: entry.topic, entry.user
+ def addProducerAcl(entry: AclEntry): Boolean = {
+ // topic
+ // user
+ val param = entry.toAclBinding
+
+ val binding = new AclBinding(new ResourcePattern(ResourceType.TOPIC, param.pattern().name(), param.pattern().patternType()),
+ new AccessControlEntry(param.entry().principal(), param.entry().host(), param.entry().operation(), AclPermissionType.ALLOW))
+
+ addAcl(new util.ArrayList[AclBinding](getAclBindings(binding, Set(AclOperation.CREATE, AclOperation.DESCRIBE, AclOperation.WRITE)).asJava))
+ }
+
+ def addConsumerAcl(topicEntry: AclEntry, groupEntry: AclEntry): Boolean = {
+ // topic
+ // group
+ // user
+ val tp = topicEntry.toAclBinding
+ val gp = groupEntry.toAclBinding
+
+ val bindingTopic = new AclBinding(new ResourcePattern(ResourceType.TOPIC, tp.pattern().name(), tp.pattern().patternType()),
+ new AccessControlEntry(tp.entry().principal(), tp.entry().host(), tp.entry().operation(), AclPermissionType.ALLOW))
+
+ val bindingGroup = new AclBinding(new ResourcePattern(ResourceType.GROUP, gp.pattern().name(), gp.pattern().patternType()),
+ new AccessControlEntry(gp.entry().principal(), gp.entry().host(), gp.entry().operation(), AclPermissionType.ALLOW))
+
+ val acls: Set[AclBinding] = getAclBindings(bindingTopic, Set(AclOperation.READ)) ++ getAclBindings(bindingGroup, Set(AclOperation.READ))
+
+ addAcl(new util.ArrayList[AclBinding](acls.asJava))
+ }
+
+ def deleteAcl(entry: AclEntry, allResource: Boolean, allPrincipal: Boolean, allOperation: Boolean): Boolean = {
+ withAdminClient(adminClient => {
+ try {
+ val result = adminClient.deleteAcls(Collections.singleton(entry.toAclBindingFilter(allResource, allPrincipal, allOperation))).all().get(3000, TimeUnit.MILLISECONDS)
+ log.info("delete acl: {}", result)
+ true
+ } catch {
+ case e: Exception => log.error("addAcl error", e)
+ false
+ }
+ }).asInstanceOf[Boolean]
+ }
+
+ def deleteAcl(filters: util.Collection[AclBindingFilter]): Boolean = {
+ withAdminClient(adminClient => {
+ try {
+ val result = adminClient.deleteAcls(filters).all().get(3000, TimeUnit.MILLISECONDS)
+ log.info("delete acl: {}", result)
+ true
+ } catch {
+ case e: Exception => log.error("deleteAcl error", e)
+ false
+ }
+ }).asInstanceOf[Boolean]
+ }
+
+ def deleteUserAcl(entry: AclEntry): Boolean = {
+ val filter: AclBindingFilter = entry.toAclBindingFilter
+ val delFilter = new AclBindingFilter(new ResourcePatternFilter(ResourceType.ANY, ResourcePattern.WILDCARD_RESOURCE, filter.patternFilter().patternType()),
+ new AccessControlEntryFilter(filter.entryFilter().principal(), filter.entryFilter().host(), AclOperation.ANY, AclPermissionType.ANY))
+
+ deleteAcl(Collections.singleton(delFilter))
+ }
+
+ def deleteProducerAcl(entry: AclEntry): Boolean = {
+ val filter: AclBindingFilter = entry.toAclBindingFilter
+ val delFilter = new AclBindingFilter(new ResourcePatternFilter(ResourceType.TOPIC, filter.patternFilter().name(), filter.patternFilter().patternType()),
+ new AccessControlEntryFilter(filter.entryFilter().principal(), filter.entryFilter().host(), AclOperation.ANY, AclPermissionType.ANY))
+
+ deleteAcl(getAclFilters(delFilter, Set(AclOperation.CREATE, AclOperation.DESCRIBE, AclOperation.WRITE)).asJava)
+ }
+
+ def deleteConsumerAcl(topic: AclEntry, group: AclEntry): Boolean = {
+ val (topicFilter, groupFilter) = (topic.toAclBindingFilter(), group.toAclBindingFilter())
+ val delTopicFilter = new AclBindingFilter(new ResourcePatternFilter(ResourceType.TOPIC, topicFilter.patternFilter().name(), topicFilter.patternFilter().patternType()),
+ new AccessControlEntryFilter(topicFilter.entryFilter().principal(), topicFilter.entryFilter().host(), AclOperation.ANY, AclPermissionType.ANY))
+ val delGroupFilter = new AclBindingFilter(new ResourcePatternFilter(ResourceType.GROUP, groupFilter.patternFilter().name(), groupFilter.patternFilter().patternType()),
+ new AccessControlEntryFilter(groupFilter.entryFilter().principal(), groupFilter.entryFilter().host(), AclOperation.ANY, AclPermissionType.ANY))
+
+ val filters = getAclFilters(delTopicFilter, Set(AclOperation.READ)) ++ getAclFilters(delGroupFilter, Set(AclOperation.READ))
+ deleteAcl(filters.asJava)
+ }
+
+ private def getAclBindings(binding: AclBinding, ops: Set[AclOperation]): Set[AclBinding] = {
+ for {
+ op <- ops
+ } yield {
+ new AclBinding(new ResourcePattern(binding.pattern().resourceType(), binding.pattern().name(), binding.pattern().patternType()),
+ new AccessControlEntry(binding.entry().principal(), binding.entry().host(), op, binding.entry().permissionType()))
+ }
+ }
+
+ private def getAclFilters(filter: AclBindingFilter, ops: Set[AclOperation]): Set[AclBindingFilter] = {
+ ops.map(o => new AclBindingFilter(new ResourcePatternFilter(filter.patternFilter().resourceType(), filter.patternFilter().name(), filter.patternFilter().patternType()),
+ new AccessControlEntryFilter(filter.entryFilter().principal(), filter.entryFilter().host(), o, filter.entryFilter().permissionType())))
+ }
+
+}
diff --git a/src/main/scala/kafka/console/KafkaConfigConsole.scala b/src/main/scala/kafka/console/KafkaConfigConsole.scala
new file mode 100644
index 0000000..efe3ce9
--- /dev/null
+++ b/src/main/scala/kafka/console/KafkaConfigConsole.scala
@@ -0,0 +1,56 @@
+package kafka.console
+
+import java.util
+import java.util.Set
+import java.util.concurrent.TimeUnit
+
+import com.xuxd.kafka.console.config.KafkaConfig
+import org.apache.kafka.clients.admin.{ScramCredentialInfo, ScramMechanism, UserScramCredentialDeletion, UserScramCredentialUpsertion}
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 11:29:48
+ * */
+class KafkaConfigConsole(config: KafkaConfig) extends KafkaConsole(config: KafkaConfig) with Logging {
+
+ private val defaultIterations = 4096
+
+ def getUserList(): Set[String] = {
+ withAdminClient({
+ adminClient => adminClient.describeUserScramCredentials().all().get().keySet()
+ }).asInstanceOf[Set[String]]
+ }
+
+ def addOrUpdateUser(name: String, pass: String): Boolean = {
+ withAdminClient(adminClient => {
+ try {
+ adminClient.alterUserScramCredentials(util.Arrays.asList(
+ new UserScramCredentialUpsertion(name,
+ new ScramCredentialInfo(ScramMechanism.fromMechanismName(config.getSaslMechanism), defaultIterations), pass)))
+ .all().get(3000, TimeUnit.MILLISECONDS)
+ true
+ } catch {
+ case ex: Exception => log.error("addOrUpdateUser error", ex)
+ false
+ }
+
+ }).asInstanceOf[Boolean]
+ }
+
+ def deleteUser(name: String): Boolean = {
+ withAdminClient(adminClient => {
+ try {
+ adminClient.alterUserScramCredentials(util.Arrays.asList(
+ new UserScramCredentialDeletion(name, ScramMechanism.fromMechanismName(config.getSaslMechanism))))
+ .all().get(3000, TimeUnit.MILLISECONDS)
+ true
+ } catch {
+ case ex: Exception => log.error("deleteUser error", ex)
+ false
+ }
+
+ }).asInstanceOf[Boolean]
+ }
+}
diff --git a/src/main/scala/kafka/console/KafkaConsole.scala b/src/main/scala/kafka/console/KafkaConsole.scala
new file mode 100644
index 0000000..78ab1a3
--- /dev/null
+++ b/src/main/scala/kafka/console/KafkaConsole.scala
@@ -0,0 +1,38 @@
+package kafka.console
+
+import java.util.Properties
+
+import com.xuxd.kafka.console.config.KafkaConfig
+import org.apache.kafka.clients.CommonClientConfigs
+import org.apache.kafka.clients.admin.{Admin, AdminClientConfig}
+import org.apache.kafka.common.config.SaslConfigs
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 11:56:48
+ * */
+class KafkaConsole(config: KafkaConfig) {
+
+ protected def withAdminClient(f: Admin => Any): Any = {
+
+ val admin = createAdminClient()
+ try {
+ f(admin)
+ } finally {
+ admin.close()
+ }
+ }
+
+ private def createAdminClient(): Admin = {
+ val props: Properties = new Properties();
+ props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServer)
+ props.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, config.getRequestTimeoutMs())
+ props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, config.getSecurityProtocol())
+ props.put(SaslConfigs.SASL_MECHANISM, config.getSaslMechanism())
+ props.put(SaslConfigs.SASL_JAAS_CONFIG, config.getSaslJaasConfig())
+
+ Admin.create(props)
+ }
+}
diff --git a/src/main/scala/kafka/console/Logging.scala b/src/main/scala/kafka/console/Logging.scala
new file mode 100644
index 0000000..f58a568
--- /dev/null
+++ b/src/main/scala/kafka/console/Logging.scala
@@ -0,0 +1,14 @@
+package kafka.console
+
+import org.slf4j.{Logger, LoggerFactory}
+
+/**
+ * kafka-console-ui.
+ *
+ * @author xuxd
+ * @date 2021-08-28 11:30:14
+ * */
+trait Logging {
+
+ protected lazy val log : Logger = LoggerFactory.getLogger(this.getClass)
+}
diff --git a/src/test/java/com/xuxd/kafka/console/KafkaConsoleUiApplicationTests.java b/src/test/java/com/xuxd/kafka/console/KafkaConsoleUiApplicationTests.java
new file mode 100644
index 0000000..bd18efa
--- /dev/null
+++ b/src/test/java/com/xuxd/kafka/console/KafkaConsoleUiApplicationTests.java
@@ -0,0 +1,13 @@
+package com.xuxd.kafka.console;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class KafkaConsoleUiApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}