added dynamic configuration of Port in Desktop app
This commit is contained in:
@@ -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
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 @@
|
|||||||
a98925179cb067ef9c9e5871faa6f8c7
|
705a38c2f133892600ec967917c4d556
|
||||||
@@ -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}`
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
@@ -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>;
|
||||||
Executable
+7
@@ -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']();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user