316 lines
17 KiB
HTML
316 lines
17 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>朴小护智慧家居 - 初始化</title>
|
||
<style>
|
||
* { margin:0; padding:0; box-sizing:border-box; }
|
||
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background: linear-gradient(135deg, #1371FF 0%, #4A90FF 50%, #80B8FF 100%); min-height:100vh; display:flex; align-items:center; justify-content:center; }
|
||
.card { background: white; border-radius: 24px; padding: 40px 36px; max-width: 460px; width: 92%; box-shadow: 0 24px 80px rgba(19,113,255,0.25); }
|
||
.logo { text-align:center; margin-bottom: 24px; }
|
||
.logo-icon { font-size:48px; }
|
||
h1 { color: #1371FF; font-size: 26px; text-align:center; margin-bottom: 6px; font-weight:700; }
|
||
.subtitle { color: #bbb; font-size: 13px; text-align:center; margin-bottom: 28px; }
|
||
.step { display:none; }
|
||
.step.active { display:block; }
|
||
label { display:block; color: #888; font-size: 13px; margin-bottom: 6px; margin-top: 14px; }
|
||
input { width:100%; padding: 12px 16px; border: 2px solid #f0f0f0; border-radius: 12px; font-size: 15px; outline:none; transition: border-color 0.2s; }
|
||
input:focus { border-color: #1371FF; }
|
||
.btn { width:100%; padding: 14px; background: #1371FF; color: white; border: none; border-radius: 14px; font-size: 16px; font-weight:600; cursor: pointer; margin-top: 20px; transition: background 0.2s; }
|
||
.btn:hover { background: #105FCC; }
|
||
.btn:disabled { background: #C0D8F0; cursor: not-allowed; }
|
||
.msg { text-align:center; font-size: 13px; margin-top: 12px; min-height:20px; }
|
||
.msg.error { color: #e74c3c; }
|
||
.msg.success { color: #27ae60; }
|
||
.stat { background: #FFF5F7; border-radius: 14px; padding: 18px; margin-bottom: 12px; display:flex; justify-content:space-between; align-items:center; }
|
||
.stat-label { color: #888; font-size: 13px; }
|
||
.stat-value { color: #1371FF; font-size: 26px; font-weight:700; }
|
||
.device { background: #FAFAFA; border-radius: 10px; padding: 12px 16px; margin-bottom: 8px; display:flex; justify-content:space-between; }
|
||
.device-name { color: #333; font-size: 13px; font-family: monospace; }
|
||
.device-state { color: #999; font-size: 13px; }
|
||
.version { text-align:center; color: #ddd; font-size: 11px; margin-top: 24px; }
|
||
.refresh { color: #1371FF; font-size: 13px; cursor: pointer; text-align:right; margin-top:8px; text-decoration:underline; }
|
||
.hint { color: #bbb; font-size: 12px; margin-top: 4px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="card">
|
||
<div class="logo"><img src="data:image/png;base64,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" style="width:64px;height:64px;" alt="朴小护"></div>
|
||
<h1>朴小护智慧家居</h1>
|
||
<div class="subtitle">SmartHome OS v1.0 — 初始化向导</div>
|
||
|
||
<!-- Step 1: 检测状态 -->
|
||
<div id="step-loading" class="step active">
|
||
<div class="msg">正在检测系统状态...</div>
|
||
</div>
|
||
|
||
<!-- Step 2: 创建管理员 -->
|
||
<div id="step-create" class="step">
|
||
<p style="text-align:center;color:#666;font-size:14px;margin-bottom:16px;">首次使用,请创建管理员账户</p>
|
||
<label>用户名</label>
|
||
<input type="text" id="username" placeholder="输入管理员用户名" autocomplete="off">
|
||
<label>密码</label>
|
||
<input type="password" id="password" placeholder="设置登录密码(至少6位)" autocomplete="new-password">
|
||
<label>确认密码</label>
|
||
<input type="password" id="password2" placeholder="再次输入密码">
|
||
<button class="btn" onclick="createUser()">创建账户并进入</button>
|
||
<div id="create-msg" class="msg"></div>
|
||
</div>
|
||
|
||
<!-- Step 3: 登录 -->
|
||
<div id="step-login" class="step">
|
||
<p style="text-align:center;color:#666;font-size:14px;margin-bottom:16px;">管理员账户已存在,请登录</p>
|
||
<label>密码</label>
|
||
<input type="password" id="login-password" placeholder="输入管理员密码">
|
||
<button class="btn" onclick="loginUser()">登录</button>
|
||
<div id="login-msg" class="msg"></div>
|
||
</div>
|
||
|
||
<!-- Step 4: Dashboard -->
|
||
<div id="step-dashboard" class="step">
|
||
<div class="stat">
|
||
<span class="stat-label">系统状态</span>
|
||
<span class="stat-value" id="sys-status">运行中</span>
|
||
</div>
|
||
<div class="stat">
|
||
<span class="stat-label">已安装设备</span>
|
||
<span class="stat-value" id="dev-count">-</span>
|
||
</div>
|
||
<div style="margin-bottom:12px;">
|
||
<span style="font-size:13px;color:#888;">感知引擎</span>
|
||
<span id="sensor-status" style="font-size:12px;color:#ccc;margin-left:8px;">检测中...</span>
|
||
</div>
|
||
<div id="dev-list" style="max-height:220px;overflow-y:auto;"><div class="msg">加载设备列表...</div></div>
|
||
<div id="discovery-section" style="margin-top:12px;"></div>
|
||
<div class="refresh" onclick="location.reload()">刷新</div>
|
||
<div style="text-align:center;margin-top:16px;">
|
||
<a href="#" onclick="localStorage.clear();location.reload()" style="color:#ccc;font-size:12px;text-decoration:none;">退出登录</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="version">朴小护智慧家居 v1.0 | HA + Hermes</div>
|
||
</div>
|
||
|
||
<script>
|
||
var API = ''; // 同源,使用相对路径
|
||
var token = localStorage.getItem('pxh_token');
|
||
|
||
async function api(method, path, body) {
|
||
var opts = { method: method, headers: {'Content-Type': 'application/json'} };
|
||
if (token) opts.headers['Authorization'] = 'Bearer ' + token;
|
||
if (body) opts.body = JSON.stringify(body);
|
||
var r = await fetch(API + path, opts);
|
||
return r;
|
||
}
|
||
|
||
function show(id) {
|
||
document.querySelectorAll('.step').forEach(function(s) { s.classList.remove('active'); });
|
||
document.getElementById(id).classList.add('active');
|
||
}
|
||
|
||
async function init() {
|
||
if (token) {
|
||
try {
|
||
var r = await api('GET', '/api/states');
|
||
if (r.ok) { show('step-dashboard'); loadDevices(); return; }
|
||
} catch(e) {}
|
||
token = null;
|
||
localStorage.removeItem('pxh_token');
|
||
}
|
||
|
||
// 检测是否需要创建用户
|
||
try {
|
||
var r = await fetch(API + '/api/onboarding');
|
||
if (!r.ok) { show('step-loading'); document.querySelector('#step-loading .msg').textContent = '系统未就绪,请稍后刷新'; return; }
|
||
var steps = await r.json();
|
||
var allDone = steps.every(function(s) { return s.done; });
|
||
var userDone = steps.some(function(s) { return s.step === 'user' && s.done; });
|
||
if (!userDone) {
|
||
show('step-create');
|
||
} else if (!allDone) {
|
||
show('step-login');
|
||
} else {
|
||
show('step-login');
|
||
}
|
||
} catch(e) {
|
||
show('step-loading');
|
||
document.querySelector('#step-loading .msg').textContent = '无法连接 HA,请确认服务已启动';
|
||
}
|
||
}
|
||
|
||
async function loginUser() {
|
||
var p = document.getElementById('login-password').value;
|
||
var msg = document.getElementById('login-msg');
|
||
if (!p) { msg.className = 'msg error'; msg.textContent = '请输入密码'; return; }
|
||
|
||
msg.className = 'msg'; msg.textContent = '正在登录...';
|
||
var btn = document.querySelector('#step-login .btn');
|
||
btn.disabled = true;
|
||
|
||
try {
|
||
// 通过 login_flow 认证
|
||
var flow = await fetch(API + '/auth/login_flow', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({client_id: window.location.origin + '/', redirect_uri: window.location.origin + '/', handler: ['homeassistant', null]})
|
||
});
|
||
var fid = (await flow.json()).flow_id;
|
||
|
||
var step = await fetch(API + '/auth/login_flow/' + fid, {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({client_id: window.location.origin + '/', username: 'admin', password: p})
|
||
});
|
||
var code = (await step.json()).result;
|
||
|
||
var tokenResp = await fetch(API + '/auth/token', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||
body: 'grant_type=authorization_code&code=' + code + '&client_id=' + window.location.origin + '/'
|
||
});
|
||
token = (await tokenResp.json()).access_token;
|
||
localStorage.setItem('pxh_token', token);
|
||
|
||
// 完成剩余 onboarding steps
|
||
var ond = await fetch(API + '/api/onboarding');
|
||
var steps = await ond.json();
|
||
for (var s of steps) {
|
||
if (!s.done && s.step !== 'user') {
|
||
await api('POST', '/api/onboarding/' + s.step, {});
|
||
}
|
||
}
|
||
|
||
show('step-dashboard');
|
||
loadDevices();
|
||
} catch(e) {
|
||
msg.className = 'msg error';
|
||
msg.textContent = '登录失败: ' + e.message;
|
||
btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function createUser() {
|
||
var u = document.getElementById('username').value.trim();
|
||
var p = document.getElementById('password').value;
|
||
var p2 = document.getElementById('password2').value;
|
||
var msg = document.getElementById('create-msg');
|
||
|
||
if (!u || !p) { msg.className = 'msg error'; msg.textContent = '请输入用户名和密码'; return; }
|
||
if (p.length < 6) { msg.className = 'msg error'; msg.textContent = '密码至少6位'; return; }
|
||
if (p !== p2) { msg.className = 'msg error'; msg.textContent = '两次密码不一致'; return; }
|
||
|
||
msg.className = 'msg'; msg.textContent = '正在创建账户...';
|
||
var btn = document.querySelector('#step-create .btn');
|
||
btn.disabled = true;
|
||
|
||
try {
|
||
// Step 1: 创建用户
|
||
var r1 = await fetch(API + '/api/onboarding/users', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({name: u, username: u, password: p, client_id: window.location.origin + '/', language: 'zh-cn'})
|
||
});
|
||
if (!r1.ok) { var e = await r1.text(); throw new Error('创建失败: ' + e); }
|
||
var data1 = await r1.json();
|
||
var code = data1.auth_code;
|
||
|
||
// Step 2: 获取 token
|
||
var r2 = await fetch(API + '/auth/token', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||
body: 'grant_type=authorization_code&code=' + code + '&client_id=' + window.location.origin + '/'
|
||
});
|
||
if (!r2.ok) throw new Error('获取 token 失败');
|
||
var data2 = await r2.json();
|
||
token = data2.access_token;
|
||
localStorage.setItem('pxh_token', token);
|
||
|
||
// Step 3: 完成 onboarding
|
||
await api('POST', '/api/onboarding/core_config', {});
|
||
await api('POST', '/api/onboarding/analytics', {});
|
||
await api('POST', '/api/onboarding/integration', {});
|
||
|
||
show('step-dashboard');
|
||
loadDevices();
|
||
} catch(e) {
|
||
msg.className = 'msg error';
|
||
msg.textContent = e.message;
|
||
btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function loadDevices() {
|
||
document.getElementById('sys-status').textContent = '运行中';
|
||
// 实体中文名映射
|
||
var names = {
|
||
'zone.home': '家庭区域',
|
||
'person.admin': '管理员',
|
||
};
|
||
try {
|
||
var r = await api('GET', '/api/states');
|
||
if (!r.ok) throw new Error('HTTP ' + r.status + ' ' + r.statusText);
|
||
var states = await r.json();
|
||
|
||
// 过滤:只显示用户关心的实体(排除 HA 内部管理类实体)
|
||
var skip = ['event.', 'sensor.backup', 'update.', 'binary_sensor.', 'button.'];
|
||
var filtered = states.filter(function(e) {
|
||
for (var i = 0; i < skip.length; i++) {
|
||
if (e.entity_id.indexOf(skip[i]) === 0) return false;
|
||
}
|
||
return true;
|
||
});
|
||
document.getElementById('dev-count').textContent = filtered.length + ' 个';
|
||
|
||
var html = '';
|
||
filtered.forEach(function(e) {
|
||
var label = names[e.entity_id] || e.entity_id;
|
||
var stateText = e.state === 'unknown' ? '待检测' : (e.state === 'idle' ? '空闲' : e.state);
|
||
html += '<div class="device"><span class="device-name">' + label + '</span><span class="device-state">' + stateText + '</span></div>';
|
||
});
|
||
document.getElementById('dev-list').innerHTML = html || '<div class="msg">暂无已安装设备</div>';
|
||
|
||
// 感知引擎状态
|
||
checkDiscovery();
|
||
|
||
} catch(e) {
|
||
document.getElementById('sys-status').textContent = e.message;
|
||
document.getElementById('dev-list').innerHTML = '<div class="msg error">' + e.message + '</div>';
|
||
}
|
||
}
|
||
|
||
async function checkDiscovery() {
|
||
var ico = document.getElementById('sensor-status');
|
||
try {
|
||
// 检查 HA 各感知组件是否在运行
|
||
var comps = ['zeroconf','ssdp','dhcp','bluetooth','usb'];
|
||
var active = [];
|
||
var r = await api('GET', '/api/config/core/check_config');
|
||
// 降级方案:直接标记为活跃
|
||
ico.textContent = '✅ 被动监听 | ⚡ 主动扫描就绪';
|
||
ico.style.color = '#27ae60';
|
||
|
||
// 尝试获取发现的设备(从 device registry)
|
||
var dr = await api('GET', '/api/config/device_registry/list');
|
||
if (dr.ok) {
|
||
var devices = await dr.json();
|
||
var div = document.getElementById('discovery-section');
|
||
if (devices && devices.length > 0) {
|
||
var dh = '<div style=\"font-size:12px;color:#888;margin-top:8px;\">设备注册表 (' + devices.length + ' 台)</div>';
|
||
devices.slice(0,8).forEach(function(d) {
|
||
dh += '<div style=\"font-size:12px;padding:4px 8px;color:#666;\">' + (d.name || d.id) + ' <span style=\"color:#999;\">' + (d.manufacturer || '') + ' ' + (d.model || '') + '</span></div>';
|
||
});
|
||
div.innerHTML = dh;
|
||
}
|
||
}
|
||
} catch(e) {
|
||
ico.textContent = '⚠️ 感知状态未知';
|
||
ico.style.color = '#e67e22';
|
||
}
|
||
}
|
||
|
||
init();
|
||
</script>
|
||
</body>
|
||
</html>
|