Files
sentinel/OfficialWebsite/index.html
T
2026-06-07 19:50:38 +08:00

675 lines
28 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>
<!--
Copyright (c) [2026] [S.A. SNOWARE-SCU]
[MineSentinel] is licensed under Mulan PubL v2.
You can use this software according to the terms and conditions of the Mulan PubL v2.
You may obtain a copy of Mulan PubL v2 at:
http://license.coscl.org.cn/MulanPubL-2.0
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.
品牌标识(Logo / SVG / 项目名称" MineSentinel")保留所有权利,未经许可不得用于衍生项目。
-->
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MineSentinel — Minecraft 服务器守护进程</title>
<script src='https://gitee.com/SCU_team/mine-sentinel/widget_preview' async defer></script>
<style>
:root {
--amber: #f59e0b;
--orange: #ea580c;
--coral: #e8613c;
--warm-bg: #ffedd5;
--card-bg: #fff7ed;
--text: #431407;
--text-dim: #7c2d12;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.7;
color: var(--text);
background: linear-gradient(160deg, #f97316 0%, #ea580c 40%, #dc2626 100%);
min-height: 100vh;
position: relative;
overflow-x: hidden;
}
/* Geometric background elements */
body::before {
content: '';
position: fixed;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background:
repeating-linear-gradient(30deg, transparent, transparent 80px, rgba(255,255,255,0.03) 80px, rgba(255,255,255,0.03) 82px),
repeating-linear-gradient(-60deg, transparent, transparent 120px, rgba(255,255,255,0.04) 120px, rgba(255,255,255,0.04) 122px);
pointer-events: none;
z-index: 0;
}
.geo-hex {
position: fixed;
pointer-events: none;
z-index: 0;
opacity: 0.06;
}
.geo-hex::before {
content: '';
position: absolute;
width: 300px; height: 520px;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
background: #fff;
}
.geo-hex-1 { top: -100px; right: -80px; transform: rotate(15deg); }
.geo-hex-2 { bottom: 10%; left: -120px; transform: rotate(-10deg) scale(0.7); }
.geo-hex-3 { top: 60%; right: 5%; transform: rotate(45deg) scale(0.4); }
.geo-tri {
position: fixed;
pointer-events: none;
z-index: 0;
opacity: 0.05;
}
.geo-tri::before {
content: '';
position: absolute;
width: 0; height: 0;
border-left: 200px solid transparent;
border-right: 200px solid transparent;
border-bottom: 350px solid #fff;
}
.geo-tri-1 { top: 30%; left: 5%; transform: rotate(-20deg); }
.geo-tri-2 { bottom: 20%; right: 10%; transform: rotate(30deg) scale(0.6); }
.container {
max-width: 1100px;
margin: 0 auto;
padding: 20px;
position: relative;
z-index: 1;
}
header {
text-align: center;
padding: 70px 20px 40px;
color: white;
}
.logo-row {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
margin: 0 auto 20px;
filter: drop-shadow(0 4px 12px rgba(0,0,0,0.35));
}
.logo-svg {
width: 100px;
display: block;
}
.logo-png {
width: 80px;
height: auto;
display: block;
}
.author-badge {
font-size: 0.85rem;
opacity: 0.75;
margin-top: 4px;
letter-spacing: 1px;
}
.author-badge strong {
color: #fcd34d;
}
h1 {
font-size: 3rem;
margin-bottom: 12px;
text-shadow: 0 3px 6px rgba(0,0,0,0.4);
letter-spacing: 2px;
}
.subtitle {
font-size: 1.4rem;
opacity: 0.92;
margin-bottom: 8px;
}
.tagline {
font-size: 1rem;
letter-spacing: 4px;
font-weight: 600;
margin-top: 16px;
opacity: 0.85;
}
/* Widget area */
.widget-container {
margin-bottom: 50px;
padding: 30px 20px;
text-align: center;
background: rgba(67, 20, 7, 0.85);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
.osc_pro_color { color: #ffffff !important; }
.osc_panel_color { background-color: #1e252b !important; }
.osc_background_color { background-color: #323d47 !important; }
.osc_border_color { border-color: #455059 !important; }
.osc_desc_color { color: #d7deea !important; }
.osc_link_color * { color: #99a0ae !important; }
/* Log Preview */
.log-preview-section {
max-width: 860px;
margin: 50px auto;
padding: 30px;
}
.log-preview-section h2 {
text-align: center;
font-size: 1.9rem;
margin-bottom: 10px;
}
.log-desc {
text-align: center;
color: var(--warm-bg);
margin-bottom: 20px;
font-size: 0.95rem;
}
.log-terminal {
background: #1a1b26;
border: 1px solid #323d47;
border-radius: 12px;
padding: 18px 22px;
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', monospace;
font-size: 0.82rem;
line-height: 1.75;
box-shadow: inset 0 2px 12px rgba(0,0,0,0.5), 0 4px 16px rgba(0,0,0,0.25);
}
.log-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #d7deea;
}
.log-time {
color: #565f89;
margin-right: 6px;
}
.log-level {
display: inline-block;
min-width: 48px;
font-weight: 700;
letter-spacing: 0.02em;
margin-right: 4px;
}
.log-level.info { color: #a9b665; }
.log-level.warn { color: #e0af68; }
.log-level.debug { color: #7dcfff; }
.log-level.error { color: #f7768e; }
/* Cards */
.section-title {
text-align: center;
color: #fff;
font-size: 2rem;
margin: 50px 0 30px;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
margin-bottom: 40px;
}
.feature-card {
background: var(--card-bg);
border-radius: 12px;
padding: 28px;
box-shadow: 0 8px 28px rgba(0,0,0,0.18);
transition: transform 0.3s, box-shadow 0.3s;
border-top: 4px solid var(--orange);
}
.feature-card:hover {
transform: translateY(-6px);
box-shadow: 0 14px 36px rgba(0,0,0,0.25);
}
.feature-card h3 {
color: var(--orange);
font-size: 1.35rem;
margin-bottom: 12px;
}
.feature-card p {
color: var(--text-dim);
line-height: 1.8;
}
/* Deploy tutorial */
.deploy-section {
background: var(--card-bg);
border-radius: 12px;
padding: 40px;
box-shadow: 0 8px 28px rgba(0,0,0,0.18);
margin-bottom: 40px;
}
.deploy-section h2 {
color: var(--orange);
font-size: 1.8rem;
margin-bottom: 24px;
padding-bottom: 12px;
border-bottom: 3px solid var(--amber);
}
.deploy-section h3 {
color: var(--text);
margin: 24px 0 10px;
font-size: 1.2rem;
}
.deploy-section h4 {
color: var(--orange);
margin: 18px 0 8px;
font-size: 1.05rem;
}
.deploy-section pre {
background: #1c1917;
color: #fcd34d;
padding: 20px;
border-radius: 8px;
overflow-x: auto;
font-size: 0.9rem;
line-height: 1.6;
margin: 10px 0 16px;
}
.deploy-section code {
background: #fff7ed;
padding: 2px 6px;
border-radius: 4px;
color: var(--orange);
font-size: 0.92em;
}
.deploy-section table {
width: 100%;
border-collapse: collapse;
margin: 12px 0 20px;
}
.deploy-section th, .deploy-section td {
padding: 10px 14px;
text-align: left;
border-bottom: 1px solid #fed7aa;
}
.deploy-section th {
background: #ffedd5;
color: var(--orange);
font-weight: 600;
}
.deploy-section ul { margin: 8px 0 16px 20px; }
.deploy-section li { margin: 4px 0; color: var(--text-dim); }
/* Flow diagram */
.flow-box {
background: #1c1917;
border-radius: 8px;
padding: 24px;
color: #fcd34d;
font-family: monospace;
font-size: 0.88rem;
line-height: 1.9;
white-space: pre;
overflow-x: auto;
margin: 16px 0;
}
/* Buttons */
.cta-section {
text-align: center;
margin: 50px 0;
padding: 30px 20px;
}
.btn {
display: inline-block;
padding: 14px 36px;
background: white;
color: var(--orange);
text-decoration: none;
border-radius: 30px;
font-weight: bold;
font-size: 1.05rem;
margin: 8px;
box-shadow: 0 4px 14px rgba(0,0,0,0.2);
transition: all 0.3s;
cursor: pointer;
border: none;
}
.btn:hover {
transform: scale(1.05);
box-shadow: 0 8px 22px rgba(0,0,0,0.35);
}
.btn-primary {
background: #f97316;
color: white;
}
.btn-primary:hover { background: #ea580c; }
/* Modal */
.modal {
display: none;
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.7);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal.show { display: flex; }
.modal-content {
background: var(--card-bg);
border-radius: 12px;
padding: 36px;
max-width: 500px;
width: 90%;
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
position: relative;
animation: modalIn 0.3s ease-out;
}
@keyframes modalIn {
from { transform: translateY(-40px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.modal-close {
position: absolute;
top: 12px; right: 18px;
font-size: 28px;
cursor: pointer;
color: #9a3412;
transition: color 0.3s;
}
.modal-close:hover { color: #431407; }
.modal-title { color: var(--orange); font-size: 1.6rem; margin-bottom: 18px; }
.modal-body { color: var(--text-dim); line-height: 1.8; margin-bottom: 20px; }
.download-info {
background: #ffedd5;
padding: 20px;
border-radius: 8px;
border-left: 4px solid var(--orange);
}
.download-link { color: var(--orange); text-decoration: none; font-weight: bold; }
.download-link:hover { text-decoration: underline; }
.password-info {
margin-top: 14px;
padding: 10px;
background: #fff3cd;
border-radius: 6px;
color: #92400e;
}
/* Footer */
footer {
text-align: center;
padding: 40px 20px;
color: white;
opacity: 0.8;
margin-top: 40px;
}
.links { margin-top: 14px; }
.links a {
color: white; text-decoration: none; margin: 0 14px; opacity: 0.9;
}
.links a:hover { opacity: 1; text-decoration: underline; }
@media (max-width: 640px) {
h1 { font-size: 2rem; }
.deploy-section { padding: 20px; }
}
</style>
</head>
<body>
<!-- Geometric decorations -->
<div class="geo-hex geo-hex-1"></div>
<div class="geo-hex geo-hex-2"></div>
<div class="geo-hex geo-hex-3"></div>
<div class="geo-tri geo-tri-1"></div>
<div class="geo-tri geo-tri-2"></div>
<div class="container">
<header>
<div class="logo-row">
<img id="logoPng" class="logo-png" src="" alt="SW Security">
<svg class="logo-svg" viewBox="0 0 100 80" xmlns="http://www.w3.org/2000/svg">
<path d="M0,1 H100" stroke="#ffffff" stroke-width="6" fill="none" opacity="0.9"/>
<path d="M0,20 H30" stroke="#ffffff" stroke-width="6" fill="none" opacity="0.9"/>
<path d="M0,40 H30 L40,20 L60,60 L70,40 H100" stroke="#ffffff" stroke-width="6" fill="none" opacity="0.9"/>
<path d="M70,60 H100" stroke="#ffffff" stroke-width="6" fill="none" opacity="0.9"/>
<path d="M0,79 H100" stroke="#ffffff" stroke-width="6" fill="none" opacity="0.9"/>
</svg>
</div>
<h1>🛡️ MineSentinel</h1>
<p class="subtitle">Minecraft 服务器智能守护进程</p>
<p class="tagline">TCP 检测 · 自动重启 · 防循环冷却</p>
<p class="author-badge"><strong>SCU 团队</strong> 拥有 · 编写者 <strong>S.A. [@SNOWARE]</strong></p>
</header>
<!-- Features -->
<div class="features">
<div class="feature-card">
<h3>🔍 高频检测</h3>
<p>每 1.5 秒对 <code>127.0.0.1:25565</code> 发起 TCP 连接,毫秒级感知服务器状态变化。</p>
</div>
<div class="feature-card">
<h3>🧠 智能判断</h3>
<p>严格区分端口超时与端口关闭——超时仅记录日志(避免网络抖动误判),关闭才触发重启。</p>
</div>
<div class="feature-card">
<h3>⏳ 冷却期保护</h3>
<p>重启后等待 200 秒,初次启动有 300 秒宽限期,彻底防止 MC 启动缓慢导致循环重启。Watchdog 同步适配(210s)。</p>
</div>
<div class="feature-card">
<h3>🛡️ 重试上限</h3>
<p>连续 10 次重启失败后进入空闲等待模式,每 10 秒检测端口,恢复后自动退出。</p>
</div>
<div class="feature-card">
<h3>🌍 跨平台</h3>
<p>核心逻辑纯 Ruby 实现,支持 systemd / Docker / screen / Windows,轻松适配任意进程管理器。</p>
</div>
<div class="feature-card">
<h3>📝 日志完备</h3>
<p>四级日志(debug / info / warn / error),输出到 systemd journal,排查问题一目了然。</p>
</div>
</div>
<!-- Log Preview -->
<div class="log-preview-section">
<h2>📋 日志示例</h2>
<p class="log-desc">通过 <code>journalctl -u minesentinel -f</code> 实时查看,以下为一次完整崩溃恢复过程的日志输出:</p>
<div class="log-terminal">
<div class="log-line"><span class="log-time">18:16:17</span><span class="log-level debug">DEBUG</span> self_diag: TCP check OK — 0.0002s</div>
<div class="log-line"><span class="log-time">18:16:17</span><span class="log-level debug">DEBUG</span> mainloop: Server health check passed.</div>
<div class="log-line"><span class="log-time">18:16:18</span><span class="log-level debug">DEBUG</span> self_diag: TCP check OK — 0.0003s</div>
<div class="log-line"><span class="log-time">18:16:18</span><span class="log-level debug">DEBUG</span> mainloop: Server health check passed.</div>
<div class="log-line"><span class="log-time">18:16:20</span><span class="log-level warn">WARN</span> self_diag: TCP port closed — server is down.</div>
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level warn">WARN</span> mainloop: Server health check FAILED: Server died for unknown reason, and will be restarted.</div>
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level warn">WARN</span> mainloop: Action: Record → Server died!</div>
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level info">INFO</span> mainloop: Action: ForceResetServerProcess triggered.</div>
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level info">INFO</span> reset: Attempt #1 to reset Minecraft server.</div>
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level info">INFO</span> reset: Spawning 'systemctl restart minecraft' (PID will be captured)...</div>
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level debug">DEBUG</span> reset: systemctl restart spawned with new PID.</div>
<div class="log-line"><span class="log-time">18:16:51</span><span class="log-level warn">WARN</span> reset: 'systemctl restart' timed out after 30s. Force-killing process...</div>
<div class="log-line"><span class="log-time">18:16:51</span><span class="log-level warn">WARN</span> reset: Running 'systemctl kill minecraft' (last resort)...</div>
<div class="log-line"><span class="log-time">18:16:52</span><span class="log-level info">INFO</span> reset: Running 'systemctl start minecraft' (rebirth)...</div>
<div class="log-line"><span class="log-time">18:16:52</span><span class="log-level info">INFO</span> reset: Force-reset sequence completed.</div>
<div class="log-line"><span class="log-time">18:16:52</span><span class="log-level info">INFO</span> mainloop: Cooling down 200s for server startup...</div>
<div class="log-line" style="opacity:.5;"><span class="log-time">... ... ...</span></div>
<div class="log-line"><span class="log-time">18:20:12</span><span class="log-level info">INFO</span> mainloop: Cooldown finished, resuming monitoring.</div>
<div class="log-line"><span class="log-time">18:20:14</span><span class="log-level debug">DEBUG</span> self_diag: TCP check OK — 0.0002s</div>
<div class="log-line"><span class="log-time">18:20:15</span><span class="log-level debug">DEBUG</span> mainloop: Server health check passed.</div>
</div>
<p class="log-note" style="margin-top:14px;color:var(--text-dim);font-size:0.88em;">💡 日志中 PID、精确时间戳等敏感信息已脱敏。实际输出格式与 <code>journald</code> 一致,支持 <code>--since</code><code>-f</code> 等标准过滤参数。</p>
</div>
<!-- Deploy Tutorial -->
<div class="deploy-section">
<h2>📦 部署教程</h2>
<h3>1. 环境要求</h3>
<table>
<tr><th>项目</th><th>说明</th></tr>
<tr><td>操作系统</td><td>Linux(需 systemd),也支持 Windows</td></tr>
<tr><td>Ruby</td><td>>= 3.0</td></tr>
<tr><td>权限</td><td>rootsystemctl 需要)</td></tr>
<tr><td>MC 服务</td><td>已注册为 systemd 服务,默认名 <code>minecraft</code></td></tr>
</table>
<h3>2. 获取源码</h3>
<p style="margin-bottom:6px;">方式一:Git 克隆(推荐,仅拉取必要文件,排除仓库冗余)</p>
<pre>git clone --depth 1 --single-branch \
https://gitee.com/SCU_team/mine-sentinel.git \
/opt/minecraft/sentinel
# --depth 1 仅拉取最新提交,不下载历史记录
# --single-branch 仅拉取 master 分支</pre>
<p style="margin-bottom:6px;">方式二:手动复制</p>
<pre>mkdir -p /opt/minecraft/sentinel
cp main.rb method_executor.rb server_status_detector.rb \
/opt/minecraft/sentinel/</pre>
<h3>3. 手动测试</h3>
<pre>cd /opt/minecraft/sentinel
ruby main.rb
# 调试模式: LOG_LEVEL=debug ruby main.rb</pre>
<h3>4. 注册 systemd 服务</h3>
<p>项目源码中已提供 <code>minesentinel.service</code>,直接复制:</p>
<pre>cp minesentinel.service /etc/systemd/system/</pre>
<p>单元文件内容:</p>
<pre>[Unit]
Description=MineSentinel Minecraft Server Monitor
After=network.target
Before=minecraft.service
Wants=minecraft.service
[Service]
Type=simple
WorkingDirectory=/opt/minecraft/sentinel
ExecStart=/usr/bin/ruby /opt/minecraft/sentinel/main.rb
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
#Environment=LOG_LEVEL=debug
WatchdogSec=210
[Install]
WantedBy=multi-user.target</pre>
<h4>🔗 启动顺序</h4>
<p style="margin-bottom:10px;">MineSentinel 通过 <code>Before=minecraft.service</code> 确保先于 MC 启动,消除守护盲区。首次启动有 <strong>300 秒宽限期</strong>等待端口就绪:</p>
<div class="flow-box">开机 → network.target
├─ MineSentinel 启动(最先)
│ └─ 进入 300s 宽限期,等待 MC 端口就绪
└─ minecraft 随后启动(Before=
└─ 端口打开 → MineSentinel 恢复正常监控</div>
<pre>systemctl daemon-reload
systemctl enable minesentinel # 同时触发 minecraftWants=
systemctl start minesentinel</pre>
<h3>5. 查看日志</h3>
<pre>journalctl -u minesentinel -f # 实时
journalctl -u minesentinel -u minecraft -f # 双服务合并</pre>
<h3>⚙️ 可调参数</h3>
<table>
<tr><th>参数</th><th>文件</th><th></th><th>默认</th></tr>
<tr><td>初始宽限期</td><td>main.rb</td><td>31</td><td>300 s</td></tr>
<tr><td>检测间隔</td><td>main.rb</td><td>69</td><td>1.5 s</td></tr>
<tr><td>冷却期</td><td>main.rb</td><td>58</td><td>200 s</td></tr>
<tr><td>systemctl 超时</td><td>method_executor.rb</td><td>17</td><td>30 s</td></tr>
<tr><td>最大重试</td><td>method_executor.rb</td><td>23</td><td>10 次</td></tr>
<tr><td>检测端口</td><td>server_status_detector.rb</td><td>60</td><td>25565</td></tr>
</table>
<h3>🔄 工作流程</h3>
<div class="flow-box">MineSentinel 启动:
└─ 300s 宽限期 → 等待 MC 端口就绪(避免误判)
每 1.5 秒循环:
├─ TCP 连接 127.0.0.1:25565
├─ 端口开放 → 继续循环
├─ 端口超时 → 仅记录日志(不重启)
└─ 端口关闭 → 触发重启 → 冷却 200 秒
└─ 连续 10 次失败 → 空闲等待</div>
<h3>🪟 Windows / Docker / 其他场景</h3>
<ul>
<li><strong>Docker</strong>替换重启命令为 <code>spawn "docker restart mc-server"</code></li>
<li><strong>screen</strong><code>system "screen -S mc -X quit"; system "screen -dmS mc java ..."</code></li>
<li><strong>Windows</strong>删除 root 检查,替换为 <code>spawn "cmd /c start /B java ..."</code>,日志输出到文件</li>
<li><strong>监控其他服务:</strong>修改 <code>test_port</code> 的 IP 和端口即可</li>
</ul>
<p style="margin-top: 12px; color: var(--text-dim);">详细适配指南见 <a href="https://gitee.com/SCU_team/mine-sentinel/blob/master/DEPLOY.md" style="color: var(--orange);">DEPLOY.md</a></p>
</div>
<!-- Gitee Widget -->
<div class="widget-container">
<div id="osc-gitee-widget-tag"></div>
</div>
<!-- CTA -->
<div class="cta-section">
<button onclick="openModal()" class="btn btn-primary">⬇️ 获取 MineSentinel</button>
<a href="https://gitee.com/SCU_team/mine-sentinel" class="btn">⭐ Gitee 仓库</a>
<a href="https://gitee.com/SCU_team/mine-sentinel/blob/master/DEPLOY.md" class="btn">📖 完整文档</a>
</div>
<!-- Download Modal -->
<div id="dlModal" class="modal">
<div class="modal-content">
<span class="modal-close" onclick="closeModal()">&times;</span>
<h2 class="modal-title">⬇️ 获取 MineSentinel</h2>
<div class="modal-body">
<div class="download-info">
<p><strong>Git 克隆:</strong></p>
<pre style="margin:8px 0;font-size:0.85rem;">git clone https://gitee.com/SCU_team/mine-sentinel.git</pre>
<p style="margin-top:10px;"><strong>直接下载:</strong></p>
<p><a href="https://gitee.com/SCU_team/mine-sentinel/repository/archive/master.zip" target="_blank" class="download-link">📦 下载 ZIP 压缩包</a></p>
<div class="password-info" style="margin-top: 12px;">
<strong>📋 要求:</strong> Ruby >= 3.0 · Linux (systemd) 或 Windows
</div>
</div>
<p style="margin-top: 16px; color: var(--text-dim); font-size: 0.9em;">💡 部署前请阅读 <a href="https://gitee.com/SCU_team/mine-sentinel/blob/master/DEPLOY.md" style="color: var(--orange);">DEPLOY.md</a></p>
</div>
</div>
</div>
<footer>
<p>&copy; 2026 MineSentinel — S.A. SNOWARE-SCU</p>
<p>源码采用 <a href="http://license.coscl.org.cn/MulanPubL-2.0" style="color:#fcd34d;">MulanPubL v2.0</a> · 品牌标识(Logo/SVG)保留所有权利</p>
<div class="links">
<a href="https://gitee.com/SCU_team/mine-sentinel">Gitee</a>
<a href="https://gitee.com/SCU_team/mine-sentinel/issues">问题反馈</a>
<a href="https://gitee.com/SCU_team/mine-sentinel/blob/master/DEPLOY.md">部署文档</a>
</div>
<script src="swsec.js"></script></footer>
</div>
<script>
function openModal() {
document.getElementById('dlModal').classList.add('show');
}
function closeModal() {
document.getElementById('dlModal').classList.remove('show');
}
window.onclick = function(e) {
if (e.target === document.getElementById('dlModal')) closeModal();
}
</script>
</body>
</html>