Rename to hkt.sh

This commit is contained in:
mango
2026-03-21 01:10:53 +08:00
parent 76a263d0f9
commit 8f1171fe99
6676 changed files with 1724268 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
#!/bin/bash
# Status Panel Agent
SECRET="$1"
SERVER="$2"
if [ -z "$SECRET" ] || [ -z "$SERVER" ]; then
echo "Usage: $0 <secret> <server_url>"
echo "Example: $0 abc123 http://1.2.3.4:3800"
exit 1
fi
while true; do
# CPU
CPU=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1 2>/dev/null || echo 0)
[ -z "$CPU" ] && CPU=$(top -bn1 | awk '/%Cpu/ {print $2}' | head -1)
# Memory
MEM_INFO=$(free -b | awk '/Mem:/ {print $3,$2}')
MEM_USED=$(echo $MEM_INFO | awk '{print $1}')
MEM_TOTAL=$(echo $MEM_INFO | awk '{print $2}')
# Disk
DISK_INFO=$(df -B1 / | awk 'NR==2 {print $3,$2}')
DISK_USED=$(echo $DISK_INFO | awk '{print $1}')
DISK_TOTAL=$(echo $DISK_INFO | awk '{print $2}')
# Network (需要计算差值)
NET_INFO=$(cat /proc/net/dev | grep -E 'eth0|ens|enp' | awk '{print $2,$10}')
NET_IN=$(echo $NET_INFO | awk '{print $1}')
NET_OUT=$(echo $NET_INFO | awk '{print $2}')
# Uptime
UPTIME=$(cat /proc/uptime | awk '{print int($1)}')
# Load
LOAD=$(cat /proc/loadavg | awk '{print $1,$2,$3}')
LOAD1=$(echo $LOAD | awk '{print $1}')
LOAD5=$(echo $LOAD | awk '{print $2}')
LOAD15=$(echo $LOAD | awk '{print $3}')
# Send
curl -s -X POST "$SERVER/report" \
-H "Content-Type: application/json" \
-d "{
\"secret\": \"$SECRET\",
\"cpu\": $CPU,
\"mem_used\": $MEM_USED,
\"mem_total\": $MEM_TOTAL,
\"disk_used\": $DISK_USED,
\"disk_total\": $DISK_TOTAL,
\"net_in\": $NET_IN,
\"net_out\": $NET_OUT,
\"uptime\": $UPTIME,
\"load1\": $LOAD1,
\"load5\": $LOAD5,
\"load15\": $LOAD15
}" > /dev/null 2>&1
sleep 5
done

View File

@@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>服务器探针</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); min-height: 100vh; color: #fff; }
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
h1 { text-align: center; padding: 30px 0; font-weight: 300; }
.nodes { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px; }
.node { background: rgba(255,255,255,0.05); border-radius: 12px; padding: 20px; backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); }
.node.offline { opacity: 0.5; border-color: #ff4757; }
.node-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.node-name { font-size: 18px; font-weight: 500; }
.status { width: 10px; height: 10px; border-radius: 50%; background: #2ed573; box-shadow: 0 0 10px #2ed573; }
.status.offline { background: #ff4757; box-shadow: 0 0 10px #ff4757; }
.row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.05); }
.row:last-child { border: none; }
.label { color: rgba(255,255,255,0.6); }
.value { font-family: 'SF Mono', Monaco, monospace; }
.progress { height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; margin-top: 5px; overflow: hidden; }
.progress-bar { height: 100%; border-radius: 3px; transition: width 0.5s; }
.progress-bar.cpu { background: linear-gradient(90deg, #2ed573, #1e90ff); }
.progress-bar.mem { background: linear-gradient(90deg, #ffa502, #ff6348); }
.progress-bar.disk { background: linear-gradient(90deg, #a55eea, #5352ed); }
.admin { position: fixed; top: 20px; right: 20px; }
.btn { background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: #fff; padding: 10px 20px; border-radius: 8px; cursor: pointer; }
.btn:hover { background: rgba(255,255,255,0.2); }
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); justify-content: center; align-items: center; }
.modal.show { display: flex; }
.modal-content { background: #1a1a2e; padding: 30px; border-radius: 12px; max-width: 500px; width: 90%; }
.modal-content h2 { margin-bottom: 20px; }
.modal-content input { width: 100%; padding: 12px; margin-bottom: 15px; border: 1px solid rgba(255,255,255,0.2); background: rgba(255,255,255,0.05); color: #fff; border-radius: 8px; }
.code-block { background: #000; padding: 15px; border-radius: 8px; font-family: monospace; font-size: 12px; overflow-x: auto; margin: 10px 0; }
</style>
</head>
<body>
<div class="container">
<h1>🖥️ 服务器探针</h1>
<div class="nodes" id="nodes"></div>
</div>
<div class="admin">
<button class="btn" onclick="showAddModal()">+ 添加节点</button>
</div>
<div class="modal" id="modal">
<div class="modal-content">
<h2 id="modalTitle">添加节点</h2>
<input type="text" id="nodeName" placeholder="节点名称">
<div id="addForm"><button class="btn" onclick="addNode()">添加</button></div>
<div id="result" style="display:none">
<p>Agent 命令(在目标服务器执行):</p>
<div class="code-block" id="agentCmd"></div>
</div>
<button class="btn" style="margin-top:15px" onclick="closeModal()">关闭</button>
</div>
</div>
<script>
const API = '/api/nodes';
let ws;
// 加载节点
async function loadNodes() {
const res = await fetch(API);
const nodes = await res.json();
render(nodes);
}
// 渲染
function render(nodes) {
const now = Math.floor(Date.now() / 1000);
document.getElementById('nodes').innerHTML = nodes.map(n => {
const online = n.last_seen && (now - n.last_seen) < 60;
const cpu = n.cpu || 0;
const mem = n.mem_total ? (n.mem_used / n.mem_total * 100) : 0;
const disk = n.disk_total ? (n.disk_used / n.disk_total * 100) : 0;
return `
<div class="node ${online ? '' : 'offline'}">
<div class="node-header">
<span class="node-name">${n.name}</span>
<span class="status ${online ? '' : 'offline'}"></span>
</div>
<div class="row">
<span class="label">CPU</span>
<span class="value">${cpu.toFixed(1)}%</span>
</div>
<div class="progress"><div class="progress-bar cpu" style="width:${cpu}%"></div></div>
<div class="row">
<span class="label">内存</span>
<span class="value">${formatSize(n.mem_used)} / ${formatSize(n.mem_total)}</span>
</div>
<div class="progress"><div class="progress-bar mem" style="width:${mem}%"></div></div>
<div class="row">
<span class="label">磁盘</span>
<span class="value">${formatSize(n.disk_used)} / ${formatSize(n.disk_total)}</span>
</div>
<div class="progress"><div class="progress-bar disk" style="width:${disk}%"></div></div>
<div class="row">
<span class="label">网络 ↑↓</span>
<span class="value">${formatSize(n.net_out)}/s | ${formatSize(n.net_in)}/s</span>
</div>
<div class="row">
<span class="label">负载</span>
<span class="value">${(n.load1||0).toFixed(2)} ${(n.load5||0).toFixed(2)} ${(n.load15||0).toFixed(2)}</span>
</div>
<div class="row">
<span class="label">在线</span>
<span class="value">${formatUptime(n.uptime)}</span>
</div>
</div>
`;
}).join('');
}
function formatSize(b) {
if (!b) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let i = 0;
while (b >= 1024 && i < 4) { b /= 1024; i++; }
return b.toFixed(1) + ' ' + units[i];
}
function formatUptime(s) {
if (!s) return '-';
const d = Math.floor(s / 86400);
const h = Math.floor((s % 86400) / 3600);
return d + '天' + h + '小时';
}
// WebSocket
function connectWS() {
ws = new WebSocket(`ws://${location.host}`);
ws.onmessage = e => {
const data = JSON.parse(e.data);
if (data.type === 'update') loadNodes();
};
ws.onclose = () => setTimeout(connectWS, 3000);
}
// 添加节点
async function addNode() {
const name = document.getElementById('nodeName').value;
if (!name) return alert('请输入名称');
const res = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
});
const data = await res.json();
if (data.error) return alert(data.error);
const server = location.host.split(':')[0];
const port = location.port || (location.protocol === 'https:' ? 443 : 80);
const cmd = `curl -sSL http://${server}:${port}/agent.sh | bash -s -- ${data.secret} http://${server}:${port}`;
document.getElementById('agentCmd').textContent = cmd;
document.getElementById('addForm').style.display = 'none';
document.getElementById('result').style.display = 'block';
loadNodes();
}
function showAddModal() {
document.getElementById('nodeName').value = '';
document.getElementById('addForm').style.display = 'block';
document.getElementById('result').style.display = 'none';
document.getElementById('modal').classList.add('show');
}
function closeModal() {
document.getElementById('modal').classList.remove('show');
}
loadNodes();
connectWS();
setInterval(loadNodes, 30000);
</script>
</body>
</html>