From ec3c16f3fc45aeae9c61ef9b1efa478eb28a6818 Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Mon, 2 Feb 2026 06:38:23 +0300 Subject: [PATCH] Add UI sync status indicator with pending badge - Create htmx-powered partial template for sync status display - Show Online/Offline indicator with color coding (green/red) - Display pending changes count badge when there are unsynced items - Add Sync button to push pending changes (appears only when needed) - Auto-refresh every 30 seconds via htmx polling - Replace JavaScript-based sync indicator with server-rendered partial - Integrate SyncStatusPartial handler with template rendering Co-Authored-By: Claude Sonnet 4.5 --- cmd/server/main.go | 6 +- internal/handlers/sync.go | 36 +++++++++++- web/templates/base.html | 76 ++----------------------- web/templates/partials/sync_status.html | 37 ++++++++++++ 4 files changed, 81 insertions(+), 74 deletions(-) create mode 100644 web/templates/partials/sync_status.html diff --git a/cmd/server/main.go b/cmd/server/main.go index 0155d87..899cc1f 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -331,7 +331,10 @@ func setupRouter(db *gorm.DB, cfg *config.Config, local *localdb.LocalDB, dbUser exportHandler := handlers.NewExportHandler(exportService, configService, componentService) pricingHandler := handlers.NewPricingHandler(db, pricingService, alertService, componentRepo, priceRepo, statsRepo) pricelistHandler := handlers.NewPricelistHandler(pricelistService, local) - syncHandler := handlers.NewSyncHandler(local, syncService, db) + syncHandler, err := handlers.NewSyncHandler(local, syncService, db, "web/templates") + if err != nil { + return nil, nil, fmt.Errorf("creating sync handler: %w", err) + } // Setup handler (for reconfiguration) setupHandler, err := handlers.NewSetupHandler(local, "web/templates") @@ -418,6 +421,7 @@ func setupRouter(db *gorm.DB, cfg *config.Config, local *localdb.LocalDB, dbUser partials := router.Group("/partials") { partials.GET("/components", webHandler.ComponentsPartial) + partials.GET("/sync-status", syncHandler.SyncStatusPartial) } // API routes diff --git a/internal/handlers/sync.go b/internal/handlers/sync.go index f9a844c..bebe4ab 100644 --- a/internal/handlers/sync.go +++ b/internal/handlers/sync.go @@ -1,8 +1,10 @@ package handlers import ( + "html/template" "log/slog" "net/http" + "path/filepath" "time" "github.com/gin-gonic/gin" @@ -16,15 +18,24 @@ type SyncHandler struct { localDB *localdb.LocalDB syncService *sync.Service mariaDB *gorm.DB + tmpl *template.Template } // NewSyncHandler creates a new sync handler -func NewSyncHandler(localDB *localdb.LocalDB, syncService *sync.Service, mariaDB *gorm.DB) *SyncHandler { +func NewSyncHandler(localDB *localdb.LocalDB, syncService *sync.Service, mariaDB *gorm.DB, templatesPath string) (*SyncHandler, error) { + // Load sync_status partial template + partialPath := filepath.Join(templatesPath, "partials", "sync_status.html") + tmpl, err := template.ParseFiles(partialPath) + if err != nil { + return nil, err + } + return &SyncHandler{ localDB: localDB, syncService: syncService, mariaDB: mariaDB, - } + tmpl: tmpl, + }, nil } // SyncStatusResponse represents the sync status @@ -270,3 +281,24 @@ func (h *SyncHandler) GetPendingChanges(c *gin.Context) { "changes": changes, }) } + +// 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") + + // Get pending count + pendingCount := h.localDB.GetPendingCount() + + data := gin.H{ + "IsOffline": isOffline.(bool), + "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") + } +} diff --git a/web/templates/base.html b/web/templates/base.html index ca7692f..0be305b 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -26,8 +26,11 @@
- -
+ +
Загрузка...
@@ -57,72 +60,6 @@ setTimeout(() => el.innerHTML = '', 3000); } - async function checkSyncStatus() { - try { - const resp = await fetch('/api/sync/status'); - const data = await resp.json(); - updateSyncIndicator(data); - } catch(e) { - console.error('Failed to check sync status:', e); - const indicator = document.getElementById('sync-indicator'); - if (indicator) { - indicator.innerHTML = 'Offline'; - } - } - } - - function updateSyncIndicator(data) { - const indicator = document.getElementById('sync-indicator'); - if (!indicator) return; - - const statusColor = data.is_online ? 'bg-green-500' : 'bg-red-500'; - const statusText = data.is_online ? 'Online' : 'Offline'; - const textColor = data.is_online ? 'text-green-700' : 'text-red-700'; - - const needSync = data.need_component_sync || data.need_pricelist_sync; - const syncWarning = needSync ? '' : ''; - - let html = ` -
- - ${statusText} - ${syncWarning} - ${data.is_online ? ` - - ` : ''} -
- `; - - indicator.innerHTML = html; - } - - async function syncAll() { - const btn = event.target; - btn.disabled = true; - btn.textContent = '...'; - - try { - const resp = await fetch('/api/sync/all', { method: 'POST' }); - const data = await resp.json(); - - if (data.success) { - showToast(`Синхронизация завершена: компоненты ${data.components_synced}, прайслисты ${data.pricelists_synced}`, 'success'); - checkSyncStatus(); - } else { - showToast('Ошибка синхронизации: ' + (data.error || 'неизвестная ошибка'), 'error'); - } - } catch(e) { - showToast('Ошибка синхронизации: ' + e.message, 'error'); - } finally { - btn.disabled = false; - btn.textContent = 'Sync'; - } - } - async function checkDbStatus() { try { const resp = await fetch('/api/db-status'); @@ -162,9 +99,6 @@ document.addEventListener('DOMContentLoaded', function() { checkDbStatus(); checkWritePermission(); - checkSyncStatus(); - // Auto-refresh sync status every 30 seconds - setInterval(checkSyncStatus, 30000); }); diff --git a/web/templates/partials/sync_status.html b/web/templates/partials/sync_status.html new file mode 100644 index 0000000..219c241 --- /dev/null +++ b/web/templates/partials/sync_status.html @@ -0,0 +1,37 @@ +{{define "sync_status"}} +
+ {{if .IsOffline}} + + + Offline + + {{else}} + + + Online + + {{end}} + + {{if gt .PendingCount 0}} + + {{.PendingCount}} pending + + + {{end}} +
+{{end}}