aws-mt5/templates/index.html

178 lines
6.3 KiB
HTML
Raw Permalink Normal View History

2026-01-04 18:58:20 +08:00
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>AWS IP 替换工具</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root {
--bg: linear-gradient(135deg, #0f172a, #1e293b);
--card: #0b1224;
--accent: #22d3ee;
--accent-2: #a855f7;
--text: #e2e8f0;
--muted: #94a3b8;
--danger: #f87171;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
background: var(--bg);
color: var(--text);
display: flex;
align-items: center;
justify-content: center;
padding: 32px;
}
.shell {
width: 100%;
max-width: 760px;
background: var(--card);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 14px;
padding: 28px;
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.45);
}
h1 {
margin: 0 0 6px;
letter-spacing: 0.8px;
}
p.lead {
margin: 0 0 18px;
color: var(--muted);
}
label {
display: block;
font-weight: 600;
margin-bottom: 6px;
color: #cbd5e1;
}
2026-01-05 11:07:55 +08:00
input, button {
2026-01-04 18:58:20 +08:00
width: 100%;
padding: 12px 14px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
color: var(--text);
font-size: 15px;
outline: none;
transition: border-color 0.2s ease, transform 0.1s ease;
}
2026-01-05 11:07:55 +08:00
input:focus {
2026-01-04 18:58:20 +08:00
border-color: var(--accent);
transform: translateY(-1px);
}
button {
cursor: pointer;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.6px;
border: none;
margin-top: 16px;
}
button:disabled { opacity: 0.6; cursor: not-allowed; }
.field { margin-bottom: 16px; }
.status {
margin-top: 14px;
padding: 14px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.status.error { border-color: rgba(248, 113, 113, 0.5); color: var(--danger); }
.mono { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; }
.badge {
display: inline-block;
padding: 2px 8px;
background: rgba(255, 255, 255, 0.08);
border-radius: 999px;
margin-right: 6px;
font-size: 12px;
}
.muted { color: var(--muted); }
.grid { display: grid; gap: 12px; }
2026-01-05 11:07:55 +08:00
.history { margin-top: 18px; }
.history-item { padding: 8px 10px; border-bottom: 1px solid rgba(255,255,255,0.06); }
.history-item:last-child { border-bottom: none; }
.history-head { display: flex; justify-content: space-between; align-items: center; }
2026-01-04 18:58:20 +08:00
@media (max-width: 600px) {
.shell { padding: 20px; }
}
</style>
</head>
<body>
<div class="shell">
<h1>AWS IP 替换</h1>
2026-01-05 11:07:55 +08:00
<p class="lead">输入当前服务器 IP系统会根据数据库中的 IP-账户映射自动确定 AWS 账户并替换实例。</p>
<p class="muted" style="margin-top:-6px;">历史查看:<a href="/history_page" style="color: var(--accent);">IP 替换链路</a></p>
2026-01-04 18:58:20 +08:00
{% if init_error %}
<div class="status error">配置加载失败:{{ init_error }}</div>
{% endif %}
<form id="replace-form" class="grid">
<div class="field">
<label for="ip_to_replace">当前 IP</label>
<input id="ip_to_replace" name="ip_to_replace" type="text" placeholder="例如54.12.34.56 或 10.0.1.23" required>
2026-01-05 11:07:55 +08:00
<div class="muted" style="margin-top:6px;">账户选择已隐藏,依赖数据库中的 IP-账户映射,请提前维护。</div>
2026-01-04 18:58:20 +08:00
</div>
<button type="submit" id="submit-btn">开始替换</button>
</form>
<div id="status-box" class="status" style="display:none;"></div>
2026-01-05 11:07:55 +08:00
2026-01-04 18:58:20 +08:00
</div>
<script>
const form = document.getElementById('replace-form');
const statusBox = document.getElementById('status-box');
const submitBtn = document.getElementById('submit-btn');
function setStatus(message, isError = false) {
statusBox.style.display = 'block';
statusBox.className = 'status' + (isError ? ' error' : '');
statusBox.innerHTML = message;
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
submitBtn.disabled = true;
setStatus('正在执行,请稍候...');
const formData = new FormData(form);
try {
const resp = await fetch('/replace_ip', {
method: 'POST',
body: formData,
});
2026-01-05 11:07:55 +08:00
let data;
try {
data = await resp.json();
} catch (jsonErr) {
const text = await resp.text();
throw new Error(text || '请求失败');
}
2026-01-04 18:58:20 +08:00
if (!resp.ok) {
throw new Error(data.error || '请求失败');
}
setStatus(
`<div><span class="badge">旧实例</span><span class="mono">${data.terminated_instance_id}</span></div>` +
`<div><span class="badge">新实例</span><span class="mono">${data.new_instance_id}</span></div>` +
2026-01-05 11:07:55 +08:00
`<div><span class="badge">新 IP</span><span class="mono">${data.new_ip}</span></div>` +
(data.terminated_network_out_mb !== undefined && data.terminated_network_out_mb !== null
? `<div><span class="badge">旧实例流量</span><span class="mono">${data.terminated_network_out_mb} MB (近30天)</span></div>`
: '')
2026-01-04 18:58:20 +08:00
);
} catch (err) {
setStatus(err.message, true);
} finally {
submitBtn.disabled = false;
}
});
</script>
</body>
</html>