Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -44,36 +49,52 @@ 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())
.versionTag(releaseInfo.tag_name());
}

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<EnabledFeaturesEnum> getEnabledFeatures() {
Expand Down Expand Up @@ -119,10 +140,13 @@ private List<OAuthProviderDTO> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
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;

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");
}

}
2 changes: 1 addition & 1 deletion frontend/src/components/Version/Version.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const Version: React.FC = () => {

return (
<S.Wrapper>
{!isLatestRelease && (
{isLatestRelease === false && (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see a visual difference between "no latest release present" and "user disabled version check" in the UI. This is crucial as it's often only the UI screenshots I have to deduct things from.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any suggestion how such information should look?

Frankly, I don't see any benefit in such information. Currently running version is displayed, if the check failed there's a warning symbol and the information if there's an update available or not can change 1 second after a screenshot was taken, because it's dynamic info.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a red (rather than yellow) exclamation mark icon (most likely it's an svg, so altering stroke-color should work) with a different text about version check disabled.
The benefit is offloading responsibility of running an oudated and possibly CVE-"enriched" version and giving the maintainers visual signal of what the user's running (red icon -> no version check = most likely outdated -> no support)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A red warning, really?

I've learned to ignore the yellow icon due do failing version checks and yet there are frequent questions if there's something wrong. So annoying users, who have no control whatsoever about the deployed version in our case, with read warning sign that typically means "something is wrong here" ...

Our use case are - you guessed it - isolated systems, mostly dev/staging environments. We allow users some access to the underlying Kafka and deployment/updates are done by some automation/opt team who don't see the UI warnings anyway.

We were looking for a solution to remove the frontend warning, not making it look worse.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You wouldn't imagine the number of issues I've seen where people come with their "up-to-date" installations, only to realize after wasting some time that it's not only an outdated version, but also a fork with completely unrelated functionality that we don't even have. Not sure of a middle ground here. @germanosin thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I extended the <WarningIcon> component to (a) use theme's warningIcon color instead of hardcoded color (was #F2C94C, now #FFDD57 ... close enough for me) and (b) allow override by parameter.

Override to infoIcon color (#ABB5BA) with adjusted message in case the check info is not available.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could hide the whole icon tbh. Even if the person reporting issues is not the same as the one managing the installation, they can still check if the version is up to date by clicking the commit hash. Given the counterpoint by @stklcode about "users screaming due to an alert", it might be alright.
@germanosin thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any update on this one?

I'd be happy to remove the latest commit again (hide the icon) if we agree on that or keep is as is.
Just rebased the code and resolved a minor merge conflict.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stklcode ok let's hide the icon (based on a property ofc)

Copy link
Author

@stklcode stklcode Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I removed the latest commit, so now the icon is gone if github.release.info.enabled=false.

I will extract a minor change (warning icon color from theme) into a separate PR, as it is now little out of scope here. (#1282)

<S.OutdatedWarning
title={`Your app version is outdated. Latest version is ${
versionTag || 'UNKNOWN'
Expand Down
Loading