Add offline RefreshPrices, fix sync bugs, implement auto-restart
- Implement RefreshPrices for local-first mode - Update prices from local_components.current_price cache - Graceful degradation when component not found - Add PriceUpdatedAt timestamp to LocalConfiguration model - Support both authenticated and no-auth price refresh - Fix sync duplicate entry bug - pushConfigurationUpdate now ensures server_id exists before update - Fetch from LocalConfiguration.ServerID or search on server if missing - Update local config with server_id after finding - Add application auto-restart after settings save - Implement restartProcess() using syscall.Exec - Setup handler signals restart via channel - Setup page polls /health endpoint and redirects when ready - Add "Back" button on setup page when settings exist - Fix setup handler password handling - Use PasswordEncrypted field consistently - Support empty password by using saved value - Improve sync status handling - Add fallback for is_offline check in SyncStatusPartial - Enhance background sync logging with prefixes - Update CLAUDE.md documentation - Mark Phase 2.5 tasks as complete - Add UI Improvements section with future tasks - Update SQLite tables documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,11 +16,12 @@ import (
|
||||
)
|
||||
|
||||
type SetupHandler struct {
|
||||
localDB *localdb.LocalDB
|
||||
templates map[string]*template.Template
|
||||
localDB *localdb.LocalDB
|
||||
templates map[string]*template.Template
|
||||
restartSig chan struct{}
|
||||
}
|
||||
|
||||
func NewSetupHandler(localDB *localdb.LocalDB, templatesPath string) (*SetupHandler, error) {
|
||||
func NewSetupHandler(localDB *localdb.LocalDB, templatesPath string, restartSig chan struct{}) (*SetupHandler, error) {
|
||||
funcMap := template.FuncMap{
|
||||
"sub": func(a, b int) int { return a - b },
|
||||
"add": func(a, b int) int { return a + b },
|
||||
@@ -37,8 +38,9 @@ func NewSetupHandler(localDB *localdb.LocalDB, templatesPath string) (*SetupHand
|
||||
templates["setup.html"] = tmpl
|
||||
|
||||
return &SetupHandler{
|
||||
localDB: localDB,
|
||||
templates: templates,
|
||||
localDB: localDB,
|
||||
templates: templates,
|
||||
restartSig: restartSig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -72,6 +74,13 @@ func (h *SetupHandler) TestConnection(c *gin.Context) {
|
||||
port = p
|
||||
}
|
||||
|
||||
// If password is empty, try to use saved password
|
||||
if password == "" {
|
||||
if settings, err := h.localDB.GetSettings(); err == nil && settings != nil {
|
||||
password = settings.PasswordEncrypted // GetSettings returns decrypted password in this field
|
||||
}
|
||||
}
|
||||
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=5s",
|
||||
user, password, host, port, database)
|
||||
|
||||
@@ -138,6 +147,13 @@ func (h *SetupHandler) SaveConnection(c *gin.Context) {
|
||||
port = p
|
||||
}
|
||||
|
||||
// If password is empty, use saved password
|
||||
if password == "" {
|
||||
if settings, err := h.localDB.GetSettings(); err == nil && settings != nil {
|
||||
password = settings.PasswordEncrypted // GetSettings returns decrypted password in this field
|
||||
}
|
||||
}
|
||||
|
||||
// Test connection first
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=5s",
|
||||
user, password, host, port, database)
|
||||
@@ -167,8 +183,16 @@ func (h *SetupHandler) SaveConnection(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "Settings saved. Please restart the application.",
|
||||
"message": "Settings saved. Restarting application...",
|
||||
})
|
||||
|
||||
// Signal restart after response is sent
|
||||
if h.restartSig != nil {
|
||||
go func() {
|
||||
time.Sleep(500 * time.Millisecond) // Give time for response to be sent
|
||||
h.restartSig <- struct{}{}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// GetStatus returns the current setup status
|
||||
|
||||
@@ -285,20 +285,30 @@ func (h *SyncHandler) GetPendingChanges(c *gin.Context) {
|
||||
// SyncStatusPartial renders the sync status partial for htmx
|
||||
// GET /partials/sync-status
|
||||
func (h *SyncHandler) SyncStatusPartial(c *gin.Context) {
|
||||
// Check online status
|
||||
isOffline, _ := c.Get("is_offline")
|
||||
// Check online status from middleware
|
||||
isOfflineValue, exists := c.Get("is_offline")
|
||||
isOffline := false
|
||||
if exists {
|
||||
isOffline = isOfflineValue.(bool)
|
||||
} else {
|
||||
// Fallback: check directly if middleware didn't set it
|
||||
isOffline = !h.checkOnline()
|
||||
slog.Warn("is_offline not found in context, checking directly")
|
||||
}
|
||||
|
||||
// Get pending count
|
||||
pendingCount := h.localDB.GetPendingCount()
|
||||
|
||||
slog.Debug("rendering sync status", "is_offline", isOffline, "pending_count", pendingCount)
|
||||
|
||||
data := gin.H{
|
||||
"IsOffline": isOffline.(bool),
|
||||
"IsOffline": isOffline,
|
||||
"PendingCount": pendingCount,
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
if err := h.tmpl.ExecuteTemplate(c.Writer, "sync_status", data); err != nil {
|
||||
slog.Error("failed to render sync_status template", "error", err)
|
||||
c.String(http.StatusInternalServerError, "Template error")
|
||||
c.String(http.StatusInternalServerError, "Template error: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user