试运营版本
This commit is contained in:
parent
b1706b606a
commit
1384945a24
264
DreamLife_easytier/app.js
Normal file
264
DreamLife_easytier/app.js
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
class SystemMonitor {
|
||||||
|
constructor() {
|
||||||
|
this.cpuValueEl = document.getElementById('cpu-value');
|
||||||
|
this.cpuBarEl = document.getElementById('cpu-bar');
|
||||||
|
this.memoryValueEl = document.getElementById('memory-value');
|
||||||
|
this.memoryBarEl = document.getElementById('memory-bar');
|
||||||
|
this.serverStatusEl = document.getElementById('server-status');
|
||||||
|
this.lastUpdateEl = document.getElementById('last-update');
|
||||||
|
|
||||||
|
this.updateInterval = 1000;
|
||||||
|
this.isOnline = false;
|
||||||
|
|
||||||
|
this.cpuApiUrl = 'https://dreamlife.indevs.in:61207/api/4/cpu';
|
||||||
|
this.memoryApiUrl = 'https://dreamlife.indevs.in:61207/api/4/mem';
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.fetchSystemInfo();
|
||||||
|
setInterval(() => this.fetchSystemInfo(), this.updateInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchSystemInfo() {
|
||||||
|
try {
|
||||||
|
const [cpuResponse, memoryResponse] = await Promise.all([
|
||||||
|
fetch(this.cpuApiUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
}),
|
||||||
|
|
||||||
|
fetch(this.memoryApiUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!cpuResponse.ok || !memoryResponse.ok) {
|
||||||
|
throw new Error('API 请求失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const cpuData = await cpuResponse.json();
|
||||||
|
const memoryData = await memoryResponse.json();
|
||||||
|
|
||||||
|
this.updateUI(cpuData, memoryData);
|
||||||
|
this.setOnlineStatus(true);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取系统信息失败:', error);
|
||||||
|
this.setOnlineStatus(false);
|
||||||
|
this.showMockData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUI(cpuData, memoryData) {
|
||||||
|
|
||||||
|
// Glances CPU API:
|
||||||
|
// { "total": 12.3, ... }
|
||||||
|
|
||||||
|
const cpuPercent = Number(cpuData.total || 0).toFixed(2);
|
||||||
|
|
||||||
|
// Glances MEM API:
|
||||||
|
// { "percent": 45.6, ... }
|
||||||
|
|
||||||
|
const memoryPercent = Number(memoryData.percent || 0).toFixed(2);
|
||||||
|
|
||||||
|
this.cpuValueEl.textContent = `${cpuPercent}%`;
|
||||||
|
this.cpuBarEl.style.width = `${Math.round(cpuPercent)}%`;
|
||||||
|
|
||||||
|
this.memoryValueEl.textContent = `${memoryPercent}%`;
|
||||||
|
this.memoryBarEl.style.width = `${Math.round(memoryPercent)}%`;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const timeString = now.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
timeZoneName: 'short'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.lastUpdateEl.textContent = timeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnlineStatus(online) {
|
||||||
|
if (this.isOnline !== online) {
|
||||||
|
this.isOnline = online;
|
||||||
|
|
||||||
|
if (online) {
|
||||||
|
this.serverStatusEl.textContent = '在线';
|
||||||
|
this.serverStatusEl.className = 'info-value status-online';
|
||||||
|
} else {
|
||||||
|
this.serverStatusEl.textContent = '离线';
|
||||||
|
this.serverStatusEl.className = 'info-value status-offline';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showMockData() {
|
||||||
|
const mockCpu = Math.floor(Math.random() * 40) + 20;
|
||||||
|
const mockMemory = Math.floor(Math.random() * 30) + 40;
|
||||||
|
|
||||||
|
this.cpuValueEl.textContent = `${mockCpu}%`;
|
||||||
|
this.cpuBarEl.style.width = `${mockCpu}%`;
|
||||||
|
|
||||||
|
this.memoryValueEl.textContent = `${mockMemory}%`;
|
||||||
|
this.memoryBarEl.style.width = `${mockMemory}%`;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
this.lastUpdateEl.textContent = now.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
timeZoneName: 'short'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let isProtocolMenuOpen = false;
|
||||||
|
|
||||||
|
function toggleProtocolMenu() {
|
||||||
|
const selector = document.getElementById('protocol-selector');
|
||||||
|
isProtocolMenuOpen = !isProtocolMenuOpen;
|
||||||
|
|
||||||
|
if (isProtocolMenuOpen) {
|
||||||
|
selector.classList.add('open');
|
||||||
|
} else {
|
||||||
|
selector.classList.remove('open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectProtocol(protocol) {
|
||||||
|
const ports = {
|
||||||
|
'tcp': 11010,
|
||||||
|
'udp': 11010,
|
||||||
|
'wg': 11011,
|
||||||
|
'ws':11011,
|
||||||
|
'wss':11012,
|
||||||
|
'quic':11012,
|
||||||
|
'faketcp':11013,
|
||||||
|
'http':80,
|
||||||
|
'https':443,
|
||||||
|
'txt':null,
|
||||||
|
'srv':null
|
||||||
|
};
|
||||||
|
|
||||||
|
const protocolNames = {
|
||||||
|
'tcp': 'TCP',
|
||||||
|
'udp': 'UDP',
|
||||||
|
'wg': 'WG',
|
||||||
|
'ws': 'WS',
|
||||||
|
'wss': 'WSS',
|
||||||
|
'quic': 'QUIC',
|
||||||
|
'faketcp': 'FakeTCP',
|
||||||
|
'http': 'HTTP',
|
||||||
|
'https': 'HTTPS',
|
||||||
|
'txt': 'TXT',
|
||||||
|
'srv': 'SRV'
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('current-protocol').textContent = protocolNames[protocol];
|
||||||
|
|
||||||
|
const addressCode = document.getElementById('node-address');
|
||||||
|
if (ports[protocol] === null) {
|
||||||
|
addressCode.firstChild.textContent = `${protocol}://dreamlife.indevs.in\n`;
|
||||||
|
} else {
|
||||||
|
addressCode.firstChild.textContent = `${protocol}://dreamlife.indevs.in:${ports[protocol]}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.protocol-option').forEach(opt => {
|
||||||
|
opt.classList.remove('active');
|
||||||
|
});
|
||||||
|
document.querySelector(`.protocol-option[data-protocol="${protocol}"]`).classList.add('active');
|
||||||
|
|
||||||
|
toggleProtocolMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyAddress() {
|
||||||
|
const address = document.getElementById('node-address').textContent;
|
||||||
|
navigator.clipboard.writeText(address).then(() => {
|
||||||
|
const copyIcon = document.getElementById('copy-icon');
|
||||||
|
const checkIcon = document.getElementById('check-icon');
|
||||||
|
|
||||||
|
copyIcon.style.display = 'none';
|
||||||
|
checkIcon.style.display = 'block';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
copyIcon.style.display = 'block';
|
||||||
|
checkIcon.style.display = 'none';
|
||||||
|
}, 2000);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('复制失败:', err);
|
||||||
|
alert('复制失败,请手动复制');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', function(event) {
|
||||||
|
const selector = document.getElementById('protocol-selector');
|
||||||
|
if (!selector.contains(event.target) && isProtocolMenuOpen) {
|
||||||
|
selector.classList.remove('open');
|
||||||
|
isProtocolMenuOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function testLatency() {
|
||||||
|
const latencyText = document.querySelector('.latency-text');
|
||||||
|
const testBtn = document.getElementById('latency-test-btn');
|
||||||
|
|
||||||
|
testBtn.disabled = true;
|
||||||
|
testBtn.textContent = '测试中...';
|
||||||
|
latencyText.textContent = '--';
|
||||||
|
latencyText.className = 'latency-text';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
await fetch('https://dreamlife.indevs.in:61207/api/4/cpu', {
|
||||||
|
method: 'GET',
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
});
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
const latency = Math.round(endTime - startTime);
|
||||||
|
|
||||||
|
latencyText.textContent = `${latency}ms`;
|
||||||
|
|
||||||
|
if (latency < 100) {
|
||||||
|
latencyText.classList.add('latency-good');
|
||||||
|
} else if (latency < 300) {
|
||||||
|
latencyText.classList.add('latency-medium');
|
||||||
|
} else {
|
||||||
|
latencyText.classList.add('latency-bad');
|
||||||
|
}
|
||||||
|
|
||||||
|
testBtn.textContent = '重测';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('延迟测试失败:', error);
|
||||||
|
latencyText.textContent = '超时';
|
||||||
|
latencyText.classList.add('latency-bad');
|
||||||
|
testBtn.textContent = '重测';
|
||||||
|
} finally {
|
||||||
|
testBtn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
new SystemMonitor();
|
||||||
|
setTimeout(() => {
|
||||||
|
testLatency();
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
BIN
DreamLife_easytier/dl.png
Normal file
BIN
DreamLife_easytier/dl.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 311 KiB |
BIN
DreamLife_easytier/dreamlife.png
Normal file
BIN
DreamLife_easytier/dreamlife.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
133
DreamLife_easytier/index.html
Normal file
133
DreamLife_easytier/index.html
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>DreamLife - EasyTier公共节点</title>
|
||||||
|
<link rel="icon" href="dl.png" type="image/png">
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header class="header">
|
||||||
|
<div class="logo-container">
|
||||||
|
<img src="dl.png" alt="DreamLife Logo" class="icon-logo">
|
||||||
|
<img src="dreamlife.png" alt="DreamLife Text" class="text-logo">
|
||||||
|
</div>
|
||||||
|
<p class="subtitle">EasyTier 公共节点服务</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<section class="connection-section">
|
||||||
|
<h2>连接信息</h2>
|
||||||
|
<div class="connection-card">
|
||||||
|
<p class="connection-tip">使用 EasyTier 客户端初始节点配置:</p>
|
||||||
|
<div class="connection-details">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">节点地址:</span>
|
||||||
|
</div>
|
||||||
|
<div class="address-center">
|
||||||
|
<div class="protocol-selector" id="protocol-selector">
|
||||||
|
<button class="protocol-toggle" id="protocol-toggle" onclick="toggleProtocolMenu()">
|
||||||
|
<span id="current-protocol">TCP</span>
|
||||||
|
<svg class="chevron" id="chevron" viewBox="0 0 16 16" width="14" height="14">
|
||||||
|
<path d="M4.427 5.427a.75.75 0 0 1 1.06 0L8 8.023l2.513-2.596a.75.75 0 1 1 1.003 1.087l-3 3.099a.75.75 0 0 1-1.003 0l-3-3.099a.75.75 0 0 1 0-1.087Z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="protocol-menu" id="protocol-menu">
|
||||||
|
<div class="protocol-option active" data-protocol="tcp" onclick="selectProtocol('tcp')">TCP</div>
|
||||||
|
<div class="protocol-option" data-protocol="udp" onclick="selectProtocol('udp')">UDP</div>
|
||||||
|
<div class="protocol-option" data-protocol="wg" onclick="selectProtocol('wg')">WG</div>
|
||||||
|
<div class="protocol-option" data-protocol="ws" onclick="selectProtocol('ws')">WS</div>
|
||||||
|
<div class="protocol-option" data-protocol="wss" onclick="selectProtocol('wss')">WSS</div>
|
||||||
|
<div class="protocol-option" data-protocol="quic" onclick="selectProtocol('quic')">QUIC</div>
|
||||||
|
<div class="protocol-option" data-protocol="faketcp" onclick="selectProtocol('faketcp')">FakeTCP</div>
|
||||||
|
<div class="protocol-option" data-protocol="http" onclick="selectProtocol('http')">HTTP</div>
|
||||||
|
<div class="protocol-option" data-protocol="https" onclick="selectProtocol('https')">HTTPS</div>
|
||||||
|
<div class="protocol-option" data-protocol="txt" onclick="selectProtocol('txt')">TXT</div>
|
||||||
|
<div class="protocol-option" data-protocol="srv" onclick="selectProtocol('srv')">SRV</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<code class="detail-value" id="node-address">tcp://dreamlife.indevs.in:11010
|
||||||
|
<button class="copy-button" id="copy-button" onclick="copyAddress()" aria-label="复制代码">
|
||||||
|
<svg class="copy-icon" id="copy-icon" viewBox="0 0 16 16" width="14" height="14">
|
||||||
|
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/>
|
||||||
|
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5a.75.75 0 0 1 0-1.5h1.5a.25.25 0 0 0 .25-.25v-7.5Z"/>
|
||||||
|
</svg>
|
||||||
|
<svg class="check-icon" id="check-icon" viewBox="0 0 16 16" width="14" height="14" style="display: none;">
|
||||||
|
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="status-section">
|
||||||
|
<h2>公共服务器状态</h2>
|
||||||
|
<div class="metrics-grid">
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-header">
|
||||||
|
<span class="metric-label">CPU 占用率</span>
|
||||||
|
<span class="metric-value" id="cpu-value">0%</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" id="cpu-bar" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-header">
|
||||||
|
<span class="metric-label">内存占用率</span>
|
||||||
|
<span class="metric-value" id="memory-value">0%</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" id="memory-bar" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-header">
|
||||||
|
<span class="metric-label">网络延迟</span>
|
||||||
|
<span class="metric-value" id="latency-value">
|
||||||
|
<span class="latency-text">--</span>
|
||||||
|
<button class="latency-test-btn" id="latency-test-btn" onclick="testLatency()">测试</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-header">
|
||||||
|
<span class="metric-label">服务器位置</span>
|
||||||
|
</div>
|
||||||
|
<div class="location-box">
|
||||||
|
<svg class="location-icon" viewBox="0 0 16 16" width="16" height="16">
|
||||||
|
<path d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10Zm0-7a3 3 0 1 1 0-6 3 3 0 0 1 0 6Z"/>
|
||||||
|
</svg>
|
||||||
|
<span class="location-text">华东-上海</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="server-info">
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">节点状态:</span>
|
||||||
|
<span class="info-value status-online" id="server-status">在线</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">最后更新:</span>
|
||||||
|
<span class="info-value" id="last-update">--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<p>© 2026 由 DreamLife 提供公益支持</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
480
DreamLife_easytier/styles.css
Normal file
480
DreamLife_easytier/styles.css
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary-color: #3b82f6;
|
||||||
|
--bg-color: #f8fafc;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--text-primary: #1e293b;
|
||||||
|
--text-secondary: #64748b;
|
||||||
|
--border-color: #e2e8f0;
|
||||||
|
--success-color: #10b981;
|
||||||
|
--hover-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-logo {
|
||||||
|
width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-logo {
|
||||||
|
height: 60px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-section {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 32px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card {
|
||||||
|
padding: 20px;
|
||||||
|
background: var(--bg-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 8px;
|
||||||
|
background: var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, var(--primary-color), var(--hover-color));
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-info {
|
||||||
|
padding: 20px;
|
||||||
|
background: var(--bg-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row:not(:last-child) {
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-online {
|
||||||
|
color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-offline {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-section {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-card {
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--bg-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-tip {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
position: relative;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
background: var(--card-bg);
|
||||||
|
padding: 8px 16px;
|
||||||
|
padding-right: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
color: #000000;
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-selector {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: rgba(175, 184, 193, 0.2);
|
||||||
|
color: #57606a;
|
||||||
|
border: 1px solid rgba(31, 35, 40, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-toggle:hover {
|
||||||
|
background: rgba(175, 184, 193, 0.4);
|
||||||
|
color: #24292f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-toggle .chevron {
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-selector.open .chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
margin-top: 4px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid rgba(31, 35, 40, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
z-index: 100;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-selector.open .protocol-menu {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-option {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #57606a;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-option:hover {
|
||||||
|
background: rgba(175, 184, 193, 0.2);
|
||||||
|
color: #24292f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-option.active {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 4px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 8px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: rgba(175, 184, 193, 0.2);
|
||||||
|
color: #57606a;
|
||||||
|
border: 1px solid rgba(31, 35, 40, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
min-width: 26px;
|
||||||
|
min-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button:hover {
|
||||||
|
background: rgba(175, 184, 193, 0.4);
|
||||||
|
color: #24292f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button:active {
|
||||||
|
transform: translateY(-50%) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-icon,
|
||||||
|
.check-icon {
|
||||||
|
fill: currentColor;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#latency-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.latency-text {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--bg-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-icon {
|
||||||
|
fill: var(--primary-color);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.latency-test-btn {
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.latency-test-btn:hover {
|
||||||
|
background: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.latency-test-btn:disabled {
|
||||||
|
background: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.latency-good {
|
||||||
|
color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.latency-medium {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.latency-bad {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-section h2 {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 60px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.container {
|
||||||
|
padding: 20px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-logo {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-logo {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-section,
|
||||||
|
.connection-section {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-center {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 13px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-selector {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-toggle {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
230
install-easytier-relay.sh
Normal file
230
install-easytier-relay.sh
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "======================================"
|
||||||
|
echo " EasyTier 公益中继节点一键部署脚本"
|
||||||
|
echo "======================================"
|
||||||
|
|
||||||
|
if [ "$(id -u)" != "0" ]; then
|
||||||
|
echo "请使用 root 运行"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 检测包管理器
|
||||||
|
########################################
|
||||||
|
|
||||||
|
if command -v dnf >/dev/null 2>&1; then
|
||||||
|
PM="dnf"
|
||||||
|
elif command -v yum >/dev/null 2>&1; then
|
||||||
|
PM="yum"
|
||||||
|
elif command -v apt >/dev/null 2>&1; then
|
||||||
|
PM="apt"
|
||||||
|
elif command -v zypper >/dev/null 2>&1; then
|
||||||
|
PM="zypper"
|
||||||
|
else
|
||||||
|
echo "不支持的 Linux 发行版"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "检测到包管理器: $PM"
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 安装基础组件
|
||||||
|
########################################
|
||||||
|
|
||||||
|
case $PM in
|
||||||
|
apt)
|
||||||
|
apt update
|
||||||
|
apt install -y curl wget sudo fail2ban
|
||||||
|
;;
|
||||||
|
dnf|yum)
|
||||||
|
$PM install -y epel-release || true
|
||||||
|
$PM install -y curl wget sudo fail2ban
|
||||||
|
;;
|
||||||
|
zypper)
|
||||||
|
zypper install -y curl wget sudo fail2ban
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 安装 EasyTier
|
||||||
|
########################################
|
||||||
|
|
||||||
|
echo "安装 EasyTier..."
|
||||||
|
|
||||||
|
wget -O /tmp/easytier.sh \
|
||||||
|
"https://raw.githubusercontent.com/EasyTier/EasyTier/main/script/install.sh"
|
||||||
|
|
||||||
|
bash /tmp/easytier.sh install --gh-proxy https://ghfast.top/
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 查找 easytier-core
|
||||||
|
########################################
|
||||||
|
|
||||||
|
EASYTIER_BIN=""
|
||||||
|
|
||||||
|
for p in \
|
||||||
|
/usr/local/bin/easytier-core \
|
||||||
|
/usr/bin/easytier-core \
|
||||||
|
/opt/easytier/easytier-core
|
||||||
|
do
|
||||||
|
if [ -f "$p" ]; then
|
||||||
|
EASYTIER_BIN="$p"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$EASYTIER_BIN" ]; then
|
||||||
|
echo "未找到 easytier-core"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "EasyTier 路径: $EASYTIER_BIN"
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 主机名
|
||||||
|
########################################
|
||||||
|
|
||||||
|
HOSTNAME=$(hostname)
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 创建 systemd 服务
|
||||||
|
########################################
|
||||||
|
|
||||||
|
cat >/etc/systemd/system/easytier.service <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=EasyTier Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
|
||||||
|
ExecStart=$EASYTIER_BIN \\
|
||||||
|
--hostname $HOSTNAME \\
|
||||||
|
--relay-network-whitelist "" \\
|
||||||
|
--relay-all-peer-rpc
|
||||||
|
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
StartLimitIntervalSec=300
|
||||||
|
StartLimitBurst=10
|
||||||
|
|
||||||
|
LimitNOFILE=1048576
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# sysctl 优化
|
||||||
|
########################################
|
||||||
|
|
||||||
|
cat >/etc/sysctl.d/99-easytier.conf <<EOF
|
||||||
|
net.core.somaxconn = 4096
|
||||||
|
net.ipv4.tcp_syncookies = 1
|
||||||
|
net.ipv4.tcp_max_syn_backlog = 4096
|
||||||
|
net.core.netdev_max_backlog = 4096
|
||||||
|
|
||||||
|
net.ipv4.udp_rmem_min = 8192
|
||||||
|
net.ipv4.udp_wmem_min = 8192
|
||||||
|
|
||||||
|
net.ipv4.ip_forward = 0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# BBR
|
||||||
|
########################################
|
||||||
|
|
||||||
|
cat >/etc/sysctl.d/99-bbr.conf <<EOF
|
||||||
|
net.core.default_qdisc = fq
|
||||||
|
net.ipv4.tcp_congestion_control = bbr
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sysctl --system
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# fail2ban
|
||||||
|
########################################
|
||||||
|
|
||||||
|
mkdir -p /etc/fail2ban/filter.d
|
||||||
|
|
||||||
|
cat >/etc/fail2ban/filter.d/easytier.conf <<EOF
|
||||||
|
[Definition]
|
||||||
|
failregex = remote: \\S+://<HOST>:\\d+, err: wait resp error:.+
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >/etc/fail2ban/jail.local <<EOF
|
||||||
|
[easytier]
|
||||||
|
enabled = true
|
||||||
|
filter = easytier
|
||||||
|
backend = systemd
|
||||||
|
|
||||||
|
journalmatch = _SYSTEMD_UNIT=easytier.service
|
||||||
|
|
||||||
|
maxretry = 3
|
||||||
|
findtime = 600
|
||||||
|
bantime = 3600
|
||||||
|
|
||||||
|
banaction = firewallcmd-ipset
|
||||||
|
EOF
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 防火墙
|
||||||
|
########################################
|
||||||
|
|
||||||
|
if systemctl is-active firewalld >/dev/null 2>&1; then
|
||||||
|
|
||||||
|
firewall-cmd --permanent --add-port=11010/tcp
|
||||||
|
firewall-cmd --permanent --add-port=11010/udp
|
||||||
|
|
||||||
|
firewall-cmd --permanent --add-port=11011/tcp
|
||||||
|
firewall-cmd --permanent --add-port=11011/udp
|
||||||
|
|
||||||
|
firewall-cmd --permanent --add-port=11012/tcp
|
||||||
|
firewall-cmd --permanent --add-port=11012/udp
|
||||||
|
|
||||||
|
firewall-cmd --permanent --add-port=11013/tcp
|
||||||
|
|
||||||
|
firewall-cmd --reload
|
||||||
|
fi
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 启动服务
|
||||||
|
########################################
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
systemctl enable --now easytier
|
||||||
|
systemctl enable --now fail2ban
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 输出状态
|
||||||
|
########################################
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "======================================"
|
||||||
|
echo " EasyTier 公益节点部署完成"
|
||||||
|
echo "======================================"
|
||||||
|
echo
|
||||||
|
|
||||||
|
systemctl --no-pager --full status easytier || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "--------------------------------------"
|
||||||
|
|
||||||
|
fail2ban-client status || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "--------------------------------------"
|
||||||
|
|
||||||
|
echo "BBR 状态:"
|
||||||
|
sysctl net.ipv4.tcp_congestion_control
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "监听端口:"
|
||||||
|
ss -lntup | grep easytier || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "部署完成"
|
||||||
Loading…
x
Reference in New Issue
Block a user