v1.6.0 #221

Merged
LukeGus merged 74 commits from dev-1.6.0 into main 2025-09-12 19:42:00 +00:00
6 changed files with 168 additions and 74 deletions
Showing only changes of commit ba6ca5de52 - Show all commits

View File

@@ -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;

View File

@@ -15,6 +15,9 @@
"!vite.config.ts",
"!eslint.config.js"
],
"asarUnpack": [
"node_modules/node-fetch/**/*"
],
"extraMetadata": {
"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) => {
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 = [

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';
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';
}

View File

@@ -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>
</>
);
}

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;
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;
});