Show sync freshness status

This commit is contained in:
liuji
2026-10-10 17:59:30 +08:00
parent 913b601609
commit a0108a3c0e
3 changed files with 87 additions and 8 deletions
+53 -8
View File
@@ -41,7 +41,8 @@
.status { color: var(--muted); text-align: right; }
.status[data-state="error"] { color: var(--error-text); }
.status[data-state="empty"] { color: var(--muted); }
.alert { margin: 0 0 18px; padding: 11px 14px; border: 1px solid var(--warning-border); border-radius: 10px; background: var(--warning-bg); color: var(--warning-text); }
.alert { margin: 0 0 18px; padding: 11px 14px; border: 1px solid var(--warning-border); border-radius: 10px; background: var(--warning-bg); color: var(--warning-text); white-space: pre-line; }
.alert[data-stale="true"] { border-color: var(--error-border); background: var(--error-bg); color: var(--error-text); }
.alert[hidden] { display: none; }
.filters { display: grid; gap: 12px; margin: 0 0 20px; padding: 16px 18px; border: 1px solid var(--border); border-radius: 14px; background: var(--surface); box-shadow: var(--shadow); }
.filter-row { display: flex; flex-wrap: wrap; align-items: end; gap: 14px 20px; }
@@ -283,24 +284,68 @@
charts = [];
}
function formatShanghaiTime(value) {
if (!value) return null;
var timestamp = Date.parse(value);
if (Number.isNaN(timestamp)) return null;
var parts = new Intl.DateTimeFormat('en-US', {
timeZone: 'Asia/Shanghai',
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hourCycle: 'h23'
}).formatToParts(new Date(timestamp));
var values = {};
parts.forEach(function (part) { values[part.type] = part.value; });
return values.year + '-' + values.month + '-' + values.day + ' ' + values.hour + ':' + values.minute + ':' + values.second;
}
function isOlderThan(value, milliseconds) {
var timestamp = Date.parse(value);
return !Number.isNaN(timestamp) && Date.now() - timestamp > milliseconds;
}
function updateNotice() {
var messages = [];
if (state.syncLog) messages.push('最后成功运行:' + state.syncLog.displayTime);
var log = state.syncLog;
var lastSuccessAt = log && log.lastSuccessAt ? formatShanghaiTime(log.lastSuccessAt) : null;
var lastDataUpdateAt = log && log.lastDataUpdateAt ? formatShanghaiTime(log.lastDataUpdateAt) : null;
var dataIsStale = false;
messages.push('最后成功运行:' + (lastSuccessAt || '暂无记录'));
messages.push('数据最后更新:' + (lastDataUpdateAt || '暂无记录'));
if (state.syncLogUnavailable) messages.push('运行日志暂不可用。');
if (log && log.lastSuccessAt && isOlderThan(log.lastSuccessAt, 24 * 60 * 60 * 1000)) {
messages.push('最后成功运行已超过24小时,请刷新页面或检查自动任务。');
}
if (log && log.lastDataUpdateAt) {
dataIsStale = isOlderThan(log.lastDataUpdateAt, 48 * 60 * 60 * 1000);
if (dataIsStale) messages.push('数据已超过48小时未更新,请检查数据同步。');
} else {
messages.push('没有数据更新时间记录。');
}
if (state.dateOnlyRows) messages.push('已过滤 ' + formatNumber(state.dateOnlyRows) + ' 条只有 Date、Commission 和 GMV 均为空的日期行。');
if (state.invalidCurrencyRows) messages.push('已排除 ' + formatNumber(state.invalidCurrencyRows) + ' 条币种为空、Unknown 或格式无效且包含金额的记录,请检查原始数据。');
if (state.sourceCurrencies.length > 1) messages.push('检测到多个已知源币种(' + state.sourceCurrencies.join('、') + '),已统一换算为 USD,不能切换到 IDR。');
elements.warning.textContent = messages.join(' ');
elements.warning.textContent = messages.join('\n');
elements.warning.dataset.stale = dataIsStale ? 'true' : 'false';
elements.warning.hidden = messages.length === 0;
}
function parseSyncLog(payload) {
if (!payload || typeof payload.last_success_at !== 'string' || !payload.last_success_at.trim() || Number.isNaN(Date.parse(payload.last_success_at))) {
throw new Error('运行日志缺少有效的 last_success_at。');
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
throw new Error('运行日志格式无效。');
}
var shanghaiTime = typeof payload.last_success_at_shanghai === 'string' ? payload.last_success_at_shanghai.trim() : '';
var displayTime = shanghaiTime && !Number.isNaN(Date.parse(shanghaiTime)) ? shanghaiTime : payload.last_success_at;
return { displayTime: displayTime };
function parseTimestamp(field) {
var value = payload[field];
if (value === null || value === undefined || value === '') return null;
if (typeof value !== 'string' || Number.isNaN(Date.parse(value))) {
throw new Error('运行日志缺少有效的 ' + field + '。');
}
return value;
}
return {
lastSuccessAt: parseTimestamp('last_success_at'),
lastDataUpdateAt: parseTimestamp('last_data_update_at')
};
}
function setRateStatus(message) { elements.rateStatus.textContent = message; }