Fix electronm api routing, fikx ssh not connecting, and OIDC redirect errors
This commit is contained in:
@@ -138,6 +138,15 @@ http {
|
||||
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(/.*)?$ {
|
||||
proxy_pass http://127.0.0.1:8085;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
"!vite.config.ts",
|
||||
"!eslint.config.js"
|
||||
],
|
||||
"asarUnpack": [
|
||||
"node_modules/node-fetch/**/*"
|
||||
],
|
||||
"extraMetadata": {
|
||||
"main": "electron/main.cjs"
|
||||
},
|
||||
|
||||
@@ -129,7 +129,53 @@ ipcMain.handle('save-server-config', (event, config) => {
|
||||
|
||||
ipcMain.handle('test-server-connection', async (event, serverUrl) => {
|
||||
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
|
||||
const testUrls = [
|
||||
|
||||
@@ -315,7 +315,15 @@ router.get('/oidc/authorize', async (req, res) => {
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -557,7 +565,14 @@ router.get('/oidc/callback', async (req, res) => {
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -572,7 +587,14 @@ router.get('/oidc/callback', async (req, res) => {
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {HomepageAuth} from "@/ui/Desktop/Homepage/HomepageAuth.tsx";
|
||||
import {HomepageUpdateLog} from "@/ui/Desktop/Homepage/HompageUpdateLog.tsx";
|
||||
import {HomepageAlertManager} from "@/ui/Desktop/Homepage/HomepageAlertManager.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";
|
||||
|
||||
interface HomepageProps {
|
||||
@@ -11,25 +11,15 @@ interface HomepageProps {
|
||||
isAuthenticated: boolean;
|
||||
authLoading: boolean;
|
||||
onAuthSuccess: (authData: { isAdmin: boolean; username: string | null; userId: string | null }) => void;
|
||||
}
|
||||
|
||||
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=/`;
|
||||
isTopbarOpen: boolean;
|
||||
}
|
||||
|
||||
export function Homepage({
|
||||
onSelectView,
|
||||
isAuthenticated,
|
||||
authLoading,
|
||||
onAuthSuccess
|
||||
onAuthSuccess,
|
||||
isTopbarOpen
|
||||
}: HomepageProps): React.ReactElement {
|
||||
const {t} = useTranslation();
|
||||
const [loggedIn, setLoggedIn] = useState(isAuthenticated);
|
||||
@@ -38,6 +28,11 @@ export function Homepage({
|
||||
const [userId, setUserId] = 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(() => {
|
||||
setLoggedIn(isAuthenticated);
|
||||
}, [isAuthenticated]);
|
||||
@@ -53,7 +48,7 @@ export function Homepage({
|
||||
.then(([meRes]) => {
|
||||
setIsAdmin(!!meRes.is_admin);
|
||||
setUsername(meRes.username || null);
|
||||
setUserId(meRes.userId || null);
|
||||
setUserId(meRes.id || null);
|
||||
setDbError(null);
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -72,66 +67,79 @@ export function Homepage({
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<>
|
||||
{!loggedIn ? (
|
||||
<HomepageAuth
|
||||
setLoggedIn={setLoggedIn}
|
||||
setIsAdmin={setIsAdmin}
|
||||
setUsername={setUsername}
|
||||
setUserId={setUserId}
|
||||
loggedIn={loggedIn}
|
||||
authLoading={authLoading}
|
||||
dbError={dbError}
|
||||
setDbError={setDbError}
|
||||
onAuthSuccess={onAuthSuccess}
|
||||
/>
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<HomepageAuth
|
||||
setLoggedIn={setLoggedIn}
|
||||
setIsAdmin={setIsAdmin}
|
||||
setUsername={setUsername}
|
||||
setUserId={setUserId}
|
||||
loggedIn={loggedIn}
|
||||
authLoading={authLoading}
|
||||
dbError={dbError}
|
||||
setDbError={setDbError}
|
||||
onAuthSuccess={onAuthSuccess}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<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="w-full h-full flex items-center justify-center"
|
||||
style={{
|
||||
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">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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')}
|
||||
>
|
||||
GitHub
|
||||
</Button>
|
||||
<div className="w-px h-4 bg-dark-border"></div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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')}
|
||||
>
|
||||
Feedback
|
||||
</Button>
|
||||
<div className="w-px h-4 bg-dark-border"></div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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')}
|
||||
>
|
||||
Discord
|
||||
</Button>
|
||||
<div className="w-px h-4 bg-dark-border"></div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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')}
|
||||
>
|
||||
Donate
|
||||
</Button>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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')}
|
||||
>
|
||||
GitHub
|
||||
</Button>
|
||||
<div className="w-px h-4 bg-dark-border"></div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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')}
|
||||
>
|
||||
Feedback
|
||||
</Button>
|
||||
<div className="w-px h-4 bg-dark-border"></div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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')}
|
||||
>
|
||||
Discord
|
||||
</Button>
|
||||
<div className="w-px h-4 bg-dark-border"></div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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')}
|
||||
>
|
||||
Donate
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
if (isElectron) {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user