mirror of
https://github.com/hotwa/luci-app-openclaw.git
synced 2026-03-31 04:52:33 +00:00
release: v1.0.0 — LuCI 管理界面、一键安装、12+ AI 模型提供商
This commit is contained in:
121
luasrc/view/openclaw/advanced.htm
Normal file
121
luasrc/view/openclaw/advanced.htm
Normal file
@@ -0,0 +1,121 @@
|
||||
<%#
|
||||
luci-app-openclaw — 终端配置页面 (简洁版)
|
||||
-%>
|
||||
<%+header%>
|
||||
|
||||
<%
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
|
||||
%>
|
||||
|
||||
<style type="text/css">
|
||||
.oc-page-header { margin: 0 0 16px 0; }
|
||||
.oc-page-header h2 { font-size: 18px; font-weight: 600; color: #333; margin: 0 0 6px 0; }
|
||||
.oc-page-header p { font-size: 13px; color: #666; margin: 0; line-height: 1.6; }
|
||||
|
||||
.oc-info-box {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
background: #f6f8fa;
|
||||
border: 1px solid #e1e4e8;
|
||||
border-left: 4px solid #4a90d9;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
color: #555;
|
||||
}
|
||||
.oc-info-box ul { margin: 6px 0 0 0; padding-left: 18px; list-style: none; }
|
||||
.oc-info-box li { margin-bottom: 2px; }
|
||||
|
||||
.oc-terminal-wrap {
|
||||
border: 2px solid #2d333b;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #1a1b26;
|
||||
}
|
||||
|
||||
#oc-terminal-iframe { width: 100%; height: 600px; border: none; display: block; }
|
||||
|
||||
.oc-terminal-loading {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
height: 200px; color: #7aa2f7; font-size: 14px; background: #1a1b26;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="oc-page-header">
|
||||
<h2>⚙️ 终端配置</h2>
|
||||
<p>通过内嵌的交互式终端 (oc-config) 进行 OpenClaw 的完整配置管理。支持 AI 模型配置、消息渠道设置、健康检查等。</p>
|
||||
</div>
|
||||
|
||||
<div class="oc-info-box">
|
||||
<strong>💡 菜单功能说明:</strong>
|
||||
<ul>
|
||||
<li><strong>1)</strong> 查看当前配置 <strong>2)</strong> 配置 AI 模型提供商 <strong>3)</strong> 设定当前活跃模型</li>
|
||||
<li><strong>4)</strong> 配置消息渠道 <strong>5)</strong> Telegram 配对向导 <strong>6)</strong> 健康检查 / 诊断</li>
|
||||
<li><strong>7)</strong> 重启网关 <strong>8)</strong> 查看/编辑原始配置 <strong>9)</strong> 恢复默认配置</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="oc-terminal-wrap">
|
||||
<div id="oc-terminal-container">
|
||||
<div class="oc-terminal-loading" id="oc-terminal-loading">
|
||||
⏳ 正在连接配置终端...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
(function() {
|
||||
var ptyPort = '<%=pty_port%>';
|
||||
var statusUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "status_api")%>';
|
||||
var tokenUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "get_token")%>';
|
||||
var container = document.getElementById('oc-terminal-container');
|
||||
var loading = document.getElementById('oc-terminal-loading');
|
||||
var ptyToken = '';
|
||||
|
||||
function checkAndLoadTerminal() {
|
||||
// 先获取 PTY token
|
||||
(new XHR()).get(tokenUrl, null, function(tx) {
|
||||
try {
|
||||
var td = JSON.parse(tx.responseText);
|
||||
ptyToken = td.pty_token || '';
|
||||
} catch(e) {}
|
||||
|
||||
(new XHR()).get(statusUrl, null, function(x) {
|
||||
try {
|
||||
var d = JSON.parse(x.responseText);
|
||||
if (d.pty_running) {
|
||||
showIframe();
|
||||
} else {
|
||||
loading.innerHTML = '❌ 配置终端未运行<br/>' +
|
||||
'<span style="font-size:12px;color:#999;">请先在「基本设置」中启用并启动服务。</span>';
|
||||
}
|
||||
} catch(e) {
|
||||
loading.textContent = '检查终端状态失败';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showIframe() {
|
||||
var proto = window.location.protocol;
|
||||
var host = window.location.hostname;
|
||||
var url = proto + '//' + host + ':' + ptyPort + '/';
|
||||
if (ptyToken) url += '?pty_token=' + encodeURIComponent(ptyToken);
|
||||
|
||||
loading.style.display = 'none';
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.id = 'oc-terminal-iframe';
|
||||
iframe.src = url;
|
||||
iframe.setAttribute('allowfullscreen', 'true');
|
||||
container.appendChild(iframe);
|
||||
}
|
||||
|
||||
checkAndLoadTerminal();
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<%+footer%>
|
||||
168
luasrc/view/openclaw/console.htm
Normal file
168
luasrc/view/openclaw/console.htm
Normal file
@@ -0,0 +1,168 @@
|
||||
<%#
|
||||
luci-app-openclaw — Web 控制台页面
|
||||
嵌入 OpenClaw 官方 Web UI,用户可在此配置模型/渠道并直接对话
|
||||
-%>
|
||||
<%+header%>
|
||||
|
||||
<%
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local port = uci:get("openclaw", "main", "port") or "18789"
|
||||
%>
|
||||
|
||||
<style type="text/css">
|
||||
.oc-page-header { margin: 0 0 16px 0; }
|
||||
.oc-page-header h2 { font-size: 18px; font-weight: 600; color: #333; margin: 0 0 6px 0; }
|
||||
.oc-page-header p { font-size: 13px; color: #666; 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-info .btn-open {
|
||||
margin-left: auto;
|
||||
padding: 4px 14px;
|
||||
background: #4a90d9;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.oc-console-info .btn-open:hover { background: #357abd; }
|
||||
|
||||
.oc-console-wrap {
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
#oc-console-iframe { width: 100%; height: 700px; border: none; display: block; }
|
||||
|
||||
.oc-console-loading {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
height: 300px; 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 模型、消息渠道,直接与 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 id="oc-console-status-text">检查中...</span>
|
||||
<a id="oc-console-open-btn" class="btn-open" href="#" target="_blank" rel="noopener" style="display:none;">
|
||||
↗ 新窗口打开
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="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 gwToken = '';
|
||||
var statusUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "status_api")%>';
|
||||
var tokenUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "get_token")%>';
|
||||
var container = document.getElementById('oc-console-container');
|
||||
var loading = document.getElementById('oc-console-loading');
|
||||
var addrEl = document.getElementById('oc-console-addr');
|
||||
var statusTextEl = document.getElementById('oc-console-status-text');
|
||||
var openBtn = document.getElementById('oc-console-open-btn');
|
||||
|
||||
function getConsoleUrl() {
|
||||
var proto = window.location.protocol;
|
||||
var host = window.location.hostname;
|
||||
var url = proto + '//' + host + ':' + gwPort + '/';
|
||||
if (gwToken) url += '#token=' + encodeURIComponent(gwToken);
|
||||
return url;
|
||||
}
|
||||
|
||||
function checkAndLoad() {
|
||||
addrEl.textContent = window.location.hostname + ':' + gwPort;
|
||||
|
||||
// 先获取 token,再检查状态
|
||||
(new XHR()).get(tokenUrl, null, 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 url = getConsoleUrl();
|
||||
if (d.gateway_running) {
|
||||
statusTextEl.innerHTML = '<span style="color:#1a7f37;">● 网关运行中</span>';
|
||||
openBtn.href = url;
|
||||
openBtn.style.display = '';
|
||||
showIframe(url);
|
||||
} else {
|
||||
statusTextEl.innerHTML = '<span style="color:#cf222e;">● 网关未运行</span>';
|
||||
openBtn.style.display = 'none';
|
||||
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>';
|
||||
}
|
||||
} catch(e) {
|
||||
statusTextEl.textContent = '查询失败';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showIframe(url) {
|
||||
loading.style.display = 'none';
|
||||
var existing = document.getElementById('oc-console-iframe');
|
||||
if (existing) return;
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.id = 'oc-console-iframe';
|
||||
iframe.src = url;
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.height = '700px';
|
||||
iframe.style.border = 'none';
|
||||
iframe.setAttribute('allowfullscreen', 'true');
|
||||
container.appendChild(iframe);
|
||||
}
|
||||
|
||||
checkAndLoad();
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<%+footer%>
|
||||
138
luasrc/view/openclaw/status.htm
Normal file
138
luasrc/view/openclaw/status.htm
Normal file
@@ -0,0 +1,138 @@
|
||||
<%#
|
||||
luci-app-openclaw — 运行状态面板 (全面汉化 + 界面优化)
|
||||
-%>
|
||||
|
||||
<style type="text/css">
|
||||
#oc-status-panel {
|
||||
margin: 0 0 20px 0;
|
||||
padding: 0;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
#oc-status-panel .panel-title {
|
||||
background: linear-gradient(135deg, #4a90d9, #357abd);
|
||||
color: #fff;
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
#oc-status-panel .panel-body {
|
||||
padding: 0;
|
||||
}
|
||||
#oc-status-panel table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
#oc-status-panel td {
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
font-size: 13px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#oc-status-panel tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
#oc-status-panel td:first-child {
|
||||
width: 120px;
|
||||
color: #888;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#oc-status-panel td:last-child {
|
||||
color: #333;
|
||||
}
|
||||
.oc-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.oc-badge-running { background: #e6f7e9; color: #1a7f37; }
|
||||
.oc-badge-stopped { background: #ffeef0; color: #cf222e; }
|
||||
.oc-badge-disabled { background: #f0f0f0; color: #656d76; }
|
||||
.oc-badge-unknown { background: #fff8c5; color: #9a6700; }
|
||||
.oc-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
|
||||
.oc-dot-green { background: #1a7f37; }
|
||||
.oc-dot-red { background: #cf222e; }
|
||||
.oc-dot-gray { background: #999; }
|
||||
</style>
|
||||
|
||||
<div id="oc-status-panel">
|
||||
<div class="panel-title">🦞 OpenClaw 服务状态</div>
|
||||
<div class="panel-body">
|
||||
<table>
|
||||
<tr><td>运行状态</td><td id="oc-st-status"><span class="oc-badge oc-badge-unknown">加载中...</span></td></tr>
|
||||
<tr><td>网关服务</td><td id="oc-st-gateway">-</td></tr>
|
||||
<tr><td>配置终端</td><td id="oc-st-pty">-</td></tr>
|
||||
<tr><td>进程 PID</td><td id="oc-st-pid">-</td></tr>
|
||||
<tr><td>内存占用</td><td id="oc-st-mem">-</td></tr>
|
||||
<tr><td>运行时间</td><td id="oc-st-uptime">-</td></tr>
|
||||
<tr><td>Node.js</td><td id="oc-st-node">-</td></tr>
|
||||
<tr><td>OpenClaw</td><td id="oc-st-ocver">-</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
(function() {
|
||||
var statusUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "status_api")%>';
|
||||
|
||||
function updateStatus() {
|
||||
(new XHR()).get(statusUrl, null, function(x) {
|
||||
try {
|
||||
var d = JSON.parse(x.responseText);
|
||||
|
||||
var stEl = document.getElementById('oc-st-status');
|
||||
if (d.enabled !== '1') {
|
||||
stEl.innerHTML = '<span class="oc-badge oc-badge-disabled">已禁用</span>';
|
||||
} else if (d.gateway_running) {
|
||||
stEl.innerHTML = '<span class="oc-badge oc-badge-running">运行中</span>';
|
||||
} else {
|
||||
stEl.innerHTML = '<span class="oc-badge oc-badge-stopped">已停止</span>';
|
||||
}
|
||||
|
||||
var gwEl = document.getElementById('oc-st-gateway');
|
||||
if (d.gateway_running) {
|
||||
gwEl.innerHTML = '<span class="oc-dot oc-dot-green"></span>监听中 :' + d.port;
|
||||
} else {
|
||||
gwEl.innerHTML = '<span class="oc-dot oc-dot-red"></span>未监听';
|
||||
}
|
||||
|
||||
var ptyEl = document.getElementById('oc-st-pty');
|
||||
if (d.pty_running) {
|
||||
ptyEl.innerHTML = '<span class="oc-dot oc-dot-green"></span>监听中 :' + d.pty_port;
|
||||
} else {
|
||||
ptyEl.innerHTML = '<span class="oc-dot oc-dot-gray"></span>未监听';
|
||||
}
|
||||
|
||||
document.getElementById('oc-st-pid').textContent = d.pid || '-';
|
||||
|
||||
var memEl = document.getElementById('oc-st-mem');
|
||||
if (d.memory_kb > 0) {
|
||||
var mb = (d.memory_kb / 1024).toFixed(1);
|
||||
memEl.textContent = mb + ' MB';
|
||||
} else {
|
||||
memEl.textContent = '-';
|
||||
}
|
||||
|
||||
document.getElementById('oc-st-uptime').textContent = d.uptime || '-';
|
||||
document.getElementById('oc-st-node').textContent = d.node_version || '未安装';
|
||||
document.getElementById('oc-st-ocver').textContent = d.openclaw_version || '未安装';
|
||||
|
||||
} catch(e) {
|
||||
document.getElementById('oc-st-status').innerHTML = '<span class="oc-badge oc-badge-unknown">查询失败</span>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
setInterval(updateStatus, 5000);
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
Reference in New Issue
Block a user