v0.243.0: FileBrowser generated admin password (R-513); per-tier whole-guest backup truth (R-517); skip absent-storage tiers (R-518); OOM-killed worker visible (R-514)
gates / gates (push) Successful in 14s
gates / gates (push) Successful in 14s
MinAgent: 0.131.0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -113,6 +113,85 @@ type guestBackupView struct {
|
||||
RestoreTestedAt time.Time
|
||||
|
||||
CanTrigger bool // a backup trigger (quiesce loop) is wired
|
||||
|
||||
// Tiers (R-517, agent >= v0.131.0) is the per-tier truth. Empty on an older agent — the tile then
|
||||
// renders the single latest record as before.
|
||||
Tiers []guestTierView
|
||||
}
|
||||
|
||||
// guestTierView is one whole-guest tier as the customer sees it. A failed attempt is shown UNDER the
|
||||
// newest success, never instead of it ("presence is not success").
|
||||
type guestTierView struct {
|
||||
Target string
|
||||
Label string // Hungarian tier name
|
||||
IsPBS bool
|
||||
NotSetUp bool // the tier's storage does not exist on the host → „nincs beállítva"
|
||||
HasSuccess bool
|
||||
SuccessAt time.Time
|
||||
SizeKnown bool // false when the success was read back from storage after a restart
|
||||
SizeBytes int64
|
||||
Current bool // newest success is inside the tier's window
|
||||
FailedAfter bool // the last attempt failed and is newer than the newest success
|
||||
FailedAt time.Time
|
||||
}
|
||||
|
||||
// tierWindow is how old a tier's newest success may be and still count as current: its cadence plus
|
||||
// half again (a scheduled run lands inside the nightly window, not on the exact second). A tier with
|
||||
// no advertised cadence uses a day.
|
||||
func tierWindow(cadenceSecs int64) time.Duration {
|
||||
if cadenceSecs <= 0 {
|
||||
cadenceSecs = 24 * 3600
|
||||
}
|
||||
return time.Duration(cadenceSecs) * time.Second * 3 / 2
|
||||
}
|
||||
|
||||
// buildTierViews turns the agent's per-tier state into page rows and derives the three legacy tile
|
||||
// fields from SUCCESSES only: HasBackup/Success/Size/Target/StartedAt from the primary tier's newest
|
||||
// success; Due when any set-up tier has no current success; Offsite only on a current PBS success.
|
||||
func buildTierViews(v *guestBackupView, tiers []agentapi.TierBackupState, cadence map[string]int64, now time.Time) {
|
||||
v.Tiers = nil
|
||||
v.Due, v.Offsite, v.HasBackup = false, false, false
|
||||
var newestOK time.Time
|
||||
for _, t := range tiers {
|
||||
tv := guestTierView{Target: t.Target, IsPBS: strings.Contains(strings.ToLower(t.Target), "pbs")}
|
||||
if tv.IsPBS {
|
||||
tv.Label = "Biztonsági szerver – külön hardver (PBS)"
|
||||
} else {
|
||||
tv.Label = "Helyi tároló (" + t.Target + ")"
|
||||
}
|
||||
tv.NotSetUp = t.Storage == "absent"
|
||||
if t.LastSuccess != nil {
|
||||
if ts, err := time.Parse(time.RFC3339, t.LastSuccess.StartedAt); err == nil {
|
||||
tv.HasSuccess, tv.SuccessAt = true, ts
|
||||
tv.SizeKnown = t.LastSuccessSource != "storage"
|
||||
tv.SizeBytes = t.LastSuccess.SizeBytes
|
||||
tv.Current = now.Sub(ts) <= tierWindow(cadence[t.Target])
|
||||
}
|
||||
}
|
||||
if a := t.LastAttempt; a != nil && !a.Success {
|
||||
if ts, err := time.Parse(time.RFC3339, a.StartedAt); err == nil && (!tv.HasSuccess || ts.After(tv.SuccessAt)) {
|
||||
tv.FailedAfter, tv.FailedAt = true, ts
|
||||
}
|
||||
}
|
||||
if !tv.NotSetUp && !tv.Current {
|
||||
v.Due = true
|
||||
}
|
||||
if tv.IsPBS && tv.Current && !tv.NotSetUp {
|
||||
v.Offsite = true
|
||||
}
|
||||
if tv.HasSuccess && (t.Primary || !v.HasBackup) && (t.Primary || tv.SuccessAt.After(newestOK)) {
|
||||
v.HasBackup, v.Success = true, true
|
||||
v.StartedAt, v.SizeBytes, v.Target = tv.SuccessAt, tv.SizeBytes, tv.Label
|
||||
newestOK = tv.SuccessAt
|
||||
}
|
||||
v.Tiers = append(v.Tiers, tv)
|
||||
}
|
||||
if v.Due {
|
||||
v.DueReason = "tier_not_current"
|
||||
}
|
||||
if v.HasBackup {
|
||||
v.AgeHours = int64(now.Sub(v.StartedAt).Hours())
|
||||
}
|
||||
}
|
||||
|
||||
// loadGuestBackup fetches the agent's whole-guest backup view (best-effort). Returns a view with
|
||||
@@ -153,6 +232,17 @@ func (s *Server) loadGuestBackup(ctx context.Context) *guestBackupView {
|
||||
v.AgeHours = *due.AgeSecs / 3600
|
||||
}
|
||||
}
|
||||
// R-517: an agent that speaks per tier replaces the single-record fields with per-tier truth.
|
||||
// Done AFTER the legacy fill so an older agent keeps exactly the old rendering.
|
||||
if len(st.Tiers) > 0 {
|
||||
cadence := map[string]int64{}
|
||||
if tr, terr := client.BackupTiers(ctx); terr == nil {
|
||||
for _, t := range tr.Tiers {
|
||||
cadence[t.Target] = t.CadenceSeconds
|
||||
}
|
||||
}
|
||||
buildTierViews(v, st.Tiers, cadence, time.Now())
|
||||
}
|
||||
// Restore-test (the "verified restorable" trust signal; nil until one runs).
|
||||
if rt, rerr := client.RestoreTestStatus(ctx); rerr == nil && rt != nil {
|
||||
v.HasRestoreTest = true
|
||||
|
||||
Reference in New Issue
Block a user