fix: 补回 servers.html 丢失的检测路径+卸载Agent功能
从 worktree 分支同步完整的 servers.html,补回: - 批量操作栏"检测路径"按钮(搜索 workerman.bat 自动设置 target_path) - 批量操作栏"卸载 Agent"按钮 - batchDetectPath() 和 batchUninstallAgent() 函数 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+78
-19
@@ -21,7 +21,7 @@
|
||||
</header>
|
||||
<main class="flex-1 overflow-y-auto p-6">
|
||||
<div id="serversTable" class="bg-slate-900 rounded-xl border border-slate-800 overflow-hidden">
|
||||
<table class="w-full text-sm"><thead class="bg-slate-800/50 text-slate-400 text-xs uppercase"><tr><th class="px-4 py-3 w-10"><input type="checkbox" id="selectAll" onchange="toggleSelectAll(this.checked)" class="rounded border-slate-600 bg-slate-700 text-brand focus:ring-brand"></th><th class="text-left px-4 py-3">状态</th><th class="text-left px-4 py-3">名称</th><th class="text-left px-4 py-3">地址</th><th class="text-left px-4 py-3">分类</th><th class="text-left px-4 py-3">Agent</th><th class="text-left px-4 py-3">最后心跳</th><th class="text-right px-4 py-3">操作</th></tr></thead><tbody id="serversTbody" class="divide-y divide-slate-800"><tr><td colspan="8" class="px-4 py-8 text-center text-slate-500">加载中...</td></tr></tbody></table>
|
||||
<table class="w-full text-sm"><thead class="bg-slate-800/50 text-slate-400 text-xs uppercase"><tr><th class="px-4 py-3 w-10"><input type="checkbox" id="selectAll" onchange="toggleSelectAll(this.checked)" class="rounded border-slate-600 bg-slate-700 text-brand focus:ring-brand"></th><th class="text-left px-4 py-3">Agent 状态</th><th class="text-left px-4 py-3">名称</th><th class="text-left px-4 py-3">地址</th><th class="text-left px-4 py-3">分类</th><th class="text-left px-4 py-3">Agent</th><th class="text-left px-4 py-3">最后心跳</th><th class="text-right px-4 py-3">操作</th></tr></thead><tbody id="serversTbody" class="divide-y divide-slate-800"><tr><td colspan="8" class="px-4 py-8 text-center text-slate-500">加载中...</td></tr></tbody></table>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center justify-between text-xs text-slate-500"><span id="serverCount">共 -- 台</span><button onclick="loadServers()" class="px-3 py-1 bg-slate-800 hover:bg-slate-700 rounded-lg transition">刷新</button></div>
|
||||
<!-- Batch Action Bar -->
|
||||
@@ -34,6 +34,8 @@
|
||||
<button onclick="batchCheck()" class="px-4 py-1.5 bg-emerald-600/80 hover:bg-emerald-600 text-white text-sm rounded-lg transition">健康检查</button>
|
||||
<button id="btnBatchInstall" onclick="batchInstallAgent()" class="px-4 py-1.5 bg-amber-600/80 hover:bg-amber-600 text-white text-sm rounded-lg transition disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:bg-amber-600/80">安装 Agent</button>
|
||||
<button id="btnBatchUpgrade" onclick="batchUpgradeAgent()" class="px-4 py-1.5 bg-cyan-600/80 hover:bg-cyan-600 text-white text-sm rounded-lg transition disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:bg-cyan-600/80">升级 Agent</button>
|
||||
<button id="btnBatchDetect" onclick="batchDetectPath()" class="px-4 py-1.5 bg-violet-600/80 hover:bg-violet-600 text-white text-sm rounded-lg transition disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:bg-violet-600/80">检测路径</button>
|
||||
<button id="btnBatchUninstall" onclick="batchUninstallAgent()" class="px-4 py-1.5 bg-red-700/80 hover:bg-red-700 text-white text-sm rounded-lg transition disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:bg-red-700/80">卸载 Agent</button>
|
||||
<button onclick="batchPush()" class="px-4 py-1.5 bg-brand hover:bg-brand-dark text-white text-sm rounded-lg transition">批量推送</button>
|
||||
<button onclick="batchDelete()" class="px-4 py-1.5 bg-red-600/80 hover:bg-red-600 text-white text-sm rounded-lg transition">批量删除</button>
|
||||
</div>
|
||||
@@ -160,9 +162,10 @@
|
||||
<div class="bg-slate-900 border border-slate-700 rounded-xl p-6 w-full max-w-lg space-y-3">
|
||||
<h2 class="text-white font-semibold text-lg">批量导入服务器</h2>
|
||||
<div class="bg-slate-800/50 rounded-lg p-3 text-xs text-slate-400 space-y-1">
|
||||
<p>1. <a href="javascript:void(0)" onclick="downloadCsvTemplate()" class="text-brand-light underline">下载 CSV 模板</a></p>
|
||||
<p>2. 填写服务器信息(名称和地址为必填列)</p>
|
||||
<p>3. 上传 CSV 文件,最多 500 行</p>
|
||||
<p>1. <a href="/app/servers_import_template.csv" download="servers_import_template.csv" class="text-brand-light underline">下载 CSV 模板</a></p>
|
||||
<p>2. 按模板格式填写(<span class="text-slate-200">名称</span>和<span class="text-slate-200">地址</span>必填,其余选填)</p>
|
||||
<p>3. 认证方式填 <span class="text-slate-200">password</span> 或 <span class="text-slate-200">key</span>,默认 password</p>
|
||||
<p>4. 上传 CSV 文件,最多 500 行</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-slate-400 text-xs mb-1">选择 CSV 文件</label>
|
||||
@@ -250,7 +253,7 @@
|
||||
if(!servers.length){tbody.innerHTML='<tr><td colspan="8" class="px-4 py-8 text-center text-slate-500">暂无服务器</td></tr>';return}
|
||||
tbody.innerHTML=servers.map(s=>`<tr class="hover:bg-slate-800/30 transition cursor-pointer ${selectedServerId===s.id?'bg-brand/5':''}" onclick="selectServer(${s.id})" id="row-${s.id}">
|
||||
<td class="px-4 py-3" onclick="event.stopPropagation()"><input type="checkbox" class="rounded border-slate-600 bg-slate-700 text-brand focus:ring-brand srv-chk" data-id="${s.id}" onchange="toggleSelect(${s.id},this.checked)" ${_selectedIds.has(s.id)?'checked':''}></td>
|
||||
<td class="px-4 py-3"><span class="inline-block w-2.5 h-2.5 rounded-full ${s.is_online?'bg-green-400':'bg-red-400'} ${s.is_online?'':'animate-pulse'}"></span></td>
|
||||
<td class="px-4 py-3">${s.agent_version?(s.is_online?'<span class="inline-flex items-center gap-1.5 text-xs"><span class="inline-block w-2 h-2 rounded-full bg-green-400"></span><span class="text-green-400">在线</span></span>':'<span class="inline-flex items-center gap-1.5 text-xs"><span class="inline-block w-2 h-2 rounded-full bg-red-400 animate-pulse"></span><span class="text-red-400">离线</span></span>'):'<span class="inline-flex items-center gap-1.5 text-xs"><span class="inline-block w-2 h-2 rounded-full bg-slate-600"></span><span class="text-slate-500">未安装</span></span>'}</td>
|
||||
<td class="px-4 py-3 text-white font-medium">${esc(s.name)}${driftBadge(s)}</td>
|
||||
<td class="px-4 py-3 text-slate-400">${esc(s.domain)}:${s.port||22}</td>
|
||||
<td class="px-4 py-3 text-slate-400">${esc(s.category||'--')}</td>
|
||||
@@ -415,6 +418,10 @@
|
||||
${s.is_online?`<button onclick="upgradeAgent()" id="upgradeAgentBtn"
|
||||
class="px-3 py-2 bg-slate-700 hover:bg-slate-600 text-white text-sm rounded-lg transition" title="下载最新 agent.py 并重启服务">
|
||||
⬆ 升级 Agent
|
||||
</button>
|
||||
<button onclick="uninstallAgent()" id="uninstallAgentBtn"
|
||||
class="px-3 py-2 bg-red-900/60 hover:bg-red-800 text-white text-sm rounded-lg transition" title="停止 Agent 服务并删除安装目录">
|
||||
🗑 卸载 Agent
|
||||
</button>`:''}
|
||||
<span class="text-slate-500 text-xs">安装约需 30-60 秒;安装完成后 Agent 发送首次心跳约需再等 60 秒,状态徽章将自动更新为 <span class="text-green-400">● Agent 在线</span></span>
|
||||
</div>
|
||||
@@ -477,6 +484,32 @@
|
||||
finally{if(btn){btn.disabled=false;btn.textContent='⬆ 升级 Agent'}}
|
||||
}
|
||||
|
||||
async function uninstallAgent(){
|
||||
if(!selectedServerId)return;
|
||||
if(!confirm('确定卸载该服务器的 Agent?\n将停止服务、删除 /opt/nexus-agent 目录和日志文件,并清除 Agent 状态。'))return;
|
||||
const btn=document.getElementById('uninstallAgentBtn');
|
||||
if(btn){btn.disabled=true;btn.textContent='卸载中...'}
|
||||
const logEl=document.getElementById('remoteInstallLog');
|
||||
const logText=document.getElementById('remoteInstallLogText');
|
||||
logEl?.classList.remove('hidden');
|
||||
logText.textContent='正在停止 Agent 服务并清理安装目录...\n';
|
||||
try{
|
||||
const r=await apiFetch(API+'/servers/'+selectedServerId+'/uninstall-agent',{method:'POST',headers:apiHeadersJSON()});
|
||||
if(!r){logText.textContent+='请求失败\n';return}
|
||||
const d=await r.json();
|
||||
if(r.ok&&d.success){
|
||||
logText.textContent+=d.stdout||'';
|
||||
logText.textContent+='\n✓ Agent 卸载成功!';
|
||||
toast('success','Agent 卸载成功');
|
||||
setTimeout(loadServers,1000);
|
||||
}else{
|
||||
logText.textContent+=(d.detail||'卸载失败')+'\n';
|
||||
toast('error','卸载失败');
|
||||
}
|
||||
}catch(e){logText.textContent+='错误: '+e.message;toast('error','卸载请求失败')}
|
||||
finally{if(btn){btn.disabled=false;btn.textContent='🗑 卸载 Agent'}}
|
||||
}
|
||||
|
||||
function copyAgentCmd(){
|
||||
const cmd=document.getElementById('agentCmdText')?.textContent;
|
||||
if(cmd){
|
||||
@@ -847,8 +880,12 @@
|
||||
});
|
||||
const btnInstall=document.getElementById('btnBatchInstall');
|
||||
const btnUpgrade=document.getElementById('btnBatchUpgrade');
|
||||
const btnDetect=document.getElementById('btnBatchDetect');
|
||||
const btnUninstall=document.getElementById('btnBatchUninstall');
|
||||
if(btnInstall) btnInstall.disabled=allInstalled; // all have agent → disable install
|
||||
if(btnUpgrade) btnUpgrade.disabled=allNotInstalled; // none have agent → disable upgrade
|
||||
if(btnDetect) btnDetect.disabled=allNotInstalled; // need agent for SSH connection
|
||||
if(btnUninstall) btnUninstall.disabled=allNotInstalled; // need agent for uninstall
|
||||
}
|
||||
function batchPush(){
|
||||
if(!_selectedIds.size){toast('warning','请先选择服务器');return}
|
||||
@@ -901,18 +938,6 @@
|
||||
}
|
||||
|
||||
// ── CSV Import ──
|
||||
async function downloadCsvTemplate(){
|
||||
try{
|
||||
const r=await apiFetch(API+'/servers/import/template');
|
||||
if(!r||!r.ok){toast('error','下载模板失败');return}
|
||||
const blob=await r.blob();
|
||||
const url=URL.createObjectURL(blob);
|
||||
const a=document.createElement('a');a.href=url;a.download='servers_import_template.csv';
|
||||
document.body.appendChild(a);a.click();document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
toast('success','模板已下载');
|
||||
}catch(e){toast('error','下载失败: '+e.message)}
|
||||
}
|
||||
function showImportModal(){
|
||||
document.getElementById('importFile').value='';
|
||||
document.getElementById('importProgress').classList.add('hidden');
|
||||
@@ -990,6 +1015,40 @@
|
||||
}catch(e){toast('error','批量升级失败: '+e.message);hideBatchAgentModal()}
|
||||
}
|
||||
|
||||
async function batchDetectPath(){
|
||||
if(!_selectedIds.size){toast('warning','请先选择服务器');return}
|
||||
const ids=[..._selectedIds];
|
||||
if(!confirm(`确定对 ${ids.length} 台服务器自动检测 target_path?\n将在 /www/wwwroot 下搜索 workerman.bat,找到后自动设置目标路径。`))return;
|
||||
document.getElementById('batchAgentTitle').textContent='自动检测路径';
|
||||
document.getElementById('batchAgentModal').classList.remove('hidden');
|
||||
document.getElementById('batchAgentProgress').classList.remove('hidden');
|
||||
document.getElementById('batchAgentResult').classList.add('hidden');
|
||||
try{
|
||||
const r=await apiFetch(API+'/servers/batch/detect-path',{method:'POST',headers:apiHeadersJSON(),body:JSON.stringify({server_ids:ids})});
|
||||
if(!r){toast('error','请求失败');return}
|
||||
const data=await r.json();
|
||||
_showBatchAgentResult(data.results||[]);
|
||||
loadServers();
|
||||
}catch(e){toast('error','检测失败: '+e.message);hideBatchAgentModal()}
|
||||
}
|
||||
|
||||
async function batchUninstallAgent(){
|
||||
if(!_selectedIds.size){toast('warning','请先选择服务器');return}
|
||||
const ids=[..._selectedIds];
|
||||
if(!confirm(`确定对 ${ids.length} 台已安装 Agent 的服务器执行卸载?\n将停止服务、删除 /opt/nexus-agent 目录和日志文件,并清除 Agent 状态。`))return;
|
||||
document.getElementById('batchAgentTitle').textContent='批量卸载 Agent';
|
||||
document.getElementById('batchAgentModal').classList.remove('hidden');
|
||||
document.getElementById('batchAgentProgress').classList.remove('hidden');
|
||||
document.getElementById('batchAgentResult').classList.add('hidden');
|
||||
try{
|
||||
const r=await apiFetch(API+'/servers/batch/uninstall-agent',{method:'POST',headers:apiHeadersJSON(),body:JSON.stringify({server_ids:ids})});
|
||||
if(!r){toast('error','请求失败');return}
|
||||
const data=await r.json();
|
||||
_showBatchAgentResult(data.results||[]);
|
||||
loadServers();
|
||||
}catch(e){toast('error','卸载失败: '+e.message);hideBatchAgentModal()}
|
||||
}
|
||||
|
||||
function _showBatchAgentResult(results){
|
||||
document.getElementById('batchAgentProgress').classList.add('hidden');
|
||||
document.getElementById('batchAgentResult').classList.remove('hidden');
|
||||
@@ -999,8 +1058,8 @@
|
||||
document.getElementById('batchAgentFail').textContent=fail;
|
||||
const details=document.getElementById('batchAgentDetails');
|
||||
details.innerHTML=results.map(r=>{
|
||||
if(r.success)return `<div class="flex items-center gap-2 text-xs text-emerald-400"><span>✓</span><span>${esc(r.server_name)}</span></div>`;
|
||||
return `<div class="flex items-center gap-2 text-xs text-red-400"><span>✗</span><span>${esc(r.server_name)}</span><span class="text-slate-500 truncate">${esc(r.error)}</span></div>`;
|
||||
if(r.success)return `<div class="flex items-center gap-2 text-xs text-emerald-400"><span>✓</span><span>${esc(r.server_name)}</span>${r.stdout?`<span class="text-slate-400">${esc(r.stdout)}</span>`:''}</div>`;
|
||||
return `<div class="flex items-center gap-2 text-xs text-red-400"><span>✗</span><span>${esc(r.server_name)}</span><span class="text-slate-500 truncate" title="${esc(r.error)}">${esc(r.error)}</span></div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user