diff --git a/electron/main.cjs b/electron/main.cjs index 0ee92297..8741f73b 100644 --- a/electron/main.cjs +++ b/electron/main.cjs @@ -30,6 +30,26 @@ function logToFile(...args) { console.log(...args); } +function parseSemver(version) { + const match = String(version || "").match(/(\d+)\.(\d+)(?:\.(\d+))?/); + if (!match) return null; + + return [Number(match[1]), Number(match[2]), Number(match[3] || 0)]; +} + +function compareSemver(a, b) { + const parsedA = parseSemver(a); + const parsedB = parseSemver(b); + if (!parsedA || !parsedB) return null; + + for (let i = 0; i < 3; i += 1) { + if (parsedA[i] > parsedB[i]) return 1; + if (parsedA[i] < parsedB[i]) return -1; + } + + return 0; +} + function httpFetch(url, options = {}) { return new Promise((resolve, reject) => { const urlObj = new URL(url); @@ -548,11 +568,17 @@ ipcMain.handle("check-electron-update", async () => { }; } - const isUpToDate = localVersion === remoteVersion; + const versionComparison = compareSemver(localVersion, remoteVersion); + const status = + versionComparison === null || versionComparison === 0 + ? "up_to_date" + : versionComparison > 0 + ? "beta" + : "requires_update"; const result = { success: true, - status: isUpToDate ? "up_to_date" : "requires_update", + status, localVersion: localVersion, remoteVersion: remoteVersion, latest_release: { diff --git a/src/backend/database/database.ts b/src/backend/database/database.ts index 661a7bd1..b03d044d 100644 --- a/src/backend/database/database.ts +++ b/src/backend/database/database.ts @@ -119,6 +119,31 @@ class GitHubCache { const githubCache = new GitHubCache(); +function parseSemver( + version: string | undefined, +): [number, number, number] | null { + const match = String(version || "").match(/(\d+)\.(\d+)(?:\.(\d+))?/); + if (!match) return null; + + return [Number(match[1]), Number(match[2]), Number(match[3] || 0)]; +} + +function compareSemver( + a: string | undefined, + b: string | undefined, +): number | null { + const parsedA = parseSemver(a); + const parsedB = parseSemver(b); + if (!parsedA || !parsedB) return null; + + for (let i = 0; i < 3; i += 1) { + if (parsedA[i] > parsedB[i]) return 1; + if (parsedA[i] < parsedB[i]) return -1; + } + + return 0; +} + const GITHUB_API_BASE = "https://api.github.com"; const REPO_OWNER = "Termix-SSH"; const REPO_NAME = "Termix"; @@ -300,12 +325,19 @@ app.get("/version", authenticateJWT, async (req, res) => { return res.status(401).send("Remote Version Not Found"); } - const isUpToDate = localVersion === remoteVersion; + const versionComparison = compareSemver(localVersion, remoteVersion); + const status = + versionComparison === null || versionComparison === 0 + ? "up_to_date" + : versionComparison > 0 + ? "beta" + : "requires_update"; const response = { - status: isUpToDate ? "up_to_date" : "requires_update", + status, localVersion: localVersion, version: remoteVersion, + remoteVersion: remoteVersion, latest_release: { tag_name: releaseData.data.tag_name, name: releaseData.data.name, diff --git a/src/components/ui/version-alert.tsx b/src/components/ui/version-alert.tsx index 45fcc780..4493e749 100644 --- a/src/components/ui/version-alert.tsx +++ b/src/components/ui/version-alert.tsx @@ -1,13 +1,13 @@ import React from "react"; import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert.tsx"; import { Button } from "@/components/ui/button.tsx"; -import { ExternalLink, Download, AlertTriangle } from "lucide-react"; +import { ExternalLink, Download, AlertTriangle, Info } from "lucide-react"; import { useTranslation } from "react-i18next"; interface VersionAlertProps { updateInfo: { success: boolean; - status?: "up_to_date" | "requires_update"; + status?: "up_to_date" | "requires_update" | "beta"; localVersion?: string; remoteVersion?: string; latest_release?: { @@ -53,6 +53,21 @@ export function VersionAlert({ updateInfo, onDownload }: VersionAlertProps) { ); } + if (updateInfo.status === "beta") { + return ( + + + {t("versionCheck.betaVersion")} + + {t("versionCheck.betaVersionDesc", { + current: updateInfo.localVersion, + latest: updateInfo.remoteVersion, + })} + + + ); + } + if (updateInfo.status === "requires_update") { return ( diff --git a/src/locales/en.json b/src/locales/en.json index 0c6ba6f0..d9a0afee 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -402,6 +402,8 @@ "currentVersion": "You are running version {{version}}", "updateAvailable": "Update Available", "newVersionAvailable": "A new version is available! You are running {{current}}, but {{latest}} is available.", + "betaVersion": "Beta Version", + "betaVersionDesc": "You are running {{current}}, which is newer than the latest stable release {{latest}}.", "releasedOn": "Released on {{date}}", "downloadUpdate": "Download Update", "dismiss": "Dismiss", @@ -2602,6 +2604,7 @@ "version": "Version", "upToDate": "Up to Date", "updateAvailable": "Update Available", + "beta": "Beta", "uptime": "Uptime", "database": "Database", "healthy": "Healthy", diff --git a/src/ui/desktop/apps/dashboard/Dashboard.tsx b/src/ui/desktop/apps/dashboard/Dashboard.tsx index 44a19254..d12303b1 100644 --- a/src/ui/desktop/apps/dashboard/Dashboard.tsx +++ b/src/ui/desktop/apps/dashboard/Dashboard.tsx @@ -68,7 +68,7 @@ export function Dashboard({ const [uptime, setUptime] = useState("0d 0h 0m"); const [versionStatus, setVersionStatus] = useState< - "up_to_date" | "requires_update" + "up_to_date" | "requires_update" | "beta" >("up_to_date"); const [versionText, setVersionText] = useState(""); const [dbHealth, setDbHealth] = useState<"healthy" | "error">("healthy"); @@ -173,7 +173,8 @@ export function Dashboard({ setVersionText(`v${versionInfo.localVersion}`); if ( versionInfo.status === "up_to_date" || - versionInfo.status === "requires_update" + versionInfo.status === "requires_update" || + versionInfo.status === "beta" ) { setVersionStatus(versionInfo.status); } diff --git a/src/ui/desktop/apps/dashboard/apps/UpdateLog.tsx b/src/ui/desktop/apps/dashboard/apps/UpdateLog.tsx index 923ce3da..b48f2e87 100644 --- a/src/ui/desktop/apps/dashboard/apps/UpdateLog.tsx +++ b/src/ui/desktop/apps/dashboard/apps/UpdateLog.tsx @@ -48,8 +48,9 @@ interface RSSResponse { } interface VersionResponse { - status: "up_to_date" | "requires_update"; + status: "up_to_date" | "requires_update" | "beta"; version: string; + localVersion?: string; latest_release: { name: string; published_at: string; @@ -136,6 +137,19 @@ export function UpdateLog({ loggedIn }: UpdateLogProps) { )} + {versionInfo && versionInfo.status === "beta" && ( + + + {t("versionCheck.betaVersion")} + + + {t("versionCheck.betaVersionDesc", { + current: versionInfo.localVersion, + latest: versionInfo.version, + })} + + + )} {loading && (
diff --git a/src/ui/desktop/apps/dashboard/cards/ServerOverviewCard.tsx b/src/ui/desktop/apps/dashboard/cards/ServerOverviewCard.tsx index 7cd9f482..4821e305 100644 --- a/src/ui/desktop/apps/dashboard/cards/ServerOverviewCard.tsx +++ b/src/ui/desktop/apps/dashboard/cards/ServerOverviewCard.tsx @@ -14,7 +14,7 @@ import { UpdateLog } from "@/ui/desktop/apps/dashboard/apps/UpdateLog"; interface ServerOverviewCardProps { loggedIn: boolean; versionText: string; - versionStatus: "up_to_date" | "requires_update"; + versionStatus: "up_to_date" | "requires_update" | "beta"; uptime: string; dbHealth: "healthy" | "error"; totalServers: number; @@ -61,11 +61,13 @@ export function ServerOverviewCard({ diff --git a/src/ui/desktop/user/ElectronVersionCheck.tsx b/src/ui/desktop/user/ElectronVersionCheck.tsx index 3833764e..bd67c383 100644 --- a/src/ui/desktop/user/ElectronVersionCheck.tsx +++ b/src/ui/desktop/user/ElectronVersionCheck.tsx @@ -32,6 +32,10 @@ export function ElectronVersionCheck({ (theme === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches); const lineColor = isDarkMode ? "#151517" : "#f9f9f9"; + const versionModalTitle = + versionInfo?.status === "beta" + ? t("versionCheck.betaVersion") + : t("versionCheck.updateRequired"); useEffect(() => { const updateCheckDisabled = @@ -183,9 +187,7 @@ export function ElectronVersionCheck({ >
-

- {t("versionCheck.updateRequired")} -

+

{versionModalTitle}

diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index 1c059452..f566a1b6 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -679,7 +679,7 @@ export async function testServerConnection( export async function checkElectronUpdate(): Promise<{ success: boolean; - status?: "up_to_date" | "requires_update"; + status?: "up_to_date" | "requires_update" | "beta"; localVersion?: string; remoteVersion?: string; latest_release?: {