Files
Nexus/web/app/scripts.html
T
Your Name c9a99f4fb3 fix: 全项目文档对齐 + 代码清理
代码修复:
- web/agent/agent.py: datetime.utcnow() → datetime.now(timezone.utc)
- requirements.txt: 移除 paramiko==3.5.0 (已无活跃引用)
- 删除 server/infrastructure/ssh/pool.py (DEPRECATED, 无引用)

连接池三层对齐 (config.py/.env.example/session.py):
- DB_POOL_SIZE 100→160, DB_MAX_OVERFLOW 100→120
- 基于 MySQL max_connections=400, install.php 公式

文档修复 (21项):
- P0: 硬编码域名/IP替换为配置变量占位符
- P1: 10个过时设计文档加归档标注 (引用旧文件结构)
- P2: step-3-webssh报告 paramiko引用修正
- 6份审查报告连接池参数勘误 100/100→160/120
- ECC安全报告 EC5 datetime.utcnow 标记已修复
- docs/README.md 文档索引重写
- docs/memory/mem_nexus_overview.md 移除硬编码凭证
- docs/project/tech-stack-inventory.md paramiko标记已移除

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 08:19:56 +08:00

40 lines
9.3 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>Nexus — 脚本库</title><script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script><script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script><style type="text/tailwindcss">@theme{--color-brand:oklch(55% 0.2 250);--color-brand-light:oklch(75% 0.15 250);--color-brand-dark:oklch(35% 0.18 250)}</style></head>
<body class="bg-slate-950 text-slate-100 min-h-screen flex" x-data="{sidebarOpen:true}">
<aside x-show="sidebarOpen" x-transition class="w-64 bg-slate-900 border-r border-slate-800 flex flex-col shrink-0"><div class="px-5 py-4 border-b border-slate-800"><div class="flex items-center gap-3"><div class="w-8 h-8 rounded-lg bg-brand/20 flex items-center justify-center"><svg class="w-5 h-5 text-brand-light" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"/></svg></div><span class="text-white font-bold text-lg">Nexus</span></div></div><nav class="flex-1 py-3 px-3 space-y-1"><a href="/app/index.html" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 text-sm transition">🏠 仪表盘</a><a href="/app/servers.html" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 text-sm transition">🖥 服务器</a><a href="/app/files.html" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 text-sm transition">📁 文件管理</a><a href="/app/push.html" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 text-sm transition">📤 推送</a><a href="/app/scripts.html" class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-brand/10 text-brand-light text-sm font-medium transition">📜 脚本库</a><a href="/app/credentials.html" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 text-sm transition">🔑 凭据</a><a href="/app/schedules.html" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 text-sm transition">⏰ 调度</a><a href="/app/retries.html" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 text-sm transition">🔄 重试队列</a><a href="/app/audit.html" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 text-sm transition">📋 审计</a><a href="/app/settings.html" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 text-sm transition">⚙️ 设置</a></nav><div class="border-t border-slate-800 p-4"><button onclick="doLogout()" class="text-slate-500 hover:text-red-400 text-xs">退出</button></div></aside>
<div class="flex-1 flex flex-col min-w-0"><header class="bg-slate-900 border-b border-slate-800 px-6 py-3"><div class="flex items-center justify-between"><div class="flex items-center gap-4"><button @click="sidebarOpen=!sidebarOpen" class="text-slate-400 hover:text-white"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg></button><h1 class="text-white font-semibold">脚本库</h1></div><button onclick="showNewScript()" class="px-3 py-1.5 bg-brand hover:bg-brand-dark text-white text-sm rounded-lg">+ 新建脚本</button></div></header>
<main class="flex-1 overflow-y-auto p-6">
<div id="scriptList" class="space-y-3"><div class="text-slate-500 text-center py-8">加载中...</div></div>
<div id="newScriptForm" class="hidden mt-6 bg-slate-900 rounded-xl border border-slate-800 p-6 space-y-3">
<input id="scriptName" placeholder="脚本名称" class="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white text-sm">
<input id="scriptCategory" placeholder="分类 (ops/deploy/check)" class="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white text-sm">
<textarea id="scriptContent" rows="8" placeholder="Shell 命令内容" class="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white text-sm font-mono"></textarea>
<div class="flex gap-2"><button onclick="createScript()" class="px-4 py-2 bg-brand hover:bg-brand-dark text-white rounded-lg text-sm">保存</button><button onclick="hideNewScript()" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg text-sm">取消</button></div>
</div>
<!-- Execute Modal -->
<div id="execModal" class="hidden fixed inset-0 bg-black/60 flex items-center justify-center z-50">
<div class="bg-slate-900 border border-slate-700 rounded-xl p-6 w-full max-w-md space-y-3">
<h2 class="text-white font-semibold text-lg">执行脚本</h2>
<p id="execScriptName" class="text-slate-400 text-sm"></p>
<div><label class="block text-slate-400 text-xs mb-1">选择服务器</label><select id="execServers" multiple class="w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded-lg text-white text-sm h-32"></select></div>
<div><label class="block text-slate-400 text-xs mb-1">超时(秒)</label><input id="execTimeout" type="number" value="60" class="w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded-lg text-white text-sm"></div>
<div id="execResult" class="hidden p-3 bg-slate-950 rounded-lg text-sm font-mono text-slate-300 max-h-48 overflow-y-auto"></div>
<div class="flex gap-2"><button onclick="doExec()" class="px-4 py-2 bg-brand hover:bg-brand-dark text-white rounded-lg text-sm">执行</button><button onclick="hideExecModal()" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg text-sm">关闭</button></div>
</div>
</div>
</main>
</div>
<script src="/app/api.js"></script>
<script>
let _scriptsCache=[];
async function loadScripts(){try{const r=await apiFetch(API+'/scripts/');if(!r)return;const scripts=await r.json();_scriptsCache=scripts;document.getElementById('scriptList').innerHTML=scripts.length?scripts.map(s=>`<div class="bg-slate-900 rounded-xl border border-slate-800 p-4"><div class="flex items-center justify-between"><span class="text-white font-medium">${esc(s.name)}</span><div class="flex items-center gap-2"><span class="text-xs px-2 py-0.5 rounded bg-slate-800 text-slate-400">${s.category||'ops'}</span><button onclick="showExec(${s.id})" class="text-brand-light text-xs hover:underline">执行</button><button onclick="deleteScript(${s.id})" class="text-red-400 text-xs hover:underline">删除</button></div></div><pre class="mt-2 text-sm text-slate-400 font-mono bg-slate-950 rounded-lg p-3 overflow-x-auto">${esc(s.content?.substring(0,500)||'')}</pre></div>`).join(''):'<div class="text-slate-500 text-center py-8">暂无脚本</div>'}catch(e){}}
function showNewScript(){document.getElementById('newScriptForm').classList.remove('hidden')}
function hideNewScript(){document.getElementById('newScriptForm').classList.add('hidden')}
async function createScript(){const b={name:document.getElementById('scriptName').value,category:document.getElementById('scriptCategory').value||'ops',content:document.getElementById('scriptContent').value};await apiFetch(API+'/scripts/',{method:'POST',headers:apiHeadersJSON(),body:JSON.stringify(b)});hideNewScript();loadScripts()}
async function deleteScript(id){if(!confirm('确定删除脚本?'))return;await apiFetch(API+'/scripts/'+id,{method:'DELETE'});loadScripts()}
async function showExec(id){const s=_scriptsCache.find(x=>x.id===id);if(!s)return;document.getElementById('execScriptName').textContent=s.name;document.getElementById('execResult').classList.add('hidden');const r=await apiFetch(API+'/servers/');if(!r)return;const data=await r.json();const servers=data.items||data;document.getElementById('execServers').innerHTML=servers.map(s=>`<option value="${s.id}">${esc(s.name)} (${esc(s.domain)})</option>`).join('');document.getElementById('execModal').classList.remove('hidden');document.getElementById('execModal').dataset.scriptId=id;document.getElementById('execModal').dataset.cmd=s.content||''}
function hideExecModal(){document.getElementById('execModal').classList.add('hidden')}
async function doExec(){const opts=document.getElementById('execServers').selectedOptions;const ids=Array.from(opts).map(o=>parseInt(o.value));if(!ids.length){alert('请选择服务器');return}const timeout=parseInt(document.getElementById('execTimeout').value)||60;const el=document.getElementById('execResult');el.classList.remove('hidden');el.textContent='执行中...';const modal=document.getElementById('execModal');const scriptId=parseInt(modal.dataset.scriptId)||null;const cmd=modal.dataset.cmd||'';const r=await apiFetch(API+'/scripts/exec',{method:'POST',headers:apiHeadersJSON(),body:JSON.stringify({script_id:scriptId,command:cmd,server_ids:ids,timeout:timeout})});if(!r){el.textContent='认证失败';return}const d=await r.json();el.textContent=JSON.stringify(d,null,2)}
function esc(s){const d=document.createElement('div');d.textContent=s||'';return d.innerHTML}
loadScripts()
</script>
</body></html>