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:
1 parent
496a3372c4
commit
fb790091fd
1 file changed
+62
-15
@@ -8,7 +8,7 @@
|
|||||||
import ConsentNotice from "../components/ConsentNotice.svelte";
|
import ConsentNotice from "../components/ConsentNotice.svelte";
|
||||||
import { open } from "@tauri-apps/plugin-dialog";
|
import { open } from "@tauri-apps/plugin-dialog";
|
||||||
import { api, errorMessage, events } from "../api";
|
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 { trapFocus } from "../actions/trapFocus";
|
||||||
import {
|
import {
|
||||||
X,
|
X,
|
||||||
@@ -343,6 +343,8 @@
|
|||||||
// ---- Add-target form ----
|
// ---- Add-target form ----
|
||||||
const PROVIDERS = ["nextcloud", "owncloud", "cloudreve", "seafile", "synology", "generic"];
|
const PROVIDERS = ["nextcloud", "owncloud", "cloudreve", "seafile", "synology", "generic"];
|
||||||
let form = $state<SyncTargetConfig>(blankForm("webdav"));
|
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 {
|
function blankForm(kind: SyncKind): SyncTargetConfig {
|
||||||
return {
|
return {
|
||||||
@@ -371,11 +373,46 @@
|
|||||||
const r = await settings.test(form);
|
const r = await settings.test(form);
|
||||||
testResult = { ok: r.ok, message: r.message };
|
testResult = { ok: r.ok, message: r.message };
|
||||||
}
|
}
|
||||||
async function addTarget() {
|
// Load an existing target into the form for editing. The secret is never
|
||||||
await settings.addTarget(form);
|
// returned to the UI (FR-SYNC-6), so it stays blank — left blank on save it
|
||||||
form = blankForm(form.kind);
|
// 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;
|
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() {
|
function linkOAuth() {
|
||||||
// begin_oauth_link → loopback PKCE flow; the target is created in the
|
// begin_oauth_link → loopback PKCE flow; the target is created in the
|
||||||
// background on success (sync://linked), which the store handles.
|
// background on success (sync://linked), which the store handles.
|
||||||
@@ -711,6 +748,9 @@
|
|||||||
>{t.third_party ? "third-party" : "your server"}</span
|
>{t.third_party ? "third-party" : "your server"}</span
|
||||||
>
|
>
|
||||||
<span class="host">{t.host ?? t.kind}</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)}
|
<button class="link danger" onclick={() => settings.removeTarget(t.id)}
|
||||||
>Remove</button
|
>Remove</button
|
||||||
>
|
>
|
||||||
@@ -719,14 +759,16 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<h4>Add a target</h4>
|
<h4>{editingId ? "Edit target" : "Add a target"}</h4>
|
||||||
<div class="kinds">
|
{#if !editingId}
|
||||||
{#each ["webdav", "onedrive", "dropbox", "box"] as k (k)}
|
<div class="kinds">
|
||||||
<button class:active={form.kind === k} onclick={() => setKind(k as SyncKind)}
|
{#each ["webdav", "onedrive", "dropbox", "box"] as k (k)}
|
||||||
>{k}</button
|
<button class:active={form.kind === k} onclick={() => setKind(k as SyncKind)}
|
||||||
>
|
>{k}</button
|
||||||
{/each}
|
>
|
||||||
</div>
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if form.kind === "webdav"}
|
{#if form.kind === "webdav"}
|
||||||
<p class="muted">
|
<p class="muted">
|
||||||
@@ -753,7 +795,9 @@
|
|||||||
>App password<input
|
>App password<input
|
||||||
type="password"
|
type="password"
|
||||||
bind:value={form.secret}
|
bind:value={form.secret}
|
||||||
placeholder="stored in OS credential store"
|
placeholder={editingId
|
||||||
|
? "leave blank to keep current password"
|
||||||
|
: "stored in OS credential store"}
|
||||||
/></label
|
/></label
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -782,9 +826,12 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button onclick={testConnection}>Test connection</button>
|
<button onclick={testConnection}>Test connection</button>
|
||||||
<button class="primary" onclick={addTarget} disabled={!form.name || !form.base_url}
|
<button class="primary" onclick={saveTarget} disabled={!form.name || !form.base_url}
|
||||||
>Add target</button
|
>{editingId ? "Save changes" : "Add target"}</button
|
||||||
>
|
>
|
||||||
|
{#if editingId}
|
||||||
|
<button class="link" onclick={cancelEdit}>Cancel</button>
|
||||||
|
{/if}
|
||||||
{#if testResult}
|
{#if testResult}
|
||||||
<span class="test" class:ok={testResult.ok} class:fail={!testResult.ok}>
|
<span class="test" class:ok={testResult.ok} class:fail={!testResult.ok}>
|
||||||
{#if testResult.ok}<Check size={14} aria-hidden="true" />{:else}<X
|
{#if testResult.ok}<Check size={14} aria-hidden="true" />{:else}<X
|
||||||
|
|||||||
Reference in new issue
Block a user