fix: Windows compatibility and localhost binding
**Windows compatibility:** - Added filepath.Join for all template and static paths - Fixes "path not found" errors on Windows **Localhost binding:** - Changed default host from 0.0.0.0 to 127.0.0.1 - Browser always opens on 127.0.0.1 (localhost) - Setup mode now listens on 127.0.0.1:8080 - Updated config.example.yaml with comment about 0.0.0.0 This ensures the app works correctly on Windows and opens browser on the correct localhost address. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,10 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -149,6 +152,18 @@ func main() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Automatically open browser after server starts (with a small delay)
|
||||||
|
go func() {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
// Always use localhost for browser, even if server binds to 0.0.0.0
|
||||||
|
browserURL := fmt.Sprintf("http://127.0.0.1:%d", cfg.Server.Port)
|
||||||
|
slog.Info("Opening browser to", "url", browserURL)
|
||||||
|
err := openBrowser(browserURL)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Failed to open browser", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-quit
|
<-quit
|
||||||
@@ -172,7 +187,7 @@ func main() {
|
|||||||
|
|
||||||
func setConfigDefaults(cfg *config.Config) {
|
func setConfigDefaults(cfg *config.Config) {
|
||||||
if cfg.Server.Host == "" {
|
if cfg.Server.Host == "" {
|
||||||
cfg.Server.Host = "0.0.0.0"
|
cfg.Server.Host = "127.0.0.1"
|
||||||
}
|
}
|
||||||
if cfg.Server.Port == 0 {
|
if cfg.Server.Port == 0 {
|
||||||
cfg.Server.Port = 8080
|
cfg.Server.Port = 8080
|
||||||
@@ -211,7 +226,8 @@ func runSetupMode(local *localdb.LocalDB) {
|
|||||||
restartSig := make(chan struct{}, 1)
|
restartSig := make(chan struct{}, 1)
|
||||||
|
|
||||||
// In setup mode, we don't have a connection manager yet (will restart after setup)
|
// In setup mode, we don't have a connection manager yet (will restart after setup)
|
||||||
setupHandler, err := handlers.NewSetupHandler(local, nil, "web/templates", restartSig)
|
templatesPath := filepath.Join("web", "templates")
|
||||||
|
setupHandler, err := handlers.NewSetupHandler(local, nil, templatesPath, restartSig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to create setup handler", "error", err)
|
slog.Error("failed to create setup handler", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -221,7 +237,8 @@ func runSetupMode(local *localdb.LocalDB) {
|
|||||||
router := gin.New()
|
router := gin.New()
|
||||||
router.Use(gin.Recovery())
|
router.Use(gin.Recovery())
|
||||||
|
|
||||||
router.Static("/static", "web/static")
|
staticPath := filepath.Join("web", "static")
|
||||||
|
router.Static("/static", staticPath)
|
||||||
|
|
||||||
// Setup routes only
|
// Setup routes only
|
||||||
router.GET("/", func(c *gin.Context) {
|
router.GET("/", func(c *gin.Context) {
|
||||||
@@ -240,9 +257,8 @@ func runSetupMode(local *localdb.LocalDB) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
addr := ":8080"
|
addr := "127.0.0.1:8080"
|
||||||
slog.Info("starting setup mode server", "address", addr)
|
slog.Info("starting setup mode server", "address", addr)
|
||||||
slog.Info("open http://localhost:8080/setup to configure database connection")
|
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
@@ -256,6 +272,17 @@ func runSetupMode(local *localdb.LocalDB) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Open browser to setup page
|
||||||
|
go func() {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
browserURL := "http://127.0.0.1:8080/setup"
|
||||||
|
slog.Info("Opening browser to setup page", "url", browserURL)
|
||||||
|
err := openBrowser(browserURL)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Failed to open browser", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
@@ -383,25 +410,28 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
|
|||||||
// Local-first configuration service (replaces old ConfigurationService)
|
// Local-first configuration service (replaces old ConfigurationService)
|
||||||
configService := services.NewLocalConfigurationService(local, syncService, quoteService, isOnline)
|
configService := services.NewLocalConfigurationService(local, syncService, quoteService, isOnline)
|
||||||
|
|
||||||
|
// Use filepath.Join for cross-platform path compatibility
|
||||||
|
templatesPath := filepath.Join("web", "templates")
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
componentHandler := handlers.NewComponentHandler(componentService, local)
|
componentHandler := handlers.NewComponentHandler(componentService, local)
|
||||||
quoteHandler := handlers.NewQuoteHandler(quoteService)
|
quoteHandler := handlers.NewQuoteHandler(quoteService)
|
||||||
exportHandler := handlers.NewExportHandler(exportService, configService, componentService)
|
exportHandler := handlers.NewExportHandler(exportService, configService, componentService)
|
||||||
pricingHandler := handlers.NewPricingHandler(mariaDB, pricingService, alertService, componentRepo, priceRepo, statsRepo)
|
pricingHandler := handlers.NewPricingHandler(mariaDB, pricingService, alertService, componentRepo, priceRepo, statsRepo)
|
||||||
pricelistHandler := handlers.NewPricelistHandler(pricelistService, local)
|
pricelistHandler := handlers.NewPricelistHandler(pricelistService, local)
|
||||||
syncHandler, err := handlers.NewSyncHandler(local, syncService, connMgr, "web/templates")
|
syncHandler, err := handlers.NewSyncHandler(local, syncService, connMgr, templatesPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("creating sync handler: %w", err)
|
return nil, nil, fmt.Errorf("creating sync handler: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup handler (for reconfiguration) - no restart signal in normal mode
|
// Setup handler (for reconfiguration) - no restart signal in normal mode
|
||||||
setupHandler, err := handlers.NewSetupHandler(local, connMgr, "web/templates", nil)
|
setupHandler, err := handlers.NewSetupHandler(local, connMgr, templatesPath, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("creating setup handler: %w", err)
|
return nil, nil, fmt.Errorf("creating setup handler: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Web handler (templates)
|
// Web handler (templates)
|
||||||
webHandler, err := handlers.NewWebHandler("web/templates", componentService)
|
webHandler, err := handlers.NewWebHandler(templatesPath, componentService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -413,8 +443,9 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
|
|||||||
router.Use(middleware.CORS())
|
router.Use(middleware.CORS())
|
||||||
router.Use(middleware.OfflineDetector(connMgr, local))
|
router.Use(middleware.OfflineDetector(connMgr, local))
|
||||||
|
|
||||||
// Static files
|
// Static files (use filepath.Join for Windows compatibility)
|
||||||
router.Static("/static", "web/static")
|
staticPath := filepath.Join("web", "static")
|
||||||
|
router.Static("/static", staticPath)
|
||||||
|
|
||||||
// Health check
|
// Health check
|
||||||
router.GET("/health", func(c *gin.Context) {
|
router.GET("/health", func(c *gin.Context) {
|
||||||
@@ -424,6 +455,18 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Restart endpoint (for development purposes)
|
||||||
|
router.POST("/api/restart", func(c *gin.Context) {
|
||||||
|
// This will cause the server to restart by exiting
|
||||||
|
// The restartProcess function will be called to restart the process
|
||||||
|
slog.Info("Restart requested via API")
|
||||||
|
go func() {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
restartProcess()
|
||||||
|
}()
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "restarting..."})
|
||||||
|
})
|
||||||
|
|
||||||
// DB status endpoint
|
// DB status endpoint
|
||||||
router.GET("/api/db-status", func(c *gin.Context) {
|
router.GET("/api/db-status", func(c *gin.Context) {
|
||||||
var lotCount, lotLogCount, metadataCount int64
|
var lotCount, lotLogCount, metadataCount int64
|
||||||
@@ -708,6 +751,25 @@ func restartProcess() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
var cmd string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
cmd = "cmd"
|
||||||
|
args = []string{"/c", "start", url}
|
||||||
|
case "darwin":
|
||||||
|
cmd = "open"
|
||||||
|
args = []string{url}
|
||||||
|
default: // "linux", "freebsd", "openbsd", "netbsd"
|
||||||
|
cmd = "xdg-open"
|
||||||
|
args = []string{url}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exec.Command(cmd, args...).Start()
|
||||||
|
}
|
||||||
|
|
||||||
func requestLogger() gin.HandlerFunc {
|
func requestLogger() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Copy this file to config.yaml and update values
|
# Copy this file to config.yaml and update values
|
||||||
|
|
||||||
server:
|
server:
|
||||||
host: "0.0.0.0"
|
host: "127.0.0.1" # Use 0.0.0.0 to listen on all interfaces
|
||||||
port: 8080
|
port: 8080
|
||||||
mode: "release" # debug | release
|
mode: "release" # debug | release
|
||||||
read_timeout: "30s"
|
read_timeout: "30s"
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func Load(path string) (*Config, error) {
|
|||||||
|
|
||||||
func (c *Config) setDefaults() {
|
func (c *Config) setDefaults() {
|
||||||
if c.Server.Host == "" {
|
if c.Server.Host == "" {
|
||||||
c.Server.Host = "0.0.0.0"
|
c.Server.Host = "127.0.0.1"
|
||||||
}
|
}
|
||||||
if c.Server.Port == 0 {
|
if c.Server.Port == 0 {
|
||||||
c.Server.Port = 8080
|
c.Server.Port = 8080
|
||||||
|
|||||||
Reference in New Issue
Block a user