试运营版本
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