Add saved remote selectors to sync menu
This commit is contained in:
@@ -275,6 +275,7 @@ type ui struct {
|
||||
toggleSyncMenu widget.Clickable
|
||||
toggleMainMenu widget.Clickable
|
||||
openAdvancedSync widget.Clickable
|
||||
openSelectedVaultRemote widget.Clickable
|
||||
openSecuritySettings widget.Clickable
|
||||
openRemotePrefsHelp widget.Clickable
|
||||
closeAdvancedSync widget.Clickable
|
||||
@@ -378,6 +379,8 @@ type ui struct {
|
||||
groupClicks []widget.Clickable
|
||||
recentVaultClicks []widget.Clickable
|
||||
recentRemoteClicks []widget.Clickable
|
||||
vaultRemoteProfileClicks []widget.Clickable
|
||||
vaultRemoteCredentialClicks []widget.Clickable
|
||||
removeCustomFields []widget.Clickable
|
||||
state appstate.State
|
||||
visible []entry
|
||||
@@ -1936,6 +1939,41 @@ func (u *ui) availableRemoteCredentialEntries() []vault.Entry {
|
||||
return entries
|
||||
}
|
||||
|
||||
func (u *ui) selectVaultRemoteProfile(id string) {
|
||||
id = strings.TrimSpace(id)
|
||||
u.selectedVaultRemoteProfileID = id
|
||||
for _, profile := range u.availableRemoteProfiles() {
|
||||
if profile.ID != id {
|
||||
continue
|
||||
}
|
||||
u.remoteBaseURL.SetText(profile.BaseURL)
|
||||
u.remotePath.SetText(profile.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (u *ui) selectVaultRemoteCredentialEntry(id string) {
|
||||
u.selectedVaultRemoteCredentialEntryID = strings.TrimSpace(id)
|
||||
}
|
||||
|
||||
func (u *ui) selectedVaultRemoteProfile() (vault.RemoteProfile, bool) {
|
||||
for _, profile := range u.availableRemoteProfiles() {
|
||||
if profile.ID == strings.TrimSpace(u.selectedVaultRemoteProfileID) {
|
||||
return profile, true
|
||||
}
|
||||
}
|
||||
return vault.RemoteProfile{}, false
|
||||
}
|
||||
|
||||
func (u *ui) selectedVaultRemoteCredentialEntry() (vault.Entry, bool) {
|
||||
for _, entry := range u.availableRemoteCredentialEntries() {
|
||||
if entry.ID == strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) {
|
||||
return entry, true
|
||||
}
|
||||
}
|
||||
return vault.Entry{}, false
|
||||
}
|
||||
|
||||
func (u *ui) selectedVaultRemoteBinding() (appstate.RemoteBinding, bool) {
|
||||
if strings.TrimSpace(u.selectedVaultRemoteProfileID) == "" || strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) == "" {
|
||||
return appstate.RemoteBinding{}, false
|
||||
@@ -3281,6 +3319,28 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := range u.vaultRemoteProfileClicks {
|
||||
for u.vaultRemoteProfileClicks[i].Clicked(gtx) {
|
||||
profiles := u.availableRemoteProfiles()
|
||||
if i < len(profiles) {
|
||||
u.selectVaultRemoteProfile(profiles[i].ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := range u.vaultRemoteCredentialClicks {
|
||||
for u.vaultRemoteCredentialClicks[i].Clicked(gtx) {
|
||||
entries := u.availableRemoteCredentialEntries()
|
||||
if i < len(entries) {
|
||||
u.selectVaultRemoteCredentialEntry(entries[i].ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
for u.openSelectedVaultRemote.Clicked(gtx) {
|
||||
if u.lifecycleBusy() {
|
||||
continue
|
||||
}
|
||||
u.startOpenRemoteAction()
|
||||
}
|
||||
for u.clearVaultSelection.Clicked(gtx) {
|
||||
if u.lifecycleBusy() {
|
||||
continue
|
||||
@@ -4389,8 +4449,16 @@ func (u *ui) syncMenuToggle(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
|
||||
func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
||||
profiles := u.availableRemoteProfiles()
|
||||
credentials := u.availableRemoteCredentialEntries()
|
||||
if len(u.vaultRemoteProfileClicks) < len(profiles) {
|
||||
u.vaultRemoteProfileClicks = make([]widget.Clickable, len(profiles))
|
||||
}
|
||||
if len(u.vaultRemoteCredentialClicks) < len(credentials) {
|
||||
u.vaultRemoteCredentialClicks = make([]widget.Clickable, len(credentials))
|
||||
}
|
||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
rows := []layout.FlexChild{
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), "Need another source or direction?")
|
||||
lbl.Color = mutedColor
|
||||
@@ -4400,7 +4468,70 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.openAdvancedSync, "Open Advanced Sync")
|
||||
}),
|
||||
)
|
||||
}
|
||||
if u.hasOpenVault() && len(profiles) > 0 && len(credentials) > 0 {
|
||||
rows = append(rows,
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), "Use a saved remote profile from this vault")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
)
|
||||
for i, profile := range profiles {
|
||||
i := i
|
||||
profile := profile
|
||||
rows = append(rows,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
selected := strings.TrimSpace(u.selectedVaultRemoteProfileID) == profile.ID
|
||||
return layout.Inset{Bottom: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions {
|
||||
return u.vaultRemoteProfileClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(13), profile.Name)
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
})
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
rows = append(rows, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||
for i, entry := range credentials {
|
||||
i := i
|
||||
entry := entry
|
||||
rows = append(rows,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
selected := strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) == entry.ID
|
||||
label := entry.Title
|
||||
if strings.TrimSpace(entry.Username) != "" {
|
||||
label += " · " + strings.TrimSpace(entry.Username)
|
||||
}
|
||||
return layout.Inset{Bottom: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions {
|
||||
return u.vaultRemoteCredentialClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(13), label)
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
})
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
if _, ok := u.selectedVaultRemoteProfile(); ok {
|
||||
if _, ok := u.selectedVaultRemoteCredentialEntry(); ok {
|
||||
rows = append(rows,
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, "Open Saved Remote")
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, rows...)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4698,6 +4698,51 @@ func TestUIAvailableRemoteProfilesReturnsEmptyWhenLocked(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUISelectVaultRemoteProfileUpdatesSelectionAndTargetFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithModel("desktop", vault.Model{
|
||||
RemoteProfiles: []vault.RemoteProfile{{
|
||||
ID: "family-webdav",
|
||||
Name: "Family Vault",
|
||||
Backend: vault.RemoteBackendWebDAV,
|
||||
BaseURL: "https://dav.example.invalid/remote.php/dav",
|
||||
Path: "files/family/keepass.kdbx",
|
||||
}},
|
||||
})
|
||||
|
||||
u.selectVaultRemoteProfile("family-webdav")
|
||||
|
||||
if got := u.selectedVaultRemoteProfileID; got != "family-webdav" {
|
||||
t.Fatalf("selectedVaultRemoteProfileID = %q, want family-webdav", got)
|
||||
}
|
||||
if got := u.remoteBaseURL.Text(); got != "https://dav.example.invalid/remote.php/dav" {
|
||||
t.Fatalf("remoteBaseURL = %q, want resolved profile base URL", got)
|
||||
}
|
||||
if got := u.remotePath.Text(); got != "files/family/keepass.kdbx" {
|
||||
t.Fatalf("remotePath = %q, want resolved profile path", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUISelectVaultRemoteCredentialEntryUpdatesSelection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithModel("desktop", vault.Model{
|
||||
Entries: []vault.Entry{{
|
||||
ID: "remote-creds-1",
|
||||
Title: "WebDAV Sign-In",
|
||||
Username: "tjulian",
|
||||
Path: []string{"Crew", "Internet"},
|
||||
}},
|
||||
})
|
||||
|
||||
u.selectVaultRemoteCredentialEntry("remote-creds-1")
|
||||
|
||||
if got := u.selectedVaultRemoteCredentialEntryID; got != "remote-creds-1" {
|
||||
t.Fatalf("selectedVaultRemoteCredentialEntryID = %q, want remote-creds-1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchToLifecycleSelectionResetsLockedLocalSession(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user