WebUI: MD3 projects/proxy pages, sidebar links, CSS animations
- Add projects + proxy nav items to sidebar - Rewrite projects.html with MD3 cards + tabs + mgmt-cards - Rewrite proxy.html with MD3 form + mgmt-cards - CSS: button ripple, card elevation, page fade, tab slide, input glow, nav dot, status pulse, log entry fade, shimmer, scrollbar, badge bounce, card hover mouse-glow (428 lines)
This commit is contained in:
@@ -1,119 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head><meta charset="UTF-8"><title>项目管理 - SenSu</title>
|
||||
<style>
|
||||
body{font-family:monospace;background:#0e1416;color:#e0e3e4;margin:0;padding:16px}
|
||||
h2{color:#00bcd4;border-bottom:1px solid #333;padding-bottom:8px}
|
||||
.card{background:#1a1f21;border-radius:8px;padding:12px;margin:8px 0}
|
||||
input,button,select{padding:6px 10px;margin:4px;border-radius:4px;border:1px solid #444;background:#222;color:#e0e3e4}
|
||||
button{background:#00bcd4;color:#000;cursor:pointer;font-weight:bold}
|
||||
button:hover{opacity:0.8}
|
||||
button.danger{background:#ff5252}
|
||||
.status-running{color:#4caf50}.status-stopped{color:#ff5252}.status-error{color:#ff9800}
|
||||
.logs{background:#000;color:#0f0;padding:8px;border-radius:4px;max-height:300px;overflow-y:auto;font-size:12px}
|
||||
.tabs{display:flex;gap:4px;margin-bottom:12px}
|
||||
.tab{padding:6px 12px;background:#333;border-radius:4px 4px 0 0;cursor:pointer}
|
||||
.tab.active{background:#00bcd4;color:#000}
|
||||
</style></head>
|
||||
<body>
|
||||
<h2>📦 项目管理</h2>
|
||||
<div>
|
||||
<h2 style="margin-bottom:16px">📦 项目管理</h2>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab active" onclick="showTab('running')">运行中</div>
|
||||
<div class="tab" onclick="showTab('add')">+ 添加项目</div>
|
||||
<div class="tab active" onclick="showTab(event,'running')">运行中</div>
|
||||
<div class="tab" onclick="showTab(event,'add')">+ 添加项目</div>
|
||||
<div class="tab" onclick="showTab(event,'git')">Git 部署</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-running">
|
||||
<div id="project-list">加载中...</div>
|
||||
<div id="project-list" style="display:flex;flex-direction:column;gap:12px">加载中...</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-add" style="display:none">
|
||||
<div class="card">
|
||||
<h3>添加新项目</h3>
|
||||
<input id="proj-name" placeholder="项目名称" style="width:200px"><br>
|
||||
<input id="proj-cmd" placeholder="启动命令 (如: python3 main.py)" style="width:400px"><br>
|
||||
<input id="proj-cwd" placeholder="工作目录 (默认: .)" value="." style="width:200px"><br>
|
||||
<input id="proj-port" placeholder="端口 (可选)" type="number" style="width:100px"><br>
|
||||
<input id="proj-proxy" placeholder="代理路径 (可选, 如: /myapp)" style="width:200px"><br>
|
||||
<button onclick="addProject()">启动项目</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>从 Git 部署</h3>
|
||||
<input id="git-url" placeholder="Git URL" style="width:400px"><br>
|
||||
<input id="git-branch" placeholder="分支 (默认: main)" value="main" style="width:200px"><br>
|
||||
<button onclick="deployGit()">克隆并部署</button>
|
||||
<div class="card outlined" style="max-width:500px">
|
||||
<h3 style="margin-bottom:12px">添加新项目</h3>
|
||||
<div class="input-group"><span class="label">项目名称</span><input class="input" id="proj-name" placeholder="如: my-app"></div>
|
||||
<div class="input-group"><span class="label">启动命令</span><input class="input" id="proj-cmd" placeholder="如: python3 main.py"></div>
|
||||
<div class="input-group"><span class="label">工作目录</span><input class="input" id="proj-cwd" value="."></div>
|
||||
<div class="input-group"><span class="label">端口 (可选)</span><input class="input" id="proj-port" type="number" placeholder="5000" style="width:120px"></div>
|
||||
<div class="input-group"><span class="label">代理路径 (可选)</span><input class="input" id="proj-proxy" placeholder="如: /myapp" style="width:200px"></div>
|
||||
<button class="btn btn-filled" onclick="addProject()" style="margin-top:8px">启动项目</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-git" style="display:none">
|
||||
<div class="card outlined" style="max-width:500px">
|
||||
<h3 style="margin-bottom:12px">从 Git 部署</h3>
|
||||
<div class="input-group"><span class="label">Git URL</span><input class="input" id="git-url" placeholder="https://git.yeij.top/user/repo.git"></div>
|
||||
<div class="input-group"><span class="label">分支</span><input class="input" id="git-branch" value="main" style="width:160px"></div>
|
||||
<button class="btn btn-filled" onclick="deployGit()" style="margin-top:8px">克隆并部署</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let projects=[];
|
||||
var projects=[];
|
||||
function showTab(e,t){document.querySelectorAll("#tab-running,#tab-add,#tab-git").forEach(el=>el.style.display="none");document.getElementById("tab-"+t).style.display="block";document.querySelectorAll(".tab").forEach(el=>el.classList.remove("active"));e.target.classList.add("active")}
|
||||
async function refresh(){
|
||||
try{
|
||||
const r=await fetch("/SenSu/api/projects");
|
||||
const d=await r.json();
|
||||
projects=d.projects;
|
||||
let h="";
|
||||
for(const p of projects){
|
||||
const cls=p.status==="running"?"status-running":p.status==="error"?"status-error":"status-stopped";
|
||||
h+=`<div class="card">
|
||||
<b>${p.name}</b> <span class="${cls}">● ${p.status}</span>
|
||||
${p.pid?` PID:${p.pid}`:""} ${p.port?` :${p.port}`:""} ${p.uptime?` ${p.uptime}s`:""}
|
||||
${p.proxy?` → ${p.proxy}`:""}
|
||||
<button class="danger" onclick="stopProject('${p.name}')">停止</button>
|
||||
<button onclick="viewLogs('${p.name}')">日志</button>
|
||||
<button onclick="sendCmd('${p.name}')">发送命令</button>
|
||||
<div id="logs-${p.name}" class="logs" style="display:none"></div>
|
||||
</div>`;
|
||||
}
|
||||
document.getElementById("project-list").innerHTML=h||"暂无项目";
|
||||
}catch(e){console.error(e)}
|
||||
try{var r=await fetch("/SenSu/api/projects");var d=await r.json();projects=d.projects;var h="";for(var p of projects){var cls=p.status==="running"?"status-running":p.status==="error"?"status-error":"status-stopped";h+='<div class="mgmt-card"><div class="status-dot '+cls+'"></div><div class="info"><b>'+p.name+'</b><br><small>'+p.status+" PID:"+(p.pid||"-")+" :"+(p.port||"-")+" "+(p.uptime?p.uptime+"s":"")+(p.proxy?" → "+p.proxy:"")+'</small></div><div class="actions"><button class="btn btn-sm btn-danger" onclick="stopP(this,'+JSON.stringify(p.name)+')">停止</button><button class="btn btn-sm btn-outlined" onclick="logsP(this,'+JSON.stringify(p.name)+')">日志</button><button class="btn btn-sm btn-text" onclick="cmdP(this,'+JSON.stringify(p.name)+')">命令</button></div></div><div class="log-box" id="logs-'+p.name+'"></div>'}document.getElementById("project-list").innerHTML=h||"暂无项目"}catch(e){console.error(e)}
|
||||
}
|
||||
async function addProject(){
|
||||
const name=document.getElementById("proj-name").value;
|
||||
const cmdStr=document.getElementById("proj-cmd").value;
|
||||
if(!name||!cmdStr){alert("请填写名称和命令");return}
|
||||
await fetch("/SenSu/api/projects/run",{
|
||||
method:"POST",headers:{"Content-Type":"application/json"},
|
||||
body:JSON.stringify({
|
||||
name,cwd:document.getElementById("proj-cwd").value,
|
||||
cmd:cmdStr.split(" "),
|
||||
port:parseInt(document.getElementById("proj-port").value)||0,
|
||||
proxy_path:document.getElementById("proj-proxy").value
|
||||
})
|
||||
});
|
||||
refresh();
|
||||
var n=document.getElementById("proj-name").value,cmd=document.getElementById("proj-cmd").value;
|
||||
if(!n||!cmd){alert("请填写名称和命令");return}
|
||||
await fetch("/SenSu/api/projects/run",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n,cmd:cmd.split(" "),cwd:document.getElementById("proj-cwd").value,port:parseInt(document.getElementById("proj-port").value)||0,proxy_path:document.getElementById("proj-proxy").value})});
|
||||
refresh()
|
||||
}
|
||||
async function deployGit(){
|
||||
const url=document.getElementById("git-url").value;
|
||||
const branch=document.getElementById("git-branch").value;
|
||||
if(!url){alert("请填写 Git URL");return}
|
||||
const name=url.split("/").pop().replace(".git","");
|
||||
const resp=await fetch("/SenSu/api/projects/run",{
|
||||
method:"POST",headers:{"Content-Type":"application/json"},
|
||||
body:JSON.stringify({name,cmd:["git","clone","-b",branch,url,name],cwd:"data/projects"})
|
||||
});
|
||||
refresh();
|
||||
var u=document.getElementById("git-url").value,b=document.getElementById("git-branch").value;
|
||||
if(!u){alert("请填写 Git URL");return}
|
||||
var n=u.split("/").pop().replace(".git","");
|
||||
await fetch("/SenSu/api/projects/run",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n,cmd:["git","clone","-b",b,u,n],cwd:"data/projects"})});
|
||||
refresh()
|
||||
}
|
||||
async function stopProject(name){
|
||||
await fetch(`/SenSu/api/projects/${name}/stop`,{method:"POST"});
|
||||
refresh();
|
||||
}
|
||||
async function viewLogs(name){
|
||||
const el=document.getElementById("logs-"+name);
|
||||
el.style.display=el.style.display==="none"?"block":"none";
|
||||
if(el.style.display==="block"){
|
||||
const r=await fetch(`/SenSu/api/projects/${name}/logs?tail=30`);
|
||||
const d=await r.json();
|
||||
el.innerHTML=d.logs.join("\n")||"(无日志)";
|
||||
}
|
||||
}
|
||||
async function sendCmd(name){
|
||||
const text=prompt("发送命令到 "+name+":");
|
||||
if(text) await fetch(`/SenSu/api/projects/${name}/stdin`,{
|
||||
method:"POST",headers:{"Content-Type":"application/json"},
|
||||
body:JSON.stringify({text})
|
||||
});
|
||||
}
|
||||
function showTab(t){document.querySelectorAll("#tab-running,#tab-add").forEach(e=>e.style.display="none");document.getElementById("tab-"+t).style.display="block"}
|
||||
setInterval(refresh,3000);refresh();
|
||||
async function stopP(el,n){await fetch("/SenSu/api/projects/"+n+"/stop",{method:"POST"});refresh()}
|
||||
async function logsP(el,n){var b=document.getElementById("logs-"+n);b.style.display=b.style.display==="none"?"block":"none";if(b.style.display==="block"){var r=await fetch("/SenSu/api/projects/"+n+"/logs?tail=30");var d=await r.json();b.innerHTML=d.logs.join("<br>")||"(无日志)"}}
|
||||
async function cmdP(el,n){var t=prompt("命令: "+n);if(t)await fetch("/SenSu/api/projects/"+n+"/stdin",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:t})})}
|
||||
setInterval(refresh,4000);refresh();
|
||||
</script>
|
||||
</body></html>
|
||||
Reference in New Issue
Block a user