From d8d4d4bb054aaae44d3b05b15734293b36cbcc7e Mon Sep 17 00:00:00 2001 From: tchiotludo Date: Thu, 11 Apr 2019 09:12:32 +0200 Subject: [PATCH 1/9] Doc : base-path close #44 --- application.example.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application.example.yml b/application.example.yml index a7572027d..0083e7176 100644 --- a/application.example.yml +++ b/application.example.yml @@ -1,6 +1,8 @@ kafkahq: server: - base-path: "" # if behind a reverse proxy, path to kafkahq with trailing slash (optionnal) + base-path: "" # if behind a reverse proxy, path to kafkahq with trailing slash (optionnal). Example: kafkahq is + # behind a reverse proxy with url http://my-server/kafkahq, set base-path: "/kafkahq/". + # Not needed if you're behind a reverse proxy with subdomain http://kafkahq.my-server/ access-log: # Access log configuration (optionnal) enabled: true # true by default name: org.kafkahq.log.access # Logger name From 36865a1e6e335d883ce4af114b8140ff7dcc9087 Mon Sep 17 00:00:00 2001 From: tchiotludo Date: Sat, 13 Apr 2019 09:18:56 +0200 Subject: [PATCH 2/9] fix login redirect with $base-path close #46 --- src/main/resources/application.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 94cced2b4..491179807 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,10 +19,10 @@ micronaut: enabled: true session: enabled: true - login-success-target-url: / - forbidden-target-url: /login/forbidden - unauthorized-target-url: /login/unauthorized - login-failure-target-url: /login/failed + login-success-target-url: "${kafkahq.server.base-path:}/" + forbidden-target-url: "${kafkahq.server.base-path:}/login/forbidden" + unauthorized-target-url: "${kafkahq.server.base-path:}/login/unauthorized" + login-failure-target-url: "${kafkahq.server.base-path:}/login/failed" intercept-url-map: - pattern: "${kafkahq.server.base-path:}/static/**" access: "isAnonymous()" From 17f58edd1f3ca3ca7da8f5ab4608f85e337b96e4 Mon Sep 17 00:00:00 2001 From: tchiotludo Date: Sat, 13 Apr 2019 09:19:24 +0200 Subject: [PATCH 3/9] Support snappy compression on docker close #45 --- Dockerfile | 6 +++++ Dockerfile-dev | 10 +++++++++ build.gradle | 6 ++++- docker-compose-dev.yml | 4 +++- docker/app/jvm.options | 50 ++++++++++++++++++++++++++++++++++++++++++ docker/app/kafkahq | 7 ++++++ 6 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 Dockerfile-dev create mode 100644 docker/app/jvm.options diff --git a/Dockerfile b/Dockerfile index bd7169062..c9bca7cee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,10 @@ FROM openjdk:8-jre-alpine + +RUN apk add --update \ + libc6-compat \ + java-snappy-native \ + && rm -rf /var/cache/apk/* + WORKDIR /app COPY docker / ENV MICRONAUT_CONFIG_FILES=/app/application.yml diff --git a/Dockerfile-dev b/Dockerfile-dev new file mode 100644 index 000000000..892e2f8e1 --- /dev/null +++ b/Dockerfile-dev @@ -0,0 +1,10 @@ +FROM gradle:5.3.1-jdk-alpine + +USER root + +RUN apk add --update \ + libc6-compat \ + java-snappy-native \ + && rm -rf /var/cache/apk/* + +USER gradle \ No newline at end of file diff --git a/build.gradle b/build.gradle index ce9b8620d..9bc57b500 100644 --- a/build.gradle +++ b/build.gradle @@ -83,7 +83,11 @@ configurations { run.classpath += configurations.developmentOnly test.classpath += configurations.developmentOnly -run.jvmArgs('-noverify', '-XX:TieredStopAtLevel=1', '-Dmicronaut.environments=dev') +run.jvmArgs('-noverify', + '-XX:TieredStopAtLevel=1', + '-Dmicronaut.environments=dev', + '-Dorg.xerial.snappy.use.systemlib=true' +) tasks.withType(JavaCompile){ options.encoding = "UTF-8" diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 00d372d01..c139ba498 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -10,7 +10,9 @@ volumes: services: kafkahq: - image: gradle:5.3.1-jdk-alpine + build: + context: . + dockerfile: Dockerfile-dev command: "gradle run --continuous" working_dir: /app volumes: diff --git a/docker/app/jvm.options b/docker/app/jvm.options new file mode 100644 index 000000000..9216c2af8 --- /dev/null +++ b/docker/app/jvm.options @@ -0,0 +1,50 @@ +########################################################################### +# jvm.options # +# # +# - all flags defined here will be used to startup the JVM # +# - one flag should be specified per line # +# - lines that do not start with '-' will be ignored # +# - only static flags are accepted (no variables or parameters) # +# - dynamic flags will be appended to these on cassandra-env # +########################################################################### + +# Server Hotspot JVM +-server + +# ensure UTF-8 encoding by default (e.g. filenames) +-Dfile.encoding=UTF-8 + +# set to headless, just in case +-Djava.awt.headless=true + +# generate a heap dump when an allocation from the Java heap fails +# heap dumps are created in the working directory of the JVM +-XX:+HeapDumpOnOutOfMemoryError +-XX:HeapDumpPath=/tmp/heapdump.log + +# GC logs +-XX:+PrintGCDetails +-XX:+PrintGCDateStamps +-XX:+PrintTenuringDistribution +-XX:+PrintGCApplicationStoppedTime +-Xloggc:/tmp/gc.log +-XX:+UseGCLogFileRotation +-XX:NumberOfGCLogFiles=32 +-XX:GCLogFileSize=64m + +# Docker aware cpu/memory otions +-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap + +# Do not rely on the system configuration +-Dfile.encoding=UTF-8 +-Duser.timezone=UTC + +# tells the Java library to use the preinstalled native library +-Dorg.xerial.snappy.use.systemlib=true + +# Jmx Remote +-Dcom.sun.management.jmxremote +-Dcom.sun.management.jmxremote.port=8686 +-Dcom.sun.management.jmxremote.local.only=false +-Dcom.sun.management.jmxremote.authenticate=false +-Dcom.sun.management.jmxremote.ssl=false diff --git a/docker/app/kafkahq b/docker/app/kafkahq index 23e485d11..3d0c88600 100755 --- a/docker/app/kafkahq +++ b/docker/app/kafkahq @@ -1,3 +1,10 @@ #!/usr/bin/env sh +# Read user-defined JVM options from jvm.options file +JVM_OPTS_FILE=${JVM_OPTS_FILE:-/app/jvm.options} +for JVM_OPT in `grep "^-" ${JVM_OPTS_FILE}` +do + JAVA_OPTS="${JAVA_OPTS} ${JVM_OPT}" +done + /usr/bin/java ${JAVA_OPTS} -jar /app/kafkahq.jar \ No newline at end of file From 0044474b19700125e83fea9b78fd3555084b013a Mon Sep 17 00:00:00 2001 From: tchiotludo Date: Sat, 13 Apr 2019 09:54:02 +0200 Subject: [PATCH 4/9] Add a proper message when no kafka cluster found close #41 --- .../java/org/kafkahq/middlewares/KafkaWrapperFilter.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/kafkahq/middlewares/KafkaWrapperFilter.java b/src/main/java/org/kafkahq/middlewares/KafkaWrapperFilter.java index 2d9f18417..fdbce158d 100644 --- a/src/main/java/org/kafkahq/middlewares/KafkaWrapperFilter.java +++ b/src/main/java/org/kafkahq/middlewares/KafkaWrapperFilter.java @@ -26,6 +26,13 @@ public KafkaWrapperFilter(KafkaModule kafkaModule, RequestHelper requestHelper) @Override public Publisher> doFilter(HttpRequest request, ServerFilterChain chain) { + if (kafkaModule.getClustersList().size() == 0) { + throw new IllegalArgumentException( + "Couldn't find any clusters on your configuration file, " + + "please ensure that the configuration file is loaded correctly" + ); + } + requestHelper .getClusterId(request) .ifPresent(s -> AbstractRepository.setWrapper(new KafkaWrapper(kafkaModule, s))); From 44cdbc013fff596aa989ec7d6168f6d1e2d064eb Mon Sep 17 00:00:00 2001 From: tchiotludo Date: Sat, 13 Apr 2019 14:36:42 +0200 Subject: [PATCH 5/9] Consume records quickly - Fetch in parallel for consume newest - Reduce default poll timeout and add a configuration variable - relate to #23 --- application.example.yml | 1 + src/main/java/org/kafkahq/models/LogDir.java | 3 +- .../repositories/RecordRepository.java | 54 ++++++++++--------- src/main/resources/application.yml | 1 + 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/application.example.yml b/application.example.yml index 0083e7176..acb106e6c 100644 --- a/application.example.yml +++ b/application.example.yml @@ -47,6 +47,7 @@ kafkahq: topic-data: sort: OLDEST # default sort order (OLDEST, NEWEST) (default: OLDEST) size: 50 # max record per page (default: 50) + poll-timeout: 1000 # The time, in milliseconds, spent waiting in poll if data is not available in the buffer. # Auth & Roles (optionnal) security: diff --git a/src/main/java/org/kafkahq/models/LogDir.java b/src/main/java/org/kafkahq/models/LogDir.java index a4e83370d..f86696937 100644 --- a/src/main/java/org/kafkahq/models/LogDir.java +++ b/src/main/java/org/kafkahq/models/LogDir.java @@ -3,6 +3,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; +import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.requests.DescribeLogDirsResponse; @ToString @@ -17,7 +18,7 @@ public class LogDir { private final long offsetLag; private final boolean isFuture; - public LogDir(Integer brokerId, String path, org.apache.kafka.common.TopicPartition topicPartition, DescribeLogDirsResponse.ReplicaInfo replicaInfo) { + public LogDir(Integer brokerId, String path, TopicPartition topicPartition, DescribeLogDirsResponse.ReplicaInfo replicaInfo) { this.brokerId = brokerId; this.path = path; this.topic = topicPartition.topic(); diff --git a/src/main/java/org/kafkahq/repositories/RecordRepository.java b/src/main/java/org/kafkahq/repositories/RecordRepository.java index ed274e811..de64a1941 100644 --- a/src/main/java/org/kafkahq/repositories/RecordRepository.java +++ b/src/main/java/org/kafkahq/repositories/RecordRepository.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; +import io.micronaut.context.annotation.Value; import io.micronaut.context.env.Environment; import io.micronaut.http.sse.Event; import io.reactivex.Flowable; @@ -35,6 +36,9 @@ public class RecordRepository extends AbstractRepository { private final TopicRepository topicRepository; private final SchemaRegistryRepository schemaRegistryRepository; + @Value("${kafkahq.topic-data.poll-timeout}") + protected int pollTimeout; + @Inject public RecordRepository(KafkaModule kafkaModule, TopicRepository topicRepository, SchemaRegistryRepository schemaRegistryRepository) { this.kafkaModule = kafkaModule; @@ -145,29 +149,31 @@ private Map getTopicPartitionForSortOldest(Topic topic, Op private List consumeNewest(Topic topic, Options options) { int pollSizePerPartition = pollSizePerPartition(topic, options); - KafkaConsumer consumer = this.kafkaModule.getConsumer( - options.clusterId, - new Properties() {{ - put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, String.valueOf(pollSizePerPartition)); - }} - ); - - List collect = topic + return topic .getPartitions() - .stream() - .map(partition -> getOffsetForSortNewest(consumer, partition, options, pollSizePerPartition) - .map(offset -> offset.withTopicPartition( - new TopicPartition( - partition.getTopic(), - partition.getId() - ) - )) + .parallelStream() + .map(partition -> { + KafkaConsumer consumer = this.kafkaModule.getConsumer( + options.clusterId, + new Properties() {{ + put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, String.valueOf(pollSizePerPartition)); + }} + ); + + return getOffsetForSortNewest(consumer, partition, options, pollSizePerPartition) + .map(offset -> offset.withTopicPartition( + new TopicPartition( + partition.getTopic(), + partition.getId() + ) + )); + } ) .filter(Optional::isPresent) .map(Optional::get) .flatMap(topicPartitionOffset -> { - consumer.assign(Collections.singleton(topicPartitionOffset.getTopicPartition())); - consumer.seek(topicPartitionOffset.getTopicPartition(), topicPartitionOffset.getBegin()); + topicPartitionOffset.getConsumer().assign(Collections.singleton(topicPartitionOffset.getTopicPartition())); + topicPartitionOffset.getConsumer().seek(topicPartitionOffset.getTopicPartition(), topicPartitionOffset.getBegin()); List list = new ArrayList<>(); int emptyPoll = 0; @@ -175,7 +181,7 @@ private List consumeNewest(Topic topic, Options options) { do { ConsumerRecords records; - records = this.poll(consumer); + records = this.poll(topicPartitionOffset.getConsumer()); if (records.isEmpty()) { emptyPoll++; @@ -196,14 +202,12 @@ private List consumeNewest(Topic topic, Options options) { Collections.reverse(list); + topicPartitionOffset.getConsumer().close(); + return Stream.of(list); }) .flatMap(List::stream) .collect(Collectors.toList()); - - consumer.close(); - - return collect; } private int pollSizePerPartition(Topic topic, Options options) { @@ -274,6 +278,7 @@ private Optional getOffsetForSortNewest(KafkaConsumer poll(KafkaConsumer consu // First one wait for metadata and send records // Hack bellow can be used to wait for metadata */ - return consumer.poll(5000); + return consumer.poll(this.pollTimeout); /* if (!records.isEmpty()) { @@ -596,5 +601,6 @@ private static class EndOffsetBound { private final TopicPartition topicPartition; private final long begin; private final long end; + private final KafkaConsumer consumer; } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 491179807..bfb64e751 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -49,6 +49,7 @@ kafkahq: topic-data: sort: OLDEST size: 50 + poll-timeout: 1000 security: default-roles: From a679a05b89616f8111d448b2a7ea2ddc71f882d6 Mon Sep 17 00:00:00 2001 From: tchiotludo Date: Sat, 13 Apr 2019 14:40:25 +0200 Subject: [PATCH 6/9] Security add login / logout button close #42 --- assets/modules/templates/sidebar.scss | 15 +++++++++++++++ .../controllers/AbstractController.java | 15 +++++++++++++++ src/main/resources/application.yml | 4 ++++ src/main/resources/views/includes/template.ftl | 18 ++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/assets/modules/templates/sidebar.scss b/assets/modules/templates/sidebar.scss index 526b4a0b6..6308b966e 100644 --- a/assets/modules/templates/sidebar.scss +++ b/assets/modules/templates/sidebar.scss @@ -8,6 +8,8 @@ font-size: $font-size-lg; border-right: 1px solid $nav-tabs-border-color; z-index: 2; + display: flex; + flex-direction: column; .sidebar-header { padding: 5px 10px; @@ -21,6 +23,7 @@ } ul { + flex-grow: 2; p { color: #fff; padding: 10px; @@ -79,6 +82,18 @@ background: $tertiary; } + .sidebar-log { + padding: 5px 10px; + background: $tertiary; + border-top: 1px solid $yellow; + a { + color: white; + &:hover { + text-decoration: none; + } + } + } + @include media-breakpoint-down(sm) { & { margin-left: -$menu-width; diff --git a/src/main/java/org/kafkahq/controllers/AbstractController.java b/src/main/java/org/kafkahq/controllers/AbstractController.java index ad14305ba..a5cc855a7 100644 --- a/src/main/java/org/kafkahq/controllers/AbstractController.java +++ b/src/main/java/org/kafkahq/controllers/AbstractController.java @@ -13,6 +13,7 @@ import lombok.Builder; import lombok.Getter; import lombok.experimental.Wither; +import org.kafkahq.configs.BasicAuth; import org.kafkahq.modules.KafkaModule; import javax.inject.Inject; @@ -39,6 +40,9 @@ abstract public class AbstractController { @Value("${kafkahq.security.default-roles}") List defaultRoles; + @Inject + private List auths; + @SuppressWarnings("unchecked") protected Map templateData(Optional cluster, Object... values) { Map datas = CollectionUtils.mapOf(values); @@ -52,6 +56,17 @@ protected Map templateData(Optional cluster, Object... values) { datas.put("registryEnabled", this.kafkaModule.getRegistryRestClient(s) != null); }); + if (applicationContext.containsBean(SecurityService.class)) { + datas.put("loginEnabled", auths.size() > 0); + + SecurityService securityService = applicationContext.getBean(SecurityService.class); + securityService + .getAuthentication() + .ifPresent(authentication -> datas.put("username", authentication.getName())); + } else { + datas.put("loginEnabled", false); + } + return datas; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bfb64e751..ef8f2ea9c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,11 +15,15 @@ micronaut: endpoints: login: enabled: true + path: "${kafkahq.server.base-path:}/login" logout: enabled: true + path: "${kafkahq.server.base-path:}/logout" + get-allowed: true session: enabled: true login-success-target-url: "${kafkahq.server.base-path:}/" + logout-target-url: "${kafkahq.server.base-path:}/" forbidden-target-url: "${kafkahq.server.base-path:}/login/forbidden" unauthorized-target-url: "${kafkahq.server.base-path:}/login/unauthorized" login-failure-target-url: "${kafkahq.server.base-path:}/login/failed" diff --git a/src/main/resources/views/includes/template.ftl b/src/main/resources/views/includes/template.ftl index d88069bff..f89873923 100644 --- a/src/main/resources/views/includes/template.ftl +++ b/src/main/resources/views/includes/template.ftl @@ -7,6 +7,8 @@ <#-- @ftlvariable name="toast" type="java.lang.String" --> <#-- @ftlvariable name="registryEnabled" type="java.lang.Boolean" --> <#-- @ftlvariable name="roles" type="java.util.ArrayList" --> +<#-- @ftlvariable name="username" type="java.lang.String" --> +<#-- @ftlvariable name="loginEnabled" type="java.lang.Boolean" --> <#macro header title tab=""> @@ -78,6 +80,22 @@ + <#if loginEnabled> + +
From 1c59d34a3840963e84be4225e57acc26a849a357 Mon Sep 17 00:00:00 2001 From: tchiotludo Date: Sat, 13 Apr 2019 15:21:47 +0200 Subject: [PATCH 7/9] Fix test & travis --- .travis.yml | 2 +- build.gradle | 2 +- src/main/java/org/kafkahq/repositories/RecordRepository.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d96efc962..aed714196 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ script: - if [ "$TRAVIS_BRANCH" == "master" ]; then docker tag $TRAVIS_COMMIT tchiotludo/kafkahq:latest; docker push tchiotludo/kafkahq:latest; - elif [ "$TRAVIS_BRANCH" == "develop" ]; then + elif [ "$TRAVIS_BRANCH" == "dev" ]; then docker tag $TRAVIS_COMMIT tchiotludo/kafkahq:$TRAVIS_BRANCH; docker push tchiotludo/kafkahq:$TRAVIS_BRANCH; fi diff --git a/build.gradle b/build.gradle index 9bc57b500..684f17780 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - micronautVersion = "1.1.+" + micronautVersion = "1.1.0" confluentVersion = "5.1.+" kafkaVersion = "2.2.+" } diff --git a/src/main/java/org/kafkahq/repositories/RecordRepository.java b/src/main/java/org/kafkahq/repositories/RecordRepository.java index de64a1941..82d3a96cd 100644 --- a/src/main/java/org/kafkahq/repositories/RecordRepository.java +++ b/src/main/java/org/kafkahq/repositories/RecordRepository.java @@ -36,7 +36,7 @@ public class RecordRepository extends AbstractRepository { private final TopicRepository topicRepository; private final SchemaRegistryRepository schemaRegistryRepository; - @Value("${kafkahq.topic-data.poll-timeout}") + @Value("${kafkahq.topic-data.poll-timeout:1000}") protected int pollTimeout; @Inject From 0704d8c8e8d92bddfadc980c95591fa83f8208de Mon Sep 17 00:00:00 2001 From: Martin Maier-Moessner Date: Sun, 14 Apr 2019 21:24:28 +0200 Subject: [PATCH 8/9] fix login path to contain base path, closes #46 (#47) --- application.example.yml | 14 +++++++------- .../org/kafkahq/controllers/LoginController.java | 2 +- src/main/resources/views/login.ftl | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/application.example.yml b/application.example.yml index acb106e6c..f6b5717f8 100644 --- a/application.example.yml +++ b/application.example.yml @@ -1,14 +1,14 @@ kafkahq: server: - base-path: "" # if behind a reverse proxy, path to kafkahq with trailing slash (optionnal). Example: kafkahq is + base-path: "" # if behind a reverse proxy, path to kafkahq with trailing slash (optional). Example: kafkahq is # behind a reverse proxy with url http://my-server/kafkahq, set base-path: "/kafkahq/". # Not needed if you're behind a reverse proxy with subdomain http://kafkahq.my-server/ - access-log: # Access log configuration (optionnal) + access-log: # Access log configuration (optional) enabled: true # true by default name: org.kafkahq.log.access # Logger name format: "[Date: {}] [Duration: {} ms] [Url: {} {} {}] [Status: {}] [Ip: {}] [Length: {}] [Port: {}]" # Logger format - # default kafka properties for each clients, available for admin / producer / consumer (optionnal) + # default kafka properties for each clients, available for admin / producer / consumer (optional) clients-defaults: consumer: properties: @@ -17,9 +17,9 @@ kafkahq: # list of kafka cluster available for kafkahq connections: my-cluster-1: # url friendly name for the cluster - properties: # standard kafka properties (optionnal) + properties: # standard kafka properties (optional) bootstrap.servers: "kafka:9092" - schema-registry: "http://schema-registry:8085" # schema registry url (optionnal) + schema-registry: "http://schema-registry:8085" # schema registry url (optional) my-cluster-2: properties: @@ -43,13 +43,13 @@ kafkahq: ssl.keystore.password: password ssl.key.password: password - # Topic display data options (optionnal) + # Topic display data options (optional) topic-data: sort: OLDEST # default sort order (OLDEST, NEWEST) (default: OLDEST) size: 50 # max record per page (default: 50) poll-timeout: 1000 # The time, in milliseconds, spent waiting in poll if data is not available in the buffer. - # Auth & Roles (optionnal) + # Auth & Roles (optional) security: default-roles: # Roles available for all the user even unlogged user - topic/read diff --git a/src/main/java/org/kafkahq/controllers/LoginController.java b/src/main/java/org/kafkahq/controllers/LoginController.java index 81521703d..0d7654816 100644 --- a/src/main/java/org/kafkahq/controllers/LoginController.java +++ b/src/main/java/org/kafkahq/controllers/LoginController.java @@ -16,7 +16,7 @@ @Requires(property = SecurityConfigurationProperties.PREFIX + ".enabled", value = StringUtils.TRUE) @Controller public class LoginController extends AbstractController { - @Get("/login{/failed:[a-zA-Z]+}") + @Get("${kafkahq.server.base-path:}/login{/failed:[a-zA-Z]+}") @View("login") public HttpResponse login(Optional failed) { return HttpResponse diff --git a/src/main/resources/views/login.ftl b/src/main/resources/views/login.ftl index 74bba550a..f5d33c635 100644 --- a/src/main/resources/views/login.ftl +++ b/src/main/resources/views/login.ftl @@ -4,7 +4,7 @@ <@template.header "Login" /> -
diff --git a/src/main/resources/views/includes/template.ftl b/src/main/resources/views/includes/template.ftl index f89873923..156a921ac 100644 --- a/src/main/resources/views/includes/template.ftl +++ b/src/main/resources/views/includes/template.ftl @@ -88,7 +88,7 @@ ${username} (Logout) <#else> - + Login