Match Advanced Sync credentials by URL

This commit is contained in:
Joe Julian
2026-04-06 18:13:30 -07:00
parent 8866d9c06f
commit fc59fb4869
4 changed files with 133 additions and 1 deletions
+79 -1
View File
@@ -395,6 +395,7 @@ type ui struct {
recentRemoteClicks []widget.Clickable
vaultRemoteProfileClicks []widget.Clickable
vaultRemoteCredentialClicks []widget.Clickable
syncRemoteCredentialClicks []widget.Clickable
removeCustomFields []widget.Clickable
state appstate.State
visible []entry
@@ -444,6 +445,7 @@ type ui struct {
syncRemotePath widget.Editor
syncRemoteUsername widget.Editor
syncRemotePassword widget.Editor
selectedSyncRemoteCredentialEntryID string
syncDialogOpen bool
syncMenuOpen bool
mainMenuOpen bool
@@ -2177,6 +2179,36 @@ func (u *ui) availableRemoteCredentialEntries() []vault.Entry {
return entries
}
func normalizeRemoteCredentialURL(raw string) string {
raw = strings.TrimSpace(raw)
raw = strings.TrimRight(raw, "/")
return raw
}
func (u *ui) matchingAdvancedSyncRemoteCredentialEntries() []vault.Entry {
if sanitizeSyncSourceMode(u.syncSourceMode) != syncSourceRemote {
return nil
}
baseURL := normalizeRemoteCredentialURL(u.syncRemoteBaseURL.Text())
if baseURL == "" {
return nil
}
var matches []vault.Entry
for _, entry := range u.availableRemoteCredentialEntries() {
if normalizeRemoteCredentialURL(entry.URL) != baseURL {
continue
}
matches = append(matches, entry)
}
return matches
}
func (u *ui) applyAdvancedSyncRemoteCredentialEntry(entry vault.Entry) {
u.selectedSyncRemoteCredentialEntryID = strings.TrimSpace(entry.ID)
u.syncRemoteUsername.SetText(strings.TrimSpace(entry.Username))
u.syncRemotePassword.SetText(entry.Password)
}
func (u *ui) selectVaultRemoteProfile(id string) {
id = strings.TrimSpace(id)
u.selectedVaultRemoteProfileID = id
@@ -4023,6 +4055,14 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
}
}
}
for i := range u.syncRemoteCredentialClicks {
for u.syncRemoteCredentialClicks[i].Clicked(gtx) {
entries := u.matchingAdvancedSyncRemoteCredentialEntries()
if i < len(entries) {
u.applyAdvancedSyncRemoteCredentialEntry(entries[i])
}
}
}
for u.openSelectedVaultRemote.Clicked(gtx) {
if u.lifecycleBusy() {
continue
@@ -4766,6 +4806,10 @@ func (u *ui) approvalDialogContent(gtx layout.Context) layout.Dimensions {
}
func (u *ui) syncDialogContent(gtx layout.Context) layout.Dimensions {
matchingCredentials := u.matchingAdvancedSyncRemoteCredentialEntries()
if len(u.syncRemoteCredentialClicks) < len(matchingCredentials) {
u.syncRemoteCredentialClicks = make([]widget.Clickable, len(matchingCredentials))
}
return material.List(u.theme, &u.lifecycleList).Layout(gtx, 1, func(gtx layout.Context, _ int) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
@@ -4814,7 +4858,7 @@ func (u *ui) syncDialogContent(gtx layout.Context) layout.Dimensions {
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.syncSourceMode == syncSourceRemote {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
children := []layout.FlexChild{
layout.Rigid(labeledEditorHelp(u.theme, "Remote Base URL", "WebDAV base URL for the other source.", &u.syncRemoteBaseURL, false)),
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
layout.Rigid(labeledEditorHelp(u.theme, "Remote Path", "Path to the other remote .kdbx file.", &u.syncRemotePath, false)),
@@ -4824,6 +4868,40 @@ func (u *ui) syncDialogContent(gtx layout.Context) layout.Dimensions {
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.syncPasswordField(gtx)
}),
}
if len(matchingCredentials) > 0 {
children = append(children,
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(11), "Matching vault credentials")
lbl.Color = mutedColor
return lbl.Layout(gtx)
}),
)
for i, entry := range matchingCredentials {
i := i
entry := entry
children = append(children,
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
label := entry.Title
if strings.TrimSpace(entry.Username) != "" {
label += " · " + strings.TrimSpace(entry.Username)
}
selected := strings.TrimSpace(u.selectedSyncRemoteCredentialEntryID) == entry.ID
return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions {
return u.syncRemoteCredentialClicks[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)
})
})
}),
)
}
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
children...,
)
}
if supportsDesktopFilePicker(runtime.GOOS) {