Add saved remote selectors to sync menu
This commit is contained in:
@@ -275,6 +275,7 @@ type ui struct {
|
|||||||
toggleSyncMenu widget.Clickable
|
toggleSyncMenu widget.Clickable
|
||||||
toggleMainMenu widget.Clickable
|
toggleMainMenu widget.Clickable
|
||||||
openAdvancedSync widget.Clickable
|
openAdvancedSync widget.Clickable
|
||||||
|
openSelectedVaultRemote widget.Clickable
|
||||||
openSecuritySettings widget.Clickable
|
openSecuritySettings widget.Clickable
|
||||||
openRemotePrefsHelp widget.Clickable
|
openRemotePrefsHelp widget.Clickable
|
||||||
closeAdvancedSync widget.Clickable
|
closeAdvancedSync widget.Clickable
|
||||||
@@ -378,6 +379,8 @@ type ui struct {
|
|||||||
groupClicks []widget.Clickable
|
groupClicks []widget.Clickable
|
||||||
recentVaultClicks []widget.Clickable
|
recentVaultClicks []widget.Clickable
|
||||||
recentRemoteClicks []widget.Clickable
|
recentRemoteClicks []widget.Clickable
|
||||||
|
vaultRemoteProfileClicks []widget.Clickable
|
||||||
|
vaultRemoteCredentialClicks []widget.Clickable
|
||||||
removeCustomFields []widget.Clickable
|
removeCustomFields []widget.Clickable
|
||||||
state appstate.State
|
state appstate.State
|
||||||
visible []entry
|
visible []entry
|
||||||
@@ -1936,6 +1939,41 @@ func (u *ui) availableRemoteCredentialEntries() []vault.Entry {
|
|||||||
return entries
|
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) {
|
func (u *ui) selectedVaultRemoteBinding() (appstate.RemoteBinding, bool) {
|
||||||
if strings.TrimSpace(u.selectedVaultRemoteProfileID) == "" || strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) == "" {
|
if strings.TrimSpace(u.selectedVaultRemoteProfileID) == "" || strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) == "" {
|
||||||
return appstate.RemoteBinding{}, false
|
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) {
|
for u.clearVaultSelection.Clicked(gtx) {
|
||||||
if u.lifecycleBusy() {
|
if u.lifecycleBusy() {
|
||||||
continue
|
continue
|
||||||
@@ -4389,8 +4449,16 @@ func (u *ui) syncMenuToggle(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) syncMenu(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 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 {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
lbl := material.Label(u.theme, unit.Sp(11), "Need another source or direction?")
|
lbl := material.Label(u.theme, unit.Sp(11), "Need another source or direction?")
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
@@ -4400,7 +4468,70 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.openAdvancedSync, "Open Advanced Sync")
|
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) {
|
func TestSwitchToLifecycleSelectionResetsLockedLocalSession(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user