added dynamic configuration of Port in Desktop app

This commit is contained in:
2026-02-25 23:03:51 +01:00
parent bb9ef4f77e
commit 670ea5f970
10 changed files with 202 additions and 12 deletions
+64
View File
@@ -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
+13 -3
View File
@@ -1,6 +1,6 @@
// BaMoRT Desktop application entry point. // BaMoRT Desktop application entry point.
// Uses Wails v2 to wrap the existing Gin HTTP server in a native desktop window. // 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 package main
import ( import (
@@ -56,8 +56,15 @@ func (a *App) startup(ctx context.Context) {
startGinServer() 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 // 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() { func startGinServer() {
cfg := config.Cfg cfg := config.Cfg
@@ -131,7 +138,7 @@ func main() {
_ = os.Setenv("ENVIRONMENT", "desktop") _ = os.Setenv("ENVIRONMENT", "desktop")
} }
if os.Getenv("API_PORT") == "" { if os.Getenv("API_PORT") == "" {
_ = os.Setenv("API_PORT", "8180") _ = os.Setenv("API_PORT", "8185")
} }
if os.Getenv("TEMPLATES_DIR") == "" { if os.Getenv("TEMPLATES_DIR") == "" {
_ = os.Setenv("TEMPLATES_DIR", "./templates") _ = os.Setenv("TEMPLATES_DIR", "./templates")
@@ -158,6 +165,9 @@ func main() {
MinWidth: 1024, MinWidth: 1024,
MinHeight: 768, MinHeight: 768,
OnStartup: app.startup, OnStartup: app.startup,
Bind: []interface{}{
app,
},
AssetServer: &assetserver.Options{ AssetServer: &assetserver.Options{
Assets: frontendFS, Assets: frontendFS,
}, },
+1 -1
View File
@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "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" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1 +1 @@
a98925179cb067ef9c9e5871faa6f8c7 705a38c2f133892600ec967917c4d556
+26 -5
View File
@@ -1,12 +1,33 @@
import axios from 'axios' import axios from 'axios'
import { getAPIBaseURL } from './config'
const API = axios.create({ // Create API instance without baseURL - will be set dynamically
baseURL: import.meta.env.VITE_API_URL || 'https://bamort-api.trokan.de', // Use env variable with fallback 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( API.interceptors.request.use(
(config) => { async (config) => {
// Ensure baseURL is set before request
await ensureBaseURL()
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (token) { if (token) {
config.headers.Authorization = `Bearer ${token}` config.headers.Authorization = `Bearer ${token}`
+82
View File
@@ -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()
}
+2 -1
View File
@@ -44,6 +44,7 @@
</style> </style>
<script> <script>
import { getAPIBaseURL } from '../utils/config'
import axios from 'axios' import axios from 'axios'
import { getVersion, getGitCommit } from '../version' import { getVersion, getGitCommit } from '../version'
@@ -80,7 +81,7 @@ export default {
methods: { methods: {
async fetchBackendVersion() { async fetchBackendVersion() {
try { try {
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8180' const apiUrl = await getAPIBaseURL()
const response = await axios.get(`${apiUrl}/api/public/version`) const response = await axios.get(`${apiUrl}/api/public/version`)
if (response.data) { if (response.data) {
+2 -1
View File
@@ -115,6 +115,7 @@
</template> </template>
<script> <script>
import { getAPIBaseURL } from '../utils/config'
import axios from 'axios' import axios from 'axios'
import { getVersion, getGitCommit } from '../version' import { getVersion, getGitCommit } from '../version'
@@ -157,7 +158,7 @@ export default {
methods: { methods: {
async fetchBackendVersion() { async fetchBackendVersion() {
try { try {
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8180' const apiUrl = await getAPIBaseURL()
const response = await axios.get(`${apiUrl}/api/public/systeminfo`) const response = await axios.get(`${apiUrl}/api/public/systeminfo`)
if (response.data) { if (response.data) {
+4
View File
@@ -0,0 +1,4 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function GetAPIBaseURL():Promise<string>;
+7
View File
@@ -0,0 +1,7 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function GetAPIBaseURL() {
return window['go']['main']['App']['GetAPIBaseURL']();
}