From 0c707771811e945d987138c055cb413e2709902f Mon Sep 17 00:00:00 2001 From: "othman.suseno" Date: Tue, 16 Dec 2025 19:58:52 +0700 Subject: [PATCH] Fix: UI not refreshing after ZFS pool operations due to caching --- .gitignore | 3 +++ internal/httpapp/api_handlers.go | 14 ++++++++++++++ internal/httpapp/app.go | 2 ++ internal/httpapp/cache_middleware.go | 7 ++----- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 2b9d0ba..816a352 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ pluto-api # Runtime *.log + +# Temporary vdevs +data/vdevs/ diff --git a/internal/httpapp/api_handlers.go b/internal/httpapp/api_handlers.go index d9ced1a..6a5d3ad 100644 --- a/internal/httpapp/api_handlers.go +++ b/internal/httpapp/api_handlers.go @@ -1,6 +1,8 @@ package httpapp import ( + "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" "log" @@ -76,6 +78,12 @@ func (a *App) handleCreatePool(w http.ResponseWriter, r *http.Request) { return } + // Invalidate cache for GET /api/v1/pools + keyToInvalidate := "GET:/api/v1/pools" + hash := sha256.Sum256([]byte(keyToInvalidate)) + cacheKey := hex.EncodeToString(hash[:]) + a.cache.Invalidate(cacheKey) + pool, err := a.zfs.GetPool(req.Name) if err != nil { writeJSON(w, http.StatusCreated, map[string]string{"message": "pool created", "name": req.Name}) @@ -114,6 +122,12 @@ func (a *App) handleDeletePool(w http.ResponseWriter, r *http.Request) { return } + // Invalidate cache for GET /api/v1/pools + keyToInvalidate := "GET:/api/v1/pools" + hash := sha256.Sum256([]byte(keyToInvalidate)) + cacheKey := hex.EncodeToString(hash[:]) + a.cache.Invalidate(cacheKey) + writeJSON(w, http.StatusOK, map[string]string{"message": "pool destroyed", "name": name}) } diff --git a/internal/httpapp/app.go b/internal/httpapp/app.go index f95344f..396b46f 100644 --- a/internal/httpapp/app.go +++ b/internal/httpapp/app.go @@ -52,6 +52,7 @@ type App struct { backupService *backup.Service maintenanceService *maintenance.Service tlsConfig *tls.Config + cache *ResponseCache } func New(cfg Config) (*App, error) { @@ -175,6 +176,7 @@ func New(cfg Config) (*App, error) { backupService: backupService, maintenanceService: maintenanceService, tlsConfig: tlsConfig, + cache: NewResponseCache(5 * time.Minute), } // Start snapshot scheduler (runs every 15 minutes) diff --git a/internal/httpapp/cache_middleware.go b/internal/httpapp/cache_middleware.go index 81c883a..0d00c24 100644 --- a/internal/httpapp/cache_middleware.go +++ b/internal/httpapp/cache_middleware.go @@ -126,9 +126,6 @@ func generateETag(content []byte) string { // cacheMiddleware provides response caching for GET requests func (a *App) cacheMiddleware(next http.Handler) http.Handler { - // Default TTL: 5 minutes for GET requests - cache := NewResponseCache(5 * time.Minute) - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Only cache GET requests if r.Method != http.MethodGet { @@ -154,7 +151,7 @@ func (a *App) cacheMiddleware(next http.Handler) http.Handler { // Check cache cacheKey := generateCacheKey(r) - if entry, found := cache.Get(cacheKey); found { + if entry, found := a.cache.Get(cacheKey); found { // Check If-None-Match header for ETag validation ifNoneMatch := r.Header.Get("If-None-Match") if ifNoneMatch == entry.ETag { @@ -202,7 +199,7 @@ func (a *App) cacheMiddleware(next http.Handler) http.Handler { ETag: etag, } - cache.Set(cacheKey, entry) + a.cache.Set(cacheKey, entry) // Add cache headers w.Header().Set("ETag", etag)