Files
shell/docs/index.html
2025-09-21 16:02:07 +08:00

585 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IP地址命令生成器</title>
<link rel="icon" href="favicon.ico">
<style>
:root {
--primary: #4f46e5;
--primary-hover: #4338ca;
--secondary: #10b981;
--secondary-hover: #059669;
--danger: #ef4444;
--danger-hover: #dc2626;
--text: #1f2937;
--text-light: #6b7280;
--bg: #f9fafb;
--card-bg: #ffffff;
--border: #e5e7eb;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: var(--bg);
color: var(--text);
line-height: 1.6;
padding: 20px;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: flex-start;
font-size: 16px;
}
.container {
background: var(--card-bg);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05), 0 4px 6px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 800px;
padding: 20px;
margin: 10px 0;
}
.header {
text-align: center;
margin-bottom: 16px;
}
.header h1 {
font-size: 1.8rem;
font-weight: 600;
margin-bottom: 8px;
color: var(--text);
}
.header p {
color: var(--text-light);
font-size: 1rem;
}
.input-section {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 16px;
}
.input-row {
display: flex;
flex-direction: column;
gap: 8px;
}
.flex-row {
display: flex;
gap: 12px;
}
.flex-col {
flex: 1;
}
label {
font-weight: 500;
font-size: 1rem;
color: var(--text);
}
input, textarea, select {
padding: 10px 12px;
font-size: 1rem;
border-radius: 6px;
border: 1px solid var(--border);
transition: border-color 0.2s, box-shadow 0.2s;
width: 100%;
}
input:focus, textarea:focus, select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
}
#interface {
max-width: 120px;
}
#ipInput {
min-height: 120px;
resize: vertical;
font-family: monospace;
font-size: 0.95rem;
}
.button-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
button {
padding: 4px 12px;
font-size: 1rem;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
flex: 1;
min-width: 100px;
display: flex;
align-items: center;
justify-content: center;
height: 36px;
}
.btn-primary {
background-color: var(--primary);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-hover);
}
.btn-secondary {
background-color: var(--secondary);
color: white;
}
.btn-secondary:hover {
background-color: var(--secondary-hover);
}
.btn-danger {
background-color: var(--danger);
color: white;
}
.btn-danger:hover {
background-color: var(--danger-hover);
}
.tab-container {
margin-bottom: 16px;
border-bottom: 1px solid var(--border);
}
.tab-buttons {
display: flex;
}
.tab-button {
background: transparent;
border: none;
padding: 8px 16px;
cursor: pointer;
font-weight: 500;
color: var(--text-light);
border-bottom: 2px solid transparent;
}
.tab-button.active {
color: var(--primary);
border-bottom: 2px solid var(--primary);
}
.tab-content {
display: none;
padding-top: 16px;
}
.tab-content.active {
display: block;
}
.result-container {
margin-top: 16px;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.result-header h3 {
font-size: 1.1rem;
font-weight: 600;
}
.result-box {
background-color: #f8fafc;
border: 1px solid var(--border);
padding: 12px;
border-radius: 6px;
min-height: 150px;
max-height: 300px;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Roboto Mono', monospace;
font-size: 0.95rem;
overflow: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.result-box::-webkit-scrollbar {
display: none;
}
.doc-section {
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--border);
font-size: 0.9rem;
}
.doc-section h4 {
margin-bottom: 8px;
font-size: 1rem;
}
.doc-section ul {
padding-left: 20px;
margin-bottom: 12px;
}
.doc-section ul li {
margin-bottom: 4px;
}
.doc-section code {
background-color: #f1f5f9;
padding: 2px 4px;
border-radius: 4px;
font-family: 'Roboto Mono', monospace;
font-size: 0.85rem;
}
@media (max-width: 640px) {
.container {
padding: 16px;
}
.header h1 {
font-size: 1.5rem;
}
.button-group {
flex-direction: column;
}
button {
width: 100%;
min-width: auto;
}
#interface {
max-width: 100%;
}
.flex-row {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>公网IP地址添加命令生成器</h1>
<p>为Linux服务器生成添加公网IP的命令</p>
</div>
<div class="tab-container">
<div class="tab-buttons">
<button class="tab-button active" onclick="changeTab(event, 'ipv4Tab')">IPv4</button>
<button class="tab-button" onclick="changeTab(event, 'ipv6Tab')">IPv6</button>
</div>
</div>
<div id="ipv4Tab" class="tab-content active">
<div class="input-section">
<div class="input-row">
<label for="interface">网卡名称</label>
<input type="text" id="interface" value="eth0" placeholder="例如: eth0" />
</div>
<div class="input-row">
<label for="ipInput">公网IPv4地址列表 (每行一个)</label>
<textarea id="ipInput" placeholder="例如:
203.0.113.10
192.0.2.100/24"></textarea>
</div>
</div>
<div class="button-group">
<button class="btn-primary" onclick="generateCommand('ipv4')">生成命令</button>
<button class="btn-danger" onclick="generateCSectionCommand()">生成C段命令</button>
</div>
</div>
<div id="ipv6Tab" class="tab-content">
<div class="input-section">
<div class="input-row">
<label for="interfaceIpv6">网卡名称</label>
<input type="text" id="interfaceIpv6" value="eth0" placeholder="例如: eth0" />
</div>
<div class="input-row">
<label for="ipv6Prefix">IPv6地址或网段前缀</label>
<input type="text" id="ipv6Prefix" placeholder="例如: 2001:475:35:3f4::6/64" />
</div>
<div class="input-row">
<div class="flex-row">
<div class="flex-col">
<label for="ipv6Count">生成IPv6地址数量</label>
<input type="number" id="ipv6Count" value="10" min="1" max="100" />
</div>
<div class="flex-col">
<label for="ipv6Mask">子网掩码长度</label>
<input type="number" id="ipv6Mask" value="64" min="1" max="128" />
</div>
</div>
</div>
</div>
<div class="button-group">
<button class="btn-primary" onclick="generateIPv6Command()">生成IPv6命令</button>
</div>
</div>
<div class="result-container">
<div class="result-header">
<h3>生成的命令:</h3>
<button class="btn-secondary" onclick="copyResult()">复制命令</button>
</div>
<div class="result-box" id="resultBox">生成的命令将显示在这里...</div>
</div>
<div class="doc-section">
<h4>Linux服务器添加公网IP说明</h4>
<ul>
<li><strong>临时添加IP</strong>: 上述命令会临时添加IP系统重启后失效</li>
<li><strong>永久添加</strong>: 需要修改网络配置文件 <code>/etc/network/interfaces</code><code>/etc/sysconfig/network-scripts/</code> 下的配置</li>
<li><strong>验证添加</strong>: 使用 <code>ip addr show</code> 命令验证IP是否添加成功</li>
<li><strong>注意事项</strong>: 添加公网IP前需确认IP已分配给您的服务器否则可能导致IP冲突</li>
<li><strong>IPv6注意</strong>: 添加IPv6地址前确保服务器已开启IPv6支持</li>
</ul>
<p><small>使用<code>sysctl -w net.ipv6.conf.all.forwarding=1</code>开启IPv6转发<code>sysctl -p</code>使配置生效</small></p>
</div>
</div>
<script>
function changeTab(evt, tabId) {
const tabContents = document.getElementsByClassName("tab-content");
for (let i = 0; i < tabContents.length; i++) {
tabContents[i].classList.remove("active");
}
const tabButtons = document.getElementsByClassName("tab-button");
for (let i = 0; i < tabButtons.length; i++) {
tabButtons[i].classList.remove("active");
}
document.getElementById(tabId).classList.add("active");
evt.currentTarget.classList.add("active");
}
function generateCommand(type) {
const interfaceName = document.getElementById(type === 'ipv6' ? 'interfaceIpv6' : 'interface').value.trim();
const ipInput = document.getElementById('ipInput').value.trim().split('\n');
let commands = '';
if (interfaceName && ipInput.length > 0) {
ipInput.forEach(ip => {
const trimmedIp = ip.trim();
if (trimmedIp) {
const ipWithMask = trimmedIp.includes('/') ? trimmedIp : `${trimmedIp}/32`;
commands += `sudo ip addr add ${ipWithMask} dev ${interfaceName}\n`;
}
});
document.getElementById('resultBox').textContent = commands || "没有有效的IP地址输入";
} else {
document.getElementById('resultBox').textContent = "请填写网卡名称和IP地址";
}
}
function generateCSectionCommand() {
const interfaceName = document.getElementById('interface').value.trim();
if (!interfaceName) {
document.getElementById('resultBox').textContent = "请先填写网卡名称";
return;
}
// 获取第一个IP作为C段基准
let firstIp = document.getElementById('ipInput').value.trim().split('\n')[0] || '';
firstIp = firstIp.split('/')[0];
if (!firstIp) {
firstIp = '198.51.100.1'; // 使用RFC 5737定义的测试网段作为默认值
}
const ipParts = firstIp.split('.');
if (ipParts.length !== 4 || ipParts.some(part => isNaN(parseInt(part)) || parseInt(part) > 255)) {
document.getElementById('resultBox').textContent = "请输入有效的IP地址作为C段基准";
return;
}
let commands = '';
const baseIp = `${ipParts[0]}.${ipParts[1]}.${ipParts[2]}`;
for (let i = 1; i <= 254; i++) {
commands += `sudo ip addr add ${baseIp}.${i}/24 dev ${interfaceName}\n`;
}
document.getElementById('resultBox').textContent = commands;
}
function generateIPv6Command() {
const interfaceName = document.getElementById('interfaceIpv6').value.trim();
const ipv6Input = document.getElementById('ipv6Prefix').value.trim();
const ipv6Count = parseInt(document.getElementById('ipv6Count').value);
let ipv6Mask = parseInt(document.getElementById('ipv6Mask').value);
if (!interfaceName || !ipv6Input) {
document.getElementById('resultBox').textContent = "请填写网卡名称和IPv6前缀";
return;
}
if (isNaN(ipv6Count) || ipv6Count < 1 || ipv6Count > 1000) {
document.getElementById('resultBox').textContent = "IPv6一次最多生成1000个";
return;
}
if (isNaN(ipv6Mask) || ipv6Mask < 1 || ipv6Mask > 128) {
document.getElementById('resultBox').textContent = "IPv6子网掩码长度必须在1-128之间";
return;
}
let prefix = ipv6Input;
let inputMask = null;
if (ipv6Input.includes('/')) {
const parts = ipv6Input.split('/');
prefix = parts[0];
inputMask = parseInt(parts[1]);
if (!isNaN(inputMask) && inputMask >= 1 && inputMask <= 128) {
ipv6Mask = inputMask;
document.getElementById('ipv6Mask').value = ipv6Mask;
}
}
const prefixSegments = Math.ceil(ipv6Mask / 16);
let networkPrefix = '';
if (prefix.includes('::')) {
const expandedAddress = expandIPv6Address(prefix);
const segments = expandedAddress.split(':');
networkPrefix = segments.slice(0, prefixSegments).join(':');
if (prefixSegments < 8) {
networkPrefix += ':';
}
} else {
const segments = prefix.split(':');
networkPrefix = segments.slice(0, prefixSegments).join(':');
if (prefixSegments < 8) {
networkPrefix += ':';
}
}
let commands = '';
for (let i = 0; i < ipv6Count; i++) {
const randomAddress = generateRandomIPv6InterfaceID(networkPrefix, ipv6Mask);
commands += `sudo ip addr add ${randomAddress}/${ipv6Mask} dev ${interfaceName}\n`;
}
document.getElementById('resultBox').textContent = commands;
}
function expandIPv6Address(address) {
if (address.includes('/')) {
address = address.split('/')[0];
}
if (!address.includes('::')) {
return address;
}
const parts = address.split('::');
const beforeDoubleColon = parts[0] ? parts[0].split(':') : [];
const afterDoubleColon = parts[1] ? parts[1].split(':') : [];
const missingGroups = 8 - (beforeDoubleColon.length + afterDoubleColon.length);
let expandedAddress = '';
if (beforeDoubleColon.length > 0) {
expandedAddress += beforeDoubleColon.join(':') + ':';
}
for (let i = 0; i < missingGroups; i++) {
expandedAddress += '0:';
}
if (afterDoubleColon.length > 0) {
expandedAddress += afterDoubleColon.join(':');
} else {
expandedAddress = expandedAddress.slice(0, -1);
}
return expandedAddress;
}
function generateRandomIPv6InterfaceID(networkPrefix, prefixLength) {
const segmentsToKeep = Math.ceil(prefixLength / 16);
const segmentsToGenerate = 8 - segmentsToKeep;
if (segmentsToGenerate <= 0) {
return networkPrefix;
}
const cleanPrefix = networkPrefix.endsWith(':') ?
networkPrefix.slice(0, -1) : networkPrefix;
const existingSegments = cleanPrefix.split(':');
let randomSegments = [];
for (let i = 0; i < segmentsToGenerate; i++) {
randomSegments.push(generateRandomHex(4));
}
return [...existingSegments, ...randomSegments].join(':');
}
function generateRandomHex(length) {
const hexChars = '0123456789abcdef';
let result = '';
for (let i = 0; i < length; i++) {
result += hexChars.charAt(Math.floor(Math.random() * hexChars.length));
}
return result;
}
function copyResult() {
const resultBox = document.getElementById('resultBox');
const textToCopy = resultBox.textContent;
navigator.clipboard.writeText(textToCopy).then(() => {
const copyBtn = document.querySelector('.btn-secondary');
const originalText = copyBtn.textContent;
copyBtn.textContent = '已复制!';
setTimeout(() => {
copyBtn.textContent = originalText;
}, 2000);
}).catch(err => {
console.error('复制失败: ', err);
alert('复制失败,请手动选择文本复制');
});
}
</script>
</body>
</html>