import { escapeHtml, fetchJson, formatBytes } from '../common.js'; // 源属性对话框:增强配置(覆盖 WebDAV/启动增强)、预览图缓存清理、删除源。 // 移植自 source.js 的 saveEnhancementSecrets / runEnhancement / clearSourceThumbnails / deleteCurrentSource。 // deps: { feedback, onSourceChanged, onSourceDeleted, close } const TABS = [ { key: 'enhance', label: '增强配置' }, { key: 'thumbnails', label: '预览图缓存' }, { key: 'danger', label: '危险操作' } ]; export function createPropsDialog(rootElement, deps = {}) { const { feedback } = deps; let source = null; let activeTab = 'enhance'; function webdavHint() { const webdav = source?.webdav; if (!webdav) { return '
正在计算这个 source 的默认 WebDAV 目标...
'; } return `
任务默认 URL${escapeHtml(webdav.effectiveWebdavBaseUrl ?? webdav.derivedWebdavBaseUrl ?? '未推导到')}
默认 URL 来源${escapeHtml(webdav.effectiveWebdavBaseUrlSource ?? 'none')}
全局默认值${webdav.globalDefaultsConfigured ? '已配置' : '未配置'}
当前 source 覆盖${webdav.sourceOverrideSaved ? '已保存' : '未保存'}
`; } function renderEnhanceTab() { return `

增强会优先使用全局 WebDAV 默认值,并尽量从 Server DB 推导目标 URL。这里只填与全局不同的部分;全部留空并保存会清掉当前 source 覆盖。

${webdavHint()}
`; } function renderThumbnailsTab() { const cache = source?.thumbnailCache ?? { count: 0, totalBytes: 0 }; return `

这里展示当前 source 已缓存到后端的预览图数量。清空后,视频会退回占位图,后续再次成功预览时会自动重建。

预览图数量${escapeHtml(String(cache.count))}
总占用空间${formatBytes(cache.totalBytes)}
`; } function renderDangerTab() { return `

删除会移除当前任务库、本地增强副本和该 source 的预览图缓存,但不会删除 Duplicati-server.sqlite。

`; } function renderBody() { if (!source) { rootElement.innerHTML = '
未找到对应数据源。
'; return; } let content = ''; if (activeTab === 'thumbnails') { content = renderThumbnailsTab(); } else if (activeTab === 'danger') { content = renderDangerTab(); } else { content = renderEnhanceTab(); } rootElement.innerHTML = `

${escapeHtml(source.displayName || source.originalFilename)} · 属性

${TABS.map((tab) => ``).join('')}
${content}
`; } function readSecretForm() { return { webdavBaseUrl: rootElement.querySelector('#propsUrl')?.value.trim() ?? '', authMode: rootElement.querySelector('#propsAuthMode')?.value ?? '', username: rootElement.querySelector('#propsUsername')?.value.trim() ?? '', password: rootElement.querySelector('#propsPassword')?.value ?? '', passphrase: rootElement.querySelector('#propsPassphrase')?.value ?? '' }; } async function reloadSource() { const payload = await fetchJson(`/api/sources/${encodeURIComponent(source.id)}`); source = payload.source; deps.onSourceChanged?.(source); } async function saveSecrets() { feedback?.set('info', '正在保存当前 source 的覆盖规则...'); try { await fetchJson(`/api/sources/${encodeURIComponent(source.id)}/secrets`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(readSecretForm()) }); await reloadSource(); renderBody(); feedback?.set('success', '覆盖规则已保存。留空项会继续继承全局默认或 Server DB 推导结果。'); } catch (error) { feedback?.set('error', error.message); } } async function runEnhancement() { feedback?.set('info', '正在提交增强任务...'); try { await fetchJson(`/api/sources/${encodeURIComponent(source.id)}/enhance`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(readSecretForm()) }); await reloadSource(); renderBody(); feedback?.set('success', '增强任务已开始。当前规则会合并全局默认、Server DB 推导 URL 和单源覆盖。'); } catch (error) { feedback?.set('error', error.message); } } async function clearThumbnails() { if (!window.confirm('确定要清空这个 source 的全部预览图吗?\n\n这不会删除任务库、增强索引或 Server DB,只会删除后端缩略图缓存。')) { return; } feedback?.set('info', '正在清空预览图缓存...'); try { const payload = await fetchJson(`/api/sources/${encodeURIComponent(source.id)}/thumbnails`, { method: 'DELETE' }); await reloadSource(); renderBody(); feedback?.set('success', `预览图已清空,删除了 ${payload.cleared.removedCount} 张,共 ${formatBytes(payload.cleared.removedBytes)}。`); } catch (error) { feedback?.set('error', error.message); } } async function deleteSource() { const label = source.displayName || source.originalFilename || source.id; if (!window.confirm(`确定要删除这个 source 吗?\n\n${label}\n\n这会删除任务库、本地增强副本和预览图缓存,但不会删除 Duplicati-server.sqlite。`)) { return; } feedback?.set('info', `正在删除 ${label}...`); try { await fetchJson(`/api/sources/${encodeURIComponent(source.id)}`, { method: 'DELETE' }); deps.onSourceDeleted?.(source.id); deps.close?.(); feedback?.set('success', `已删除 ${label}。`); } catch (error) { feedback?.set('error', error.message); } } rootElement.addEventListener('click', (event) => { const tab = event.target.closest('[data-props-tab]'); if (tab instanceof HTMLElement) { activeTab = tab.dataset.propsTab; renderBody(); return; } if (event.target.closest('[data-props-close]')) { deps.close?.(); return; } if (event.target.closest('#propsRunEnhance')) { void runEnhancement(); } else if (event.target.closest('#propsClearThumbs')) { void clearThumbnails(); } else if (event.target.closest('#propsDeleteSource')) { void deleteSource(); } }); rootElement.addEventListener('submit', (event) => { if (event.target.id === 'propsSecretForm') { event.preventDefault(); void saveSecrets(); } }); return { async open(sourceId, tab = 'enhance') { activeTab = TABS.some((item) => item.key === tab) ? tab : 'enhance'; try { const payload = await fetchJson(`/api/sources/${encodeURIComponent(sourceId)}`); source = payload.source; renderBody(); } catch (error) { source = null; rootElement.innerHTML = `
${escapeHtml(error.message)}
`; } } }; }