Files
planet/frontend/public/earth/js/oobe.js
rayd1o 5bf5c73ca0
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
release: bump version to 0.66.0
2026-05-26 03:41:47 +08:00

137 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
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);
}
}