import { redirectToLogin, redirectToSetup } from './auth-client.js'; export function escapeHtml(value) { return String(value) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } export function formatBytes(bytes) { if (bytes === null || bytes === undefined || Number.isNaN(Number(bytes))) { return 'Unknown'; } const numeric = Number(bytes); if (numeric === 0) { return '0 B'; } const units = ['B', 'KB', 'MB', 'GB', 'TB']; let value = numeric; let unitIndex = 0; while (value >= 1024 && unitIndex < units.length - 1) { value /= 1024; unitIndex += 1; } return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`; } export async function fetchJson(url, options = {}) { const response = await fetch(url, options); const payload = await response.json().catch(() => ({})); if (!response.ok) { if (response.status === 401 && window.location.pathname !== '/login.html') { if (payload.error?.code === 'AUTH_SETUP_REQUIRED') { redirectToSetup(); } else { redirectToLogin(); } } throw new Error(`${payload.error?.code ?? 'REQUEST_FAILED'}: ${payload.error?.message ?? 'Request failed.'}`); } return payload; } export function requireQueryParam(name) { const params = new URLSearchParams(window.location.search); const value = params.get(name)?.trim(); if (!value) { throw new Error(`MISSING_${name.toUpperCase()}: Query parameter "${name}" is required.`); } return value; } export function optionalQueryParam(name, fallback = '') { const params = new URLSearchParams(window.location.search); return params.get(name)?.trim() || fallback; } export function createFeedbackController(element) { return { clear() { element.hidden = true; element.className = 'feedback'; element.textContent = ''; }, set(kind, message) { element.hidden = false; element.className = `feedback feedback-${kind}`; element.textContent = message; } }; } export function inferEnhancementLabel(source) { switch (source?.enhancement?.status) { case 'ready': return '已增强'; case 'running': return '增强中'; case 'queued': return '排队中'; case 'failed': return '增强失败'; default: return '未增强'; } } export function inferEnhancementClass(source) { switch (source?.enhancement?.status) { case 'ready': return 'status-ready'; case 'running': case 'queued': return 'status-busy'; case 'failed': return 'status-error'; default: return 'status-idle'; } } export function renderSourceSummaryCard(source, extraActionsHtml = '') { const title = escapeHtml(source.displayName || source.originalFilename); const originalFilename = escapeHtml(source.originalFilename); const matchedHint = source.matchedBackupName ? '任务名来自 Server DB' : '尚未匹配任务名'; const progress = source.enhancement.totalVolumes > 0 ? `${source.enhancement.processedVolumes}/${source.enhancement.totalVolumes}` : '0/0'; return `

${title}

数据库文件:${originalFilename}

Source ID:${escapeHtml(source.id)}

${inferEnhancementLabel(source)}
${escapeHtml(matchedHint)}
上传大小 ${formatBytes(source.fileSize)}
最新快照 ${escapeHtml(source.latestSnapshot?.timestamp ?? '无')}
目录浏览 ${source.canBrowse ? '可用' : '不可用'}
增强进度 ${escapeHtml(progress)}
${extraActionsHtml}
`; } export function assertBrowserFeature(condition, message) { if (!condition) { throw new Error(message); } }