Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57ff19b8ee | |||
| 261a6294cb |
@@ -0,0 +1,174 @@
|
||||
# Runtime Configuration Implementation Summary
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
Enhanced the BaMoRT frontend to support runtime configuration for **both desktop (Wails) and web production** deployments, eliminating the need to rebuild when changing API URLs.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Desktop (Wails)
|
||||
- Uses Go bindings to read API URL from `.env` file at runtime
|
||||
- Method: `window['go']['main']['App']['GetAPIBaseURL']()`
|
||||
- Configuration: `desktop/.env` → `API_PORT` variable
|
||||
- No rebuild needed when changing port
|
||||
|
||||
### Web Production (NEW)
|
||||
Uses a **multi-strategy fallback system**:
|
||||
|
||||
1. **config.json** (Priority 1)
|
||||
- Loads `/config.json` from web root
|
||||
- Can be modified after deployment
|
||||
- Example: `{"apiBaseURL": "https://api.yourdomain.com"}`
|
||||
|
||||
2. **Auto-detection** (Priority 2)
|
||||
- Probes `/api/public/version` at same origin
|
||||
- Works automatically with reverse proxy setups
|
||||
- Detects if backend and frontend share same domain
|
||||
|
||||
3. **VITE_API_URL** (Priority 3)
|
||||
- Environment variable at build time
|
||||
- Used for development: `VITE_API_URL=http://localhost:8180`
|
||||
|
||||
4. **Same Origin Fallback** (Priority 4)
|
||||
- Uses `window.location.origin`
|
||||
- Assumes backend and frontend on same domain
|
||||
|
||||
## Files Modified
|
||||
|
||||
### Core Implementation
|
||||
- `/data/dev/bamort/frontend/src/utils/config.js` - Enhanced with multi-strategy config loading
|
||||
- `/data/dev/bamort/frontend/src/utils/api.js` - Uses dynamic baseURL (already done for desktop)
|
||||
- `/data/dev/bamort/desktop/main.go` - GetAPIBaseURL() Go binding (already done for desktop)
|
||||
|
||||
### Documentation
|
||||
- `/data/dev/bamort/frontend/RUNTIME_CONFIG.md` - Complete web configuration guide
|
||||
- `/data/dev/bamort/desktop/RUNTIME_CONFIG.md` - Desktop configuration guide (existing)
|
||||
|
||||
### Configuration Files
|
||||
- `/data/dev/bamort/frontend/public/config.json.example` - Template for deployment
|
||||
- `/data/dev/bamort/frontend/.gitignore` - Excludes `public/config.json` from git
|
||||
|
||||
## How It Works
|
||||
|
||||
### For Web Development
|
||||
```bash
|
||||
cd frontend
|
||||
VITE_API_URL=http://localhost:8180 npm run dev
|
||||
```
|
||||
|
||||
### For Web Production Deployment
|
||||
|
||||
**Option A: Using config.json (Recommended)**
|
||||
```bash
|
||||
# 1. Build once
|
||||
cd frontend && npm run build
|
||||
|
||||
# 2. Deploy dist/ to web server
|
||||
|
||||
# 3. Create config.json in web root
|
||||
cat > /var/www/bamort/config.json <<EOF
|
||||
{
|
||||
"apiBaseURL": "https://api.production.com"
|
||||
}
|
||||
EOF
|
||||
|
||||
# 4. Change API URL anytime without rebuild!
|
||||
```
|
||||
|
||||
**Option B: Reverse Proxy Setup**
|
||||
Example nginx configuration:
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name yourdomain.com;
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
root /var/www/bamort;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Backend API
|
||||
location /api {
|
||||
proxy_pass http://localhost:8180;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
Frontend auto-detects and uses same origin.
|
||||
|
||||
**Option C: Build with Embedded URL**
|
||||
```bash
|
||||
VITE_API_URL=https://api.production.com npm run build
|
||||
```
|
||||
(Not recommended - requires rebuild for URL changes)
|
||||
|
||||
### For Desktop (Wails)
|
||||
```bash
|
||||
# 1. Build once
|
||||
cd desktop && wails build
|
||||
|
||||
# 2. Change port in .env (no rebuild!)
|
||||
echo "API_PORT=9000" > desktop/.env
|
||||
|
||||
# 3. Run app - uses new port automatically
|
||||
./desktop/build/bin/bamort
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
Open browser console when loading the app. Look for one of these messages:
|
||||
|
||||
**Desktop:**
|
||||
```
|
||||
Desktop app using API URL from config: http://localhost:8185
|
||||
```
|
||||
|
||||
**Web:**
|
||||
```
|
||||
Loaded API URL from config.json: https://api.yourdomain.com
|
||||
```
|
||||
or
|
||||
```
|
||||
Detected backend at same origin: https://yourdomain.com
|
||||
```
|
||||
or
|
||||
```
|
||||
Web app using VITE_API_URL: http://localhost:8180
|
||||
```
|
||||
or
|
||||
```
|
||||
Web app using same origin: https://yourdomain.com
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
✅ **No rebuild needed** for API URL changes in production web deployments
|
||||
✅ **Same build artifact** can be deployed to dev/staging/production
|
||||
✅ **Infrastructure-friendly** - works with reverse proxies, Docker, static hosting
|
||||
✅ **Flexible** - multiple configuration strategies with sensible fallbacks
|
||||
✅ **Dev-friendly** - VITE_API_URL still works for development
|
||||
✅ **Desktop-friendly** - reads .env at runtime (already implemented)
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [x] Frontend builds successfully (`npm run build`)
|
||||
- [x] Desktop app reads runtime config from .env
|
||||
- [ ] Web dev mode works with VITE_API_URL
|
||||
- [ ] Web production with config.json works
|
||||
- [ ] Web production with reverse proxy auto-detection works
|
||||
- [ ] Web production with same-origin fallback works
|
||||
|
||||
## Migration Path
|
||||
|
||||
**For existing deployments:**
|
||||
1. No changes needed! Current builds continue working
|
||||
2. To enable runtime config: Just add `config.json` to your web root
|
||||
3. Old builds with VITE_API_URL still work as fallback
|
||||
|
||||
**For new deployments:**
|
||||
1. Build once: `npm run build`
|
||||
2. Deploy `dist/` contents
|
||||
3. Add `config.json` with your API URL
|
||||
4. Done!
|
||||
@@ -147,7 +147,17 @@ func UpdateCharacter(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, character)
|
||||
// Reload character to get updated data
|
||||
var updatedCharacter models.Char
|
||||
err = updatedCharacter.FirstID(id)
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to reload character")
|
||||
return
|
||||
}
|
||||
|
||||
// Return as FeChar with categorized skills
|
||||
feChar := ToFeChar(&updatedCharacter)
|
||||
c.JSON(http.StatusOK, feChar)
|
||||
}
|
||||
func DeleteCharacter(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
@@ -32,7 +32,7 @@ func SetupGin(r *gin.Engine) {
|
||||
}
|
||||
corsConfig = cors.Config{
|
||||
AllowOrigins: allowedOrigins,
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
AllowCredentials: true,
|
||||
|
||||
@@ -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.
|
||||
// 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,
|
||||
},
|
||||
|
||||
+5
-5
@@ -1,9 +1,5 @@
|
||||
# Environment variables for Bamort development environment
|
||||
|
||||
# API Configuration
|
||||
# API_URL=http://localhost:8180
|
||||
|
||||
# Database Configuration (for development)
|
||||
DATABASE_TYPE=mysql
|
||||
DATABASE_URL=bamort:bG4)efozrc@tcp(mariadb-dev:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local
|
||||
|
||||
@@ -13,9 +9,13 @@ MARIADB_PASSWORD=bG4)efozrc
|
||||
MARIADB_DATABASE=bamort
|
||||
MARIADB_USER=bamort
|
||||
|
||||
# Frontend Configuration
|
||||
# API Configuration
|
||||
# API_URL is used by the frontend to connect to the backend
|
||||
# For dev environment with Vite dev server, this goes to VITE_API_URL
|
||||
API_URL=http://192.168.0.48:8180
|
||||
API_PORT=8180
|
||||
|
||||
# Frontend Configuration
|
||||
BASE_URL=http://localhost:5173
|
||||
TEMPLATES_DIR=./templates
|
||||
EXPORT_TEMP_DIR=./export_temp
|
||||
|
||||
+16
-3
@@ -1,9 +1,16 @@
|
||||
# Environment variables for Bamort production deployment
|
||||
# Copy this file to .env and adjust the values as needed
|
||||
# Copy this file to .env.dev or .env.prd and adjust the values as needed
|
||||
|
||||
#- API Configuration
|
||||
# ===== FRONTEND API CONFIGURATION =====
|
||||
# API_URL: The backend API endpoint that the frontend will use
|
||||
# For production: Use your public API domain (https://api.yourdomain.com)
|
||||
# For development: Use your local network IP (http://192.168.x.x:8180)
|
||||
#
|
||||
# IMPORTANT: After changing API_URL, just restart containers - NO REBUILD NEEDED!
|
||||
# Production: docker-compose -f docker-compose.yml restart frontend
|
||||
# Development: docker-compose -f docker-compose.dev.yml restart frontend-dev
|
||||
API_URL=https://backend.domain.de
|
||||
VITE_API_URL=https://backend.domain.de
|
||||
API_PORT=8180
|
||||
|
||||
#- Database Configuration Backend
|
||||
DATABASE_TYPE=mysql
|
||||
@@ -20,3 +27,9 @@ BASE_URL=https://frontend.domain.de
|
||||
TEMPLATES_DIR=./templates
|
||||
EXPORT_TEMP_DIR=./export_temp
|
||||
COMPOSE_PROJECT_NAME=bamort
|
||||
|
||||
# Mail Configuration (for development)
|
||||
MAIL_HOST=mail.server.de
|
||||
MAIL_PORT=465
|
||||
MAIL_USERNAME=bamort@server.de
|
||||
MAIL_PASSWORD=XXXZZZZZ.YYYXXX
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
# Environment variables for Bamort production environment
|
||||
|
||||
# API Configuration
|
||||
# API_URL is used by the frontend to generate config.json at container startup
|
||||
# Change this value and restart containers (no rebuild needed!)
|
||||
API_URL=https://bamort-api.trokan.de
|
||||
VITE_API_URL=https://bamort-api.trokan.de
|
||||
|
||||
# Database Configuration Backend
|
||||
API_PORT=8180
|
||||
DATABASE_TYPE=mysql
|
||||
#DATABASE_URL=bamort:bG4)efozrc@tcp(mariadb:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local
|
||||
|
||||
@@ -14,7 +14,7 @@ MARIADB_PASSWORD=bG4)efozrc
|
||||
MARIADB_DATABASE=bamort
|
||||
MARIADB_USER=bamort
|
||||
|
||||
API_PORT=8180
|
||||
# Frontend Configuration
|
||||
BASE_URL=https://bamort.trokan.de
|
||||
TEMPLATES_DIR=./templates
|
||||
EXPORT_TEMP_DIR=./export_temp
|
||||
|
||||
+12
-17
@@ -1,42 +1,37 @@
|
||||
# =========== 1) Build stage ===========
|
||||
FROM node:22-alpine AS build
|
||||
|
||||
# Accept build arguments for Vite environment variables
|
||||
ARG VITE_API_URL
|
||||
ARG VITE_BASE_URL
|
||||
ARG VITE_API_PORT
|
||||
|
||||
# Set them as environment variables for the build process
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
ENV VITE_BASE_URL=$VITE_BASE_URL
|
||||
ENV VITE_API_PORT=$VITE_API_PORT
|
||||
|
||||
# No build args needed - using runtime configuration instead
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy the rest of the frontend code
|
||||
COPY . .
|
||||
|
||||
# Build the production bundle
|
||||
# Build the production bundle WITHOUT baked-in API URL
|
||||
# Runtime configuration will be generated from environment variables
|
||||
RUN npm run build
|
||||
|
||||
# =========== 2) Serve stage ===========
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy production build to Nginx html folder.
|
||||
# Adjust /usr/src/app/build -> /usr/src/app/dist if you’re using Angular/Vue
|
||||
#COPY --from=build /usr/src/app/build /usr/share/nginx/html
|
||||
# Copy production build to Nginx html folder
|
||||
COPY --from=build /usr/src/app/dist /usr/share/nginx/html
|
||||
|
||||
# Copy custom nginx configuration for SPA routing
|
||||
COPY --from=build /usr/src/app/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Copy entrypoint script that generates runtime config
|
||||
COPY --from=build /usr/src/app/docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
# Expose HTTP port
|
||||
EXPOSE 80
|
||||
|
||||
# Run Nginx in foreground
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
# Use custom entrypoint that generates config.json from environment
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
@@ -9,8 +9,12 @@ COPY package*.json ./
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy entrypoint script that generates config.json
|
||||
COPY docker-dev-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
# Expose Vite dev server port
|
||||
EXPOSE 5173
|
||||
|
||||
# Start development server with host binding for Docker
|
||||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
||||
# Use entrypoint that generates config.json before starting Vite
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
@@ -25,18 +25,12 @@ services:
|
||||
build:
|
||||
context: ../frontend
|
||||
dockerfile: ../docker/Dockerfile.frontend
|
||||
args:
|
||||
VITE_API_URL: ${API_URL:-https://bamort-api.trokan.de}
|
||||
VITE_BASE_URL: ${BASE_URL:-https://bamort.trokan.de}
|
||||
VITE_API_PORT: ${API_PORT:-443}
|
||||
container_name: bamort-frontend
|
||||
ports:
|
||||
- "8181:80"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- VITE_API_URL=${API_URL:-https://bamort.trokan.de:8180}
|
||||
- VITE_BASE_URL=${BASE_URL:-https://bamort.trokan.de}
|
||||
- VITE_API_PORT=${API_PORT:-8180}
|
||||
- API_URL=${API_URL:-https://bamort-api.trokan.de}
|
||||
depends_on:
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Generate config.json from VITE_API_URL for development
|
||||
# This makes the dev environment behave the same as production
|
||||
|
||||
CONFIG_FILE="/app/public/config.json"
|
||||
|
||||
echo "🔧 Generating development config.json..."
|
||||
|
||||
# Use VITE_API_URL from environment
|
||||
API_BASE_URL="${VITE_API_URL:-}"
|
||||
|
||||
if [ -z "$API_BASE_URL" ]; then
|
||||
echo "⚠️ VITE_API_URL not set, creating minimal config"
|
||||
cat > "$CONFIG_FILE" <<EOF
|
||||
{
|
||||
"_comment": "Development mode - VITE_API_URL will be used as fallback"
|
||||
}
|
||||
EOF
|
||||
else
|
||||
echo "✅ API URL configured: $API_BASE_URL"
|
||||
cat > "$CONFIG_FILE" <<EOF
|
||||
{
|
||||
"apiBaseURL": "$API_BASE_URL"
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo "📄 Created config.json:"
|
||||
cat "$CONFIG_FILE"
|
||||
|
||||
# Start Vite dev server
|
||||
echo "🚀 Starting Vite dev server..."
|
||||
exec npm run dev -- --host 0.0.0.0
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Generate config.json from environment variables at container startup
|
||||
# This allows runtime configuration without rebuilding the container
|
||||
|
||||
CONFIG_FILE="/usr/share/nginx/html/config.json"
|
||||
|
||||
echo "🔧 Generating frontend runtime configuration..."
|
||||
|
||||
# Use API_URL from environment, or fallback to same origin
|
||||
API_BASE_URL="${API_URL:-}"
|
||||
|
||||
if [ -z "$API_BASE_URL" ]; then
|
||||
echo "⚠️ API_URL not set, frontend will auto-detect or use same origin"
|
||||
# Create minimal config that triggers auto-detection
|
||||
cat > "$CONFIG_FILE" <<EOF
|
||||
{
|
||||
"_comment": "API_URL not configured, using auto-detection"
|
||||
}
|
||||
EOF
|
||||
else
|
||||
echo "✅ API URL configured: $API_BASE_URL"
|
||||
cat > "$CONFIG_FILE" <<EOF
|
||||
{
|
||||
"apiBaseURL": "$API_BASE_URL"
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo "📄 Generated config.json:"
|
||||
cat "$CONFIG_FILE"
|
||||
|
||||
# Start nginx
|
||||
echo "🚀 Starting nginx..."
|
||||
exec nginx -g "daemon off;"
|
||||
+18
-1
@@ -11,16 +11,33 @@ fi
|
||||
# Gehe ins Docker-Verzeichnis
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Load development environment variables
|
||||
if [ -f .env.dev ]; then
|
||||
echo "📝 Loading configuration from .env.dev"
|
||||
export $(grep -v '^#' .env.dev | xargs)
|
||||
else
|
||||
if [ -f .env ]; then
|
||||
echo "📝 Loading configuration from .env"
|
||||
export $(grep -v '^#' .env | xargs)
|
||||
else
|
||||
echo "⚠️ Warning: .env not found, using defaults"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get current git commit
|
||||
export GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
echo "📝 Git Commit: $GIT_COMMIT"
|
||||
|
||||
echo "📦 Building and starting development containers..."
|
||||
echo "🔧 Frontend will use API: ${API_URL:-http://localhost:8180}"
|
||||
|
||||
# Stoppe vorhandene Container
|
||||
docker-compose -f docker-compose.dev.yml down
|
||||
|
||||
# Baue und starte die Container
|
||||
docker-compose -f docker-compose.dev.yml up --build -d
|
||||
docker-compose -f docker-compose.dev.yml --env-file .env.dev up --build -d
|
||||
|
||||
echo "✅ Development environment started."
|
||||
echo "📱 Frontend: http://localhost:5173"
|
||||
echo "🔌 Backend: http://localhost:8180"
|
||||
echo "🗄️ phpMyAdmin: http://localhost:8081"
|
||||
|
||||
+24
-3
@@ -11,14 +11,35 @@ fi
|
||||
# Gehe ins Docker-Verzeichnis
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Load production environment variables
|
||||
if [ -f .env.prd ]; then
|
||||
echo "📝 Loading configuration from .env.prd"
|
||||
export $(grep -v '^#' .env.prd | xargs)
|
||||
else
|
||||
if [ -f .env ]; then
|
||||
echo "📝 Loading configuration from .env"
|
||||
export $(grep -v '^#' .env | xargs)
|
||||
else
|
||||
echo "⚠️ Warning: .env not found, using defaults"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "📦 Building and starting production containers..."
|
||||
echo "🔧 Frontend will use API: ${API_URL:-https://bamort-api.trokan.de}"
|
||||
|
||||
# Build before stopping existing containers
|
||||
docker-compose -f docker-compose.yml build
|
||||
docker-compose -f docker-compose.yml --env-file .env.prd build
|
||||
|
||||
# Stoppe vorhandene Container
|
||||
docker-compose -f docker-compose.yml down
|
||||
|
||||
# Baue und starte die Container
|
||||
docker-compose -f docker-compose.yml up --build -d
|
||||
docker-compose -f docker-compose.yml --env-file .env.prd up -d
|
||||
|
||||
echo "✅ Production environment started."
|
||||
echo "✅ Production environment started."
|
||||
echo "📱 Frontend: http://localhost:8181"
|
||||
echo "🔌 Backend: http://localhost:8182"
|
||||
echo ""
|
||||
echo "💡 To change API URL: Edit .env.prd and run:"
|
||||
echo " docker-compose -f docker-compose.yml restart frontend"
|
||||
echo " (No rebuild needed!)"
|
||||
@@ -13,6 +13,13 @@ dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Runtime configuration (use config.json.example as template)
|
||||
public/config.json
|
||||
|
||||
# Docker entrypoint scripts (copied from ../docker/ during build)
|
||||
docker-entrypoint.sh
|
||||
docker-dev-entrypoint.sh
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.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": {
|
||||
|
||||
@@ -1 +1 @@
|
||||
a98925179cb067ef9c9e5871faa6f8c7
|
||||
705a38c2f133892600ec967917c4d556
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"apiBaseURL": "http://localhost:8180",
|
||||
"_comment": "This file can be modified at deployment without rebuilding. Copy to config.json and adjust apiBaseURL for your environment."
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<!-- Submenu Content -->
|
||||
<!-- <div class="character-aspect"> -->
|
||||
<component :is="currentView" :character="character" :isOwner="isOwner" @character-updated="refreshCharacter"/>
|
||||
<component :is="currentView" :character="character" :isOwner="isOwner" @character-updated="refreshCharacter" @character-data-updated="updateCharacterData"/>
|
||||
<!-- </div> -->
|
||||
|
||||
<!-- Submenu -->
|
||||
@@ -156,6 +156,12 @@ export default {
|
||||
alert('Fehler beim Aktualisieren der Charakterdaten: ' + (error.response?.data?.error || error.message));
|
||||
}
|
||||
},
|
||||
|
||||
updateCharacterData(updatedData) {
|
||||
// Update character data directly without reloading from server
|
||||
this.character = updatedData;
|
||||
console.log('Character data updated directly from response');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -55,6 +55,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Warning message when editing skill name -->
|
||||
<div v-if="showNameEditWarning" class="warning-message">
|
||||
<span class="warning-icon">⚠️</span>
|
||||
<span class="warning-text">
|
||||
{{ $t('characters.datasheet.editnamewarning') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -384,6 +393,29 @@
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.warning-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
margin: 10px 0;
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 6px;
|
||||
color: #856404;
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 1.2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -431,6 +463,7 @@ export default {
|
||||
editingSkillId: null,
|
||||
editingField: null,
|
||||
editValue: '',
|
||||
showNameEditWarning: false,
|
||||
|
||||
isLoading: false
|
||||
};
|
||||
@@ -752,6 +785,11 @@ export default {
|
||||
this.editingField = field;
|
||||
this.editValue = skill[field] || '';
|
||||
|
||||
// Show warning when editing skill name
|
||||
if (field === 'name') {
|
||||
this.showNameEditWarning = true;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
const input = this.$refs.editInput;
|
||||
if (input) {
|
||||
@@ -779,19 +817,50 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// Update local character object
|
||||
skill[field] = newValue;
|
||||
// Find and update the original skill in character.fertigkeiten or character.waffenfertigkeiten
|
||||
let originalSkillFound = false;
|
||||
|
||||
// Check in fertigkeiten
|
||||
if (this.character.fertigkeiten) {
|
||||
const originalSkill = this.character.fertigkeiten.find(s => s.name === skill.name);
|
||||
if (originalSkill) {
|
||||
originalSkill[field] = newValue;
|
||||
originalSkillFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check in waffenfertigkeiten if not found in fertigkeiten
|
||||
if (!originalSkillFound && this.character.waffenfertigkeiten) {
|
||||
const originalWeaponSkill = this.character.waffenfertigkeiten.find(s => s.name === skill.name);
|
||||
if (originalWeaponSkill) {
|
||||
originalWeaponSkill[field] = newValue;
|
||||
originalSkillFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!originalSkillFound) {
|
||||
console.warn('Original skill not found in character data:', skill.name);
|
||||
alert('Warnung: Originalfertigkeit nicht gefunden.');
|
||||
this.cancelEditSkill();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Save to backend
|
||||
await API.put(`/api/characters/${this.character.id}`, this.character);
|
||||
const response = await API.put(`/api/characters/${this.character.id}`, this.character);
|
||||
|
||||
console.log('Skill updated successfully:', skill.name, field, newValue);
|
||||
|
||||
// Update the parent component with the response data
|
||||
// The backend now returns the full FeChar with categorizedskills
|
||||
this.$emit('character-data-updated', response.data);
|
||||
|
||||
this.$emit('character-updated');
|
||||
this.cancelEditSkill();
|
||||
} catch (error) {
|
||||
console.error('Failed to update skill:', error);
|
||||
alert('Fehler beim Speichern: ' + (error.response?.data?.error || error.message));
|
||||
this.cancelEditSkill();
|
||||
// Revert changes on error by reloading
|
||||
this.$emit('character-updated');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -799,6 +868,7 @@ export default {
|
||||
this.editingSkillId = null;
|
||||
this.editingField = null;
|
||||
this.editValue = '';
|
||||
this.showNameEditWarning = false;
|
||||
},
|
||||
|
||||
isEditingSkill(skill, field) {
|
||||
|
||||
@@ -563,7 +563,8 @@ export default {
|
||||
bRollTooltip: 'B würfeln: 1d6 + Modifikator je nach Rasse'
|
||||
},
|
||||
datasheet: {
|
||||
editHelp: 'Doppelklicken auf ein Feld, um es zu bearbeiten.'
|
||||
editHelp: 'Doppelklicken auf ein Feld, um es zu bearbeiten.',
|
||||
editnamewarning: 'Wenn der Name der Fertigkeit geändert wird kann diese nicht mehr nach den Regeln verbessert werden. Auch beim Export können Boni und andere Werte fehlen!'
|
||||
}
|
||||
},
|
||||
audit: {
|
||||
|
||||
@@ -559,7 +559,8 @@ export default {
|
||||
bRollTooltip: 'Roll B: 1d6 + modifier based on race'
|
||||
},
|
||||
datasheet: {
|
||||
editHelp: 'Click twice on a field to edit it.'
|
||||
editHelp: 'Click twice on a field to edit it.',
|
||||
editnamewarning: 'If the name of the skill is changed, it can no longer be improved according to the rules. Also, bonuses and other values may be missing when exporting!'
|
||||
}
|
||||
},
|
||||
audit: {
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 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 runtime config.json or auto-detection:
|
||||
* 1. Try to load /config.json (can be modified at deployment without rebuild)
|
||||
* 2. Try to detect backend at same origin (production reverse proxy setup)
|
||||
* 3. Fall back to VITE_API_URL (development) or same origin
|
||||
*/
|
||||
|
||||
let cachedAPIBaseURL = null
|
||||
|
||||
/**
|
||||
* Try to load configuration from /config.json
|
||||
*/
|
||||
async function loadConfigFile() {
|
||||
try {
|
||||
const response = await fetch('/config.json', {
|
||||
cache: 'no-cache',
|
||||
headers: { 'Accept': 'application/json' }
|
||||
})
|
||||
if (response.ok) {
|
||||
// Check if response is actually JSON (not HTML from SPA fallback)
|
||||
const contentType = response.headers.get('content-type')
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
const config = await response.json()
|
||||
if (config.apiBaseURL) {
|
||||
console.log('Loaded API URL from config.json:', config.apiBaseURL)
|
||||
return config.apiBaseURL
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// config.json doesn't exist or is invalid, that's okay
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to detect backend at same origin (production setup)
|
||||
*/
|
||||
async function detectBackendAtOrigin() {
|
||||
try {
|
||||
const origin = window.location.origin
|
||||
const response = await fetch(`${origin}/api/public/version`, {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
signal: AbortSignal.timeout(2000) // 2 second timeout
|
||||
})
|
||||
if (response.ok) {
|
||||
console.log('Detected backend at same origin:', origin)
|
||||
return origin
|
||||
}
|
||||
} catch (error) {
|
||||
// Backend not at same origin, that's okay
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API base URL dynamically
|
||||
* - In Wails desktop app: calls Go backend to get configured URL
|
||||
* - In web app: tries config.json, auto-detection, or VITE_API_URL
|
||||
*/
|
||||
export async function getAPIBaseURL() {
|
||||
// Return cached value if available
|
||||
if (cachedAPIBaseURL) {
|
||||
return cachedAPIBaseURL
|
||||
}
|
||||
|
||||
// Try Wails desktop app first (with timeout)
|
||||
if (typeof window !== 'undefined' && window['go']) {
|
||||
// Wait up to 3 seconds for Wails bindings to be ready
|
||||
for (let i = 0; i < 30; i++) {
|
||||
try {
|
||||
if (window['go']?.['main']?.['App']?.['GetAPIBaseURL']) {
|
||||
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)
|
||||
break
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
}
|
||||
|
||||
// Wails detected but binding failed - use desktop fallback
|
||||
cachedAPIBaseURL = 'http://localhost:8185'
|
||||
console.log('Desktop app using fallback:', cachedAPIBaseURL)
|
||||
return cachedAPIBaseURL
|
||||
}
|
||||
|
||||
// Web app - try multiple strategies
|
||||
|
||||
// Strategy 1: Load from config.json (can be modified at deployment)
|
||||
const configFileURL = await loadConfigFile()
|
||||
if (configFileURL) {
|
||||
cachedAPIBaseURL = configFileURL
|
||||
return cachedAPIBaseURL
|
||||
}
|
||||
|
||||
// Strategy 2: Check if backend is at same origin (production reverse proxy)
|
||||
if (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
|
||||
const sameOriginURL = await detectBackendAtOrigin()
|
||||
if (sameOriginURL) {
|
||||
cachedAPIBaseURL = sameOriginURL
|
||||
return cachedAPIBaseURL
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: Use VITE_API_URL (development) or fallback to same origin
|
||||
if (import.meta.env.VITE_API_URL) {
|
||||
cachedAPIBaseURL = import.meta.env.VITE_API_URL
|
||||
console.log('Web app using VITE_API_URL:', cachedAPIBaseURL)
|
||||
} else {
|
||||
// Final fallback: assume same origin for production
|
||||
cachedAPIBaseURL = window.location.origin
|
||||
console.log('Web app using same origin:', cachedAPIBaseURL)
|
||||
}
|
||||
|
||||
return cachedAPIBaseURL
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if running in desktop mode (Wails)
|
||||
*/
|
||||
export function isDesktopMode() {
|
||||
return isWailsApp()
|
||||
}typeof window !== 'undefined' &&
|
||||
window['go']?.['main']?.['App'] !== undefined
|
||||
@@ -44,6 +44,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { getAPIBaseURL } from '../utils/config'
|
||||
import axios from 'axios'
|
||||
import { getVersion, getGitCommit } from '../version'
|
||||
|
||||
@@ -80,7 +81,7 @@ export default {
|
||||
methods: {
|
||||
async fetchBackendVersion() {
|
||||
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`)
|
||||
|
||||
if (response.data) {
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAPIBaseURL } from '../utils/config'
|
||||
import axios from 'axios'
|
||||
import { getVersion, getGitCommit } from '../version'
|
||||
|
||||
@@ -157,7 +158,7 @@ export default {
|
||||
methods: {
|
||||
async fetchBackendVersion() {
|
||||
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`)
|
||||
|
||||
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