feat: l for details; details for vaults, scopes and files; getting started; the age of cached lists
#17
No files matched your search
+3
-2
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user