From 670ea5f970b66c4f3a6f5e8e8a0705e47f7ab260 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 25 Feb 2026 23:03:51 +0100 Subject: [PATCH] added dynamic configuration of Port in Desktop app --- desktop/RUNTIME_CONFIG.md | 64 +++++++++++++++++++++ desktop/main.go | 16 +++++- frontend/package.json | 2 +- frontend/package.json.md5 | 2 +- frontend/src/utils/api.js | 31 ++++++++-- frontend/src/utils/config.js | 82 +++++++++++++++++++++++++++ frontend/src/views/LandingView.vue | 3 +- frontend/src/views/SystemInfoView.vue | 3 +- frontend/wailsjs/go/main/App.d.ts | 4 ++ frontend/wailsjs/go/main/App.js | 7 +++ 10 files changed, 202 insertions(+), 12 deletions(-) create mode 100644 desktop/RUNTIME_CONFIG.md create mode 100644 frontend/src/utils/config.js create mode 100755 frontend/wailsjs/go/main/App.d.ts create mode 100755 frontend/wailsjs/go/main/App.js diff --git a/desktop/RUNTIME_CONFIG.md b/desktop/RUNTIME_CONFIG.md new file mode 100644 index 0000000..7c190d5 --- /dev/null +++ b/desktop/RUNTIME_CONFIG.md @@ -0,0 +1,64 @@ +# Desktop Runtime Configuration + +The BaMoRT desktop app now uses **runtime configuration** for the API port, meaning you can change the port in the `.env` file without rebuilding the app. + +## How It Works + +### Backend (Go) +- **[desktop/main.go](desktop/main.go)**: The `App.GetAPIBaseURL()` method reads the configured port from `config.Cfg` and returns the full API URL +- This method is bound to the frontend via Wails, making it callable from JavaScript + +### Frontend (Vue/JS) +- **[frontend/src/utils/config.js](frontend/src/utils/config.js)**: Detects if running in Wails and calls the Go backend to get the API URL at runtime +- **[frontend/src/utils/api.js](frontend/src/utils/api.js)**: Uses the dynamic config instead of a hardcoded VITE_API_URL +- The API URL is cached after first retrieval for performance + +### Build Process +- **[frontend/package.json](frontend/package.json)**: The `build:desktop` script no longer hardcodes `VITE_API_URL` +- The frontend bundle is now port-agnostic + +## Usage + +### Change the API Port + +1. Edit `desktop/.env`: + ```env + API_PORT=8185 # Change to any port you want + ``` + +2. Run the app - no rebuild needed! + ```bash + ./desktop/build/bin/bamort + ``` + +The frontend will automatically connect to the configured port. + +### Environment Detection + +The config system automatically detects the environment: + +- **Desktop (Wails)**: Calls `window.go.main.App.GetAPIBaseURL()` to get the URL from Go backend +- **Web (Development)**: Uses `import.meta.env.VITE_API_URL` from Vite +- **Web (Production)**: Defaults to `https://bamort-api.trokan.de` + +## Fallback Behavior + +If the desktop app can't reach the Go backend (shouldn't happen), it falls back to `http://localhost:8185`. + +## Benefits + +✅ Change API port without rebuilding +✅ Faster iteration during development +✅ Same binary works with different configurations +✅ Cleaner separation of config from code + +## Technical Details + +**Request Flow:** +1. Frontend makes API request +2. Axios interceptor checks if `baseURL` is set +3. If not set, calls `getAPIBaseURL()` from config.js +4. Config.js detects Wails environment via `window.go` +5. Calls Go backend's `GetAPIBaseURL()` method +6. Caches the result for subsequent requests +7. Request proceeds with correct baseURL diff --git a/desktop/main.go b/desktop/main.go index 44675b1..21457df 100644 --- a/desktop/main.go +++ b/desktop/main.go @@ -1,6 +1,6 @@ // BaMoRT Desktop application entry point. // Uses Wails v2 to wrap the existing Gin HTTP server in a native desktop window. -// The backend runs on localhost:8180 (SQLite); the WebView loads the bundled frontend. +// The backend runs on localhost:8185 (SQLite); the WebView loads the bundled frontend. package main import ( @@ -56,8 +56,15 @@ func (a *App) startup(ctx context.Context) { startGinServer() } +// GetAPIBaseURL returns the HTTP server address for the frontend to connect to. +// This allows the API port to be configured at runtime via .env without rebuilding. +func (a *App) GetAPIBaseURL() string { + // GetServerAddress returns ":port", so we need to add localhost + return "http://localhost" + config.Cfg.GetServerAddress() +} + // startGinServer initialises the database, runs migrations, and starts the -// Gin HTTP server on the configured port (default 8180) in a goroutine. +// Gin HTTP server on the configured port (default 8185) in a goroutine. func startGinServer() { cfg := config.Cfg @@ -131,7 +138,7 @@ func main() { _ = os.Setenv("ENVIRONMENT", "desktop") } if os.Getenv("API_PORT") == "" { - _ = os.Setenv("API_PORT", "8180") + _ = os.Setenv("API_PORT", "8185") } if os.Getenv("TEMPLATES_DIR") == "" { _ = os.Setenv("TEMPLATES_DIR", "./templates") @@ -158,6 +165,9 @@ func main() { MinWidth: 1024, MinHeight: 768, OnStartup: app.startup, + Bind: []interface{}{ + app, + }, AssetServer: &assetserver.Options{ Assets: frontendFS, }, diff --git a/frontend/package.json b/frontend/package.json index a2e9d01..b9a360e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,7 +7,7 @@ "scripts": { "dev": "vite", "build": "vite build", - "build:desktop": "DESKTOP_BUILD=1 VITE_API_URL=http://localhost:8180 vite build", + "build:desktop": "DESKTOP_BUILD=1 vite build", "preview": "vite preview" }, "dependencies": { diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 6dca786..b9c142e 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -a98925179cb067ef9c9e5871faa6f8c7 \ No newline at end of file +705a38c2f133892600ec967917c4d556 \ No newline at end of file diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index d75f838..c758343 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -1,12 +1,33 @@ import axios from 'axios' +import { getAPIBaseURL } from './config' -const API = axios.create({ - baseURL: import.meta.env.VITE_API_URL || 'https://bamort-api.trokan.de', // Use env variable with fallback -}) +// Create API instance without baseURL - will be set dynamically +const API = axios.create({}) -// Request interceptor to add auth token +let baseURLPromise = null +let baseURLResolved = false + +// Get base URL (cached after first call) +async function ensureBaseURL() { + if (baseURLResolved) { + return + } + + if (!baseURLPromise) { + baseURLPromise = getAPIBaseURL() + } + + const baseURL = await baseURLPromise + API.defaults.baseURL = baseURL + baseURLResolved = true +} + +// Request interceptor to add auth token and ensure baseURL is set API.interceptors.request.use( - (config) => { + async (config) => { + // Ensure baseURL is set before request + await ensureBaseURL() + const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` diff --git a/frontend/src/utils/config.js b/frontend/src/utils/config.js new file mode 100644 index 0000000..7d2219c --- /dev/null +++ b/frontend/src/utils/config.js @@ -0,0 +1,82 @@ +/** + * Runtime configuration for BaMoRT frontend + * + * For desktop builds (Wails), reads the API base URL from the Go backend + * to allow runtime configuration via .env without rebuilding. + * + * For web builds, uses VITE_API_URL environment variable. + */ + +let cachedAPIBaseURL = null + +/** + * Check if we're running in a Wails desktop app + */ +function isWailsApp() { + return typeof window !== 'undefined' && + window['go'] !== undefined && + window['go']['main'] !== undefined && + window['go']['main']['App'] !== undefined +} + +/** + * Wait for Wails to initialize (max 3 seconds) + */ +async function waitForWails(maxAttempts = 30) { + for (let i = 0; i < maxAttempts; i++) { + if (isWailsApp()) { + return true + } + await new Promise(resolve => setTimeout(resolve, 100)) + } + return false +} + +/** + * Get the API base URL dynamically + * - In Wails desktop app: calls Go backend to get configured URL + * - In web app: uses VITE_API_URL or defaults to production API + */ +export async function getAPIBaseURL() { + // Return cached value if available + if (cachedAPIBaseURL) { + return cachedAPIBaseURL + } + + // Check if this looks like a desktop environment + const isDesktop = typeof window !== 'undefined' && + (window.location.protocol === 'wails:' || + window.location.hostname === 'wails.localhost') + + // Wails desktop app - wait for Wails to be ready + if (isDesktop || typeof window !== 'undefined' && window['go']) { + const wailsReady = await waitForWails() + + if (wailsReady) { + try { + // Access Wails Go bindings via window object + const url = await window['go']['main']['App']['GetAPIBaseURL']() + cachedAPIBaseURL = url + console.log('Desktop app using API URL from config:', url) + return url + } catch (error) { + console.error('Failed to get API URL from Wails:', error) + // Fallback to localhost for desktop + cachedAPIBaseURL = 'http://localhost:8185' + return cachedAPIBaseURL + } + } + } + + // Web app - use environment variable or production default + cachedAPIBaseURL = import.meta.env.VITE_API_URL || 'https://bamort-api.trokan.de' + console.log('Web app using API URL:', cachedAPIBaseURL) + return cachedAPIBaseURL +} + +/** + * Check if running in desktop mode (Wails) + */ +export function isDesktopMode() { + return isWailsApp() +} diff --git a/frontend/src/views/LandingView.vue b/frontend/src/views/LandingView.vue index 0d121ec..0fe0369 100644 --- a/frontend/src/views/LandingView.vue +++ b/frontend/src/views/LandingView.vue @@ -44,6 +44,7 @@