137 lines
5.4 KiB
JavaScript
137 lines
5.4 KiB
JavaScript
const EARTH_OOBE_STATUS_API = "/api/v1/earth/oobe-status";
|
||
const EARTH_OOBE_SKIP_KEY = "planet-earth-oobe-skip-until-v1";
|
||
const EARTH_OOBE_SKIP_MS = 6 * 60 * 60 * 1000;
|
||
|
||
function getAuthToken() {
|
||
try {
|
||
const raw = window.localStorage?.getItem("auth-storage");
|
||
if (!raw) return "";
|
||
const parsed = JSON.parse(raw);
|
||
return parsed?.state?.token || parsed?.token || "";
|
||
} catch {
|
||
return "";
|
||
}
|
||
}
|
||
|
||
function isTemporarilySkipped() {
|
||
try {
|
||
const value = Number(window.localStorage?.getItem(EARTH_OOBE_SKIP_KEY) || 0);
|
||
return Number.isFinite(value) && value > Date.now();
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function markTemporarilySkipped() {
|
||
try {
|
||
window.localStorage?.setItem(EARTH_OOBE_SKIP_KEY, String(Date.now() + EARTH_OOBE_SKIP_MS));
|
||
} catch {
|
||
// Local storage is only a soft "remind later" hint.
|
||
}
|
||
}
|
||
|
||
function escapeAttribute(value = "") {
|
||
return String(value)
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
async function fetchOobeStatus() {
|
||
const token = getAuthToken();
|
||
const headers = token ? { Authorization: `Bearer ${token}` } : {};
|
||
const response = await fetch(EARTH_OOBE_STATUS_API, { cache: "no-store", headers });
|
||
if (!response.ok) throw new Error(`Earth OOBE status request failed: ${response.status}`);
|
||
return response.json();
|
||
}
|
||
|
||
function statusSteps(status) {
|
||
const steps = [
|
||
{ label: "后端服务在线", done: true },
|
||
{ label: "登录控制台", done: Boolean(status.authenticated), warn: !status.authenticated },
|
||
{ label: "配置或确认数据源", done: Number(status.datasource_count || 0) > 0 || Number(status.custom_config_count || 0) > 0 },
|
||
{ label: "触发首次采集", done: Boolean(status.has_collected_data), warn: !status.has_collected_data },
|
||
{ label: "回到 Earth 查看结果", done: Boolean(status.ready) },
|
||
];
|
||
if (status.demo_mode) {
|
||
steps.unshift({ label: "演示模式已开启", done: true });
|
||
}
|
||
return steps;
|
||
}
|
||
|
||
function renderOobe(status) {
|
||
const authenticated = Boolean(status.authenticated);
|
||
const demoMode = Boolean(status.demo_mode);
|
||
const primaryHref = authenticated ? status.datasources_url || "/datasources" : status.login_url || "/login?next=/datasources";
|
||
const primaryText = authenticated ? "去采集数据" : "登录并采集数据";
|
||
const secondaryHref = status.collection_url || "/collection-management";
|
||
const docsHref = status.docs_url || "/docs/manual";
|
||
const subtitle = demoMode
|
||
? "演示模式已开启,本次访问将直接展示初始化引导,不受现有采集数据影响。"
|
||
: authenticated
|
||
? "当前还没有检测到可展示的数据,可以直接进入后台触发首次采集。"
|
||
: "登录控制台并完成首次采集后,Earth 将显示实时数据层。";
|
||
const steps = statusSteps(status)
|
||
.map((step, index) => {
|
||
const state = step.done ? "done" : step.warn ? "warn" : "pending";
|
||
return `
|
||
<li class="earth-oobe__step earth-oobe__step--${state}" style="--step-index: ${index}">
|
||
<span class="earth-oobe__step-dot" aria-hidden="true"></span>
|
||
<span>${step.label}</span>
|
||
</li>
|
||
`;
|
||
})
|
||
.join("");
|
||
|
||
const root = document.createElement("div");
|
||
root.className = "earth-oobe";
|
||
root.setAttribute("role", "dialog");
|
||
root.setAttribute("aria-modal", "true");
|
||
root.setAttribute("aria-labelledby", "earth-oobe-title");
|
||
root.innerHTML = `
|
||
<div class="earth-oobe__scrim" data-oobe-close></div>
|
||
<section class="earth-oobe__panel">
|
||
<div class="earth-oobe__scan" aria-hidden="true"></div>
|
||
<button type="button" class="earth-oobe__close" aria-label="先浏览 Earth" data-oobe-close>×</button>
|
||
<div class="earth-oobe__eyebrow">SYSTEM INITIALIZATION</div>
|
||
<h2 id="earth-oobe-title">欢迎使用智能星球计划</h2>
|
||
<p class="earth-oobe__subtitle">${subtitle}</p>
|
||
<ul class="earth-oobe__steps">${steps}</ul>
|
||
<div class="earth-oobe__stats">
|
||
<span><strong>${Number(status.current_record_count || 0)}</strong> 当前记录</span>
|
||
<span><strong>${Number(status.tv_source_count || 0)}</strong> 直播源</span>
|
||
<span><strong>${Number(status.active_datasource_count || 0)}</strong> 活跃数据源</span>
|
||
</div>
|
||
<div class="earth-oobe__actions">
|
||
<a class="earth-oobe__primary" href="${escapeAttribute(primaryHref)}">${primaryText}</a>
|
||
${authenticated ? `<a class="earth-oobe__secondary" href="${escapeAttribute(secondaryHref)}">进入采集管理</a>` : ""}
|
||
${authenticated ? `<a class="earth-oobe__secondary" href="${escapeAttribute(docsHref)}">查看文档</a>` : ""}
|
||
<button type="button" class="earth-oobe__ghost" data-oobe-close>先浏览 Earth</button>
|
||
</div>
|
||
</section>
|
||
`;
|
||
|
||
root.querySelectorAll("[data-oobe-close]").forEach((element) => {
|
||
element.addEventListener("click", () => {
|
||
markTemporarilySkipped();
|
||
root.classList.add("earth-oobe--closing");
|
||
window.setTimeout(() => root.remove(), 220);
|
||
});
|
||
});
|
||
|
||
return root;
|
||
}
|
||
|
||
export async function initEarthOobe() {
|
||
try {
|
||
const status = await fetchOobeStatus();
|
||
const demoMode = Boolean(status?.demo_mode);
|
||
if ((status?.ready && !demoMode) || (isTemporarilySkipped() && !demoMode)) return;
|
||
document.body.appendChild(renderOobe(status));
|
||
} catch (error) {
|
||
console.warn("Earth OOBE status unavailable.", error);
|
||
}
|
||
}
|