fix: Desktop app login issues and rename version check and host manager folder

This commit is contained in:
LukeGus
2025-11-01 20:46:40 -05:00
parent 73144fb0a0
commit 994d00e91f
10 changed files with 99 additions and 119 deletions

View File

@@ -68,7 +68,7 @@ app.use(
return callback(null, true); return callback(null, true);
} }
callback(null, true); callback(new Error("Not allowed by CORS"));
}, },
credentials: true, credentials: true,
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
@@ -77,6 +77,8 @@ app.use(
"Authorization", "Authorization",
"User-Agent", "User-Agent",
"X-Electron-App", "X-Electron-App",
"Accept",
"Origin",
], ],
}), }),
); );

View File

@@ -44,10 +44,10 @@ function parseElectronUserAgent(userAgent: string): DeviceInfo {
let os = "Unknown OS"; let os = "Unknown OS";
let version = "Unknown"; let version = "Unknown";
const termixMatch = userAgent.match(/Termix-Desktop\/([\d.]+) \(([^;]+);/); const termixMatch = userAgent.match(/Termix-Desktop\/([\d.]+)\s*\(([^;)]+)/);
if (termixMatch) { if (termixMatch) {
version = termixMatch[1]; version = termixMatch[1];
os = termixMatch[2]; os = termixMatch[2].trim();
} else { } else {
if (userAgent.includes("Windows")) { if (userAgent.includes("Windows")) {
os = parseWindowsVersion(userAgent); os = parseWindowsVersion(userAgent);

View File

@@ -5,6 +5,7 @@ import "./index.css";
import DesktopApp from "@/ui/desktop/DesktopApp.tsx"; import DesktopApp from "@/ui/desktop/DesktopApp.tsx";
import { MobileApp } from "@/ui/mobile/MobileApp.tsx"; import { MobileApp } from "@/ui/mobile/MobileApp.tsx";
import { ThemeProvider } from "@/components/theme-provider"; import { ThemeProvider } from "@/components/theme-provider";
import { ElectronVersionCheck } from "@/ui/desktop/user/ElectronVersionCheck.tsx";
import "./i18n/i18n"; import "./i18n/i18n";
import { isElectron } from "./ui/main-axios.ts"; import { isElectron } from "./ui/main-axios.ts";
@@ -55,11 +56,13 @@ function useWindowWidth() {
function RootApp() { function RootApp() {
const width = useWindowWidth(); const width = useWindowWidth();
const isMobile = width < 768; const isMobile = width < 768;
const [showVersionCheck, setShowVersionCheck] = useState(true);
const userAgent = const userAgent =
navigator.userAgent || navigator.vendor || (window as any).opera || ""; navigator.userAgent || navigator.vendor || (window as any).opera || "";
const isTermixMobile = /Termix-Mobile/.test(userAgent); const isTermixMobile = /Termix-Mobile/.test(userAgent);
const renderApp = () => {
if (isElectron()) { if (isElectron()) {
return <DesktopApp />; return <DesktopApp />;
} }
@@ -69,6 +72,41 @@ function RootApp() {
} }
return isMobile ? <MobileApp key="mobile" /> : <DesktopApp key="desktop" />; return isMobile ? <MobileApp key="mobile" /> : <DesktopApp key="desktop" />;
};
return (
<>
{isElectron() && (
<div
className="fixed inset-0 pointer-events-none"
style={{
backgroundColor: "#09090b",
backgroundImage: `linear-gradient(
135deg,
transparent 0%,
transparent 49%,
rgba(255, 255, 255, 0.03) 49%,
rgba(255, 255, 255, 0.03) 51%,
transparent 51%,
transparent 100%
)`,
backgroundSize: "80px 80px",
zIndex: 0,
}}
/>
)}
<div className="relative min-h-screen" style={{ zIndex: 1 }}>
{isElectron() && showVersionCheck ? (
<ElectronVersionCheck
onContinue={() => setShowVersionCheck(false)}
isAuthenticated={false}
/>
) : (
renderApp()
)}
</div>
</>
);
} }
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import { LeftSidebar } from "@/ui/desktop/navigation/LeftSidebar.tsx"; import { LeftSidebar } from "@/ui/desktop/navigation/LeftSidebar.tsx";
import { Dashboard } from "@/ui/desktop/apps/dashboard/Dashboard.tsx"; import { Dashboard } from "@/ui/desktop/apps/dashboard/Dashboard.tsx";
import { AppView } from "@/ui/desktop/navigation/AppView.tsx"; import { AppView } from "@/ui/desktop/navigation/AppView.tsx";
import { HostManager } from "@/ui/desktop/apps/host manager/HostManager.tsx"; import { HostManager } from "@/ui/desktop/apps/host-manager/HostManager.tsx";
import { import {
TabProvider, TabProvider,
useTabs, useTabs,
@@ -11,7 +11,6 @@ import { TopNavbar } from "@/ui/desktop/navigation/TopNavbar.tsx";
import { AdminSettings } from "@/ui/desktop/admin/AdminSettings.tsx"; import { AdminSettings } from "@/ui/desktop/admin/AdminSettings.tsx";
import { UserProfile } from "@/ui/desktop/user/UserProfile.tsx"; import { UserProfile } from "@/ui/desktop/user/UserProfile.tsx";
import { Toaster } from "@/components/ui/sonner.tsx"; import { Toaster } from "@/components/ui/sonner.tsx";
import { VersionCheckModal } from "@/components/ui/version-check-modal.tsx";
import { getUserInfo } from "@/ui/main-axios.ts"; import { getUserInfo } from "@/ui/main-axios.ts";
function AppContent() { function AppContent() {
@@ -19,7 +18,6 @@ function AppContent() {
const [username, setUsername] = useState<string | null>(null); const [username, setUsername] = useState<string | null>(null);
const [isAdmin, setIsAdmin] = useState(false); const [isAdmin, setIsAdmin] = useState(false);
const [authLoading, setAuthLoading] = useState(true); const [authLoading, setAuthLoading] = useState(true);
const [showVersionCheck, setShowVersionCheck] = useState(true);
const [isTopbarOpen, setIsTopbarOpen] = useState<boolean>(() => { const [isTopbarOpen, setIsTopbarOpen] = useState<boolean>(() => {
const saved = localStorage.getItem("topNavbarOpen"); const saved = localStorage.getItem("topNavbarOpen");
return saved !== null ? JSON.parse(saved) : true; return saved !== null ? JSON.parse(saved) : true;
@@ -31,9 +29,16 @@ function AppContent() {
setAuthLoading(true); setAuthLoading(true);
getUserInfo() getUserInfo()
.then((meRes) => { .then((meRes) => {
// Check if response is actually HTML (Vite dev server page)
if (typeof meRes === "string" || !meRes.username) {
setIsAuthenticated(false);
setIsAdmin(false);
setUsername(null);
} else {
setIsAuthenticated(true); setIsAuthenticated(true);
setIsAdmin(!!meRes.is_admin); setIsAdmin(!!meRes.is_admin);
setUsername(meRes.username || null); setUsername(meRes.username || null);
}
}) })
.catch((err) => { .catch((err) => {
setIsAuthenticated(false); setIsAuthenticated(false);
@@ -45,7 +50,9 @@ function AppContent() {
console.warn("Session expired - please log in again"); console.warn("Session expired - please log in again");
} }
}) })
.finally(() => setAuthLoading(false)); .finally(() => {
setAuthLoading(false);
});
}; };
checkAuth(); checkAuth();
@@ -84,35 +91,7 @@ function AppContent() {
return ( return (
<div> <div>
{showVersionCheck && ( {!isAuthenticated && !authLoading && (
<VersionCheckModal
onDismiss={() => setShowVersionCheck(false)}
onContinue={() => setShowVersionCheck(false)}
isAuthenticated={isAuthenticated}
/>
)}
{!isAuthenticated && !authLoading && !showVersionCheck && (
<div>
<div
className="absolute inset-0"
style={{
backgroundImage: `linear-gradient(
135deg,
transparent 0%,
transparent 49%,
rgba(255, 255, 255, 0.03) 49%,
rgba(255, 255, 255, 0.03) 51%,
transparent 51%,
transparent 100%
)`,
backgroundSize: "80px 80px",
}}
/>
</div>
)}
{!isAuthenticated && !authLoading && !showVersionCheck && (
<div className="fixed inset-0 flex items-center justify-center z-[10000]"> <div className="fixed inset-0 flex items-center justify-center z-[10000]">
<Dashboard <Dashboard
onSelectView={handleSelectView} onSelectView={handleSelectView}

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { HostManagerViewer } from "@/ui/desktop/apps/host manager/HostManagerViewer.tsx"; import { HostManagerViewer } from "@/ui/desktop/apps/host-manager/HostManagerViewer.tsx";
import { import {
Tabs, Tabs,
TabsContent, TabsContent,
@@ -7,7 +7,7 @@ import {
TabsTrigger, TabsTrigger,
} from "@/components/ui/tabs.tsx"; } from "@/components/ui/tabs.tsx";
import { Separator } from "@/components/ui/separator.tsx"; import { Separator } from "@/components/ui/separator.tsx";
import { HostManagerEditor } from "@/ui/desktop/apps/host manager/HostManagerEditor.tsx"; import { HostManagerEditor } from "@/ui/desktop/apps/host-manager/HostManagerEditor.tsx";
import { CredentialsManager } from "@/ui/desktop/apps/credentials/CredentialsManager.tsx"; import { CredentialsManager } from "@/ui/desktop/apps/credentials/CredentialsManager.tsx";
import { CredentialEditor } from "@/ui/desktop/apps/credentials/CredentialEditor.tsx"; import { CredentialEditor } from "@/ui/desktop/apps/credentials/CredentialEditor.tsx";
import { useSidebar } from "@/components/ui/sidebar.tsx"; import { useSidebar } from "@/components/ui/sidebar.tsx";

View File

@@ -104,20 +104,12 @@ export function Auth({
}, [loggedIn]); }, [loggedIn]);
useEffect(() => { useEffect(() => {
if (isInElectronWebView()) {
return;
}
getRegistrationAllowed().then((res) => { getRegistrationAllowed().then((res) => {
setRegistrationAllowed(res.allowed); setRegistrationAllowed(res.allowed);
}); });
}, []); }, []);
useEffect(() => { useEffect(() => {
if (isInElectronWebView()) {
return;
}
getPasswordLoginAllowed() getPasswordLoginAllowed()
.then((res) => { .then((res) => {
setPasswordLoginAllowed(res.allowed); setPasswordLoginAllowed(res.allowed);
@@ -130,10 +122,6 @@ export function Auth({
}, []); }, []);
useEffect(() => { useEffect(() => {
if (isInElectronWebView()) {
return;
}
getOIDCConfig() getOIDCConfig()
.then((response) => { .then((response) => {
if (response) { if (response) {

View File

@@ -9,7 +9,7 @@ interface VersionCheckModalProps {
isAuthenticated?: boolean; isAuthenticated?: boolean;
} }
export function VersionCheckModal({ export function ElectronVersionCheck({
onContinue, onContinue,
isAuthenticated = false, isAuthenticated = false,
}: VersionCheckModalProps) { }: VersionCheckModalProps) {
@@ -35,7 +35,26 @@ export function VersionCheckModal({
const updateInfo = await checkElectronUpdate(); const updateInfo = await checkElectronUpdate();
setVersionInfo(updateInfo); setVersionInfo(updateInfo);
// Get current app version
const currentVersion = await (window as any).electronAPI?.getAppVersion();
const dismissedVersion = localStorage.getItem(
"electron-version-check-dismissed",
);
// If this version was already dismissed, skip the modal
if (dismissedVersion === currentVersion) {
onContinue();
return;
}
if (updateInfo?.status === "up_to_date") { if (updateInfo?.status === "up_to_date") {
// Store this version as checked (but don't show modal since up to date)
if (currentVersion) {
localStorage.setItem(
"electron-version-check-dismissed",
currentVersion,
);
}
onContinue(); onContinue();
return; return;
} }
@@ -53,7 +72,12 @@ export function VersionCheckModal({
} }
}; };
const handleContinue = () => { const handleContinue = async () => {
// Store the current version as dismissed
const currentVersion = await (window as any).electronAPI?.getAppVersion();
if (currentVersion) {
localStorage.setItem("electron-version-check-dismissed", currentVersion);
}
onContinue(); onContinue();
}; };
@@ -64,23 +88,6 @@ export function VersionCheckModal({
if (versionChecking && !versionInfo) { if (versionChecking && !versionInfo) {
return ( return (
<div className="fixed inset-0 flex items-center justify-center z-50"> <div className="fixed inset-0 flex items-center justify-center z-50">
{!isAuthenticated && (
<div
className="absolute inset-0"
style={{
backgroundImage: `linear-gradient(
135deg,
transparent 0%,
transparent 49%,
rgba(255, 255, 255, 0.03) 49%,
rgba(255, 255, 255, 0.03) 51%,
transparent 51%,
transparent 100%
)`,
backgroundSize: "80px 80px",
}}
/>
)}
<div className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-md w-full mx-4 relative z-10"> <div className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-md w-full mx-4 relative z-10">
<div className="flex items-center justify-center mb-4"> <div className="flex items-center justify-center mb-4">
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
@@ -96,23 +103,6 @@ export function VersionCheckModal({
if (!versionInfo || versionDismissed) { if (!versionInfo || versionDismissed) {
return ( return (
<div className="fixed inset-0 flex items-center justify-center z-50"> <div className="fixed inset-0 flex items-center justify-center z-50">
{!isAuthenticated && (
<div
className="absolute inset-0"
style={{
backgroundImage: `linear-gradient(
135deg,
transparent 0%,
transparent 49%,
rgba(255, 255, 255, 0.03) 49%,
rgba(255, 255, 255, 0.03) 51%,
transparent 51%,
transparent 100%
)`,
backgroundSize: "80px 80px",
}}
/>
)}
<div className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-md w-full mx-4 relative z-10"> <div className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-md w-full mx-4 relative z-10">
<div className="mb-4"> <div className="mb-4">
<h2 className="text-lg font-semibold"> <h2 className="text-lg font-semibold">
@@ -141,23 +131,6 @@ export function VersionCheckModal({
return ( return (
<div className="fixed inset-0 flex items-center justify-center z-50"> <div className="fixed inset-0 flex items-center justify-center z-50">
{!isAuthenticated && (
<div
className="absolute inset-0"
style={{
backgroundImage: `linear-gradient(
135deg,
transparent 0%,
transparent 49%,
rgba(255, 255, 255, 0.03) 49%,
rgba(255, 255, 255, 0.03) 51%,
transparent 51%,
transparent 100%
)`,
backgroundSize: "80px 80px",
}}
/>
)}
<div className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-md w-full mx-4 relative z-10"> <div className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-md w-full mx-4 relative z-10">
<div className="mb-4"> <div className="mb-4">
<h2 className="text-lg font-semibold"> <h2 className="text-lg font-semibold">

View File

@@ -484,12 +484,7 @@ function getApiUrl(path: string, defaultPort: number): string {
const devMode = isDev(); const devMode = isDev();
const electronMode = isElectron(); const electronMode = isElectron();
if (devMode) { if (electronMode) {
const protocol = window.location.protocol === "https:" ? "https" : "http";
const sslPort = protocol === "https" ? 8443 : defaultPort;
const url = `${protocol}://${apiHost}:${sslPort}${path}`;
return url;
} else if (electronMode) {
if (configuredServerUrl) { if (configuredServerUrl) {
const baseUrl = configuredServerUrl.replace(/\/$/, ""); const baseUrl = configuredServerUrl.replace(/\/$/, "");
const url = `${baseUrl}${path}`; const url = `${baseUrl}${path}`;
@@ -497,6 +492,11 @@ function getApiUrl(path: string, defaultPort: number): string {
} }
console.warn("Electron mode but no server configured!"); console.warn("Electron mode but no server configured!");
return "http://no-server-configured"; return "http://no-server-configured";
} else if (devMode) {
const protocol = window.location.protocol === "https:" ? "https" : "http";
const sslPort = protocol === "https" ? 8443 : defaultPort;
const url = `${protocol}://${apiHost}:${sslPort}${path}`;
return url;
} else { } else {
return path; return path;
} }