Add configurable upstream sync proxy and schedule settings

This commit is contained in:
2026-03-19 18:05:22 +08:00
parent 1b420cd492
commit a64725d60c
8 changed files with 480 additions and 5 deletions

View File

@@ -269,6 +269,35 @@
padding: 10px;
margin: 0;
}
.sync-schedule-card {
margin: 14px 0;
padding: 12px;
border: 1px solid var(--line);
border-radius: 12px;
background: #fbfcff;
}
.sync-schedule-grid {
display: grid;
grid-template-columns: minmax(220px, 280px) minmax(180px, 240px);
gap: 12px;
align-items: end;
}
.sync-schedule-grid .full-row {
grid-column: 1 / -1;
}
.check-row {
display: flex;
align-items: center;
gap: 8px;
margin: 0;
min-height: 42px;
}
.check-row input {
width: 16px;
height: 16px;
margin: 0;
flex: 0 0 auto;
}
.hidden { display: none; }
.modal-backdrop {
position: fixed;
@@ -397,6 +426,28 @@
<section id="syncTabPanel" class="manage-panel hidden">
<h3 class="title">原始数据同步</h3>
<p class="sub">从上游 `KHwang9883/MobileModels` 拉取原始 markdown 数据,并重建 `dist/device_index.json`。如已开启 MySQL 自动装载,也会同步刷新 MySQL。请先启动完整服务。</p>
<div class="sync-schedule-card">
<h4 class="title">每日自动同步</h4>
<p class="sub">在项目容器内按固定时间自动拉取上游原始数据,并重建索引与 MySQL Seed。时间按容器时区执行设置会持久化到运行期数据目录。</p>
<div class="sync-schedule-grid">
<label class="check-row">
<input id="scheduleEnabled" type="checkbox" />
<span>启用每日自动同步</span>
</label>
<div>
<label for="scheduleTimeInput">每日同步时间</label>
<input id="scheduleTimeInput" type="time" step="60" value="03:00" />
</div>
<div class="full-row">
<label for="githubProxyPrefixInput">GitHub 加速前缀</label>
<input id="githubProxyPrefixInput" type="text" placeholder="例如 https://ghfast.top/" />
</div>
</div>
<div class="btns">
<button id="saveSyncScheduleBtn" type="button" class="primary">保存同步设置</button>
</div>
<div id="scheduleStatus" class="sub">正在读取自动同步设置。</div>
</div>
<div class="btns">
<button id="syncUpstreamBtn" type="button" class="primary">开始同步原始数据</button>
<button id="refreshSyncStatusBtn" type="button">刷新同步状态</button>
@@ -470,12 +521,18 @@
const syncLogEl = document.getElementById("syncLog");
const syncUpstreamBtnEl = document.getElementById("syncUpstreamBtn");
const refreshSyncStatusBtnEl = document.getElementById("refreshSyncStatusBtn");
const scheduleEnabledEl = document.getElementById("scheduleEnabled");
const scheduleTimeInputEl = document.getElementById("scheduleTimeInput");
const githubProxyPrefixInputEl = document.getElementById("githubProxyPrefixInput");
const saveSyncScheduleBtnEl = document.getElementById("saveSyncScheduleBtn");
const scheduleStatusEl = document.getElementById("scheduleStatus");
const reloadIndexBtnEl = document.getElementById("reloadIndexBtn");
const indexStatusEl = document.getElementById("indexStatus");
const indexSummaryEl = document.getElementById("indexSummary");
let syncSupported = false;
let syncRunning = false;
let scheduleSaving = false;
function normalizeText(text) {
return (text || "").toLowerCase().replace(/[^0-9a-z\u4e00-\u9fff]+/g, "");
@@ -502,6 +559,7 @@
function updateSyncButtons() {
syncUpstreamBtnEl.disabled = syncRunning || !syncSupported;
refreshSyncStatusBtnEl.disabled = syncRunning;
saveSyncScheduleBtnEl.disabled = syncRunning || scheduleSaving;
}
function renderIndexStatus(message, details) {
@@ -522,6 +580,8 @@
if (data.workspace_root) lines.push(`工作空间目录: ${data.workspace_root}`);
if (data.storage_mode) lines.push(`存储模式: ${data.storage_mode}`);
if (data.upstream_repo_url) lines.push(`上游仓库: ${data.upstream_repo_url}`);
if (data.github_proxy_prefix) lines.push(`GitHub 加速前缀: ${data.github_proxy_prefix}`);
if (data.effective_upstream_repo_url) lines.push(`实际同步地址: ${data.effective_upstream_repo_url}`);
if (data.upstream_branch) lines.push(`上游分支: ${data.upstream_branch}`);
if (data.last_sync_time) lines.push(`最近同步时间: ${data.last_sync_time}`);
if (data.last_upstream_commit) lines.push(`最近同步提交: ${data.last_upstream_commit}`);
@@ -543,8 +603,33 @@
syncLogEl.textContent = lines.join("\n").trim() || "暂无同步记录";
}
function renderScheduleStatus(data, options = {}) {
const preserveMessage = !!options.preserveMessage;
const enabled = !!(data && data.sync_schedule_enabled);
const dailyTime = (data && data.sync_schedule_time) || "03:00";
const githubProxyPrefix = (data && data.github_proxy_prefix) || "";
scheduleEnabledEl.checked = enabled;
scheduleTimeInputEl.value = dailyTime;
githubProxyPrefixInputEl.value = githubProxyPrefix;
if (preserveMessage) return;
const lines = [
`每日自动同步: ${enabled ? "已启用" : "未启用"}`,
`同步时间: ${dailyTime}`,
];
if (data && data.sync_schedule_timezone) lines.push(`容器时区: ${data.sync_schedule_timezone}`);
if (githubProxyPrefix) lines.push(`GitHub 加速前缀: ${githubProxyPrefix}`);
if (data && data.effective_upstream_repo_url) lines.push(`实际同步地址: ${data.effective_upstream_repo_url}`);
if (data && data.sync_schedule_next_run) lines.push(`下次执行: ${data.sync_schedule_next_run}`);
if (data && data.sync_schedule_last_run_time) lines.push(`最近自动执行: ${data.sync_schedule_last_run_time}`);
if (data && data.sync_schedule_last_run_status) lines.push(`最近执行结果: ${data.sync_schedule_last_run_status}`);
if (data && data.sync_schedule_last_run_message) lines.push(`结果详情: ${data.sync_schedule_last_run_message}`);
scheduleStatusEl.textContent = lines.join("");
}
async function loadSyncStatus(options = {}) {
const preserveLog = !!options.preserveLog;
const preserveScheduleMessage = !!options.preserveScheduleMessage;
syncStatusEl.textContent = "正在检测同步能力。";
try {
const data = await fetchJson("/api/status", { cache: "no-store" });
@@ -552,12 +637,16 @@
syncStatusEl.textContent = syncSupported
? "已连接 Docker Compose 服务,可以直接从页面同步原始数据、索引和 MySQL。"
: "当前服务不支持原始数据同步。";
renderScheduleStatus(data, { preserveMessage: preserveScheduleMessage });
if (!preserveLog) {
renderSyncLog(data, "服务状态");
}
} catch (err) {
syncSupported = false;
syncStatusEl.textContent = `当前页面未连接支持同步的 Docker Compose 服务:${err.message}`;
if (!preserveScheduleMessage) {
scheduleStatusEl.textContent = `自动同步设置读取失败: ${err.message}`;
}
if (!preserveLog) {
syncLogEl.textContent = "请使用 `docker compose up --build -d` 启动完整服务后,再使用这个功能。";
}
@@ -593,6 +682,31 @@
}
}
async function saveSyncSchedule() {
scheduleSaving = true;
updateSyncButtons();
scheduleStatusEl.textContent = "正在保存每日自动同步设置...";
try {
const payload = await fetchJson("/api/sync-schedule", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
enabled: !!scheduleEnabledEl.checked,
daily_time: scheduleTimeInputEl.value || "03:00",
github_proxy_prefix: githubProxyPrefixInputEl.value || "",
}),
});
scheduleStatusEl.textContent = payload.message || "每日自动同步设置已保存。";
await loadSyncStatus({ preserveLog: true, preserveScheduleMessage: true });
renderScheduleStatus(payload);
} catch (err) {
scheduleStatusEl.textContent = `保存失败: ${err.message}`;
} finally {
scheduleSaving = false;
updateSyncButtons();
}
}
function normalizeAliasList(name, aliases) {
const out = [];
const seen = new Set();
@@ -1235,6 +1349,7 @@
manufacturerCountBtnEl.addEventListener("click", openManufacturerListModal);
syncUpstreamBtnEl.addEventListener("click", runUpstreamSync);
refreshSyncStatusBtnEl.addEventListener("click", loadSyncStatus);
saveSyncScheduleBtnEl.addEventListener("click", saveSyncSchedule);
reloadIndexBtnEl.addEventListener("click", loadIndexFromPath);
brandModalCancelBtnEl.addEventListener("click", closeBrandModal);