From bc55bceb5b65dccb2ed179b7ecf2bd269317c1af Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Thu, 29 May 2025 11:22:00 +0200 Subject: [PATCH 1/2] Allow disabling GitHub release info The application info object contains details about the latest version which is queried online from GitHub API. While this info can be useful, it raises warning when Kafbat UI is deployed in environments with restricted internet access. Introduce a new property "github.release.info.enabled" (default: true) that can be set to "false" to disable release info and just display the currently running version/commit. --- .../ui/service/ApplicationInfoService.java | 34 ++++++++++++++++--- .../io/kafbat/ui/util/GithubReleaseInfo.java | 1 + .../service/ApplicationInfoServiceTest.java | 26 ++++++++++++++ frontend/src/components/Version/Version.tsx | 2 +- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/io/kafbat/ui/service/ApplicationInfoService.java b/api/src/main/java/io/kafbat/ui/service/ApplicationInfoService.java index 7ee28b62d..dd5d9c4f9 100644 --- a/api/src/main/java/io/kafbat/ui/service/ApplicationInfoService.java +++ b/api/src/main/java/io/kafbat/ui/service/ApplicationInfoService.java @@ -3,6 +3,7 @@ import static io.kafbat.ui.api.model.AuthType.DISABLED; import static io.kafbat.ui.api.model.AuthType.OAUTH2; import static io.kafbat.ui.model.ApplicationInfoDTO.EnabledFeaturesEnum; +import static io.kafbat.ui.util.GithubReleaseInfo.GITHUB_RELEASE_INFO_ENABLED; import static io.kafbat.ui.util.GithubReleaseInfo.GITHUB_RELEASE_INFO_TIMEOUT; import com.google.common.annotations.VisibleForTesting; @@ -15,12 +16,14 @@ import io.kafbat.ui.model.OAuthProviderDTO; import io.kafbat.ui.util.DynamicConfigOperations; import io.kafbat.ui.util.GithubReleaseInfo; +import jakarta.annotation.Nullable; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Properties; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.info.BuildProperties; @@ -33,7 +36,9 @@ import org.springframework.stereotype.Service; @Service +@Slf4j public class ApplicationInfoService { + @Nullable private final GithubReleaseInfo githubReleaseInfo; private final ApplicationContext applicationContext; private final DynamicConfigOperations dynamicConfigOperations; @@ -44,23 +49,34 @@ public ApplicationInfoService(DynamicConfigOperations dynamicConfigOperations, ApplicationContext applicationContext, @Autowired(required = false) BuildProperties buildProperties, @Autowired(required = false) GitProperties gitProperties, + @Value("${" + GITHUB_RELEASE_INFO_ENABLED + ":true}") boolean githubInfoEnabled, @Value("${" + GITHUB_RELEASE_INFO_TIMEOUT + ":10}") int githubApiMaxWaitTime) { this.applicationContext = applicationContext; this.dynamicConfigOperations = dynamicConfigOperations; this.buildProperties = Optional.ofNullable(buildProperties).orElse(new BuildProperties(new Properties())); this.gitProperties = Optional.ofNullable(gitProperties).orElse(new GitProperties(new Properties())); - githubReleaseInfo = new GithubReleaseInfo(githubApiMaxWaitTime); + if (githubInfoEnabled) { + this.githubReleaseInfo = new GithubReleaseInfo(githubApiMaxWaitTime); + } else { + this.githubReleaseInfo = null; + log.warn("Check for latest release is disabled." + + " Note that old versions are not supported, please make sure that your system is up to date."); + } } public ApplicationInfoDTO getApplicationInfo() { - var releaseInfo = githubReleaseInfo.get(); + var releaseInfo = githubReleaseInfo != null ? githubReleaseInfo.get() : null; return new ApplicationInfoDTO() .build(getBuildInfo(releaseInfo)) .enabledFeatures(getEnabledFeatures()) .latestRelease(convert(releaseInfo)); } + @Nullable private ApplicationInfoLatestReleaseDTO convert(GithubReleaseInfo.GithubReleaseDto releaseInfo) { + if (releaseInfo == null) { + return null; + } return new ApplicationInfoLatestReleaseDTO() .htmlUrl(releaseInfo.html_url()) .publishedAt(releaseInfo.published_at()) @@ -68,12 +84,17 @@ private ApplicationInfoLatestReleaseDTO convert(GithubReleaseInfo.GithubReleaseD } private ApplicationInfoBuildDTO getBuildInfo(GithubReleaseInfo.GithubReleaseDto release) { - return new ApplicationInfoBuildDTO() - .isLatestRelease(release.tag_name() != null && release.tag_name().equals(buildProperties.getVersion())) + var buildInfo = new ApplicationInfoBuildDTO() .commitId(gitProperties.getShortCommitId()) .version(buildProperties.getVersion()) .buildTime(buildProperties.getTime() != null ? DateTimeFormatter.ISO_INSTANT.format(buildProperties.getTime()) : null); + if (release != null) { + buildInfo = buildInfo.isLatestRelease( + release.tag_name() != null && release.tag_name().equals(buildProperties.getVersion()) + ); + } + return buildInfo; } private List getEnabledFeatures() { @@ -119,10 +140,13 @@ private List getOAuthProviders() { // updating on startup and every hour @Scheduled(fixedRateString = "${github-release-info-update-rate:3600000}") public void updateGithubReleaseInfo() { - githubReleaseInfo.refresh().subscribe(); + if (githubReleaseInfo != null) { + githubReleaseInfo.refresh().subscribe(); + } } @VisibleForTesting + @Nullable GithubReleaseInfo githubReleaseInfo() { return githubReleaseInfo; } diff --git a/api/src/main/java/io/kafbat/ui/util/GithubReleaseInfo.java b/api/src/main/java/io/kafbat/ui/util/GithubReleaseInfo.java index ba767a65a..8e87796ad 100644 --- a/api/src/main/java/io/kafbat/ui/util/GithubReleaseInfo.java +++ b/api/src/main/java/io/kafbat/ui/util/GithubReleaseInfo.java @@ -8,6 +8,7 @@ @Slf4j public class GithubReleaseInfo { + public static final String GITHUB_RELEASE_INFO_ENABLED = "github.release.info.enabled"; public static final String GITHUB_RELEASE_INFO_TIMEOUT = "github.release.info.timeout"; private static final String GITHUB_LATEST_RELEASE_RETRIEVAL_URL = diff --git a/api/src/test/java/io/kafbat/ui/service/ApplicationInfoServiceTest.java b/api/src/test/java/io/kafbat/ui/service/ApplicationInfoServiceTest.java index a4cae637b..f81429d59 100644 --- a/api/src/test/java/io/kafbat/ui/service/ApplicationInfoServiceTest.java +++ b/api/src/test/java/io/kafbat/ui/service/ApplicationInfoServiceTest.java @@ -1,8 +1,11 @@ package io.kafbat.ui.service; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import io.kafbat.ui.AbstractIntegrationTest; +import io.kafbat.ui.util.DynamicConfigOperations; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -10,8 +13,31 @@ class ApplicationInfoServiceTest extends AbstractIntegrationTest { @Autowired private ApplicationInfoService service; + @Autowired + private DynamicConfigOperations dynamicConfigOperations; + @Test void testCustomGithubReleaseInfoTimeout() { assertEquals(100, service.githubReleaseInfo().getGithubApiMaxWaitTime()); } + + @Test + void testDisabledReleaseInfo() { + var service2 = new ApplicationInfoService( + dynamicConfigOperations, + null, + null, + null, + false, + 101 + ); + + assertNull(service2.githubReleaseInfo(), "unexpected GitHub release info when disabled"); + var appInfo = service2.getApplicationInfo(); + assertNotNull(appInfo, "application info must not be NULL"); + assertNull(appInfo.getLatestRelease(), "latest release should be NULL when disabled"); + assertNotNull(appInfo.getBuild(), "build info must not be NULL"); + assertNotNull(appInfo.getEnabledFeatures(), "enabled features must not be NULL"); + } + } diff --git a/frontend/src/components/Version/Version.tsx b/frontend/src/components/Version/Version.tsx index 775fbfa5d..ae14e312b 100644 --- a/frontend/src/components/Version/Version.tsx +++ b/frontend/src/components/Version/Version.tsx @@ -19,7 +19,7 @@ const Version: React.FC = () => { return ( - {!isLatestRelease && ( + {isLatestRelease === false && ( Date: Thu, 29 May 2025 18:26:44 +0200 Subject: [PATCH 2/2] Display a neutral warning icon if version check is disabled Extend he WarningIcon template to allow custom coloring with fallback to theme's "warningIcon" color. If version check is disabled, show a corresponding info with a "infoIcon" (gray) colored icon instead. --- frontend/src/components/Version/Version.tsx | 8 ++++++++ frontend/src/components/common/Icons/WarningIcon.tsx | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Version/Version.tsx b/frontend/src/components/Version/Version.tsx index ae14e312b..dd46131b4 100644 --- a/frontend/src/components/Version/Version.tsx +++ b/frontend/src/components/Version/Version.tsx @@ -3,6 +3,7 @@ import WarningIcon from 'components/common/Icons/WarningIcon'; import { gitCommitPath } from 'lib/paths'; import { useLatestVersion } from 'lib/hooks/api/latestVersion'; import { formatTimestamp } from 'lib/dateTimeHelpers'; +import { useTheme } from 'styled-components'; import * as S from './Version.styled'; @@ -17,8 +18,15 @@ const Version: React.FC = () => { ? versionTag : formatTimestamp(buildTime); + const theme = useTheme(); + return ( + {isLatestRelease === null && ( + + + + )} {isLatestRelease === false && ( { +const WarningIcon: React.FC<{ color?: string }> = ({ color }) => { + const theme = useTheme(); return ( { fillRule="evenodd" clipRule="evenodd" d="M8.09265 1.06679C7.60703 0.250524 6.39297 0.250524 5.90735 1.06679L0.170916 10.7089C-0.314707 11.5252 0.292322 12.5455 1.26357 12.5455H12.7364C13.7077 12.5455 14.3147 11.5252 13.8291 10.7089L8.09265 1.06679ZM6 5.00006C6 4.44778 6.44772 4.00006 7 4.00006C7.55228 4.00006 8 4.44778 8 5.00006V7.00006C8 7.55235 7.55228 8.00006 7 8.00006C6.44772 8.00006 6 7.55235 6 7.00006V5.00006ZM6 10.0001C6 9.44778 6.44772 9.00006 7 9.00006C7.55228 9.00006 8 9.44778 8 10.0001C8 10.5523 7.55228 11.0001 7 11.0001C6.44772 11.0001 6 10.5523 6 10.0001Z" - fill="#F2C94C" + fill={color || theme.icons.warningIcon} />