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?: {