mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-12 03:37:51 +08:00
493 lines
21 KiB
HTML
493 lines
21 KiB
HTML
<%#
|
||
luci-app-openclaw — Web 控制台页面
|
||
打开 OpenClaw 官方 Web UI,用户可在此配置模型/渠道并直接对话
|
||
-%>
|
||
<%+header%>
|
||
|
||
<%
|
||
local uci = require "luci.model.uci".cursor()
|
||
local console_utils = require "openclaw.console"
|
||
local port = uci:get("openclaw", "main", "port") or "18789"
|
||
local raw_console_url = tostring(uci:get("openclaw", "main", "console_url") or ""):gsub("^%s+", ""):gsub("%s+$", "")
|
||
local https_console_url = console_utils.normalize_https_url(raw_console_url) or ""
|
||
%>
|
||
|
||
<style type="text/css">
|
||
.oc-page-header { margin: 0 0 16px 0; }
|
||
.oc-page-header h2 { font-size: 18px; font-weight: 600; color: inherit; margin: 0 0 6px 0; }
|
||
.oc-page-header p { font-size: 13px; color: inherit; opacity: 0.76; margin: 0; line-height: 1.6; }
|
||
|
||
.oc-console-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 10px 16px;
|
||
margin-bottom: 16px;
|
||
background: #f6f8fa;
|
||
border: 1px solid #e1e4e8;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
color: #555;
|
||
flex-wrap: wrap;
|
||
}
|
||
.oc-console-info .label { color: #888; }
|
||
.oc-console-info .value { font-family: monospace; color: #333; font-weight: 500; }
|
||
.oc-console-info .sep { color: #ddd; }
|
||
.oc-console-actions {
|
||
margin-left: auto;
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
.oc-console-actions .btn-action {
|
||
padding: 4px 12px;
|
||
background: #f0f2f5;
|
||
color: #333;
|
||
border: 1px solid #d0d7de;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
text-decoration: none;
|
||
transition: background 0.2s;
|
||
}
|
||
.oc-console-actions .btn-action:hover {
|
||
background: #e1e4e8;
|
||
text-decoration: none;
|
||
}
|
||
.oc-console-actions .btn-open {
|
||
padding: 4px 14px;
|
||
background: #4a90d9;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
text-decoration: none;
|
||
}
|
||
.oc-console-actions .btn-open:hover {
|
||
background: #357abd;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.oc-console-wrap {
|
||
border: 1px solid rgba(127, 127, 127, 0.35);
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||
position: relative;
|
||
}
|
||
|
||
/* OpenClaw Control UI 固定返回 X-Frame-Options: DENY 与 CSP frame-ancestors 'none',
|
||
浏览器禁止任何页面内嵌它,这里改为在独立窗口打开 (#108) */
|
||
.oc-console-launcher {
|
||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||
padding: 40px 20px; text-align: center; gap: 12px;
|
||
}
|
||
.oc-console-launcher .oc-launch-title { font-size: 16px; font-weight: 600; }
|
||
.oc-console-launcher .oc-launch-desc { font-size: 13px; opacity: 0.8; line-height: 1.7; max-width: 640px; }
|
||
.oc-console-launcher .oc-launch-btn {
|
||
display: inline-block; padding: 10px 28px; background: #4a90d9; color: #fff;
|
||
border-radius: 6px; font-size: 15px; font-weight: 600; text-decoration: none;
|
||
}
|
||
.oc-console-launcher .oc-launch-btn:hover { background: #357abd; text-decoration: none; }
|
||
|
||
.oc-console-loading {
|
||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||
height: 360px; color: #666; font-size: 14px; background: #fafafa;
|
||
}
|
||
.oc-console-loading .spinner {
|
||
width: 32px; height: 32px; border: 3px solid #e0e0e0; border-top: 3px solid #4a90d9;
|
||
border-radius: 50%; animation: oc-spin .8s linear infinite; margin-bottom: 12px;
|
||
}
|
||
@keyframes oc-spin { to { transform: rotate(360deg); } }
|
||
</style>
|
||
|
||
<div class="oc-page-header">
|
||
<h2>🖥️ Web 控制台</h2>
|
||
<p>OpenClaw 官方 Web 管理界面 — 在这里可以配置 AI 模型、消息渠道(QQ、Telegram、Discord 等),直接与 AI 进行对话,以及管理所有功能。</p>
|
||
</div>
|
||
|
||
<div class="oc-console-info">
|
||
<span class="label">网关地址:</span>
|
||
<span class="value" id="oc-console-addr">-</span>
|
||
<span class="sep">|</span>
|
||
<span class="label">活跃模型:</span>
|
||
<span class="value" id="oc-console-model" style="color:#555;">-</span>
|
||
<span class="sep">|</span>
|
||
<span class="label">状态:</span>
|
||
<span id="oc-console-status-text">检查中...</span>
|
||
<div class="oc-console-actions" id="oc-console-actions" style="display:none;">
|
||
<button type="button" id="oc-console-pairing-toggle-btn" class="btn-action" title="管理/批准设备配对请求">📱 设备配对 <span id="oc-console-pairing-nav-badge" style="display:none; background:#ff4d4f; color:#fff; padding:0 6px; border-radius:10px; font-size:11px; font-weight:600; margin-left:2px;">0</span></button>
|
||
<a id="oc-console-open-btn" class="btn-open" href="#" target="_blank" rel="noopener noreferrer" title="新窗口打开">
|
||
↗ 新窗口直达
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="oc-pairing-banner" style="display:none; padding:12px 16px; margin-bottom:14px; background:#fffbe6; border:1px solid #ffe58f; border-left:4px solid #faad14; border-radius:6px;">
|
||
<div style="display:flex; align-items:flex-start; justify-content:space-between; gap:12px; flex-wrap:wrap;">
|
||
<div style="flex:1; min-width:280px;">
|
||
<div style="font-weight:600; color:#d48806; font-size:14px; margin-bottom:4px; display:flex; align-items:center; gap:8px;">
|
||
<span>🔔 检测到待配对设备请求</span>
|
||
<span id="oc-pairing-badge" style="background:#faad14; color:#fff; padding:1px 7px; border-radius:10px; font-size:11px;">0</span>
|
||
</div>
|
||
<div style="font-size:13px; color:#555; line-height:1.5; margin-bottom:6px;">
|
||
首次访问 Control UI 时网关需要主机批准该浏览器。点击「一键批准配对」即可完成授权并进入控制台。
|
||
</div>
|
||
<div style="font-size:12px; color:#cf1322; background:#fff1f0; border:1px solid #ffa39e; padding:6px 10px; border-radius:4px; line-height:1.4; margin-bottom:6px;">
|
||
⚠️ <strong>风险提示</strong>:批准设备配对后,该浏览器将获得 OpenClaw 网关的完全控制权限。请确保在受信任的局域网环境,并仅在您本人正在连接时批准。
|
||
</div>
|
||
<div id="oc-pairing-items" style="font-size:12px; font-family:monospace; color:#333; line-height:1.6;"></div>
|
||
</div>
|
||
<div style="display:flex; gap:8px; align-items:center; margin-top:4px;">
|
||
<button type="button" id="oc-pairing-approve-all-btn" class="btn-open" style="background:#52c41a; font-weight:600; padding:6px 14px;">
|
||
⚡ 一键批准配对
|
||
</button>
|
||
<button type="button" id="oc-pairing-refresh-btn" class="btn-action" style="padding:6px 10px;">
|
||
🔄 刷新
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div id="oc-pairing-msg" style="display:none; margin-top:8px; font-size:13px; font-weight:500;"></div>
|
||
</div>
|
||
|
||
<div id="oc-console-https-hint" style="display:none; padding:10px 14px; margin-bottom:14px; background:#fffbe6; border:1px solid #ffe58f; border-radius:6px; font-size:13px; color:#d48806; line-height:1.6;">
|
||
⚠️ <strong>浏览器安全提示</strong>:当前正在通过 HTTPS 访问 LuCI,而 OpenClaw 网关默认提供 HTTP 服务。在新窗口打开 HTTP 控制台时浏览器可能提示「不安全」,如需全程 HTTPS,请在基本设置中配置 HTTPS 控制台地址(反向代理)。
|
||
</div>
|
||
|
||
<div id="oc-console-context-hint" style="display:none; padding:10px 14px; margin-bottom:14px; background:#e6f4ff; border:1px solid #91caff; border-left:4px solid #1677ff; border-radius:6px; font-size:13px; color:#0958d9; line-height:1.6;">
|
||
<div style="font-weight:600; margin-bottom:4px; display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px;">
|
||
<span>💡 访问条件提示(图片/附件与安全上下文)</span>
|
||
<a id="oc-console-direct-btn" href="#" target="_blank" rel="noopener noreferrer" class="btn-action" style="background:#fff; color:#0958d9; border-color:#91caff; font-weight:normal; text-decoration:none; display:inline-flex; align-items:center; gap:4px;">
|
||
↗ 打开控制台
|
||
</a>
|
||
</div>
|
||
<div id="oc-console-context-detail" style="font-size:12px; color:#333;">
|
||
HTTP 局域网地址不属于浏览器安全上下文,图片粘贴、附件拖拽、麦克风和剪贴板能力可能不可用。<br/>
|
||
<strong>完整附件功能请通过 SSH localhost 隧道访问顶级页面:</strong><br/>
|
||
<code id="oc-console-tunnel-command"></code><br/>
|
||
然后打开 <code id="oc-console-tunnel-url">http://localhost:18790/</code>。若本地 18790 端口被占用,请将命令和地址中的 18790 一并改为空闲端口。<br/>
|
||
配置了可信 HTTPS 控制台地址时,「打开控制台」仅打开该地址,不会自动传递 Gateway Token。
|
||
</div>
|
||
</div>
|
||
|
||
<span id="oc-console-config" data-console-url="<%=console_utils.html_attr(https_console_url)%>" style="display:none;"></span>
|
||
|
||
<div class="oc-console-wrap" id="oc-console-wrap">
|
||
<div id="oc-console-container">
|
||
<div class="oc-console-loading" id="oc-console-loading">
|
||
<div class="spinner"></div>
|
||
<span>正在连接 OpenClaw 控制台...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
//<![CDATA[
|
||
(function() {
|
||
var gwPort = '<%=port%>';
|
||
var consoleConfigEl = document.getElementById('oc-console-config');
|
||
var uciHttpsConsoleUrl = consoleConfigEl ? (consoleConfigEl.getAttribute('data-console-url') || '') : '';
|
||
var gwToken = '';
|
||
var statusUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "status_api")%>';
|
||
var tokenUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "get_token")%>';
|
||
var ocCsrfToken = '<%=token%>';
|
||
var container = document.getElementById('oc-console-container');
|
||
var loading = document.getElementById('oc-console-loading');
|
||
var addrEl = document.getElementById('oc-console-addr');
|
||
var modelEl = document.getElementById('oc-console-model');
|
||
var statusTextEl = document.getElementById('oc-console-status-text');
|
||
var actionsEl = document.getElementById('oc-console-actions');
|
||
var openBtn = document.getElementById('oc-console-open-btn');
|
||
var launcherEl = null;
|
||
var tunnelCommandEl = document.getElementById('oc-console-tunnel-command');
|
||
if (tunnelCommandEl) tunnelCommandEl.textContent = 'ssh -N -L 127.0.0.1:18790:127.0.0.1:' + gwPort + ' root@' + window.location.hostname;
|
||
|
||
function getGatewayBaseUrl() {
|
||
var host = window.location.hostname;
|
||
return 'http://' + host + ':' + gwPort + '/';
|
||
}
|
||
|
||
function appendGatewayToken(url) {
|
||
if (gwToken) {
|
||
url += (url.indexOf('#') === -1 ? '#' : '&') + 'token=' + encodeURIComponent(gwToken);
|
||
}
|
||
return url;
|
||
}
|
||
|
||
function getExternalConsoleUrl() {
|
||
if (uciHttpsConsoleUrl && uciHttpsConsoleUrl.indexOf('https://') === 0) return uciHttpsConsoleUrl;
|
||
return appendGatewayToken(getGatewayBaseUrl());
|
||
}
|
||
|
||
function updateContextHint() {
|
||
var hintEl = document.getElementById('oc-console-context-hint');
|
||
var directBtn = document.getElementById('oc-console-direct-btn');
|
||
if (directBtn) directBtn.href = getExternalConsoleUrl();
|
||
if (hintEl) hintEl.style.display = getGatewayBaseUrl().indexOf('http://localhost') === 0 || getGatewayBaseUrl().indexOf('http://127.0.0.1') === 0 ? 'none' : 'block';
|
||
}
|
||
|
||
var retryTimer = null;
|
||
function scheduleCheck(ms) {
|
||
if (retryTimer) clearTimeout(retryTimer);
|
||
retryTimer = setTimeout(function() {
|
||
retryTimer = null;
|
||
checkAndLoad();
|
||
}, ms || 3000);
|
||
}
|
||
|
||
function checkAndLoad() {
|
||
if (uciHttpsConsoleUrl && uciHttpsConsoleUrl.indexOf('https://') === 0) {
|
||
addrEl.textContent = uciHttpsConsoleUrl;
|
||
} else {
|
||
addrEl.textContent = window.location.hostname + ':' + gwPort;
|
||
}
|
||
|
||
(new XHR()).post(tokenUrl, { token: ocCsrfToken }, function(tx) {
|
||
try {
|
||
var td = JSON.parse(tx.responseText);
|
||
gwToken = td.token || '';
|
||
} catch(e) {}
|
||
|
||
(new XHR()).get(statusUrl, null, function(x) {
|
||
try {
|
||
var d = JSON.parse(x.responseText);
|
||
var externalUrl = getExternalConsoleUrl();
|
||
if (d.active_model) {
|
||
modelEl.textContent = d.active_model;
|
||
}
|
||
if (d.gateway_running) {
|
||
if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; }
|
||
statusTextEl.innerHTML = '<span style="color:#1a7f37;">● 网关运行中</span>';
|
||
openBtn.href = externalUrl;
|
||
actionsEl.style.display = 'flex';
|
||
updateContextHint();
|
||
if (location.protocol === 'https:' && externalUrl.indexOf('https://') !== 0) {
|
||
var hintEl = document.getElementById('oc-console-https-hint');
|
||
if (hintEl) hintEl.style.display = 'block';
|
||
}
|
||
showLauncher(externalUrl);
|
||
checkDevices(false);
|
||
setTimeout(function() { checkDevices(false); }, 2500);
|
||
setTimeout(function() { checkDevices(false); }, 6000);
|
||
} else if (d.gateway_failed) {
|
||
if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; }
|
||
statusTextEl.innerHTML = '<span style="color:#cf222e;">● 网关启动失败</span>';
|
||
actionsEl.style.display = 'none';
|
||
loading.style.display = '';
|
||
loading.innerHTML = '<div style="text-align:center;color:#666;">' +
|
||
'<div style="font-size:40px;margin-bottom:12px;">×</div>' +
|
||
'<div style="font-size:15px;margin-bottom:6px;">OpenClaw 网关启动失败</div>' +
|
||
'<div style="font-size:12px;color:#999;">退出码:' + (d.gateway_exit_code || '-') + '。请查看系统日志或安装日志。</div>' +
|
||
'</div>';
|
||
} else if (d.gateway_starting) {
|
||
statusTextEl.innerHTML = '<span style="color:#9a6700;">⏳ 网关正在启动</span>';
|
||
actionsEl.style.display = 'none';
|
||
loading.style.display = '';
|
||
loading.innerHTML = '<div style="text-align:center;color:#666;">' +
|
||
'<div style="font-size:40px;margin-bottom:12px;">⏳</div>' +
|
||
'<div style="font-size:15px;margin-bottom:6px;">OpenClaw 网关正在启动中...</div>' +
|
||
'<div style="font-size:12px;color:#999;">通常 20~40 秒;首次安装或路由器负载较高时会更久,页面会自动刷新。</div>' +
|
||
'</div>';
|
||
scheduleCheck(3000);
|
||
} else {
|
||
statusTextEl.innerHTML = '<span style="color:#cf222e;">● 网关未运行</span>';
|
||
actionsEl.style.display = 'none';
|
||
loading.style.display = '';
|
||
loading.innerHTML = '<div style="text-align:center;color:#666;">' +
|
||
'<div style="font-size:40px;margin-bottom:12px;">🧠</div>' +
|
||
'<div style="font-size:15px;margin-bottom:6px;">OpenClaw 网关未运行</div>' +
|
||
'<div style="font-size:12px;color:#999;">请先在「基本设置」页面启用服务并启动。</div>' +
|
||
'</div>';
|
||
scheduleCheck(5000);
|
||
}
|
||
} catch(e) {
|
||
statusTextEl.textContent = '查询失败';
|
||
scheduleCheck(5000);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// OpenClaw Control UI 对所有响应固定设置 X-Frame-Options: DENY 与 frame-ancestors 'none'
|
||
// (上游无配置项可关闭),任何 iframe 内嵌都会被浏览器拒绝并显示「拒绝连接」(#108)。
|
||
// 因此只提供顶级窗口入口;顶级页面同时也是图片/附件等安全上下文能力的前提。
|
||
function showLauncher(url) {
|
||
loading.style.display = 'none';
|
||
if (!launcherEl) {
|
||
launcherEl = document.createElement('div');
|
||
launcherEl.className = 'oc-console-launcher';
|
||
launcherEl.innerHTML =
|
||
'<div style="font-size:40px;">🦞</div>' +
|
||
'<div class="oc-launch-title">OpenClaw 控制台已就绪</div>' +
|
||
'<a id="oc-console-launch-btn" class="oc-launch-btn" href="#" target="_blank" rel="noopener noreferrer">↗ 在新窗口打开控制台</a>' +
|
||
'<div class="oc-launch-desc">OpenClaw 官方控制台出于安全策略禁止被嵌入其它页面(X-Frame-Options: DENY),' +
|
||
'因此需要在独立窗口中使用。首次打开若提示「需要设备配对」,回到本页点击上方「📱 设备配对」批准后,在控制台窗口刷新即可。</div>';
|
||
container.appendChild(launcherEl);
|
||
}
|
||
var launchBtn = document.getElementById('oc-console-launch-btn');
|
||
if (launchBtn) launchBtn.href = url;
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
return String(str || '')
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
var devicesListUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "devices_list")%>';
|
||
var devicesApproveUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "devices_approve")%>';
|
||
var pairingBanner = document.getElementById('oc-pairing-banner');
|
||
var pairingBadge = document.getElementById('oc-pairing-badge');
|
||
var pairingItems = document.getElementById('oc-pairing-items');
|
||
var pairingMsg = document.getElementById('oc-pairing-msg');
|
||
var pairingApproveAllBtn = document.getElementById('oc-pairing-approve-all-btn');
|
||
var pairingRefreshBtn = document.getElementById('oc-pairing-refresh-btn');
|
||
var pairingToggleBtn = document.getElementById('oc-console-pairing-toggle-btn');
|
||
|
||
function showPairingItemsMessage(message, color) {
|
||
pairingItems.textContent = '';
|
||
var line = document.createElement('div');
|
||
line.style.color = color || '#cf222e';
|
||
line.style.fontSize = '12px';
|
||
line.textContent = message;
|
||
pairingItems.appendChild(line);
|
||
}
|
||
|
||
function checkDevices(manualOpen) {
|
||
(new XHR()).get(devicesListUrl, null, function(x) {
|
||
try {
|
||
var data = JSON.parse(x.responseText);
|
||
if (!data || data.status !== 'ok') {
|
||
if (pairingBadge) pairingBadge.textContent = '!';
|
||
var errMsg = (data && data.message) || '查询设备列表失败';
|
||
if (manualOpen) {
|
||
showPairingItemsMessage('❌ ' + errMsg);
|
||
pairingBanner.style.display = 'block';
|
||
}
|
||
return;
|
||
}
|
||
if (!Array.isArray(data.pending) || !Array.isArray(data.paired)) throw new Error('设备列表结构无效');
|
||
var pending = data.pending;
|
||
if (pairingBadge) pairingBadge.textContent = pending.length;
|
||
var navBadge = document.getElementById('oc-console-pairing-nav-badge');
|
||
if (navBadge) {
|
||
if (pending.length > 0) {
|
||
navBadge.textContent = pending.length;
|
||
navBadge.style.display = 'inline';
|
||
} else {
|
||
navBadge.style.display = 'none';
|
||
}
|
||
}
|
||
if (pending.length > 0) {
|
||
var html = '';
|
||
for (var i = 0; i < pending.length; i++) {
|
||
var item = pending[i];
|
||
var ip = escapeHtml(item.remoteIp || item.ip || '未知IP');
|
||
var client = escapeHtml(item.clientId || item.clientMode || 'webchat');
|
||
var plat = escapeHtml(item.platform || '');
|
||
var rid = escapeHtml(item.requestId || '');
|
||
html += '<div style="background:#fff; border:1px solid #e1e4e8; border-radius:4px; padding:6px 10px; margin-bottom:6px; display:flex; justify-content:space-between; align-items:center; gap:8px; flex-wrap:wrap;">' +
|
||
'<div><strong>IP:</strong> ' + ip + ' | <strong>客户端:</strong> ' + client + (plat ? ' (' + plat + ')' : '') + ' <br/><span style="color:#888; font-size:11px;">ID: ' + rid + '</span></div>' +
|
||
'<button type="button" class="btn-action btn-approve-single" data-rid="' + rid + '" style="font-size:12px; padding:3px 10px; background:#f6ffed; border-color:#b7eb8f; color:#389e0d; cursor:pointer;">批准配对</button>' +
|
||
'</div>';
|
||
}
|
||
pairingItems.innerHTML = html;
|
||
pairingBanner.style.display = 'block';
|
||
|
||
var singleBtns = pairingItems.querySelectorAll('.btn-approve-single');
|
||
for (var j = 0; j < singleBtns.length; j++) {
|
||
singleBtns[j].addEventListener('click', function(ev) {
|
||
var rid = this.getAttribute('data-rid');
|
||
approveDevice(rid);
|
||
});
|
||
}
|
||
} else {
|
||
pairingItems.innerHTML = '<div style="color:#666; font-size:12px; padding:4px 0;">当前无待配对设备请求。当浏览器连接控制台提示「需要设备配对」时,在此刷新即可显示并批准。</div>';
|
||
if (manualOpen) {
|
||
pairingBanner.style.display = 'block';
|
||
} else {
|
||
pairingBanner.style.display = 'none';
|
||
}
|
||
}
|
||
} catch(e) {
|
||
if (manualOpen) {
|
||
showPairingItemsMessage('查询设备列表失败');
|
||
pairingBanner.style.display = 'block';
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function approveDevice(rid) {
|
||
pairingMsg.style.display = 'block';
|
||
pairingMsg.style.color = '#1890ff';
|
||
pairingMsg.textContent = '⏳ 正在批准设备配对...';
|
||
var postData = { token: ocCsrfToken };
|
||
if (rid) {
|
||
postData.request_id = rid;
|
||
} else {
|
||
postData.all = '1';
|
||
}
|
||
(new XHR()).post(devicesApproveUrl, postData, function(x) {
|
||
try {
|
||
var res = JSON.parse(x.responseText);
|
||
if (res.status === 'ok') {
|
||
if ((res.success_count || 0) > 0) {
|
||
pairingMsg.style.color = '#52c41a';
|
||
pairingMsg.textContent = '✅ ' + (res.message || '批准成功!') + ' 请在控制台窗口刷新页面。';
|
||
setTimeout(function() {
|
||
checkDevices(false);
|
||
}, 1500);
|
||
} else {
|
||
pairingMsg.style.color = '#faad14';
|
||
pairingMsg.textContent = 'ℹ️ ' + (res.message || '当前没有待配对的设备请求');
|
||
setTimeout(function() {
|
||
checkDevices(false);
|
||
}, 1500);
|
||
}
|
||
} else if (res.status === 'partial' || res.status === 'unconfirmed') {
|
||
pairingMsg.style.color = '#d48806';
|
||
pairingMsg.textContent = '⚠️ ' + (res.message || '部分设备状态尚未确认,请刷新后重试');
|
||
setTimeout(function() { checkDevices(true); }, 1500);
|
||
} else {
|
||
pairingMsg.style.color = '#cf222e';
|
||
pairingMsg.textContent = '❌ ' + (res.message || '批准失败');
|
||
}
|
||
} catch(e) {
|
||
pairingMsg.style.color = '#cf222e';
|
||
pairingMsg.textContent = '❌ 批准失败:请求异常';
|
||
}
|
||
});
|
||
}
|
||
|
||
if (pairingApproveAllBtn) {
|
||
pairingApproveAllBtn.addEventListener('click', function() {
|
||
approveDevice(null);
|
||
});
|
||
}
|
||
if (pairingRefreshBtn) {
|
||
pairingRefreshBtn.addEventListener('click', function() {
|
||
checkDevices(true);
|
||
});
|
||
}
|
||
if (pairingToggleBtn) {
|
||
pairingToggleBtn.addEventListener('click', function() {
|
||
if (pairingBanner.style.display === 'none') {
|
||
checkDevices(true);
|
||
} else {
|
||
pairingBanner.style.display = 'none';
|
||
}
|
||
});
|
||
}
|
||
|
||
checkAndLoad();
|
||
})();
|
||
//]]>
|
||
</script>
|
||
|
||
<%+footer%>
|