Skip to content

Commit 6292e6b

Browse files
committed
feat(ui): support show update button
1 parent 6f332a9 commit 6292e6b

File tree

6 files changed

+176
-12
lines changed

6 files changed

+176
-12
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.reajason.javaweb.boot.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.http.client.SimpleClientHttpRequestFactory;
6+
import org.springframework.web.client.RestTemplate;
7+
8+
/**
9+
* @author ReaJason
10+
*/
11+
@Configuration
12+
public class WebConfig {
13+
14+
@Bean
15+
public RestTemplate restTemplate() {
16+
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
17+
factory.setConnectTimeout(3000);
18+
factory.setReadTimeout(3000);
19+
return new RestTemplate(factory);
20+
}
21+
}

boot/src/main/java/com/reajason/javaweb/boot/controller/VersionController.java

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
package com.reajason.javaweb.boot.controller;
22

3+
import com.reajason.javaweb.boot.entity.VersionInfo;
34
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.core.ParameterizedTypeReference;
6+
import org.springframework.http.HttpMethod;
7+
import org.springframework.http.HttpStatus;
8+
import org.springframework.http.ResponseEntity;
49
import org.springframework.web.bind.annotation.CrossOrigin;
510
import org.springframework.web.bind.annotation.GetMapping;
611
import org.springframework.web.bind.annotation.RequestMapping;
712
import org.springframework.web.bind.annotation.RestController;
13+
import org.springframework.web.client.RestTemplate;
14+
import org.thymeleaf.util.StringUtils;
15+
16+
import java.util.List;
17+
import java.util.Map;
818

919
/**
1020
* @author ReaJason
@@ -16,10 +26,61 @@
1626
public class VersionController {
1727

1828
@Value("${spring.application.version}")
19-
String version;
29+
private String version;
30+
31+
private final RestTemplate restTemplate;
32+
33+
public VersionController(RestTemplate restTemplate) {
34+
this.restTemplate = restTemplate;
35+
}
2036

2137
@GetMapping
22-
public String version() {
38+
public VersionInfo version() {
39+
String latestVersion = getLatestGithubRelease();
40+
return VersionInfo.builder()
41+
.currentVersion(version)
42+
.latestVersion(latestVersion)
43+
.hasUpdate(!StringUtils.equals(version, latestVersion))
44+
.build();
45+
}
46+
47+
private String getLatestGithubRelease() {
48+
try {
49+
String latestVersion = tryFetchRelease("https://api.github.com");
50+
if (latestVersion != null) {
51+
return latestVersion;
52+
}
53+
latestVersion = tryFetchRelease("https://gh.llkk.cc/https://api.github.com");
54+
if (latestVersion != null) {
55+
return latestVersion;
56+
}
57+
} catch (Exception ignored) {
58+
}
2359
return version;
2460
}
61+
62+
private String tryFetchRelease(String baseUrl) {
63+
String apiUrl = String.format("%s/repos/%s/%s/releases", baseUrl, "ReaJason", "MemShellParty");
64+
65+
ResponseEntity<List<Map<String, Object>>> response = restTemplate.exchange(
66+
apiUrl,
67+
HttpMethod.GET,
68+
null,
69+
new ParameterizedTypeReference<>() {
70+
}
71+
);
72+
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
73+
List<Map<String, Object>> body = response.getBody();
74+
for (Map<String, Object> map : body) {
75+
String targetCommitish = (String) map.get("target_commitish");
76+
Boolean prerelease = (Boolean) map.get("prerelease");
77+
Boolean draft = (Boolean) map.get("draft");
78+
if ("master".equals(targetCommitish) && !prerelease && !draft) {
79+
String tagName = (String) map.get("name");
80+
return tagName.startsWith("v") ? tagName.substring(1) : tagName;
81+
}
82+
}
83+
}
84+
return null;
85+
}
2586
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.reajason.javaweb.boot.entity;
2+
3+
import lombok.Data;
4+
import lombok.Builder;
5+
6+
/**
7+
* @author ReaJason
8+
*/
9+
@Data
10+
@Builder
11+
public class VersionInfo {
12+
private String currentVersion;
13+
private String latestVersion;
14+
private boolean hasUpdate;
15+
}

web/src/components/version-badge.tsx

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,89 @@
11
import { env } from "@/config";
22
import { useQuery } from "@tanstack/react-query";
3-
import { LoaderCircle } from "lucide-react";
3+
import { CircleX, LoaderCircle, RefreshCcw } from "lucide-react";
44
import type React from "react";
5+
import { useTranslation } from "react-i18next";
56
import { Button } from "./ui/button";
7+
import { TooltipContent, TooltipTrigger } from "./ui/tooltip";
8+
import { Tooltip } from "./ui/tooltip";
9+
import { TooltipProvider } from "./ui/tooltip";
10+
type VersionInfo = {
11+
currentVersion: string;
12+
latestVersion: string;
13+
hasUpdate: boolean;
14+
};
615

716
const VersionBadge: React.FC = () => {
8-
const { isPending, data } = useQuery<string>({
17+
const { isPending, data, isError } = useQuery<VersionInfo>({
918
queryKey: ["version"],
1019
queryFn: async () => {
1120
const response = await fetch(`${env.API_URL}/version`);
1221
if (response.ok) {
13-
return await response.text();
22+
return await response.json();
1423
}
1524
return "unknown";
1625
},
1726
});
27+
const inProduction = env.MODE === "production";
28+
const { t } = useTranslation();
1829

1930
return (
2031
<div className="flex items-center space-x-2">
21-
<Button
22-
className={`rounded-full ${data && "bg-green-500 text-white"}`}
23-
size="sm"
24-
variant={isPending ? "ghost" : "default"}
25-
>
26-
{isPending ? <LoaderCircle className="h-3.5 w-3.5 animate-spin" /> : <span>v{data}</span>}
27-
</Button>
32+
{isPending && (
33+
<Button className="rounded-full" size="sm" variant="ghost">
34+
<LoaderCircle className="h-3.5 w-3.5 animate-spin" />
35+
</Button>
36+
)}
37+
{isError && (
38+
<Button
39+
className="rounded-full"
40+
size="sm"
41+
variant="ghost"
42+
onClick={() => {
43+
window.open("https://github.com/ReaJason/MemShellParty/releases");
44+
}}
45+
>
46+
<CircleX className="h-3.5 w-3.5" />
47+
</Button>
48+
)}
49+
{data?.hasUpdate && inProduction && (
50+
<TooltipProvider>
51+
<Tooltip>
52+
<TooltipTrigger asChild>
53+
<Button
54+
className="rounded-full bg-yellow-500 text-white hover:bg-yellow-600 hover:text-white"
55+
size="sm"
56+
variant="ghost"
57+
onClick={() => {
58+
window.open(`https://github.com/ReaJason/MemShellParty/releases/tag/v${data.latestVersion}`);
59+
}}
60+
>
61+
<span className="flex items-center gap-1">
62+
<RefreshCcw className="h-3.5 w-3.5" />
63+
{t("version.updateAvailable")}
64+
</span>
65+
</Button>
66+
</TooltipTrigger>
67+
<TooltipContent>
68+
<p>
69+
{t("version.updateAvailableTooltip", {
70+
currentVersion: data.currentVersion,
71+
latestVersion: data.latestVersion,
72+
})}
73+
</p>
74+
</TooltipContent>
75+
</Tooltip>
76+
</TooltipProvider>
77+
)}
78+
{data && (!data.hasUpdate || !inProduction) && (
79+
<Button
80+
className="rounded-full bg-green-500 text-white hover:bg-green-600 hover:text-white"
81+
size="sm"
82+
variant="ghost"
83+
>
84+
<span>v{data.currentVersion}</span>
85+
</Button>
86+
)}
2887
</div>
2988
);
3089
};

web/src/i18n/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,5 +129,9 @@
129129
"targetServerRequest": "Request",
130130
"try-to-use-shell": "Try to use the memory shell",
131131
"waitingForGeneration": "// Waiting for generation..."
132+
},
133+
"version": {
134+
"updateAvailable": "Update Available",
135+
"updateAvailableTooltip": "Click to Open Github Release Page ( v{{currentVersion}} -> v{{latestVersion}})"
132136
}
133137
}

web/src/i18n/zh-CN.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,5 +129,9 @@
129129
"targetServerRequest": "请求适配",
130130
"try-to-use-shell": "尝试利用内存马",
131131
"waitingForGeneration": "// 等待填写参数生成中..."
132+
},
133+
"version": {
134+
"updateAvailable": "有可用升级",
135+
"updateAvailableTooltip": "点击前往 GitHub Release ( v{{currentVersion}} -> v{{latestVersion}})"
132136
}
133137
}

0 commit comments

Comments
 (0)