feat(profile): display version number from .env in profile menu #182
11
package-lock.json
generated
11
package-lock.json
generated
@@ -69,6 +69,7 @@
|
|||||||
"react-hook-form": "^7.60.0",
|
"react-hook-form": "^7.60.0",
|
||||||
"react-i18next": "^15.7.3",
|
"react-i18next": "^15.7.3",
|
||||||
"react-resizable-panels": "^3.0.3",
|
"react-resizable-panels": "^3.0.3",
|
||||||
|
"react-simple-keyboard": "^3.8.120",
|
||||||
"react-xtermjs": "^1.0.10",
|
"react-xtermjs": "^1.0.10",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
@@ -7818,6 +7819,16 @@
|
|||||||
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-simple-keyboard": {
|
||||||
|
"version": "3.8.120",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-simple-keyboard/-/react-simple-keyboard-3.8.120.tgz",
|
||||||
|
"integrity": "sha512-VREEGZWXUeqRKvRVg0n8hmoAqz/TSWZEs5UwbfLuan4yKvOQZUFHtS11QGnvIVYjkThh+JYslO2CHT4Lxf5d0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-style-singleton": {
|
"node_modules/react-style-singleton": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||||
|
|||||||
36
package.json
36
package.json
@@ -1,29 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "termix",
|
"name": "termix",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.6.0",
|
"version": "0.0.0",
|
||||||
"description": "Open-source server management platform with SSH terminal access, tunnel management, and file editing",
|
|
||||||
"author": {
|
|
||||||
"name": "LukeGus",
|
|
||||||
"email": "support@termix.site"
|
|
||||||
},
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "electron/main-simple.cjs",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build && npm run build:backend",
|
"build": "vite build",
|
||||||
"build:frontend": "vite build",
|
|
||||||
"build:backend": "tsc -p tsconfig.node.json",
|
"build:backend": "tsc -p tsconfig.node.json",
|
||||||
"dev:backend": "tsc -p tsconfig.node.json && node ./dist/backend/starter.js",
|
"dev:backend": "tsc -p tsconfig.node.json && node ./dist/backend/starter.js",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview"
|
||||||
"electron": "electron .",
|
|
||||||
"electron:dev": "npm run build:backend && NODE_ENV=development electron .",
|
|
||||||
"electron:package": "npm run build && electron-packager . Termix --platform=win32 --arch=x64 --out=release --overwrite --ignore=\"^/src|^/public|^/node_modules|^/repo-images\" --prune=true --icon=public/favicon.ico",
|
|
||||||
"electron:package:win": "npm run build && electron-packager . Termix --platform=win32 --arch=x64 --out=release --overwrite --ignore=\"^/src|^/public|^/node_modules|^/repo-images\" --prune=true --icon=public/favicon.ico",
|
|
||||||
"electron:package:mac": "npm run build && electron-packager . Termix --platform=darwin --arch=universal --out=release --overwrite --ignore=\"^/src|^/public|^/node_modules|^/repo-images\" --prune=true --icon=public/icon.png",
|
|
||||||
"electron:package:linux": "npm run build && electron-packager . Termix --platform=linux --arch=x64 --out=release --overwrite --ignore=\"^/src|^/public|^/node_modules|^/repo-images\" --prune=true --icon=public/icon.png",
|
|
||||||
"electron:package:all": "npm run build && npm run electron:package:win && npm run electron:package:mac && npm run electron:package:linux"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.1.1",
|
"@hookform/resolvers": "^5.1.1",
|
||||||
@@ -44,6 +30,11 @@
|
|||||||
"@radix-ui/react-switch": "^1.2.5",
|
"@radix-ui/react-switch": "^1.2.5",
|
||||||
"@radix-ui/react-tabs": "^1.1.12",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
|
"@types/bcryptjs": "^2.4.6",
|
||||||
|
"@types/multer": "^2.0.0",
|
||||||
|
"@types/qrcode": "^1.5.5",
|
||||||
|
"@types/speakeasy": "^2.0.10",
|
||||||
"@uiw/codemirror-extensions-hyper-link": "^4.24.1",
|
"@uiw/codemirror-extensions-hyper-link": "^4.24.1",
|
||||||
"@uiw/codemirror-extensions-langs": "^4.24.1",
|
"@uiw/codemirror-extensions-langs": "^4.24.1",
|
||||||
"@uiw/codemirror-themes": "^4.24.1",
|
"@uiw/codemirror-themes": "^4.24.1",
|
||||||
@@ -82,43 +73,34 @@
|
|||||||
"react-hook-form": "^7.60.0",
|
"react-hook-form": "^7.60.0",
|
||||||
"react-i18next": "^15.7.3",
|
"react-i18next": "^15.7.3",
|
||||||
"react-resizable-panels": "^3.0.3",
|
"react-resizable-panels": "^3.0.3",
|
||||||
"react-responsive": "^10.0.1",
|
|
||||||
"react-simple-keyboard": "^3.8.120",
|
"react-simple-keyboard": "^3.8.120",
|
||||||
"react-xtermjs": "^1.0.10",
|
"react-xtermjs": "^1.0.10",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
"ssh2": "^1.16.0",
|
"ssh2": "^1.16.0",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
|
"tailwindcss": "^4.1.11",
|
||||||
"validator": "^13.15.15",
|
"validator": "^13.15.15",
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.18.3",
|
||||||
"xterm": "^5.3.0",
|
|
||||||
"zod": "^4.0.5"
|
"zod": "^4.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.34.0",
|
"@eslint/js": "^9.34.0",
|
||||||
"@tailwindcss/vite": "^4.1.12",
|
|
||||||
"@types/bcryptjs": "^2.4.6",
|
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/cors": "^2.8.19",
|
"@types/cors": "^2.8.19",
|
||||||
"@types/express": "^5.0.3",
|
"@types/express": "^5.0.3",
|
||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/multer": "^2.0.0",
|
|
||||||
"@types/node": "^24.3.0",
|
"@types/node": "^24.3.0",
|
||||||
"@types/qrcode": "^1.5.5",
|
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@types/speakeasy": "^2.0.10",
|
|
||||||
"@types/ssh2": "^1.15.5",
|
"@types/ssh2": "^1.15.5",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@vitejs/plugin-react-swc": "^3.10.2",
|
"@vitejs/plugin-react-swc": "^3.10.2",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"electron": "^31.7.0",
|
|
||||||
"electron-packager": "^17.1.2",
|
|
||||||
"eslint": "^9.34.0",
|
"eslint": "^9.34.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"globals": "^16.3.0",
|
"globals": "^16.3.0",
|
||||||
"tailwindcss": "^4.1.12",
|
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tw-animate-css": "^1.3.5",
|
"tw-animate-css": "^1.3.5",
|
||||||
"typescript": "~5.9.2",
|
"typescript": "~5.9.2",
|
||||||
|
|||||||
@@ -199,6 +199,7 @@
|
|||||||
"register": "Register",
|
"register": "Register",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
|
"version" : "Version",
|
||||||
"confirmPassword": "Confirm Password",
|
"confirmPassword": "Confirm Password",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
|
|||||||
@@ -148,22 +148,16 @@ app.get('/health', (req, res) => {
|
|||||||
app.get('/version', async (req, res) => {
|
app.get('/version', async (req, res) => {
|
||||||
let localVersion = process.env.VERSION;
|
let localVersion = process.env.VERSION;
|
||||||
|
|
||||||
// Fallback to package.json version if env variable not set
|
|
||||||
if (!localVersion) {
|
if (!localVersion) {
|
||||||
try {
|
try {
|
||||||
const packagePath = path.resolve(process.cwd(), 'package.json');
|
const packagePath = path.resolve(process.cwd(), 'package.json');
|
||||||
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
||||||
localVersion = packageJson.version;
|
localVersion = packageJson.version;
|
||||||
logger.info(`Using version from package.json: ${localVersion}`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to read version from package.json:', error);
|
logger.error('Failed to read version from package.json:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug logging
|
|
||||||
logger.debug(`Final version: ${localVersion}`);
|
|
||||||
logger.debug(`Working directory: ${process.cwd()}`);
|
|
||||||
|
|
||||||
if (!localVersion) {
|
if (!localVersion) {
|
||||||
logger.error('No version information available');
|
logger.error('No version information available');
|
||||||
return res.status(404).send('Local Version Not Set');
|
return res.status(404).send('Local Version Not Set');
|
||||||
@@ -186,6 +180,7 @@ app.get('/version', async (req, res) => {
|
|||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
status: localVersion === remoteVersion ? 'up_to_date' : 'requires_update',
|
status: localVersion === remoteVersion ? 'up_to_date' : 'requires_update',
|
||||||
|
localVersion: localVersion,
|
||||||
version: remoteVersion,
|
version: remoteVersion,
|
||||||
latest_release: {
|
latest_release: {
|
||||||
tag_name: releaseData.data.tag_name,
|
tag_name: releaseData.data.tag_name,
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx
|
|||||||
import {User, Shield, Key, AlertCircle} from "lucide-react";
|
import {User, Shield, Key, AlertCircle} from "lucide-react";
|
||||||
import {TOTPSetup} from "@/ui/Desktop/User/TOTPSetup.tsx";
|
import {TOTPSetup} from "@/ui/Desktop/User/TOTPSetup.tsx";
|
||||||
import {getUserInfo} from "@/ui/main-axios.ts";
|
import {getUserInfo} from "@/ui/main-axios.ts";
|
||||||
|
import {getVersionInfo} from "@/ui/main-axios.ts";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {PasswordReset} from "@/ui/Desktop/User/PasswordReset.tsx";
|
import {PasswordReset} from "@/ui/Desktop/User/PasswordReset.tsx";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {LanguageSwitcher} from "@/components/LanguageSwitcher.tsx";
|
import {LanguageSwitcher} from "@/components/LanguageSwitcher.tsx";
|
||||||
|
|
||||||
|
|
||||||
interface UserProfileProps {
|
interface UserProfileProps {
|
||||||
isTopbarOpen?: boolean;
|
isTopbarOpen?: boolean;
|
||||||
}
|
}
|
||||||
@@ -27,11 +29,23 @@ export function UserProfile({isTopbarOpen = true}: UserProfileProps) {
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [versionInfo, setVersionInfo] = useState<{ version: string } | null>(null);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUserInfo();
|
fetchUserInfo();
|
||||||
|
fetchVersion();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const fetchVersion = async () => {
|
||||||
|
try {
|
||||||
|
const info = await getVersionInfo();
|
||||||
|
setVersionInfo({version: info.localVersion});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load version info", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchUserInfo = async () => {
|
const fetchUserInfo = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -146,6 +160,13 @@ export function UserProfile({isTopbarOpen = true}: UserProfileProps) {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>{t('common.version')}</Label>
|
||||||
|
<p className="text-lg font-medium mt-1">
|
||||||
|
{versionInfo?.version || t('common.loading')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 pt-6 border-t">
|
<div className="mt-6 pt-6 border-t">
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ interface AuthResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface UserInfo {
|
interface UserInfo {
|
||||||
|
totp_enabled: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
is_admin: boolean;
|
is_admin: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user