feat(web): support WebDAV config sync on web
This commit is contained in:
1 parent
8187ef9baf
commit
4d7d4ab74e
14 files changed
+140
-33
No files matched your search
@@ -1575,7 +1575,7 @@ const settingsCategoryNav = computed<{ value: SettingsCategory; label: string }[
|
||||
{ value: "tunnels", label: t("settings.tunnelsTab") },
|
||||
{ value: "shortcuts", label: t("settings.shortcutsTab") },
|
||||
{ value: "snippets", label: t("settings.snippetsTab") },
|
||||
...(isWeb ? [] : [{ value: "sync" as const, label: t("settings.syncTab") }]),
|
||||
{ value: "sync", label: t("settings.syncTab") },
|
||||
{ value: "ai", label: t("settings.aiTab") },
|
||||
{ value: "mcp" as const, label: t("settings.mcpTab") },
|
||||
...(isWeb ? [{ value: "security" as const, label: t("settings.securityTab") }] : []),
|
||||
@@ -5748,7 +5748,7 @@ onUnmounted(() => {
|
||||
|
||||
<section v-else-if="activeSettingsTab === 'sync'" data-settings-search-id="sync" :class="['py-2', settingsSearchTargetClass('sync')]">
|
||||
<Tabs v-model="syncMethodTab" class="w-full">
|
||||
<TabsList class="grid w-full grid-cols-2">
|
||||
<TabsList v-if="!isWeb" class="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="webdav">WebDAV</TabsTrigger>
|
||||
<TabsTrigger value="snippet">GitHub / Gitee</TabsTrigger>
|
||||
</TabsList>
|
||||
@@ -5762,6 +5762,9 @@ onUnmounted(() => {
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.syncWebDavDescription") }}
|
||||
</p>
|
||||
<p v-if="isWeb" class="text-xs text-muted-foreground">
|
||||
{{ t("settings.syncWebDavWebDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
@@ -5974,7 +5977,7 @@ onUnmounted(() => {
|
||||
<div class="space-y-1">
|
||||
<Label for="sync-secrets">{{ t("settings.syncSecrets") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.syncSecretsSharedDescription") }}
|
||||
{{ t(isWeb ? "settings.syncSecretsDescription" : "settings.syncSecretsSharedDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="sync-secrets" v-model="webdavSyncSecrets" />
|
||||
|
||||
@@ -6296,6 +6296,7 @@ export default {
|
||||
snippetsEditTitle: "Edit Snippet",
|
||||
syncWebDavTitle: "WebDAV Sync",
|
||||
syncWebDavDescription: "Upload or restore a DBX snapshot, including saved SQL library entries, from a WebDAV-compatible storage service.",
|
||||
syncWebDavWebDescription: "In Web mode, the DBX server accesses WebDAV without browser CORS restrictions. Saved passwords remain in the current DBX instance.",
|
||||
syncEndpoint: "WebDAV URL",
|
||||
syncUsername: "Username",
|
||||
syncPassword: "Password",
|
||||
@@ -6303,7 +6304,7 @@ export default {
|
||||
syncClearSavedPassword: "Clear saved password",
|
||||
syncRememberWebDavPassword: "Remember WebDAV app password",
|
||||
syncSavedPassword: "(saved)",
|
||||
syncRememberWebDavPasswordDescription: "The password is encrypted and stored on this device only. It is not synced to WebDAV and does not replace the sync password.",
|
||||
syncRememberWebDavPasswordDescription: "The password is encrypted and stored in the current DBX instance only. It is not synced to WebDAV and does not replace the sync password.",
|
||||
syncRemotePath: "Remote snapshot path",
|
||||
syncRemotePathDescription: "DBX will create missing parent folders when uploading.",
|
||||
syncSecretNotice: "By default, DBX syncs connection details and settings only. Database passwords, SSH passwords, proxy passwords, HTTP tunnel tokens, connection strings, and AI API keys stay local.",
|
||||
@@ -6311,7 +6312,7 @@ export default {
|
||||
syncSecretsDescription: "When enabled, DBX encrypts database passwords, SSH passwords, proxy passwords, HTTP tunnel tokens, and AI API keys before uploading them to remote storage.",
|
||||
syncSecretsSharedDescription: "For WebDAV, this password encrypts included credentials. GitHub Gist and Gitee snippets use independent encryption and credential controls.",
|
||||
syncSecretsPassphrase: "Sync password",
|
||||
syncSecretsPassphraseDescription: "This password is only used to encrypt and restore sensitive data. DBX does not save it; enter it again on each device.",
|
||||
syncSecretsPassphraseDescription: "This password is only used to encrypt and restore sensitive data. It is encrypted in the current DBX instance; enter it again in other instances.",
|
||||
syncTest: "Test",
|
||||
syncUpload: "Upload",
|
||||
syncDownload: "Download",
|
||||
|
||||
@@ -6008,6 +6008,7 @@ export default withEnglishFallback({
|
||||
snippetsEditTitle: "Editar fragmento",
|
||||
syncWebDavTitle: "Sincronización WebDAV",
|
||||
syncWebDavDescription: "Sube o restaura una instantánea de DBX, incluida la biblioteca SQL guardada, desde almacenamiento compatible con WebDAV.",
|
||||
syncWebDavWebDescription: "En modo Web, el servidor DBX accede a WebDAV sin las restricciones CORS del navegador. Las contraseñas guardadas permanecen en la instancia DBX actual.",
|
||||
syncEndpoint: "URL WebDAV",
|
||||
syncUsername: "Usuario",
|
||||
syncPassword: "Contraseña",
|
||||
@@ -6015,14 +6016,14 @@ export default withEnglishFallback({
|
||||
syncClearSavedPassword: "Borrar contraseña guardada",
|
||||
syncRememberWebDavPassword: "Recordar contraseña de app WebDAV",
|
||||
syncSavedPassword: "(guardada)",
|
||||
syncRememberWebDavPasswordDescription: "La contraseña se cifra y se guarda solo en este dispositivo. No se sincroniza a WebDAV ni reemplaza la contraseña de sync.",
|
||||
syncRememberWebDavPasswordDescription: "La contraseña se cifra y se guarda solo en la instancia DBX actual. No se sincroniza a WebDAV ni reemplaza la contraseña de sync.",
|
||||
syncRemotePath: "Ruta remota",
|
||||
syncRemotePathDescription: "DBX creará las carpetas padre faltantes al subir.",
|
||||
syncSecretNotice: "De forma predeterminada, DBX solo sincroniza detalles de conexión y ajustes. Las contraseñas de base de datos, SSH, proxy, cadenas de conexión y API keys de IA quedan locales.",
|
||||
syncSecrets: "Sincronizar secretos cifrados",
|
||||
syncSecretsDescription: "Al activarlo, DBX cifra contraseñas de base de datos, contraseñas SSH y API keys de IA antes de subirlas a WebDAV.",
|
||||
syncSecretsPassphrase: "Contraseña de sync",
|
||||
syncSecretsPassphraseDescription: "Esta contraseña solo se usa para cifrar y restaurar datos sensibles. DBX no la guarda; tendrás que ingresarla en cada dispositivo.",
|
||||
syncSecretsPassphraseDescription: "Esta contraseña solo se usa para cifrar y restaurar datos sensibles. Se cifra en la instancia DBX actual; vuelve a ingresarla en otras instancias.",
|
||||
syncTest: "Probar",
|
||||
syncUpload: "Subir",
|
||||
syncDownload: "Descargar",
|
||||
|
||||
@@ -6008,6 +6008,7 @@ export default withEnglishFallback({
|
||||
snippetsEditTitle: "Modifica Snippet",
|
||||
syncWebDavTitle: "Sincronizzazione WebDAV",
|
||||
syncWebDavDescription: "Carica o ripristina uno snapshot di DBX, inclusa la libreria SQL salvata, da un servizio di archiviazione compatibile con WebDAV.",
|
||||
syncWebDavWebDescription: "In modalità Web, il server DBX accede a WebDAV senza le limitazioni CORS del browser. Le password salvate restano nell'istanza DBX corrente.",
|
||||
syncEndpoint: "URL WebDAV",
|
||||
syncUsername: "Nome utente",
|
||||
syncPassword: "Password",
|
||||
@@ -6015,14 +6016,14 @@ export default withEnglishFallback({
|
||||
syncClearSavedPassword: "Cancella password salvata",
|
||||
syncRememberWebDavPassword: "Ricorda la password dell'app WebDAV",
|
||||
syncSavedPassword: "(salvata)",
|
||||
syncRememberWebDavPasswordDescription: "La password viene crittografata e memorizzata solo su questo dispositivo. Non viene sincronizzata su WebDAV e non sostituisce la password di sincronizzazione.",
|
||||
syncRememberWebDavPasswordDescription: "La password viene crittografata e memorizzata solo nell'istanza DBX corrente. Non viene sincronizzata su WebDAV e non sostituisce la password di sincronizzazione.",
|
||||
syncRemotePath: "Percorso snapshot remoto",
|
||||
syncRemotePathDescription: "DBX creerà le cartelle superiori mancanti durante il caricamento.",
|
||||
syncSecretNotice: "Per impostazione predefinita, DBX sincronizza solo i dettagli e le impostazioni di connessione. Le password dei database, le password SSH, le password proxy, le stringhe di connessione e le chiavi API AI rimangono locali.",
|
||||
syncSecrets: "Sincronizza i segreti crittografati",
|
||||
syncSecretsDescription: "Se abilitato, DBX crittografa le password del database, le password SSH e le chiavi API AI prima di caricarle su WebDAV.",
|
||||
syncSecretsPassphrase: "Password di sincronizzazione",
|
||||
syncSecretsPassphraseDescription: "Questa password viene utilizzata solo per crittografare e ripristinare i dati sensibili. DBX non la salva; inseriscila nuovamente su ciascun dispositivo.",
|
||||
syncSecretsPassphraseDescription: "Questa password viene utilizzata solo per crittografare e ripristinare i dati sensibili. È crittografata nell'istanza DBX corrente; inseriscila di nuovo nelle altre istanze.",
|
||||
syncTest: "Verifica",
|
||||
syncUpload: "Carica",
|
||||
syncDownload: "Scarica",
|
||||
|
||||
@@ -6034,6 +6034,7 @@ export default withEnglishFallback({
|
||||
snippetsEditTitle: "スニペットを編集",
|
||||
syncWebDavTitle: "WebDAV同期",
|
||||
syncWebDavDescription: "保存済みSQLライブラリを含むDBXスナップショットをWebDAV互換ストレージへアップロードまたは復元します。",
|
||||
syncWebDavWebDescription: "WebモードではDBXサーバーがWebDAVへ接続するため、ブラウザーのCORS制限を受けません。保存したパスワードは現在のDBXインスタンス内に保持されます。",
|
||||
syncEndpoint: "WebDAV URL",
|
||||
syncUsername: "ユーザー名",
|
||||
syncPassword: "パスワード",
|
||||
@@ -6041,14 +6042,14 @@ export default withEnglishFallback({
|
||||
syncClearSavedPassword: "保存されたパスワードをクリア",
|
||||
syncRememberWebDavPassword: "WebDAVアプリパスワードを記憶",
|
||||
syncSavedPassword: "(保存済み)",
|
||||
syncRememberWebDavPasswordDescription: "パスワードは暗号化され、このデバイスにのみ保存されます。WebDAVには同期されず、同期パスワードを置き換えるものでもありません。",
|
||||
syncRememberWebDavPasswordDescription: "パスワードは暗号化され、現在のDBXインスタンスにのみ保存されます。WebDAVには同期されず、同期パスワードを置き換えるものでもありません。",
|
||||
syncRemotePath: "リモートスナップショットパス",
|
||||
syncRemotePathDescription: "アップロード時に不足している親フォルダをDBXが作成します。",
|
||||
syncSecretNotice: "デフォルトでは、DBXは接続詳細と設定のみを同期します。データベースパスワード、SSHパスワード、プロキシパスワード、接続文字列、AI APIキーはローカルに保持されます。",
|
||||
syncSecrets: "暗号化されたシークレットを同期",
|
||||
syncSecretsDescription: "有効時、DBXはデータベースパスワード、SSHパスワード、AI APIキーを暗号化してからWebDAVにアップロードします。",
|
||||
syncSecretsPassphrase: "同期パスワード",
|
||||
syncSecretsPassphraseDescription: "このパスワードは機密データの暗号化と復元にのみ使用されます。DBXは保存せず、各デバイスで再度入力する必要があります。",
|
||||
syncSecretsPassphraseDescription: "このパスワードは機密データの暗号化と復元にのみ使用され、現在のDBXインスタンスに暗号化して保存されます。他のインスタンスでは再入力してください。",
|
||||
syncTest: "テスト",
|
||||
syncUpload: "アップロード",
|
||||
syncDownload: "ダウンロード",
|
||||
|
||||
@@ -5687,6 +5687,7 @@ export default withEnglishFallback({
|
||||
snippetsEditTitle: "스니펫 편집",
|
||||
syncWebDavTitle: "WebDAV 동기화",
|
||||
syncWebDavDescription: "WebDAV 호환 저장 서비스에서 DBX 스냅샷을 업로드하거나 복원합니다(저장된 SQL 라이브러리 항목 포함).",
|
||||
syncWebDavWebDescription: "Web 모드에서는 DBX 서버가 WebDAV에 연결하므로 브라우저 CORS 제한을 받지 않습니다. 저장된 비밀번호는 현재 DBX 인스턴스에 유지됩니다.",
|
||||
syncEndpoint: "WebDAV URL",
|
||||
syncUsername: "사용자 이름",
|
||||
syncPassword: "비밀번호",
|
||||
@@ -5694,7 +5695,7 @@ export default withEnglishFallback({
|
||||
syncClearSavedPassword: "저장된 비밀번호 지우기",
|
||||
syncRememberWebDavPassword: "WebDAV 앱 비밀번호 기억",
|
||||
syncSavedPassword: "(저장됨)",
|
||||
syncRememberWebDavPasswordDescription: "비밀번호는 이 기기에만 암호화되어 저장됩니다. WebDAV에 동기화되지 않으며 동기화 비밀번호를 대체하지 않습니다.",
|
||||
syncRememberWebDavPasswordDescription: "비밀번호는 현재 DBX 인스턴스에만 암호화되어 저장됩니다. WebDAV에 동기화되지 않으며 동기화 비밀번호를 대체하지 않습니다.",
|
||||
syncRemotePath: "원격 스냅샷 경로",
|
||||
syncRemotePathDescription: "DBX가 업로드 시 누락된 상위 폴더를 만듭니다.",
|
||||
syncSecretNotice: "기본적으로 DBX는 연결 세부 정보와 설정만 동기화합니다. 데이터베이스 비밀번호, SSH 비밀번호, 프록시 비밀번호, HTTP 터널 토큰, 연결 문자열 및 AI API 키는 로컬에 유지됩니다.",
|
||||
@@ -5702,7 +5703,7 @@ export default withEnglishFallback({
|
||||
syncSecretsDescription: "활성화하면 DBX가 데이터베이스 비밀번호, SSH 비밀번호, 프록시 비밀번호, HTTP 터널 토큰 및 AI API 키를 암호화하여 원격 저장소에 업로드합니다.",
|
||||
syncSecretsSharedDescription: "WebDAV에서는 이 비밀번호로 포함할 자격 증명을 암호화합니다. GitHub Gist 및 Gitee 스니펫은 별도의 암호화 및 자격 증명 설정을 사용합니다.",
|
||||
syncSecretsPassphrase: "동기화 비밀번호",
|
||||
syncSecretsPassphraseDescription: "이 비밀번호는 민감한 데이터를 암호화하고 복원하는 데만 사용됩니다. DBX가 저장하지 않으므로 각 기기에서 다시 입력하세요.",
|
||||
syncSecretsPassphraseDescription: "이 비밀번호는 민감한 데이터를 암호화하고 복원하는 데만 사용되며 현재 DBX 인스턴스에 암호화되어 저장됩니다. 다른 인스턴스에서는 다시 입력하세요.",
|
||||
syncTest: "테스트",
|
||||
syncUpload: "업로드",
|
||||
syncDownload: "다운로드",
|
||||
|
||||
@@ -6010,6 +6010,7 @@ export default withEnglishFallback({
|
||||
snippetsEditTitle: "Editar snippet",
|
||||
syncWebDavTitle: "Sincronização WebDAV",
|
||||
syncWebDavDescription: "Envie ou restaure um snapshot do DBX, incluindo a biblioteca SQL salva, a partir de um serviço de armazenamento compatível com WebDAV.",
|
||||
syncWebDavWebDescription: "No modo Web, o servidor DBX acessa o WebDAV sem as restrições de CORS do navegador. As senhas salvas permanecem na instância DBX atual.",
|
||||
syncEndpoint: "URL do WebDAV",
|
||||
syncUsername: "Nome de usuário",
|
||||
syncPassword: "Senha",
|
||||
@@ -6017,14 +6018,14 @@ export default withEnglishFallback({
|
||||
syncClearSavedPassword: "Limpar senha salva",
|
||||
syncRememberWebDavPassword: "Lembrar senha do app WebDAV",
|
||||
syncSavedPassword: "(salva)",
|
||||
syncRememberWebDavPasswordDescription: "A senha é criptografada e armazenada apenas neste dispositivo. Ela não é sincronizada para o WebDAV e não substitui a senha de sincronização.",
|
||||
syncRememberWebDavPasswordDescription: "A senha é criptografada e armazenada apenas na instância DBX atual. Ela não é sincronizada para o WebDAV e não substitui a senha de sincronização.",
|
||||
syncRemotePath: "Caminho remoto do snapshot",
|
||||
syncRemotePathDescription: "O DBX criará as pastas pai ausentes ao enviar.",
|
||||
syncSecretNotice: "Por padrão, o DBX sincroniza apenas detalhes de conexão e configurações. Senhas de banco de dados, senhas SSH, senhas de proxy, strings de conexão e chaves de API de IA permanecem locais.",
|
||||
syncSecrets: "Sincronizar segredos criptografados",
|
||||
syncSecretsDescription: "Quando ativado, o DBX criptografa senhas de banco de dados, senhas SSH e chaves de API de IA antes de enviá-las para o WebDAV.",
|
||||
syncSecretsPassphrase: "Senha de sincronização",
|
||||
syncSecretsPassphraseDescription: "Esta senha é usada apenas para criptografar e restaurar dados sensíveis. O DBX não a salva; digite-a novamente em cada dispositivo.",
|
||||
syncSecretsPassphraseDescription: "Esta senha é usada apenas para criptografar e restaurar dados sensíveis. Ela é criptografada na instância DBX atual; digite-a novamente em outras instâncias.",
|
||||
syncTest: "Testar",
|
||||
syncUpload: "Enviar",
|
||||
syncDownload: "Baixar",
|
||||
|
||||
@@ -6279,6 +6279,7 @@ export default withEnglishFallback({
|
||||
snippetsEditTitle: "编辑片段",
|
||||
syncWebDavTitle: "WebDAV 同步",
|
||||
syncWebDavDescription: "将包含 SQL 库的 DBX 快照上传到兼容 WebDAV 的存储服务,或从远端恢复。",
|
||||
syncWebDavWebDescription: "Web 模式下由 DBX 服务端访问 WebDAV,不受浏览器 CORS 限制;保存的密码会留在当前 DBX 实例中。",
|
||||
syncEndpoint: "WebDAV 地址",
|
||||
syncUsername: "用户名",
|
||||
syncPassword: "密码",
|
||||
@@ -6286,7 +6287,7 @@ export default withEnglishFallback({
|
||||
syncClearSavedPassword: "清除已保存的密码",
|
||||
syncRememberWebDavPassword: "记住 WebDAV 应用密码",
|
||||
syncSavedPassword: "(已保存)",
|
||||
syncRememberWebDavPasswordDescription: "密码会加密保存在本机,不会同步到 WebDAV,也不会替代同步密码。",
|
||||
syncRememberWebDavPasswordDescription: "密码会加密保存在当前 DBX 实例中,不会同步到 WebDAV,也不会替代同步密码。",
|
||||
syncRemotePath: "远端快照路径",
|
||||
syncRemotePathDescription: "上传时 DBX 会自动创建缺失的父目录。",
|
||||
syncSecretNotice: "默认只同步连接信息和设置,不会同步数据库密码、SSH 密码、代理密码、HTTP 隧道 Token、连接串或 AI API Key。",
|
||||
@@ -6294,7 +6295,7 @@ export default withEnglishFallback({
|
||||
syncSecretsDescription: "开启后,DBX 会先加密数据库密码、SSH 密码、代理密码、HTTP 隧道 Token 和 AI API Key,再上传到远端存储。",
|
||||
syncSecretsSharedDescription: "WebDAV 使用此密码加密包含的敏感信息。GitHub Gist 和 Gitee 代码片段使用独立的加密和敏感信息选项。",
|
||||
syncSecretsPassphrase: "同步密码",
|
||||
syncSecretsPassphraseDescription: "这个密码只用于加密和恢复敏感信息,DBX 不会保存;换设备恢复时需要再次输入。",
|
||||
syncSecretsPassphraseDescription: "这个密码只用于加密和恢复敏感信息,并会加密保存在当前 DBX 实例中;在其他实例恢复时需要再次输入。",
|
||||
syncTest: "测试",
|
||||
syncUpload: "上传",
|
||||
syncDownload: "下载",
|
||||
|
||||
@@ -5326,6 +5326,7 @@ export default withEnglishFallback({
|
||||
snippetsEditTitle: "編輯片段",
|
||||
syncWebDavTitle: "WebDAV 同步",
|
||||
syncWebDavDescription: "上傳包含 SQL 庫的 DBX 快照到相容 WebDAV 的儲存服務,或從該服務還原快照。",
|
||||
syncWebDavWebDescription: "Web 模式由 DBX 伺服器端存取 WebDAV,不受瀏覽器 CORS 限制;儲存的密碼會保留在目前 DBX 執行個體中。",
|
||||
syncEndpoint: "WebDAV 地址",
|
||||
syncUsername: "使用者名稱",
|
||||
syncPassword: "密碼",
|
||||
@@ -5333,14 +5334,14 @@ export default withEnglishFallback({
|
||||
syncClearSavedPassword: "清除已儲存的密碼",
|
||||
syncRememberWebDavPassword: "記住 WebDAV 應用密碼",
|
||||
syncSavedPassword: "(已儲存)",
|
||||
syncRememberWebDavPasswordDescription: "密碼會加密儲存在本機,不會同步到 WebDAV,也不會取代同步密碼。",
|
||||
syncRememberWebDavPasswordDescription: "密碼會加密儲存在目前 DBX 執行個體中,不會同步到 WebDAV,也不會取代同步密碼。",
|
||||
syncRemotePath: "遠端快照路徑",
|
||||
syncRemotePathDescription: "上傳時 DBX 會自動建立缺失的父目錄。",
|
||||
syncSecretNotice: "預設只同步連線詳細資料和設定。資料庫密碼、SSH 密碼、代理伺服器密碼、連線字串和 AI API Key 會保留在本機。",
|
||||
syncSecrets: "同步加密後的敏感資訊",
|
||||
syncSecretsDescription: "開啟後,DBX 會先加密資料庫密碼、SSH 密碼和 AI API Key,再上傳到 WebDAV。",
|
||||
syncSecretsPassphrase: "同步密碼",
|
||||
syncSecretsPassphraseDescription: "此密碼只用於加密和還原敏感資料。DBX 不會儲存;每台裝置都需要再次輸入。",
|
||||
syncSecretsPassphraseDescription: "此密碼只用於加密和還原敏感資料,並會加密儲存在目前 DBX 執行個體中;在其他執行個體還原時需要再次輸入。",
|
||||
syncTest: "測試",
|
||||
syncUpload: "上傳",
|
||||
syncDownload: "下載",
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("WebDAV sync HTTP API", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
function stubFetch(response: unknown = undefined) {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue(response),
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
return fetchMock;
|
||||
}
|
||||
|
||||
function lastCall(fetchMock: ReturnType<typeof stubFetch>): { url: string; body: Record<string, unknown> } {
|
||||
const [url, init] = fetchMock.mock.calls.at(-1) as [string, RequestInit];
|
||||
return { url, body: JSON.parse(String(init.body)) as Record<string, unknown> };
|
||||
}
|
||||
|
||||
const config = {
|
||||
endpoint: "https://dav.example.com/remote.php/dav/files/alice/",
|
||||
username: "alice",
|
||||
password: "app-password",
|
||||
remotePath: "DBX/sync/snapshot.json",
|
||||
};
|
||||
|
||||
it("routes WebDAV connectivity and saved-password operations through the Web backend", async () => {
|
||||
const fetchMock = stubFetch({ hasSavedPassword: true });
|
||||
const { forgetWebdavSavedPassword, saveWebdavSavedPassword, webdavPasswordStatus, webdavSyncTest } = await import("@/lib/backend/http");
|
||||
|
||||
await webdavSyncTest(config);
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/test", body: { config } });
|
||||
|
||||
await expect(webdavPasswordStatus(config)).resolves.toEqual({ hasSavedPassword: true });
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/password-status", body: { config } });
|
||||
|
||||
await saveWebdavSavedPassword(config, "saved-password");
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/save-password", body: { config, password: "saved-password" } });
|
||||
|
||||
await forgetWebdavSavedPassword(config);
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/forget-password", body: { config } });
|
||||
});
|
||||
|
||||
it("routes WebDAV secret preferences and snapshot transfers through the Web backend", async () => {
|
||||
const fetchMock = stubFetch({ bytes: 42, remotePath: config.remotePath });
|
||||
const { forgetWebdavSyncSecretsPassphrase, saveWebdavSyncSecretsPreference, webdavSyncDownload, webdavSyncSecretsStatus, webdavSyncUpload } = await import("@/lib/backend/http");
|
||||
|
||||
await webdavSyncSecretsStatus();
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/sync-secrets-status", body: {} });
|
||||
|
||||
await saveWebdavSyncSecretsPreference(true, "sync-passphrase");
|
||||
expect(lastCall(fetchMock)).toEqual({
|
||||
url: "/api/cloud-sync/webdav/save-sync-secrets-preference",
|
||||
body: { enabled: true, passphrase: "sync-passphrase" },
|
||||
});
|
||||
|
||||
await forgetWebdavSyncSecretsPassphrase();
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/forget-sync-secrets-passphrase", body: {} });
|
||||
|
||||
const editorSettings = { theme: "dark" };
|
||||
await webdavSyncUpload(config, editorSettings, "sync-passphrase");
|
||||
expect(lastCall(fetchMock)).toEqual({
|
||||
url: "/api/cloud-sync/webdav/upload",
|
||||
body: { config, editorSettings, secretsPassphrase: "sync-passphrase" },
|
||||
});
|
||||
|
||||
await webdavSyncDownload(config, "sync-passphrase");
|
||||
expect(lastCall(fetchMock)).toEqual({
|
||||
url: "/api/cloud-sync/webdav/download",
|
||||
body: { config, secretsPassphrase: "sync-passphrase" },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -55,6 +55,22 @@ describe("settings search", () => {
|
||||
expect(searchSettings(webEntries, "desktop", "en")).toEqual([]);
|
||||
});
|
||||
|
||||
it("exposes WebDAV sync in Web settings without exposing snippet sync", () => {
|
||||
const webEntries = resolveSettingsSearchEntries(SETTINGS_SEARCH_DEFINITIONS, { isWeb: true, visibleCategories: new Set<SettingsCategory>(["sync"]) }, translate, categoryLabels);
|
||||
|
||||
expect(webEntries.map((entry) => entry.id)).toEqual(["sync-webdav", "sync-webdav-endpoint", "sync-webdav-username", "sync-webdav-password", "sync-webdav-remote-path", "sync-webdav-auto-upload", "sync-secrets", "sync-secrets-passphrase"]);
|
||||
expect(settingsDialogSource).toContain('{ value: "sync", label: t("settings.syncTab") }');
|
||||
expect(settingsDialogSource).not.toContain('...(isWeb ? [] : [{ value: "sync"');
|
||||
expect(settingsDialogSource).toContain('<TabsList v-if="!isWeb"');
|
||||
});
|
||||
|
||||
it("defines the WebDAV Web-runtime notice in every supported locale", () => {
|
||||
for (const locale of ["zh-CN", "zh-TW", "en", "es", "it", "ja", "ko", "pt-BR"]) {
|
||||
const source = readFileSync(new URL(`../../../i18n/locales/${locale}.ts`, import.meta.url), "utf8");
|
||||
expect(source, locale).toContain("syncWebDavWebDescription:");
|
||||
}
|
||||
});
|
||||
|
||||
it("matches Chinese text as a Unicode substring", () => {
|
||||
expect(searchSettings([{ id: "font", category: "editor", title: "界面字体", description: "选择应用字体", categoryLabel: "编辑器", targetId: "editor" }], "字体", "zh-CN").map((entry) => entry.id)).toEqual(["font"]);
|
||||
});
|
||||
|
||||
@@ -211,18 +211,18 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
|
||||
{ id: "tunnels", category: "tunnels", titleKey: "settings.tunnelsTab", targetId: "tunnels" },
|
||||
{ id: "shortcuts", category: "shortcuts", titleKey: "settings.shortcutsTab", targetId: "shortcuts" },
|
||||
{ id: "snippets", category: "snippets", titleKey: "settings.snippetsTab", descriptionKey: "settings.snippetsDescription", targetId: "snippets" },
|
||||
{ id: "sync-webdav", category: "sync", titleKey: "settings.syncWebDavTitle", descriptionKey: "settings.syncWebDavDescription", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav-endpoint", category: "sync", titleKey: "settings.syncEndpoint", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav-username", category: "sync", titleKey: "settings.syncUsername", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav-password", category: "sync", titleKey: "settings.syncPassword", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav-remote-path", category: "sync", titleKey: "settings.syncRemotePath", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav-auto-upload", category: "sync", titleKey: "settings.syncAutoUploadInterval", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav", category: "sync", titleKey: "settings.syncWebDavTitle", descriptionKey: "settings.syncWebDavDescription", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-webdav-endpoint", category: "sync", titleKey: "settings.syncEndpoint", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-webdav-username", category: "sync", titleKey: "settings.syncUsername", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-webdav-password", category: "sync", titleKey: "settings.syncPassword", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-webdav-remote-path", category: "sync", titleKey: "settings.syncRemotePath", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-webdav-auto-upload", category: "sync", titleKey: "settings.syncAutoUploadInterval", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-snippet", category: "sync", titleKey: "settings.syncSnippetTitle", descriptionKey: "settings.syncSnippetDescription", targetId: "sync-snippet", route: { syncMethodTab: "snippet" }, visible: desktopOnly },
|
||||
{ id: "sync-snippet-provider", category: "sync", titleKey: "settings.syncSnippetProvider", targetId: "sync-snippet", route: { syncMethodTab: "snippet" }, visible: desktopOnly },
|
||||
{ id: "sync-snippet-id", category: "sync", titleKey: "settings.syncSnippetId", targetId: "sync-snippet", route: { syncMethodTab: "snippet" }, visible: desktopOnly },
|
||||
{ id: "sync-snippet-token", category: "sync", titleKey: "settings.syncSnippetToken", targetId: "sync-snippet", route: { syncMethodTab: "snippet" }, visible: desktopOnly },
|
||||
{ id: "sync-secrets", category: "sync", titleKey: "settings.syncSecrets", targetId: "sync", visible: desktopOnly },
|
||||
{ id: "sync-secrets-passphrase", category: "sync", titleKey: "settings.syncSecretsPassphrase", targetId: "sync", visible: desktopOnly },
|
||||
{ id: "sync-secrets", category: "sync", titleKey: "settings.syncSecrets", targetId: "sync" },
|
||||
{ id: "sync-secrets-passphrase", category: "sync", titleKey: "settings.syncSecretsPassphrase", targetId: "sync" },
|
||||
{ id: "ai-config", category: "ai", titleKey: "ai.configList", targetId: "ai" },
|
||||
{ id: "ai-prompts", category: "ai", titleKey: "ai.promptTemplates", descriptionKey: "ai.promptTemplatesDescription", targetId: "ai" },
|
||||
{ id: "ai-default-mode", category: "ai", titleKey: "ai.defaultAiMode", descriptionKey: "ai.defaultAiModeDescription", targetId: "ai" },
|
||||
|
||||
Reference in new issue
Block a user