feat(settings): edit a saved sync target (fix URL/username/options)

Each webdav target gets an Edit button that loads it into the form; save routes
to update_sync_target. Password left blank keeps the stored one; kind is locked
during edit. Fixes not being able to correct a mistyped Server URL.
This commit is contained in:
iamdoubz committed 2026-07-06 10:41:02 -05:00
1 parent 496a3372c4
commit fb790091fd
1 file changed
+62 -15
+62 -15
View File
@@ -8,7 +8,7 @@
import ConsentNotice from "../components/ConsentNotice.svelte";
import { open } from "@tauri-apps/plugin-dialog";
import { api, errorMessage, events } from "../api";
import type { BackendId, SyncKind, SyncTargetConfig } from "../api";
import type { BackendId, SyncKind, SyncTargetConfig, SyncTargetInfo } from "../api";
import { trapFocus } from "../actions/trapFocus";
import {
X,
@@ -343,6 +343,8 @@
// ---- Add-target form ----
const PROVIDERS = ["nextcloud", "owncloud", "cloudreve", "seafile", "synology", "generic"];
let form = $state<SyncTargetConfig>(blankForm("webdav"));
// When set, the form is editing an existing target rather than adding one.
let editingId = $state<string | null>(null);
function blankForm(kind: SyncKind): SyncTargetConfig {
return {
@@ -371,11 +373,46 @@
const r = await settings.test(form);
testResult = { ok: r.ok, message: r.message };
}
async function addTarget() {
await settings.addTarget(form);
form = blankForm(form.kind);
// Load an existing target into the form for editing. The secret is never
// returned to the UI (FR-SYNC-6), so it stays blank — left blank on save it
// keeps the stored password; typed, it rotates. No `id` on the form itself, so
// "Test connection" checks the typed values (needs the password re-entered).
function startEdit(t: SyncTargetInfo) {
editingId = t.id;
form = {
name: t.name,
kind: t.kind,
provider_hint: t.provider_hint ?? undefined,
base_url: t.base_url ?? "",
remote_base_path: t.remote_base_path,
username: t.username ?? "",
secret: "",
enabled: t.enabled,
upload_transcript: t.upload_transcript,
upload_notes: t.upload_notes,
upload_summary: t.upload_summary,
upload_recording: t.upload_recording,
trigger_on_finalize: t.trigger_on_finalize,
allow_plaintext_lan: t.allow_plaintext_lan,
encrypt_before_upload: t.encrypt_before_upload,
};
testResult = null;
}
function cancelEdit() {
editingId = null;
form = blankForm("webdav");
testResult = null;
}
async function saveTarget() {
if (editingId) {
await settings.updateTarget({ ...form, id: editingId });
cancelEdit();
} else {
await settings.addTarget(form);
form = blankForm(form.kind);
testResult = null;
}
}
function linkOAuth() {
// begin_oauth_link → loopback PKCE flow; the target is created in the
// background on success (sync://linked), which the store handles.
@@ -711,6 +748,9 @@
>{t.third_party ? "third-party" : "your server"}</span
>
<span class="host">{t.host ?? t.kind}</span>
{#if !t.third_party}
<button class="link" onclick={() => startEdit(t)}>Edit</button>
{/if}
<button class="link danger" onclick={() => settings.removeTarget(t.id)}
>Remove</button
>
@@ -719,14 +759,16 @@
</ul>
{/if}
<h4>Add a target</h4>
<div class="kinds">
{#each ["webdav", "onedrive", "dropbox", "box"] as k (k)}
<button class:active={form.kind === k} onclick={() => setKind(k as SyncKind)}
>{k}</button
>
{/each}
</div>
<h4>{editingId ? "Edit target" : "Add a target"}</h4>
{#if !editingId}
<div class="kinds">
{#each ["webdav", "onedrive", "dropbox", "box"] as k (k)}
<button class:active={form.kind === k} onclick={() => setKind(k as SyncKind)}
>{k}</button
>
{/each}
</div>
{/if}
{#if form.kind === "webdav"}
<p class="muted">
@@ -753,7 +795,9 @@
>App password<input
type="password"
bind:value={form.secret}
placeholder="stored in OS credential store"
placeholder={editingId
? "leave blank to keep current password"
: "stored in OS credential store"}
/></label
>
</div>
@@ -782,9 +826,12 @@
</label>
<div class="actions">
<button onclick={testConnection}>Test connection</button>
<button class="primary" onclick={addTarget} disabled={!form.name || !form.base_url}
>Add target</button
<button class="primary" onclick={saveTarget} disabled={!form.name || !form.base_url}
>{editingId ? "Save changes" : "Add target"}</button
>
{#if editingId}
<button class="link" onclick={cancelEdit}>Cancel</button>
{/if}
{#if testResult}
<span class="test" class:ok={testResult.ok} class:fail={!testResult.ok}>
{#if testResult.ok}<Check size={14} aria-hidden="true" />{:else}<X