controller: immediate hub report push on geo change + always-report geo (B)

- Geo settings save and manual geo sync now fire an out-of-band, non-blocking hub
  report push (Router.reportPushNow seam, wired in main.go to BuildReport+Push in a
  goroutine) so the hub reflects the new geo state / clears a stale last_sync_error
  within seconds instead of after the next ~15-min cycle. Scope: geo handlers only.
- builder.go always populates report.GeoRestriction (Enabled=false, empty countries
  when nil/disabled) via new buildGeoRestrictionReport helper, so the hub always
  renders the geo section ("Inaktív" when off) instead of hiding it via omitempty.
- Tests: geo save success → push once; invalid country → no push (companion);
  buildGeoRestrictionReport(nil) → non-nil disabled (companion vs old nil-omit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 13:00:32 +02:00
parent ba87412508
commit 02a4ba0491
5 changed files with 145 additions and 16 deletions
+28 -16
View File
@@ -155,22 +155,11 @@ func BuildReport(
// App telemetry (metrics + log scan)
r.AppTelemetry = buildAppTelemetrySection(stackMgr, metricsStore, logger)
// Geo-restriction status
if geoRestriction != nil {
gr := &GeoRestrictionReport{
Enabled: geoRestriction.Enabled,
AllowedCountries: geoRestriction.AllowedCountries,
LastSync: geoRestriction.LastSync,
LastSyncError: geoRestriction.LastSyncError,
}
if len(geoRestriction.AppOverrides) > 0 {
gr.AppOverrides = make(map[string]GeoAppOverrideReport, len(geoRestriction.AppOverrides))
for k, v := range geoRestriction.AppOverrides {
gr.AppOverrides[k] = GeoAppOverrideReport{AllowedCountries: v.AllowedCountries}
}
}
r.GeoRestriction = gr
}
// Geo-restriction status — ALWAYS present (even when never configured) so the hub
// always renders the section. A nil pointer (omitempty) made the hub hide the whole
// section for a never-configured controller; a present-but-disabled report renders
// "Inaktív" hub-side.
r.GeoRestriction = buildGeoRestrictionReport(geoRestriction)
if debug && logger != nil {
logger.Printf("[DEBUG] [report] BuildReport: complete — containers=%d, health=%s, deployed=%d, available=%d, app_telemetry=%d",
@@ -180,6 +169,29 @@ func BuildReport(
return r
}
// buildGeoRestrictionReport always returns a non-nil report so the hub always renders the
// geo-restriction section. A nil or never-configured input yields Enabled=false with an
// empty (non-nil) country list — the hub renders that as "Inaktív".
func buildGeoRestrictionReport(geo *settings.GeoRestriction) *GeoRestrictionReport {
gr := &GeoRestrictionReport{Enabled: false, AllowedCountries: []string{}}
if geo == nil {
return gr
}
gr.Enabled = geo.Enabled
if geo.AllowedCountries != nil {
gr.AllowedCountries = geo.AllowedCountries
}
gr.LastSync = geo.LastSync
gr.LastSyncError = geo.LastSyncError
if len(geo.AppOverrides) > 0 {
gr.AppOverrides = make(map[string]GeoAppOverrideReport, len(geo.AppOverrides))
for k, v := range geo.AppOverrides {
gr.AppOverrides[k] = GeoAppOverrideReport{AllowedCountries: v.AllowedCountries}
}
}
return gr
}
func buildContainerReport(stackMgr *stacks.Manager, metricsStore *metrics.MetricsStore) ContainerReport {
cr := ContainerReport{}
@@ -0,0 +1,40 @@
package report
import (
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)
// buildGeoRestrictionReport must ALWAYS return a non-nil, present-but-disabled report for
// a nil input. The old inline code left Report.GeoRestriction nil (omitempty), which made
// the hub hide the whole geo section for a never-configured controller — this test fails
// against that old behaviour (companion).
func TestBuildGeoRestrictionReport_NilIsPresentAndDisabled(t *testing.T) {
gr := buildGeoRestrictionReport(nil)
if gr == nil {
t.Fatal("geo report is nil for nil input — the hub would omit the section")
}
if gr.Enabled {
t.Fatalf("Enabled = true, want false for nil input")
}
if gr.AllowedCountries == nil {
t.Fatalf("AllowedCountries is nil, want empty non-nil slice (renders as [], not null)")
}
}
func TestBuildGeoRestrictionReport_PopulatedPassesThrough(t *testing.T) {
in := &settings.GeoRestriction{Enabled: true, AllowedCountries: []string{"HU", "DE"}}
gr := buildGeoRestrictionReport(in)
if gr == nil || !gr.Enabled || len(gr.AllowedCountries) != 2 {
t.Fatalf("passthrough failed: %+v", gr)
}
}
// A disabled config with a nil country list must still yield an empty (non-nil) list.
func TestBuildGeoRestrictionReport_DisabledNilCountries(t *testing.T) {
gr := buildGeoRestrictionReport(&settings.GeoRestriction{Enabled: false})
if gr == nil || gr.Enabled || gr.AllowedCountries == nil {
t.Fatalf("disabled report wrong: %+v", gr)
}
}