From 38f3a1e01ec91589c99410ee0fbcc324b1e93163 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 23 Feb 2026 15:05:46 +0100 Subject: [PATCH] feat: per-app telemetry reset button on app detail page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds "Telemetria törlése" button that deletes all telemetry records and known issues for a specific app. Useful after major app updates when old data is no longer representative. Co-Authored-By: Claude Opus 4.6 --- hub/CHANGELOG.md | 7 ++++++ hub/internal/store/telemetry.go | 18 ++++++++++++++++ hub/internal/web/apps.go | 25 ++++++++++++++++++++++ hub/internal/web/server.go | 8 +++++++ hub/internal/web/templates/app_detail.html | 13 ++++++++++- 5 files changed, 70 insertions(+), 1 deletion(-) diff --git a/hub/CHANGELOG.md b/hub/CHANGELOG.md index ce0c42a..4a24f03 100644 --- a/hub/CHANGELOG.md +++ b/hub/CHANGELOG.md @@ -1,5 +1,12 @@ # Felhom Hub — Changelog +## v0.4.1 (2026-02-23) + +### Added +- **Per-app telemetry reset** (`store/telemetry.go`, `web/apps.go`) — New "Telemetria törlése" button on the app detail page that deletes all telemetry records and known issues for the selected app. Useful after major app updates when old data is no longer representative. Includes confirmation dialog and flash notification. +- **`DeleteAppTelemetry()`** and **`DeleteAppIssues()`** store methods (`store/telemetry.go`) — Delete all telemetry/issue rows for a specific app_name. +- **`POST /apps/{name}/reset-telemetry`** route (`web/server.go`) — CSRF-protected endpoint that triggers the reset and redirects back with flash message. + ## v0.4.0 (2026-02-23) **App Telemetry & Analytics Dashboard** diff --git a/hub/internal/store/telemetry.go b/hub/internal/store/telemetry.go index 3e2637e..2f7c132 100644 --- a/hub/internal/store/telemetry.go +++ b/hub/internal/store/telemetry.go @@ -446,3 +446,21 @@ func (s *Store) PruneStaleIssues(notSeenSince time.Time) (int64, error) { } return res.RowsAffected() } + +// DeleteAppTelemetry removes all telemetry records for a specific app. +func (s *Store) DeleteAppTelemetry(appName string) (int64, error) { + res, err := s.db.Exec("DELETE FROM app_telemetry WHERE app_name = ?", appName) + if err != nil { + return 0, err + } + return res.RowsAffected() +} + +// DeleteAppIssues removes all known-issue records for a specific app. +func (s *Store) DeleteAppIssues(appName string) (int64, error) { + res, err := s.db.Exec("DELETE FROM app_log_issues WHERE app_name = ?", appName) + if err != nil { + return 0, err + } + return res.RowsAffected() +} diff --git a/hub/internal/web/apps.go b/hub/internal/web/apps.go index 88cf916..2de26e4 100644 --- a/hub/internal/web/apps.go +++ b/hub/internal/web/apps.go @@ -99,12 +99,37 @@ func (s *Server) handleAppDetail(w http.ResponseWriter, r *http.Request, appName "SuggestedLimit": suggestedLimit, "Period": period, "CSRFToken": csrfToken, + "Flash": r.URL.Query().Get("flash"), } if err := s.templates.ExecuteTemplate(w, "app_detail.html", data); err != nil { s.logger.Printf("[ERROR] app_detail.html template: %v", err) } } +// handleResetAppTelemetry deletes all telemetry and issues for an app, then redirects back. +func (s *Server) handleResetAppTelemetry(w http.ResponseWriter, r *http.Request, appName string) { + telRows, err := s.store.DeleteAppTelemetry(appName) + if err != nil { + s.logger.Printf("[ERROR] Failed to reset telemetry for %s: %v", appName, err) + http.Error(w, "Internal error", http.StatusInternalServerError) + return + } + issueRows, err := s.store.DeleteAppIssues(appName) + if err != nil { + s.logger.Printf("[ERROR] Failed to reset issues for %s: %v", appName, err) + http.Error(w, "Internal error", http.StatusInternalServerError) + return + } + s.logger.Printf("[INFO] Telemetry reset for %s: %d telemetry rows, %d issues deleted", appName, telRows, issueRows) + + period := r.URL.Query().Get("period") + target := "/apps/" + appName + "?flash=telemetry_reset" + if period != "" { + target += "&period=" + period + } + http.Redirect(w, r, target, http.StatusSeeOther) +} + // parsePeriod converts a period string to a time.Time cutoff. func parsePeriod(s string, defaultDur time.Duration) time.Time { switch s { diff --git a/hub/internal/web/server.go b/hub/internal/web/server.go index 9edeea7..faed699 100644 --- a/hub/internal/web/server.go +++ b/hub/internal/web/server.go @@ -140,6 +140,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write(chartJS) case path == "/apps" || path == "/apps/": s.handleApps(w, r) + case strings.HasPrefix(path, "/apps/") && strings.HasSuffix(path, "/reset-telemetry"): + appName := strings.TrimPrefix(path, "/apps/") + appName = strings.TrimSuffix(appName, "/reset-telemetry") + if r.Method == http.MethodPost { + s.handleResetAppTelemetry(w, r, appName) + } else { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } case strings.HasPrefix(path, "/apps/"): appName := strings.TrimPrefix(path, "/apps/") s.handleAppDetail(w, r, appName) diff --git a/hub/internal/web/templates/app_detail.html b/hub/internal/web/templates/app_detail.html index 64f7937..7a1edae 100644 --- a/hub/internal/web/templates/app_detail.html +++ b/hub/internal/web/templates/app_detail.html @@ -27,9 +27,20 @@ 30 nap + {{if eq .Flash "telemetry_reset"}} +
Telemetria sikeresen törölve.
+ {{end}} +
-

{{if .Summary}}{{if .Summary.DisplayName}}{{.Summary.DisplayName}}{{else}}{{.AppName}}{{end}}{{else}}{{.AppName}}{{end}}

+
+

{{if .Summary}}{{if .Summary.DisplayName}}{{.Summary.DisplayName}}{{else}}{{.AppName}}{{end}}{{else}}{{.AppName}}{{end}}

+
+ + +
+
App neve