Files
openclaw 81939825d1 feat: 模块化命令框架 + runbook 严格断言与查询结果回传
- core/module: 命令模板、门控(feature flag/confirm/dry-run)、ecode 错误标签、ResultFormatter
- runbook executor: http.get/http.post/assert.json(expect_success 严格断言)、目标主机管理、任务落库、最终 stdout 直接回传
- cf 模块: /cf dns list 支持域名自动解析 zone_id;dns list/zones/workers list 格式化回传;API success:false 判真失败
- 全部 CF/CPA runbook 接入严格断言,杜绝假阳性 success
- 新增 cpa/cfdav/mail/sub 模块与 runbooks;QQ 订阅到期提醒调度
- Web 后台: 模块设置(CF/CPA/cfdav/AI)、OPS 任务、渠道管理、审计页
2026-09-13 07:42:17 +08:00

158 lines
6.8 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>cfdav 配置 - Ops-Assistant</title>
<style>
:root{--bg:#0f172a;--text:#e5e7eb;--card:#111827;--border:#1f2937;--muted:#9ca3af;--accent:#f97316;--accent-hover:#ea580c;--header-bg:linear-gradient(135deg,#7c2d12,#ea580c);}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
.header{background:var(--header-bg);color:#fff;padding:18px 20px;position:sticky;top:0;z-index:100;box-shadow:0 2px 10px rgba(0,0,0,.15);display:flex;justify-content:space-between;align-items:center;gap:10px}
.header .right{display:flex;align-items:center;gap:8px}
.header a{color:#fff;text-decoration:none;background:rgba(255,255,255,.2);padding:0 10px;border-radius:8px;font-size:13px;line-height:28px;height:28px;display:inline-flex;align-items:center}
.wrap{max-width:980px;margin:0 auto;padding:14px}
.card{background:var(--card);border-radius:6px;padding:14px;border:1px solid var(--border);box-shadow:none;margin-bottom:10px}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:10px 0}
.row label{width:180px;color:var(--muted);font-size:13px}
.row input{flex:1;min-width:240px;padding:8px;border:1px solid var(--border);border-radius:6px;font-size:13px;background:#0b1220;color:var(--text)}
.btns{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
button{border:none;border-radius:6px;padding:8px 12px;cursor:pointer;color:#fff;background:var(--accent);font-size:13px}
button.secondary{background:#6b7280}
button.danger{background:#9b1c1c}
small{color:var(--muted)}
.table{width:100%;border-collapse:collapse;font-size:13px}
.table th,.table td{border-bottom:1px solid var(--border);padding:8px;text-align:left}
.badge{display:inline-block;padding:2px 8px;border-radius:999px;font-size:12px}
.on{background:#dcfce7;color:#166534}.off{background:#e5e7eb;color:#374151}
</style>
</head>
<body>
<div class="header">
<div>🗂️ cfdav 管理</div>
<div class="right">
<a href="/">返回首页</a>
<a href="/logout">退出</a>
</div>
</div>
<div class="wrap">
<div class="card">
<h3>连接配置</h3>
<div class="row"><label>API Base</label><input id="apiBase" placeholder="例如 https://cfdav.ouai.nyc.mn"></div>
<div class="row"><label>管理员账号</label><input id="adminUser" placeholder="例如 kyy"></div>
<div class="row"><label>管理员密码</label><input id="adminPass" placeholder="请输入密码"></div>
<div class="btns">
<button onclick="saveSettings()">保存</button>
<button class="secondary" onclick="loadSettings()">刷新</button>
</div>
<small id="settingsMsg"></small>
</div>
<div class="card">
<h3>创建用户</h3>
<div class="row"><label>邮箱/用户名</label><input id="newEmail" placeholder="例如 user01"></div>
<div class="row"><label>密码</label><input id="newPass" placeholder="请输入密码"></div>
<div class="row"><label>管理员</label><input id="newAdmin" type="checkbox"></div>
<div class="btns">
<button onclick="createUser()">新增用户</button>
<button class="secondary" onclick="loadUsers()">刷新列表</button>
</div>
<small id="userMsg"></small>
</div>
<div class="card">
<h3>用户列表</h3>
<table class="table">
<thead><tr><th>ID</th><th>账号</th><th>管理员</th><th>创建时间</th><th>操作</th></tr></thead>
<tbody id="usersBody"></tbody>
</table>
</div>
</div>
<script>
async function api(url,opt={}){
const r=await fetch(url,opt);
const out=await r.json().catch(()=>({}));
if(!r.ok) throw new Error(out.message||('HTTP '+r.status));
return out?.data||{};
}
function esc(s){return String(s||'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}
async function loadSettings(){
try{
const d=await api('/api/v1/admin/cfdav/settings');
const s=d.settings||{};
document.getElementById('apiBase').value=s.cfdav_api_base||'';
document.getElementById('adminUser').value=s.cfdav_admin_user||'';
document.getElementById('adminPass').value=s.cfdav_admin_pass||'';
document.getElementById('settingsMsg').textContent='已加载';
}catch(e){document.getElementById('settingsMsg').textContent='加载失败:'+esc(e.message||e);}
}
async function saveSettings(){
const payload={
api_base:document.getElementById('apiBase').value.trim(),
admin_user:document.getElementById('adminUser').value.trim(),
admin_pass:document.getElementById('adminPass').value.trim()
};
try{
await api('/api/v1/admin/cfdav/settings',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});
document.getElementById('settingsMsg').textContent='已保存';
}catch(e){document.getElementById('settingsMsg').textContent='保存失败:'+esc(e.message||e);}
}
function normalizeUsers(data){
if(Array.isArray(data)) return data;
if(Array.isArray(data?.data)) return data.data;
return [];
}
async function loadUsers(){
const tbody=document.getElementById('usersBody');
try{
const d=await api('/api/v1/admin/cfdav/users');
const list=normalizeUsers(d);
if(!list.length){tbody.innerHTML='<tr><td colspan="5"><small>暂无用户</small></td></tr>';return;}
tbody.innerHTML=list.map(u=>`<tr>
<td>${esc(u.id)}</td>
<td>${esc(u.email)}</td>
<td>${u.is_admin===1?'<span class="badge on">YES</span>':'<span class="badge off">NO</span>'}</td>
<td>${esc(u.created_at)}</td>
<td><button class="danger" onclick="deleteUser('${encodeURIComponent(u.id)}')">删除</button></td>
</tr>`).join('');
}catch(e){
tbody.innerHTML='<tr><td colspan="5"><small>加载失败:'+esc(e.message||e)+'</small></td></tr>';
}
}
async function createUser(){
const payload={
email:document.getElementById('newEmail').value.trim(),
password:document.getElementById('newPass').value,
isAdmin:document.getElementById('newAdmin').checked
};
try{
await api('/api/v1/admin/cfdav/users',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});
document.getElementById('userMsg').textContent='创建成功';
document.getElementById('newPass').value='';
await loadUsers();
}catch(e){document.getElementById('userMsg').textContent='创建失败:'+esc(e.message||e);}
}
async function deleteUser(id){
if(!confirm('确认删除该用户?')) return;
try{
await api('/api/v1/admin/cfdav/users/'+id,{method:'DELETE'});
document.getElementById('userMsg').textContent='删除成功';
await loadUsers();
}catch(e){document.getElementById('userMsg').textContent='删除失败:'+esc(e.message||e);}
}
(async function init(){
await loadSettings();
await loadUsers();
})();
</script>
</body>
</html>