Merge remote-tracking branch 'origin/feat/cache-age' into feat/l-for-details

This commit is contained in:
keb committed 2026-10-11 13:50:46 +02:00
commit 1d7efb135d
4 files changed
+60 -2

No files matched your search

+3 -2
View File
@@ -114,8 +114,9 @@ What lazysecrets has loaded is kept in
you: the Sources lists, vaults and scopes, and secrets' names and metadata you: the Sources lists, vaults and scopes, and secrets' names and metadata
(tags, dates, references), never values. On the next start they show at (tags, dates, references), never values. On the next start they show at
once, and anything older than two minutes reloads in the background when once, and anything older than two minutes reloads in the background when
you go to it, or when Go to searches it. Revealing and copying always you go to it, or when Go to searches it. While it does, the pane's
fetch the value live. bottom border says how old what it shows is (`cached 3h ago`). Revealing
and copying always fetch the value live.
Lists not loaded for 30 days are dropped, and so is everything from an Lists not loaded for 30 days are dropped, and so is everything from an
account you're no longer signed in to. Repos aren't cached: their files account you're no longer signed in to. Repos aren't cached: their files
+24
View File
@@ -456,6 +456,8 @@ func New(providers []provider.Provider, opts Options) Model {
m.loadCacheFile() m.loadCacheFile()
if len(m.sourceResults) > 0 { if len(m.sourceResults) > 0 {
m.rebuildSources() m.rebuildSources()
m.sources.Loading = true // reloading what the file had
m.sources.Age = m.cachedAge(m.oldestSources())
} }
} }
if m.placeDir != "" && opts.Open == nil { if m.placeDir != "" && opts.Open == nil {
@@ -1156,6 +1158,7 @@ func (m *Model) showContainers(src provider.Source, key string) tea.Cmd {
return nil return nil
} }
m.containers.Refresh() m.containers.Refresh()
m.containers.Age = m.cachedAge(e.at)
return m.loadContainers(src, key) return m.loadContainers(src, key)
} }
@@ -1172,6 +1175,7 @@ func (m *Model) showObjects(c provider.Container, key string) tea.Cmd {
return nil return nil
} }
m.secrets.Refresh() m.secrets.Refresh()
m.secrets.Age = m.cachedAge(e.at)
return m.loadObjects(c, m.objType, key) return m.loadObjects(c, m.objType, key)
} }
@@ -1301,6 +1305,26 @@ func (m *Model) detailStale() bool {
return hasSecret || shown != m.containers.SelectedID() return hasSecret || shown != m.containers.SelectedID()
} }
// cachedAge is how old a list loaded at at is, if it's older than
// cacheTTL: the panes say it while it reloads.
func (m Model) cachedAge(at time.Time) string {
if d := m.now().Sub(at); d >= cacheTTL {
return ago(d)
}
return ""
}
// oldestSources is when the oldest of the cached Sources lists loaded.
func (m Model) oldestSources() time.Time {
var oldest time.Time
for _, at := range m.sourcesAt {
if oldest.IsZero() || at.Before(oldest) {
oldest = at
}
}
return oldest
}
func (m *Model) handleSources(msg sourcesMsg) tea.Cmd { func (m *Model) handleSources(msg sourcesMsg) tea.Cmd {
if msg.gen != m.sourcesGen { if msg.gen != m.sourcesGen {
return nil return nil
+26
View File
@@ -39,6 +39,9 @@ func TestCacheFileShowsLastRunAtOnce(t *testing.T) {
// Next run, with a backend too slow to answer during the test: the // Next run, with a backend too slow to answer during the test: the
// lists show from the file, and the cursor is where it was. // lists show from the file, and the cursor is where it was.
d2 := newDriverOpts(t, Options{CacheFile: file}, fake.Azure(time.Minute)) d2 := newDriverOpts(t, Options{CacheFile: file}, fake.Azure(time.Minute))
if strings.Contains(d2.screen(), "cached") {
t.Errorf("a fresh cache said its age:\n%s", d2.screen())
}
d2.keys("2") d2.keys("2")
if _, v, _ := d2.selected(); v != vault { if _, v, _ := d2.selected(); v != vault {
t.Errorf("vaults from the cache: selected %q, want %q\n%s", v, vault, d2.screen()) t.Errorf("vaults from the cache: selected %q, want %q\n%s", v, vault, d2.screen())
@@ -49,6 +52,29 @@ func TestCacheFileShowsLastRunAtOnce(t *testing.T) {
} }
} }
// Lists from an older cache say how old they are while they reload.
func TestCacheFileSaysItsAge(t *testing.T) {
t.Parallel()
file := filepath.Join(t.TempDir(), "cache.json")
d := newDriverOpts(t, Options{CacheFile: file}, fake.Azure(0))
d.keys("3")
at := time.Now().Add(-3 * time.Hour)
for k, e := range d.m.cache.containers {
d.m.cache.containers[k] = entry[[]provider.Container]{e.value, at}
}
for id := range d.m.sourcesAt {
d.m.sourcesAt[id] = at
}
if err := d.m.Persist(); err != nil {
t.Fatal(err)
}
d2 := newDriverOpts(t, Options{CacheFile: file}, fake.Azure(time.Minute))
d2.keys("2")
if n := strings.Count(d2.screen(), "cached 3h ago"); n != 2 {
t.Errorf("Sources and Vaults should say they're from 3h ago (%d do):\n%s", n, d2.screen())
}
}
func TestCacheFileDropsOldAndSignedOut(t *testing.T) { func TestCacheFileDropsOldAndSignedOut(t *testing.T) {
t.Parallel() t.Parallel()
file := filepath.Join(t.TempDir(), "cache.json") file := filepath.Join(t.TempDir(), "cache.json")
+7
View File
@@ -34,6 +34,9 @@ type List[T any] struct {
Empty string // shown when loaded with no items Empty string // shown when loaded with no items
Loading bool Loading bool
Err error Err error
// Age says how old the items are ("2h ago") while they reload, by
// the counter.
Age string
item Item[T] item Item[T]
items []T items []T
@@ -88,6 +91,7 @@ func (l *List[T]) SetError(err error) {
func (l *List[T]) Refresh() { func (l *List[T]) Refresh() {
l.Loading = true l.Loading = true
l.Err = nil l.Err = nil
l.Age = ""
} }
func (l *List[T]) SetLoading() { func (l *List[T]) SetLoading() {
@@ -369,6 +373,9 @@ func (l List[T]) View(st styles.Styles, focused bool, spin string) string {
if len(l.visible) > 0 { if len(l.visible) > 0 {
counter = fmt.Sprintf("%d of %d", l.cursor+1, len(l.visible)) counter = fmt.Sprintf("%d of %d", l.cursor+1, len(l.visible))
} }
if l.Loading && l.Age != "" && len(l.items) > 0 {
counter = "cached " + l.Age + " · " + counter
}
return BoxCounted(st, title, "", counter, body, l.w, l.h, focused) return BoxCounted(st, title, "", counter, body, l.w, l.h, focused)
} }