fix: localize home and split auth setup flow
This commit is contained in:
+57
-57
@@ -136,13 +136,13 @@ function renderOverviewSummary() {
|
||||
const mappedCount = state.sources.filter((source) => source.displayNameSource === 'server-db').length;
|
||||
|
||||
overviewSummaryElement.innerHTML = renderSummaryRows([
|
||||
{ label: 'Total sources', value: String(sourceCount) },
|
||||
{ label: 'Enhanced sources', value: String(readyCount) },
|
||||
{ label: 'Failed enhancements', value: String(failedCount) },
|
||||
{ label: 'Mapped task names', value: String(mappedCount) },
|
||||
{ label: 'Server DB', value: state.serverDb?.available ? state.serverDb.originalFilename : 'Not uploaded' },
|
||||
{ label: 'Global WebDAV', value: state.globalDefaults?.configured ? 'Configured' : 'Not configured' },
|
||||
{ label: 'Active certificate', value: state.tls?.activeSource ?? 'Unavailable' }
|
||||
{ label: '数据源总数', value: String(sourceCount) },
|
||||
{ label: '已增强数据源', value: String(readyCount) },
|
||||
{ label: '增强失败', value: String(failedCount) },
|
||||
{ label: '已映射任务名', value: String(mappedCount) },
|
||||
{ label: 'Server DB', value: state.serverDb?.available ? state.serverDb.originalFilename : '未上传' },
|
||||
{ label: '全局 WebDAV', value: state.globalDefaults?.configured ? '已配置' : '未配置' },
|
||||
{ label: '当前证书来源', value: state.tls?.activeSource ?? '不可用' }
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -150,15 +150,15 @@ function renderServerDbSummary() {
|
||||
if (!state.serverDb?.available) {
|
||||
serverDbSummaryElement.innerHTML = renderSummaryRows([
|
||||
{ label: 'Status', value: 'No server database uploaded yet' },
|
||||
{ label: 'Note', value: 'Task names and inferred target URLs stay unavailable until you upload Duplicati-server.sqlite.' }
|
||||
{ label: '说明', value: '未上传 Duplicati-server.sqlite 之前,任务名和推导目标 URL 不可用。' }
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
serverDbSummaryElement.innerHTML = renderSummaryRows([
|
||||
{ label: 'Current file', value: state.serverDb.originalFilename },
|
||||
{ label: 'Uploaded at', value: state.serverDb.uploadedAt },
|
||||
{ label: 'Backup entries', value: String(state.serverDb.backupCount) }
|
||||
{ label: '当前文件', value: state.serverDb.originalFilename },
|
||||
{ label: '上传时间', value: state.serverDb.uploadedAt },
|
||||
{ label: '任务条目数', value: String(state.serverDb.backupCount) }
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -167,46 +167,46 @@ function renderGlobalDefaultsSummary() {
|
||||
if (!defaults?.configured) {
|
||||
writeGlobalDefaultsForm(null);
|
||||
globalDefaultsSummaryElement.innerHTML = renderSummaryRows([
|
||||
{ label: 'Status', value: 'No global defaults saved' },
|
||||
{ label: 'Note', value: 'Save shared credentials here if most tasks use the same WebDAV account and passphrase.' }
|
||||
{ label: '状态', value: '尚未保存全局默认值' },
|
||||
{ label: '说明', value: '如果大多数任务共用同一套 WebDAV 账号和口令,可以在这里统一保存。' }
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
writeGlobalDefaultsForm(defaults);
|
||||
globalDefaultsSummaryElement.innerHTML = renderSummaryRows([
|
||||
{ label: 'Fallback URL', value: defaults.webdavBaseUrl ?? 'Not set' },
|
||||
{ label: 'Auth mode', value: defaults.authMode ?? 'Auto' },
|
||||
{ label: '兜底 URL', value: defaults.webdavBaseUrl ?? '未设置' },
|
||||
{ label: '认证方式', value: defaults.authMode ?? '自动' },
|
||||
{
|
||||
label: 'Stored secrets',
|
||||
label: '已保存凭据',
|
||||
value: `username=${defaults.hasUsername ? 'yes' : 'no'}, password=${defaults.hasPassword ? 'yes' : 'no'}, passphrase=${defaults.hasPassphrase ? 'yes' : 'no'}`
|
||||
},
|
||||
{ label: 'Updated at', value: defaults.updatedAt ?? 'Unknown' }
|
||||
{ label: '更新时间', value: defaults.updatedAt ?? '未知' }
|
||||
]);
|
||||
}
|
||||
|
||||
function renderTlsSummary() {
|
||||
if (!state.tls) {
|
||||
tlsSummaryElement.innerHTML = renderSummaryRows([{ label: 'Status', value: 'TLS settings not loaded yet' }]);
|
||||
tlsSummaryElement.innerHTML = renderSummaryRows([{ label: '状态', value: 'TLS 配置尚未加载' }]);
|
||||
return;
|
||||
}
|
||||
|
||||
writeTlsForm(state.tls);
|
||||
tlsDeleteCustomButton.disabled = !state.tls.hasCustomCertificate;
|
||||
tlsSummaryElement.innerHTML = renderSummaryRows([
|
||||
{ label: 'HTTP endpoint', value: state.tls.access?.httpBaseUrl ?? 'Unknown' },
|
||||
{ label: 'HTTPS endpoint', value: state.tls.access?.httpsBaseUrl ?? 'Unknown' },
|
||||
{ label: 'Configured mode', value: state.tls.mode ?? 'self-signed' },
|
||||
{ label: 'Active certificate', value: state.tls.activeSource ?? 'self-signed' },
|
||||
{ label: 'Primary domain', value: state.tls.primaryDomain || 'localhost' },
|
||||
{ label: 'Extra SAN entries', value: (state.tls.subjectAltNames ?? []).join(', ') || 'None' },
|
||||
{ label: 'HTTP 访问地址', value: state.tls.access?.httpBaseUrl ?? '未知' },
|
||||
{ label: 'HTTPS 访问地址', value: state.tls.access?.httpsBaseUrl ?? '未知' },
|
||||
{ label: '配置模式', value: state.tls.mode ?? 'self-signed' },
|
||||
{ label: '当前生效证书', value: state.tls.activeSource ?? 'self-signed' },
|
||||
{ label: '主域名', value: state.tls.primaryDomain || 'localhost' },
|
||||
{ label: '额外 SAN', value: (state.tls.subjectAltNames ?? []).join(', ') || '无' },
|
||||
{
|
||||
label: 'Validity',
|
||||
value: state.tls.certificate ? `${state.tls.certificate.validFrom} -> ${state.tls.certificate.validTo}` : 'Unknown'
|
||||
label: '有效期',
|
||||
value: state.tls.certificate ? `${state.tls.certificate.validFrom} -> ${state.tls.certificate.validTo}` : '未知'
|
||||
},
|
||||
{ label: 'Fingerprint', value: state.tls.certificate?.fingerprint256 ?? 'Unknown' },
|
||||
{ label: 'Last error', value: state.tls.lastErrorMessage ?? 'None' },
|
||||
{ label: 'Cookie policy', value: state.tls.cookieNote ?? 'Shared between HTTP and HTTPS' }
|
||||
{ label: '指纹', value: state.tls.certificate?.fingerprint256 ?? '未知' },
|
||||
{ label: '最近错误', value: state.tls.lastErrorMessage ?? '无' },
|
||||
{ label: 'Cookie 说明', value: state.tls.cookieNote ?? 'HTTP/HTTPS 共用登录态' }
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -224,16 +224,16 @@ function renderSources() {
|
||||
sourceListElement.innerHTML = state.sources
|
||||
.map((source) => {
|
||||
const targetHint = source.webdav?.effectiveWebdavBaseUrl
|
||||
? `<div class="summary-chip">Default target URL source: ${escapeHtml(source.webdav.effectiveWebdavBaseUrlSource ?? 'unknown')}</div>`
|
||||
: '<div class="summary-chip">No effective default target URL yet</div>';
|
||||
? `<div class="summary-chip">默认目标 URL 来源:${escapeHtml(source.webdav.effectiveWebdavBaseUrlSource ?? 'unknown')}</div>`
|
||||
: '<div class="summary-chip">暂无可用的默认目标 URL</div>';
|
||||
|
||||
return renderSourceSummaryCard(
|
||||
source,
|
||||
`
|
||||
${targetHint}
|
||||
<div class="card-actions">
|
||||
<a class="ghost-button button-link" href="${linkToSource(source.id)}#overview">Open workspace</a>
|
||||
<button class="ghost-button danger-button" type="button" data-delete-source-id="${escapeHtml(source.id)}">Delete source</button>
|
||||
<a class="ghost-button button-link" href="${linkToSource(source.id)}#overview">进入工作台</a>
|
||||
<button class="ghost-button danger-button" type="button" data-delete-source-id="${escapeHtml(source.id)}">删除 source</button>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
@@ -275,13 +275,13 @@ async function handleTaskDbUpload(event) {
|
||||
|
||||
const file = databaseInput.files?.[0];
|
||||
if (!file) {
|
||||
feedback.set('error', 'Choose a task SQLite file first.');
|
||||
feedback.set('error', '请先选择一个任务 SQLite 文件。');
|
||||
return;
|
||||
}
|
||||
|
||||
uploadButton.disabled = true;
|
||||
refreshButton.disabled = true;
|
||||
feedback.set('info', 'Uploading task database...');
|
||||
feedback.set('info', '正在上传任务数据库...');
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
@@ -302,8 +302,8 @@ async function handleTaskDbUpload(event) {
|
||||
feedback.set(
|
||||
payload.reused ? 'info' : 'success',
|
||||
payload.reused
|
||||
? 'That task database was already uploaded, so the existing source was reused.'
|
||||
: 'Task database uploaded successfully.'
|
||||
? '这个任务数据库之前已经上传过,系统复用了现有 source。'
|
||||
: '任务数据库上传成功。'
|
||||
);
|
||||
} catch (error) {
|
||||
feedback.set('error', error.message);
|
||||
@@ -319,12 +319,12 @@ async function handleServerDbUpload(event) {
|
||||
|
||||
const file = serverDbInput.files?.[0];
|
||||
if (!file) {
|
||||
feedback.set('error', 'Choose Duplicati-server.sqlite first.');
|
||||
feedback.set('error', '请先选择 Duplicati-server.sqlite。');
|
||||
return;
|
||||
}
|
||||
|
||||
serverDbUploadButton.disabled = true;
|
||||
feedback.set('info', 'Uploading Server DB and refreshing task-name mappings...');
|
||||
feedback.set('info', '正在上传 Server DB 并刷新任务名映射...');
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
@@ -341,7 +341,7 @@ async function handleServerDbUpload(event) {
|
||||
|
||||
await Promise.all([loadSources(), loadServerDbSummary()]);
|
||||
serverDbForm.reset();
|
||||
feedback.set('success', 'Server DB uploaded successfully.');
|
||||
feedback.set('success', 'Server DB 上传成功。');
|
||||
} catch (error) {
|
||||
feedback.set('error', error.message);
|
||||
} finally {
|
||||
@@ -353,7 +353,7 @@ async function handleGlobalDefaultsSave(event) {
|
||||
event.preventDefault();
|
||||
feedback.clear();
|
||||
globalDefaultsSaveButton.disabled = true;
|
||||
feedback.set('info', 'Saving global WebDAV defaults...');
|
||||
feedback.set('info', '正在保存全局 WebDAV 默认值...');
|
||||
|
||||
try {
|
||||
const payload = await fetchJson('/api/webdav-defaults', {
|
||||
@@ -370,7 +370,7 @@ async function handleGlobalDefaultsSave(event) {
|
||||
await loadSources();
|
||||
feedback.set(
|
||||
payload.defaults.configured ? 'success' : 'info',
|
||||
payload.defaults.configured ? 'Global defaults saved.' : 'Global defaults cleared.'
|
||||
payload.defaults.configured ? '全局默认值已保存。' : '全局默认值已清空。'
|
||||
);
|
||||
} catch (error) {
|
||||
feedback.set('error', error.message);
|
||||
@@ -384,7 +384,7 @@ async function handleTlsJsonSave(event) {
|
||||
feedback.clear();
|
||||
tlsSaveButton.disabled = true;
|
||||
tlsUploadButton.disabled = true;
|
||||
feedback.set('info', 'Saving TLS configuration and applying certificate...');
|
||||
feedback.set('info', '正在保存 TLS 配置并应用证书...');
|
||||
|
||||
try {
|
||||
const payload = await fetchJson('/api/system/tls', {
|
||||
@@ -397,7 +397,7 @@ async function handleTlsJsonSave(event) {
|
||||
state.tls = payload.tls;
|
||||
renderTlsSummary();
|
||||
renderOverviewSummary();
|
||||
feedback.set('success', `TLS settings applied. Active certificate source: ${payload.tls.activeSource}.`);
|
||||
feedback.set('success', `TLS 配置已应用,当前证书来源:${payload.tls.activeSource}。`);
|
||||
} catch (error) {
|
||||
feedback.set('error', error.message);
|
||||
} finally {
|
||||
@@ -415,13 +415,13 @@ async function handleTlsUpload(event) {
|
||||
const chainFile = document.querySelector('#tlsChainInput')?.files?.[0] ?? null;
|
||||
|
||||
if (!certificateFile || !privateKeyFile) {
|
||||
feedback.set('error', 'Upload both the certificate file and the private key file.');
|
||||
feedback.set('error', '请同时上传证书文件和私钥文件。');
|
||||
return;
|
||||
}
|
||||
|
||||
tlsSaveButton.disabled = true;
|
||||
tlsUploadButton.disabled = true;
|
||||
feedback.set('info', 'Uploading custom certificate...');
|
||||
feedback.set('info', '正在上传自定义证书...');
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
@@ -446,7 +446,7 @@ async function handleTlsUpload(event) {
|
||||
renderTlsSummary();
|
||||
renderOverviewSummary();
|
||||
tlsUploadForm.reset();
|
||||
feedback.set('success', 'Custom certificate uploaded and applied.');
|
||||
feedback.set('success', '自定义证书已上传并生效。');
|
||||
} catch (error) {
|
||||
feedback.set('error', error.message);
|
||||
} finally {
|
||||
@@ -457,12 +457,12 @@ async function handleTlsUpload(event) {
|
||||
|
||||
async function handleDeleteCustomTls() {
|
||||
feedback.clear();
|
||||
if (!window.confirm('Delete the stored custom certificate and fall back to a self-signed certificate?')) {
|
||||
if (!window.confirm('确定要删除当前自定义证书,并回退到自签证书吗?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
tlsDeleteCustomButton.disabled = true;
|
||||
feedback.set('info', 'Deleting custom certificate and switching back to self-signed...');
|
||||
feedback.set('info', '正在删除自定义证书,并切回自签证书...');
|
||||
|
||||
try {
|
||||
const payload = await fetchJson('/api/system/tls/custom-certificate', {
|
||||
@@ -471,7 +471,7 @@ async function handleDeleteCustomTls() {
|
||||
state.tls = payload.tls;
|
||||
renderTlsSummary();
|
||||
renderOverviewSummary();
|
||||
feedback.set('success', 'Custom certificate removed. The service is back on a self-signed certificate.');
|
||||
feedback.set('success', '自定义证书已删除,系统已回退到自签证书。');
|
||||
} catch (error) {
|
||||
feedback.set('error', error.message);
|
||||
} finally {
|
||||
@@ -484,20 +484,20 @@ async function deleteSource(sourceId) {
|
||||
const source = state.sources.find((item) => item.id === sourceId);
|
||||
const label = source?.displayName || source?.originalFilename || sourceId;
|
||||
const confirmed = window.confirm(
|
||||
`Delete this source?\n\n${label}\n\nThis removes only the selected source and its local enhancement data. It does not delete Duplicati-server.sqlite.`
|
||||
`确定要删除这个 source 吗?\n\n${label}\n\n这只会删除当前 source 及其本地增强数据,不会删除 Duplicati-server.sqlite。`
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
feedback.set('info', `Deleting ${label}...`);
|
||||
feedback.set('info', `正在删除 ${label}...`);
|
||||
|
||||
try {
|
||||
const payload = await fetchJson(`/api/sources/${encodeURIComponent(sourceId)}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
await Promise.all([loadSources(), loadServerDbSummary()]);
|
||||
feedback.set('success', `Deleted ${payload.deleted.originalFilename}. Server DB was kept untouched.`);
|
||||
feedback.set('success', `已删除 ${payload.deleted.originalFilename},Server DB 保持不变。`);
|
||||
} catch (error) {
|
||||
feedback.set('error', error.message);
|
||||
}
|
||||
@@ -529,7 +529,7 @@ tlsDeleteCustomButton.addEventListener('click', () => {
|
||||
|
||||
refreshButton.addEventListener('click', () => {
|
||||
feedback.clear();
|
||||
sourceListElement.textContent = 'Refreshing sources...';
|
||||
sourceListElement.textContent = '正在刷新数据源列表...';
|
||||
void Promise.all([loadSources(), loadServerDbSummary(), loadGlobalDefaults(), loadTlsSummary()]).catch((error) => {
|
||||
feedback.set('error', error.message);
|
||||
});
|
||||
@@ -571,8 +571,8 @@ async function bootstrap() {
|
||||
|
||||
const authentication = await loadAuthState();
|
||||
authStatusElement.textContent = authentication.authenticated
|
||||
? `Signed in as ${authentication.username}`
|
||||
: 'Not signed in';
|
||||
? `已登录:${authentication.username}`
|
||||
: '未登录';
|
||||
bindLogoutButton(logoutButton, authStatusElement);
|
||||
|
||||
await Promise.all([loadServerDbSummary(), loadGlobalDefaults(), loadTlsSummary(), loadSources()]);
|
||||
@@ -582,7 +582,7 @@ void bootstrap().catch((error) => {
|
||||
feedback.set('error', error.message);
|
||||
sourceListElement.innerHTML = `
|
||||
<div class="empty-state-card">
|
||||
<p>Could not load the control center.</p>
|
||||
<p>无法加载控制台。</p>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user