Remember recent remote vault connections

This commit is contained in:
Joe Julian
2026-03-29 17:05:42 -07:00
parent bf56e38bc5
commit bda28eef4b
3 changed files with 190 additions and 0 deletions
+115
View File
@@ -78,6 +78,7 @@ type attachmentItem struct {
type statePaths struct {
DefaultSaveAsPath string
RecentVaultsPath string
RecentRemotesPath string
}
type recentVaultRecord struct {
@@ -85,6 +86,13 @@ type recentVaultRecord struct {
LastGroup []string `json:"lastGroup,omitempty"`
}
type recentRemoteRecord struct {
BaseURL string `json:"baseUrl"`
Path string `json:"path"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
type ui struct {
mode string
theme *material.Theme
@@ -159,12 +167,14 @@ type ui struct {
showRecycle widget.Clickable
showLocalLifecycle widget.Clickable
showRemoteLifecycle widget.Clickable
rememberRemoteAuth widget.Bool
entryClicks []widget.Clickable
historyClicks []widget.Clickable
attachmentClicks []widget.Clickable
breadcrumbs []widget.Clickable
groupClicks []widget.Clickable
recentVaultClicks []widget.Clickable
recentRemoteClicks []widget.Clickable
removeCustomFields []widget.Clickable
state appstate.State
visible []entry
@@ -189,9 +199,11 @@ type ui struct {
keyboardFocus focusID
defaultSaveAsPath string
recentVaultsPath string
recentRemotesPath string
editingEntry bool
groupControlsHidden bool
recentVaults []string
recentRemotes []recentRemoteRecord
recentVaultGroups map[string][]string
deleteGroupPath []string
statusExpiresAt time.Time
@@ -276,6 +288,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
lifecycleMode: "local",
defaultSaveAsPath: paths.DefaultSaveAsPath,
recentVaultsPath: paths.RecentVaultsPath,
recentRemotesPath: paths.RecentRemotesPath,
recentVaultGroups: map[string][]string{},
now: time.Now,
}
@@ -290,6 +303,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
u.keyboardFocus = focusSearch
u.setCustomFieldRows(nil)
u.loadRecentVaults()
u.loadRecentRemotes()
u.filter()
return u
}
@@ -322,6 +336,7 @@ func defaultStatePaths(stateDir string) statePaths {
return statePaths{
DefaultSaveAsPath: filepath.Join(baseDir, "vault.kdbx"),
RecentVaultsPath: filepath.Join(baseDir, "recent-vaults.json"),
RecentRemotesPath: filepath.Join(baseDir, "recent-remotes.json"),
}
}
@@ -551,6 +566,13 @@ func (u *ui) openRemoteAction() error {
if err := u.state.OpenRemoteVault(client, strings.TrimSpace(u.remotePath.Text()), key); err != nil {
return err
}
u.noteRecentRemote(
strings.TrimSpace(u.remoteBaseURL.Text()),
strings.TrimSpace(u.remotePath.Text()),
strings.TrimSpace(u.remoteUsername.Text()),
u.remotePassword.Text(),
u.rememberRemoteAuth.Value,
)
u.enterHiddenVaultRoot()
u.editingEntry = false
u.filter()
@@ -695,6 +717,42 @@ func (u *ui) applyRecentVaultRecords(records []recentVaultRecord) {
}
}
func (u *ui) loadRecentRemotes() {
if strings.TrimSpace(u.recentRemotesPath) == "" {
return
}
content, err := os.ReadFile(u.recentRemotesPath)
if err != nil {
return
}
var records []recentRemoteRecord
if err := json.Unmarshal(content, &records); err != nil {
return
}
filtered := make([]recentRemoteRecord, 0, len(records))
seen := map[string]bool{}
for _, record := range records {
record.BaseURL = strings.TrimSpace(record.BaseURL)
record.Path = strings.TrimSpace(record.Path)
if record.BaseURL == "" || record.Path == "" {
continue
}
key := record.BaseURL + "|" + record.Path
if seen[key] {
continue
}
seen[key] = true
filtered = append(filtered, record)
if len(filtered) == 6 {
break
}
}
u.recentRemotes = filtered
if len(u.recentRemoteClicks) < len(u.recentRemotes) {
u.recentRemoteClicks = make([]widget.Clickable, len(u.recentRemotes))
}
}
func (u *ui) saveRecentVaults() {
if strings.TrimSpace(u.recentVaultsPath) == "" {
return
@@ -716,6 +774,51 @@ func (u *ui) saveRecentVaults() {
_ = os.WriteFile(u.recentVaultsPath, content, 0o600)
}
func (u *ui) saveRecentRemotes() {
if strings.TrimSpace(u.recentRemotesPath) == "" {
return
}
if err := os.MkdirAll(filepath.Dir(u.recentRemotesPath), 0o700); err != nil {
return
}
content, err := json.MarshalIndent(u.recentRemotes, "", " ")
if err != nil {
return
}
_ = os.WriteFile(u.recentRemotesPath, content, 0o600)
}
func (u *ui) noteRecentRemote(baseURL, path, username, password string, rememberAuth bool) {
baseURL = strings.TrimSpace(baseURL)
path = strings.TrimSpace(path)
if baseURL == "" || path == "" {
return
}
record := recentRemoteRecord{
BaseURL: baseURL,
Path: path,
}
if rememberAuth {
record.Username = strings.TrimSpace(username)
record.Password = password
}
next := []recentRemoteRecord{record}
for _, existing := range u.recentRemotes {
if existing.BaseURL == baseURL && existing.Path == path {
continue
}
next = append(next, existing)
if len(next) == 6 {
break
}
}
u.recentRemotes = next
if len(u.recentRemoteClicks) < len(u.recentRemotes) {
u.recentRemoteClicks = make([]widget.Clickable, len(u.recentRemotes))
}
u.saveRecentRemotes()
}
func (u *ui) recentVaultGroup(path string) []string {
if u.recentVaultGroups == nil {
return nil
@@ -1128,6 +1231,18 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
}
}
}
for i := range u.recentRemoteClicks {
for u.recentRemoteClicks[i].Clicked(gtx) {
if i < len(u.recentRemotes) {
record := u.recentRemotes[i]
u.remoteBaseURL.SetText(record.BaseURL)
u.remotePath.SetText(record.Path)
u.remoteUsername.SetText(record.Username)
u.remotePassword.SetText(record.Password)
u.rememberRemoteAuth.Value = strings.TrimSpace(record.Username) != "" || record.Password != ""
}
}
}
for u.addEntry.Clicked(gtx) {
u.state.BeginNewEntry()
u.loadSelectedEntryIntoEditor()