Fix electronm api routing, fikx ssh not connecting, and OIDC redirect errors

This commit is contained in:
LukeGus
2025-09-11 14:13:38 -05:00
parent 2d3fb53fbe
commit ba6ca5de52
6 changed files with 168 additions and 74 deletions

View File

@@ -138,6 +138,15 @@ http {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
} }
location /health {
proxy_pass http://127.0.0.1:8081;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/status(/.*)?$ { location ~ ^/status(/.*)?$ {
proxy_pass http://127.0.0.1:8085; proxy_pass http://127.0.0.1:8085;
proxy_http_version 1.1; proxy_http_version 1.1;

View File

@@ -15,6 +15,9 @@
"!vite.config.ts", "!vite.config.ts",
"!eslint.config.js" "!eslint.config.js"
], ],
"asarUnpack": [
"node_modules/node-fetch/**/*"
],
"extraMetadata": { "extraMetadata": {
"main": "electron/main.cjs" "main": "electron/main.cjs"
}, },

View File

@@ -129,7 +129,53 @@ ipcMain.handle('save-server-config', (event, config) => {
ipcMain.handle('test-server-connection', async (event, serverUrl) => { ipcMain.handle('test-server-connection', async (event, serverUrl) => {
try { try {
const fetch = require('node-fetch'); // Use Node.js built-in fetch (available in Node 18+) or fallback to https module
let fetch;
try {
// Try to use built-in fetch first (Node 18+)
fetch = globalThis.fetch || require('node:fetch');
} catch (e) {
// Fallback to https module for older Node versions
const https = require('https');
const http = require('http');
const { URL } = require('url');
fetch = (url, options = {}) => {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const isHttps = urlObj.protocol === 'https:';
const client = isHttps ? https : http;
const req = client.request(url, {
method: options.method || 'GET',
headers: options.headers || {},
timeout: options.timeout || 5000
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
resolve({
ok: res.statusCode >= 200 && res.statusCode < 300,
status: res.statusCode,
text: () => Promise.resolve(data),
json: () => Promise.resolve(JSON.parse(data))
});
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timeout'));
});
if (options.body) {
req.write(options.body);
}
req.end();
});
};
}
// Try multiple endpoints to test the connection // Try multiple endpoints to test the connection
const testUrls = [ const testUrls = [

View File

@@ -315,7 +315,15 @@ router.get('/oidc/authorize', async (req, res) => {
let origin = req.get('Origin') || req.get('Referer')?.replace(/\/[^\/]*$/, '') || 'http://localhost:5173'; let origin = req.get('Origin') || req.get('Referer')?.replace(/\/[^\/]*$/, '') || 'http://localhost:5173';
if (origin.includes('localhost')) { // Handle Electron app - check for custom headers or user agent
const userAgent = req.get('User-Agent') || '';
const isElectron = userAgent.includes('Electron') || req.get('X-Electron-App') === 'true';
if (isElectron) {
// For Electron, use the configured server URL or fallback to localhost
const serverUrl = process.env.SERVER_URL || 'http://localhost:8081';
origin = serverUrl;
} else if (origin.includes('localhost')) {
origin = 'http://localhost:8081'; origin = 'http://localhost:8081';
} }
@@ -557,7 +565,14 @@ router.get('/oidc/callback', async (req, res) => {
let frontendUrl = redirectUri.replace('/users/oidc/callback', ''); let frontendUrl = redirectUri.replace('/users/oidc/callback', '');
if (frontendUrl.includes('localhost')) { // Handle Electron app redirects
const userAgent = req.get('User-Agent') || '';
const isElectron = userAgent.includes('Electron') || req.get('X-Electron-App') === 'true';
if (isElectron) {
// For Electron, redirect back to the same server URL (the frontend is served from there)
frontendUrl = redirectUri.replace('/users/oidc/callback', '');
} else if (frontendUrl.includes('localhost')) {
frontendUrl = 'http://localhost:5173'; frontendUrl = 'http://localhost:5173';
} }
@@ -572,7 +587,14 @@ router.get('/oidc/callback', async (req, res) => {
let frontendUrl = redirectUri.replace('/users/oidc/callback', ''); let frontendUrl = redirectUri.replace('/users/oidc/callback', '');
if (frontendUrl.includes('localhost')) { // Handle Electron app redirects
const userAgent = req.get('User-Agent') || '';
const isElectron = userAgent.includes('Electron') || req.get('X-Electron-App') === 'true';
if (isElectron) {
// For Electron, redirect back to the same server URL (the frontend is served from there)
frontendUrl = redirectUri.replace('/users/oidc/callback', '');
} else if (frontendUrl.includes('localhost')) {
frontendUrl = 'http://localhost:5173'; frontendUrl = 'http://localhost:5173';
} }

View File

@@ -3,7 +3,7 @@ import {HomepageAuth} from "@/ui/Desktop/Homepage/HomepageAuth.tsx";
import {HomepageUpdateLog} from "@/ui/Desktop/Homepage/HompageUpdateLog.tsx"; import {HomepageUpdateLog} from "@/ui/Desktop/Homepage/HompageUpdateLog.tsx";
import {HomepageAlertManager} from "@/ui/Desktop/Homepage/HomepageAlertManager.tsx"; import {HomepageAlertManager} from "@/ui/Desktop/Homepage/HomepageAlertManager.tsx";
import {Button} from "@/components/ui/button.tsx"; import {Button} from "@/components/ui/button.tsx";
import { getUserInfo, getDatabaseHealth } from "@/ui/main-axios.ts"; import { getUserInfo, getDatabaseHealth, setCookie, getCookie } from "@/ui/main-axios.ts";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
interface HomepageProps { interface HomepageProps {
@@ -11,25 +11,15 @@ interface HomepageProps {
isAuthenticated: boolean; isAuthenticated: boolean;
authLoading: boolean; authLoading: boolean;
onAuthSuccess: (authData: { isAdmin: boolean; username: string | null; userId: string | null }) => void; onAuthSuccess: (authData: { isAdmin: boolean; username: string | null; userId: string | null }) => void;
} isTopbarOpen: boolean;
function getCookie(name: string) {
return document.cookie.split('; ').reduce((r, v) => {
const parts = v.split('=');
return parts[0] === name ? decodeURIComponent(parts[1]) : r;
}, "");
}
function setCookie(name: string, value: string, days = 7) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/`;
} }
export function Homepage({ export function Homepage({
onSelectView, onSelectView,
isAuthenticated, isAuthenticated,
authLoading, authLoading,
onAuthSuccess onAuthSuccess,
isTopbarOpen
}: HomepageProps): React.ReactElement { }: HomepageProps): React.ReactElement {
const {t} = useTranslation(); const {t} = useTranslation();
const [loggedIn, setLoggedIn] = useState(isAuthenticated); const [loggedIn, setLoggedIn] = useState(isAuthenticated);
@@ -38,6 +28,11 @@ export function Homepage({
const [userId, setUserId] = useState<string | null>(null); const [userId, setUserId] = useState<string | null>(null);
const [dbError, setDbError] = useState<string | null>(null); const [dbError, setDbError] = useState<string | null>(null);
// Calculate margins based on topbar state (same logic as AppView.tsx)
const topMarginPx = isTopbarOpen ? 74 : 26;
const leftMarginPx = 26; // Assuming sidebar is collapsed for homepage
const bottomMarginPx = 8;
useEffect(() => { useEffect(() => {
setLoggedIn(isAuthenticated); setLoggedIn(isAuthenticated);
}, [isAuthenticated]); }, [isAuthenticated]);
@@ -53,7 +48,7 @@ export function Homepage({
.then(([meRes]) => { .then(([meRes]) => {
setIsAdmin(!!meRes.is_admin); setIsAdmin(!!meRes.is_admin);
setUsername(meRes.username || null); setUsername(meRes.username || null);
setUserId(meRes.userId || null); setUserId(meRes.id || null);
setDbError(null); setDbError(null);
}) })
.catch((err) => { .catch((err) => {
@@ -72,66 +67,79 @@ export function Homepage({
return ( return (
<div className="w-full h-full flex items-center justify-center"> <>
{!loggedIn ? ( {!loggedIn ? (
<HomepageAuth <div className="w-full h-full flex items-center justify-center">
setLoggedIn={setLoggedIn} <HomepageAuth
setIsAdmin={setIsAdmin} setLoggedIn={setLoggedIn}
setUsername={setUsername} setIsAdmin={setIsAdmin}
setUserId={setUserId} setUsername={setUsername}
loggedIn={loggedIn} setUserId={setUserId}
authLoading={authLoading} loggedIn={loggedIn}
dbError={dbError} authLoading={authLoading}
setDbError={setDbError} dbError={dbError}
onAuthSuccess={onAuthSuccess} setDbError={setDbError}
/> onAuthSuccess={onAuthSuccess}
/>
</div>
) : ( ) : (
<div className="flex flex-row items-center justify-center gap-8 relative z-10"> <div
<div className="flex flex-col items-center gap-6 w-[400px]"> className="w-full h-full flex items-center justify-center"
<HomepageUpdateLog style={{
loggedIn={loggedIn} marginLeft: leftMarginPx,
/> marginRight: 17,
marginTop: topMarginPx,
marginBottom: bottomMarginPx,
height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`,
}}
>
<div className="flex flex-row items-center justify-center gap-8 relative z-10">
<div className="flex flex-col items-center gap-6 w-[400px]">
<HomepageUpdateLog
loggedIn={loggedIn}
/>
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors" className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() => window.open('https://github.com/LukeGus/Termix', '_blank')} onClick={() => window.open('https://github.com/LukeGus/Termix', '_blank')}
> >
GitHub GitHub
</Button> </Button>
<div className="w-px h-4 bg-dark-border"></div> <div className="w-px h-4 bg-dark-border"></div>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors" className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() => window.open('https://github.com/LukeGus/Termix/issues/new', '_blank')} onClick={() => window.open('https://github.com/LukeGus/Termix/issues/new', '_blank')}
> >
Feedback Feedback
</Button> </Button>
<div className="w-px h-4 bg-dark-border"></div> <div className="w-px h-4 bg-dark-border"></div>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors" className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() => window.open('https://discord.com/invite/jVQGdvHDrf', '_blank')} onClick={() => window.open('https://discord.com/invite/jVQGdvHDrf', '_blank')}
> >
Discord Discord
</Button> </Button>
<div className="w-px h-4 bg-dark-border"></div> <div className="w-px h-4 bg-dark-border"></div>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors" className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() => window.open('https://github.com/sponsors/LukeGus', '_blank')} onClick={() => window.open('https://github.com/sponsors/LukeGus', '_blank')}
> >
Donate Donate
</Button> </Button>
</div>
</div> </div>
</div> </div>
</div> </div>
)} )}
</div> </>
); );
} }

View File

@@ -102,7 +102,7 @@ export function setCookie(name: string, value: string, days = 7): void {
} }
} }
function getCookie(name: string): string | undefined { export function getCookie(name: string): string | undefined {
const isElectron = (window as any).IS_ELECTRON === true || (window as any).electronAPI?.isElectron === true; const isElectron = (window as any).IS_ELECTRON === true || (window as any).electronAPI?.isElectron === true;
if (isElectron) { if (isElectron) {
const token = localStorage.getItem(name) || undefined; const token = localStorage.getItem(name) || undefined;
@@ -157,6 +157,12 @@ function createApiInstance(baseURL: string, serviceName: string = 'API'): AxiosI
authLogger.warn('No JWT token found, request will be unauthenticated', context); authLogger.warn('No JWT token found, request will be unauthenticated', context);
} }
// Add Electron-specific headers for OIDC and other backend detection
if (isElectron) {
config.headers['X-Electron-App'] = 'true';
config.headers['User-Agent'] = 'Termix-Electron/1.6.0';
}
return config; return config;
}); });