Add sync mode choice to remote setup

This commit is contained in:
Joe Julian
2026-04-06 20:41:21 -07:00
parent 94ee7fd4c0
commit 554d4f760d
2 changed files with 84 additions and 1 deletions
+23 -1
View File
@@ -380,6 +380,7 @@ type ui struct {
cancelLifecycleProgress widget.Clickable cancelLifecycleProgress widget.Clickable
retryLifecycleOpen widget.Clickable retryLifecycleOpen widget.Clickable
approvalPermanent widget.Bool approvalPermanent widget.Bool
syncSetupAutomatic widget.Bool
apiPolicyAllow widget.Bool apiPolicyAllow widget.Bool
apiPolicyGroupScopeW widget.Bool apiPolicyGroupScopeW widget.Bool
apiTokenDisabled widget.Bool apiTokenDisabled widget.Bool
@@ -1387,11 +1388,15 @@ func (u *ui) openRemoteSyncSetupDialog() {
u.syncDialogPurpose = syncDialogPurposeRemoteSetup u.syncDialogPurpose = syncDialogPurposeRemoteSetup
u.syncSourceMode = syncSourceRemote u.syncSourceMode = syncSourceRemote
u.syncDirection = syncDirectionPush u.syncDirection = syncDirectionPush
u.syncSetupAutomatic.Value = true
if strings.TrimSpace(u.syncLocalPath.Text()) == "" { if strings.TrimSpace(u.syncLocalPath.Text()) == "" {
u.syncLocalPath.SetText(strings.TrimSpace(u.vaultPath.Text())) u.syncLocalPath.SetText(strings.TrimSpace(u.vaultPath.Text()))
} }
u.syncSavedRemoteBindingSelection() u.syncSavedRemoteBindingSelection()
u.prefillAdvancedSyncRemoteFromSavedBinding() u.prefillAdvancedSyncRemoteFromSavedBinding()
if _, ok := u.selectedVaultRemoteBinding(); ok && u.selectedVaultRemoteSyncMode == appstate.SyncModeManual {
u.syncSetupAutomatic.Value = false
}
} }
func (u *ui) clearSyncLocalImport() { func (u *ui) clearSyncLocalImport() {
@@ -1576,7 +1581,7 @@ func (u *ui) persistSyncDialogRemoteBinding(baseURL, remotePath string) error {
Username: strings.TrimSpace(u.syncRemoteUsername.Text()), Username: strings.TrimSpace(u.syncRemoteUsername.Text()),
Password: u.syncRemotePassword.Text(), Password: u.syncRemotePassword.Text(),
CredentialPath: append([]string(nil), u.currentPath...), CredentialPath: append([]string(nil), u.currentPath...),
SyncMode: u.newRemoteBindingSyncMode(), SyncMode: u.syncSetupMode(),
} }
binding, err := u.state.ConfigureRemoteBinding(input) binding, err := u.state.ConfigureRemoteBinding(input)
if err != nil { if err != nil {
@@ -2380,6 +2385,13 @@ func (u *ui) shouldShowSyncSourceChoices() bool {
return u.syncDialogPurpose != syncDialogPurposeRemoteSetup return u.syncDialogPurpose != syncDialogPurposeRemoteSetup
} }
func (u *ui) syncSetupMode() appstate.SyncMode {
if u.syncSetupAutomatic.Value {
return appstate.SyncModeAutomaticOnOpenSave
}
return appstate.SyncModeManual
}
func (u *ui) selectVaultRemoteProfile(id string) { func (u *ui) selectVaultRemoteProfile(id string) {
id = strings.TrimSpace(id) id = strings.TrimSpace(id)
u.selectedVaultRemoteProfileID = id u.selectedVaultRemoteProfileID = id
@@ -5079,6 +5091,16 @@ func (u *ui) syncDialogContent(gtx layout.Context) layout.Dimensions {
return u.syncPasswordField(gtx) return u.syncPasswordField(gtx)
}), }),
} }
if u.syncDialogPurpose == syncDialogPurposeRemoteSetup {
children = append(children,
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
check := material.CheckBox(u.theme, &u.syncSetupAutomatic, "Sync automatically on open and save")
check.Color = accentColor
return check.Layout(gtx)
}),
)
}
if len(matchingCredentials) > 0 { if len(matchingCredentials) > 0 {
children = append(children, children = append(children,
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout), layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
+61
View File
@@ -5942,6 +5942,7 @@ func TestUISyncDialogUsesRemoteSetupCopy(t *testing.T) {
u := newUIWithSession("desktop", &session.Manager{}) u := newUIWithSession("desktop", &session.Manager{})
u.syncDialogPurpose = syncDialogPurposeRemoteSetup u.syncDialogPurpose = syncDialogPurposeRemoteSetup
u.syncSetupAutomatic.Value = true
if got := u.syncDialogTitle(); got != "Set Up Remote Sync" { if got := u.syncDialogTitle(); got != "Set Up Remote Sync" {
t.Fatalf("syncDialogTitle() = %q, want Set Up Remote Sync", got) t.Fatalf("syncDialogTitle() = %q, want Set Up Remote Sync", got)
@@ -5961,6 +5962,9 @@ func TestUISyncDialogUsesRemoteSetupCopy(t *testing.T) {
if got := syncDialogSummaryText(syncDialogPurposeRemoteSetup, syncSourceRemote, syncDirectionPush); got != "Push this local vault to a WebDAV target and save that target for future sync." { if got := syncDialogSummaryText(syncDialogPurposeRemoteSetup, syncSourceRemote, syncDirectionPush); got != "Push this local vault to a WebDAV target and save that target for future sync." {
t.Fatalf("syncDialogSummaryText(remote setup) = %q, want setup-specific summary", got) t.Fatalf("syncDialogSummaryText(remote setup) = %q, want setup-specific summary", got)
} }
if got := u.syncSetupMode(); got != appstate.SyncModeAutomaticOnOpenSave {
t.Fatalf("syncSetupMode() = %q, want automatic_on_open_save", got)
}
} }
func TestUISyncDialogUsesAdvancedCopy(t *testing.T) { func TestUISyncDialogUsesAdvancedCopy(t *testing.T) {
@@ -6056,6 +6060,9 @@ func TestUIRemoteSyncSetupPersistsBindingAfterSuccessfulPush(t *testing.T) {
if got := u.recentRemotes[0].LocalVaultPath; got != currentPath { if got := u.recentRemotes[0].LocalVaultPath; got != currentPath {
t.Fatalf("recentRemotes[0].LocalVaultPath = %q, want %q", got, currentPath) t.Fatalf("recentRemotes[0].LocalVaultPath = %q, want %q", got, currentPath)
} }
if got := u.selectedVaultRemoteSyncMode; got != appstate.SyncModeAutomaticOnOpenSave {
t.Fatalf("selectedVaultRemoteSyncMode = %q, want automatic_on_open_save", got)
}
if got := u.state.StatusMessage; got != "Remote sync is set up for this vault." { if got := u.state.StatusMessage; got != "Remote sync is set up for this vault." {
t.Fatalf("StatusMessage = %q, want setup success message", got) t.Fatalf("StatusMessage = %q, want setup success message", got)
} }
@@ -6090,6 +6097,60 @@ func TestUIRemoteSyncSetupPersistsBindingAfterSuccessfulPush(t *testing.T) {
} }
} }
func TestUIRemoteSyncSetupCanPersistManualSyncMode(t *testing.T) {
t.Parallel()
key := vault.MasterKey{Password: "correct horse battery staple"}
currentPath := filepath.Join(t.TempDir(), "current.kdbx")
writeKDBXMainTestFile(t, currentPath, vault.Model{
Entries: []vault.Entry{{ID: "entry-current", Title: "Vault Console", Path: []string{"Root", "Internet"}}},
}, key)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
w.WriteHeader(http.StatusNotFound)
case http.MethodPut:
w.Header().Set("ETag", "\"v1\"")
w.WriteHeader(http.StatusNoContent)
default:
t.Fatalf("unexpected method %s", r.Method)
}
}))
defer server.Close()
dir := t.TempDir()
u := newUIWithSession("desktop", &session.Manager{}, statePaths{
DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"),
RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"),
RecentRemotesPath: filepath.Join(dir, "recent-remotes.json"),
UIPreferencesPath: filepath.Join(dir, "ui-prefs.json"),
})
u.masterPassword.SetText(key.Password)
u.vaultPath.SetText(currentPath)
if err := u.openVaultAction(); err != nil {
t.Fatalf("openVaultAction() error = %v", err)
}
u.openRemoteSyncSetupDialog()
u.syncSetupAutomatic.Value = false
u.syncRemoteBaseURL.SetText(server.URL)
u.syncRemotePath.SetText("vaults/manual.kdbx")
u.syncRemoteUsername.SetText("tjulian")
u.syncRemotePassword.SetText("token-1")
if err := u.advancedSyncAction(); err != nil {
t.Fatalf("advancedSyncAction() error = %v", err)
}
if got := u.selectedVaultRemoteSyncMode; got != appstate.SyncModeManual {
t.Fatalf("selectedVaultRemoteSyncMode = %q, want manual", got)
}
if got := u.recentRemotes[0].SyncMode; got != string(appstate.SyncModeManual) {
t.Fatalf("recentRemotes[0].SyncMode = %q, want manual", got)
}
}
func TestUISaveCurrentRemoteBindingActionPersistsBindingIntoVault(t *testing.T) { func TestUISaveCurrentRemoteBindingActionPersistsBindingIntoVault(t *testing.T) {
t.Parallel() t.Parallel()