admin dashboard, metrics and telemetry
Cross Compile Go / build (push) Canceled after 0s

- embedded React admin UI at /admin/ui (go:embed), login with ADMIN_SECRET
  via signed HttpOnly session cookie, X-ADMIN-KEY still works for scripts
- admin API: overview stats, indexing queue, addon management (priority,
  reindex, delete/restore/purge, bulk), whitelist CRUD, duplicate hashes,
  traffic, metrics explorer, services, events/audit log, system info
- built-in metrics: request latency per route, visitors, top addons and
  searches, indexer/scraper counters; minute buckets in the DB with hourly
  rollups and retention; optional Prometheus /metrics (METRICS_TOKEN)
- POST /back/telemetry for indexers/scrapers (metrics, events, heartbeat)
  and a stdlib-only Go client in telemetry/
- auth hardening: empty secrets no longer allow access, constant-time
  compare, CORS not applied to /admin and /back, TRUSTED_PROXIES setting
- Dockerfile builds the UI and no longer bakes config.yaml into the image;
  CI builds the UI, runs tests and builds arm64 with CGO
This commit is contained in:
ilbinek committed 2026-10-03 18:10:57 +02:00
1 parent 0ef27b0072
commit 56d9e10621
79 files changed
+13146 -118

No files matched your search

+7 -4
View File
@@ -4,6 +4,7 @@ import (
"strconv"
"gitea.tbdevent.eu/TBD/reforger_crawler_main/initializers"
"gitea.tbdevent.eu/TBD/reforger_crawler_main/metrics"
"gitea.tbdevent.eu/TBD/reforger_crawler_main/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -53,6 +54,8 @@ func SetAddonDependencies(c *gin.Context) {
return
}
metrics.DependencyReports.Inc()
metrics.DependencyEdges.Add(float64(len(edges)))
c.JSON(200, gin.H{"status": "success", "stored": len(edges)})
}
@@ -69,7 +72,7 @@ func GetDependents(c *gin.Context) {
limit := parseLimit(c.Query("limit"))
offset := parseOffset(c.Query("offset"))
dependents, total, err := queryRelatedAddons(addonID, "ad.addon_id", "ad.dependency_id", limit, offset)
dependents, total, err := QueryRelatedAddons(addonID, "ad.addon_id", "ad.dependency_id", limit, offset)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
@@ -97,7 +100,7 @@ func GetDependencies(c *gin.Context) {
limit := parseLimit(c.Query("limit"))
offset := parseOffset(c.Query("offset"))
dependencies, total, err := queryRelatedAddons(addonID, "ad.dependency_id", "ad.addon_id", limit, offset)
dependencies, total, err := QueryRelatedAddons(addonID, "ad.dependency_id", "ad.addon_id", limit, offset)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
@@ -112,7 +115,7 @@ func GetDependencies(c *gin.Context) {
})
}
// queryRelatedAddons returns one page of addons linked to addonID through the
// QueryRelatedAddons returns one page of addons linked to addonID through the
// addon_dependencies edge table, plus the total number of matches.
//
// joinCol is the edge column joined to addons.id; filterCol is the edge column
@@ -120,7 +123,7 @@ func GetDependencies(c *gin.Context) {
//
// The addons.id tiebreaker in the ORDER BY is required: subscriber_count alone
// is not a unique ordering, so without it paging could repeat or skip rows.
func queryRelatedAddons(addonID, joinCol, filterCol string, limit, offset int) ([]models.AddonSearchResult, int, error) {
func QueryRelatedAddons(addonID, joinCol, filterCol string, limit, offset int) ([]models.AddonSearchResult, int, error) {
base := func() *gorm.DB {
return initializers.DB.Model(&models.Addon{}).
Joins("JOIN addon_dependencies ad ON "+joinCol+" = addons.id").