feat(ui): support show update button

This commit is contained in:
ReaJason
2025-03-01 23:11:41 +08:00
parent 6f332a97d8
commit 6292e6b8b2
6 changed files with 176 additions and 12 deletions
@@ -0,0 +1,21 @@
package com.reajason.javaweb.boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* @author ReaJason
*/
@Configuration
public class WebConfig {
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(3000);
factory.setReadTimeout(3000);
return new RestTemplate(factory);
}
}
@@ -1,10 +1,20 @@
package com.reajason.javaweb.boot.controller;
import com.reajason.javaweb.boot.entity.VersionInfo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.thymeleaf.util.StringUtils;
import java.util.List;
import java.util.Map;
/**
* @author ReaJason
@@ -16,10 +26,61 @@ import org.springframework.web.bind.annotation.RestController;
public class VersionController {
@Value("${spring.application.version}")
String version;
private String version;
private final RestTemplate restTemplate;
public VersionController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@GetMapping
public String version() {
public VersionInfo version() {
String latestVersion = getLatestGithubRelease();
return VersionInfo.builder()
.currentVersion(version)
.latestVersion(latestVersion)
.hasUpdate(!StringUtils.equals(version, latestVersion))
.build();
}
private String getLatestGithubRelease() {
try {
String latestVersion = tryFetchRelease("https://api.github.com");
if (latestVersion != null) {
return latestVersion;
}
latestVersion = tryFetchRelease("https://gh.llkk.cc/https://api.github.com");
if (latestVersion != null) {
return latestVersion;
}
} catch (Exception ignored) {
}
return version;
}
private String tryFetchRelease(String baseUrl) {
String apiUrl = String.format("%s/repos/%s/%s/releases", baseUrl, "ReaJason", "MemShellParty");
ResponseEntity<List<Map<String, Object>>> response = restTemplate.exchange(
apiUrl,
HttpMethod.GET,
null,
new ParameterizedTypeReference<>() {
}
);
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
List<Map<String, Object>> body = response.getBody();
for (Map<String, Object> map : body) {
String targetCommitish = (String) map.get("target_commitish");
Boolean prerelease = (Boolean) map.get("prerelease");
Boolean draft = (Boolean) map.get("draft");
if ("master".equals(targetCommitish) && !prerelease && !draft) {
String tagName = (String) map.get("name");
return tagName.startsWith("v") ? tagName.substring(1) : tagName;
}
}
}
return null;
}
}
@@ -0,0 +1,15 @@
package com.reajason.javaweb.boot.entity;
import lombok.Data;
import lombok.Builder;
/**
* @author ReaJason
*/
@Data
@Builder
public class VersionInfo {
private String currentVersion;
private String latestVersion;
private boolean hasUpdate;
}
+69 -10
View File
@@ -1,30 +1,89 @@
import { env } from "@/config";
import { useQuery } from "@tanstack/react-query";
import { LoaderCircle } from "lucide-react";
import { CircleX, LoaderCircle, RefreshCcw } from "lucide-react";
import type React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "./ui/button";
import { TooltipContent, TooltipTrigger } from "./ui/tooltip";
import { Tooltip } from "./ui/tooltip";
import { TooltipProvider } from "./ui/tooltip";
type VersionInfo = {
currentVersion: string;
latestVersion: string;
hasUpdate: boolean;
};
const VersionBadge: React.FC = () => {
const { isPending, data } = useQuery<string>({
const { isPending, data, isError } = useQuery<VersionInfo>({
queryKey: ["version"],
queryFn: async () => {
const response = await fetch(`${env.API_URL}/version`);
if (response.ok) {
return await response.text();
return await response.json();
}
return "unknown";
},
});
const inProduction = env.MODE === "production";
const { t } = useTranslation();
return (
<div className="flex items-center space-x-2">
<Button
className={`rounded-full ${data && "bg-green-500 text-white"}`}
size="sm"
variant={isPending ? "ghost" : "default"}
>
{isPending ? <LoaderCircle className="h-3.5 w-3.5 animate-spin" /> : <span>v{data}</span>}
</Button>
{isPending && (
<Button className="rounded-full" size="sm" variant="ghost">
<LoaderCircle className="h-3.5 w-3.5 animate-spin" />
</Button>
)}
{isError && (
<Button
className="rounded-full"
size="sm"
variant="ghost"
onClick={() => {
window.open("https://github.com/ReaJason/MemShellParty/releases");
}}
>
<CircleX className="h-3.5 w-3.5" />
</Button>
)}
{data?.hasUpdate && inProduction && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
className="rounded-full bg-yellow-500 text-white hover:bg-yellow-600 hover:text-white"
size="sm"
variant="ghost"
onClick={() => {
window.open(`https://github.com/ReaJason/MemShellParty/releases/tag/v${data.latestVersion}`);
}}
>
<span className="flex items-center gap-1">
<RefreshCcw className="h-3.5 w-3.5" />
{t("version.updateAvailable")}
</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>
{t("version.updateAvailableTooltip", {
currentVersion: data.currentVersion,
latestVersion: data.latestVersion,
})}
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{data && (!data.hasUpdate || !inProduction) && (
<Button
className="rounded-full bg-green-500 text-white hover:bg-green-600 hover:text-white"
size="sm"
variant="ghost"
>
<span>v{data.currentVersion}</span>
</Button>
)}
</div>
);
};
+4
View File
@@ -129,5 +129,9 @@
"targetServerRequest": "Request",
"try-to-use-shell": "Try to use the memory shell",
"waitingForGeneration": "// Waiting for generation..."
},
"version": {
"updateAvailable": "Update Available",
"updateAvailableTooltip": "Click to Open Github Release Page ( v{{currentVersion}} -> v{{latestVersion}})"
}
}
+4
View File
@@ -129,5 +129,9 @@
"targetServerRequest": "请求适配",
"try-to-use-shell": "尝试利用内存马",
"waitingForGeneration": "// 等待填写参数生成中..."
},
"version": {
"updateAvailable": "有可用升级",
"updateAvailableTooltip": "点击前往 GitHub Release ( v{{currentVersion}} -> v{{latestVersion}})"
}
}