feat: add mihomo service start/stop/restart control

This commit is contained in:
2026-06-14 15:29:05 +08:00
parent 4d488ee6bd
commit e93f15f670
2 changed files with 161 additions and 3 deletions
+99 -3
View File
@@ -216,12 +216,39 @@
<div class="stat-value" id="stat-subs">-</div>
</div>
</div>
<!-- Mihomo Service Control -->
<div class="card">
<div class="card-header">
<span class="card-title">快捷操作</span>
<span class="card-title">🔧 Mihomo 服务控制</span>
<button class="btn btn-ghost btn-sm" onclick="loadMihomoStatus()">🔄 刷新状态</button>
</div>
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:16px">
<div style="display:flex;align-items:center;gap:8px">
<span style="font-weight:600">运行状态:</span>
<span id="mihomo-status" class="badge badge-danger">未知</span>
</div>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-weight:600">自启:</span>
<span id="mihomo-enabled" class="badge badge-warning">未知</span>
</div>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-weight:600">PID</span>
<span id="mihomo-pid" style="color:var(--text2);font-family:monospace">-</span>
</div>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-success" id="btn-start" onclick="mihomoAction('start')">▶ 启动</button>
<button class="btn btn-danger" id="btn-stop" onclick="mihomoAction('stop')">■ 停止</button>
<button class="btn btn-primary" id="btn-restart" onclick="mihomoAction('restart')">🔄 重启</button>
<button class="btn btn-ghost" onclick="reloadConfig()">📋 重载配置</button>
<a class="btn btn-ghost" href="/yacd/" target="_blank">🎨 Yacd 面板</a>
</div>
<div id="mihomo-logs" style="margin-top:16px;display:none">
<details>
<summary style="cursor:pointer;color:var(--text2);font-size:13px">📝 查看服务日志</summary>
<pre id="mihomo-log-content" style="margin-top:8px;font-size:11px;max-height:300px;overflow-y:auto;background:var(--bg);padding:12px;border-radius:6px;color:var(--text2)"></pre>
</details>
</div>
<button class="btn btn-primary" onclick="reloadConfig()">🔄 重载配置</button>
<a class="btn btn-ghost" href="/yacd/" target="_blank" style="margin-left:8px">🎨 打开 Yacd 面板</a>
</div>
</div>
@@ -556,9 +583,78 @@
} catch(e) { toast('加载日志失败', 'error'); }
}
// ========== Mihomo Service Control ==========
async function loadMihomoStatus() {
try {
const status = await api('GET', '/api/mihomo/status');
const statusEl = document.getElementById('mihomo-status');
const enabledEl = document.getElementById('mihomo-enabled');
const pidEl = document.getElementById('mihomo-pid');
if (status.active) {
statusEl.textContent = '运行中';
statusEl.className = 'badge badge-success';
document.getElementById('btn-start').disabled = true;
document.getElementById('btn-stop').disabled = false;
document.getElementById('btn-restart').disabled = false;
} else {
statusEl.textContent = '已停止';
statusEl.className = 'badge badge-danger';
document.getElementById('btn-start').disabled = false;
document.getElementById('btn-stop').disabled = true;
document.getElementById('btn-restart').disabled = true;
}
if (status.enabled) {
enabledEl.textContent = '已启用';
enabledEl.className = 'badge badge-success';
} else {
enabledEl.textContent = '未启用';
enabledEl.className = 'badge badge-warning';
}
pidEl.textContent = status.pid || '-';
} catch(e) {
console.error('Failed to load mihomo status:', e);
}
}
async function mihomoAction(action) {
const btnMap = { start: 'btn-start', stop: 'btn-stop', restart: 'btn-restart' };
const btn = document.getElementById(btnMap[action]);
const origText = btn.textContent;
btn.disabled = true;
btn.innerHTML = '<span class="spinner"></span> 处理中...';
try {
const res = await api('POST', '/api/mihomo/' + action);
if (res.error) throw new Error(res.error);
toast('Mihomo ' + (action === 'start' ? '已启动' : action === 'stop' ? '已停止' : '已重启'), 'success');
await new Promise(r => setTimeout(r, 1500));
loadMihomoStatus();
if (action !== 'stop') loadDashboard();
} catch(e) {
toast(action + ' 失败: ' + e.message, 'error');
} finally {
btn.textContent = origText;
btn.disabled = false;
}
}
async function loadMihomoLogs() {
try {
const res = await api('GET', '/api/mihomo/logs');
if (res.logs) {
document.getElementById('mihomo-log-content').textContent = res.logs;
document.getElementById('mihomo-logs').style.display = 'block';
}
} catch {}
}
// ========== Init ==========
window.addEventListener('load', () => {
loadDashboard();
loadMihomoStatus();
// Handle hash navigation
const hash = location.hash.replace('#', '');
if (hash) showPage(hash);
+62
View File
@@ -91,6 +91,15 @@ function sendError(res, msg, status = 500) {
sendJson(res, { error: msg }, status);
}
function execCommand(cmd) {
return new Promise((resolve, reject) => {
exec(cmd, { timeout: 15000 }, (err, stdout, stderr) => {
if (err) reject(new Error(stderr || err.message));
else resolve(stdout.trim());
});
});
}
// ========== Static File Server ==========
const MIME_TYPES = {
'.html': 'text/html; charset=utf-8',
@@ -381,6 +390,59 @@ async function handleApi(req, res, apiPath) {
}
}
// GET /api/mihomo/status - Get mihomo service status
if (apiPath === '/api/mihomo/status' && method === 'GET') {
try {
const active = await execCommand('systemctl is-active mihomo');
const enabled = await execCommand('systemctl is-enabled mihomo');
let pid = '';
try { pid = await execCommand('systemctl show mihomo --property=MainPID --value'); } catch {}
return sendJson(res, { active: active === 'active', enabled: enabled === 'enabled', pid, status: active });
} catch (e) {
return sendJson(res, { active: false, enabled: false, pid: '', status: 'unknown' });
}
}
// POST /api/mihomo/start - Start mihomo
if (apiPath === '/api/mihomo/start' && method === 'POST') {
try {
await execCommand('systemctl start mihomo');
return sendJson(res, { ok: true, message: 'Mihomo started' });
} catch (e) {
return sendError(res, 'Failed to start: ' + e.message);
}
}
// POST /api/mihomo/stop - Stop mihomo
if (apiPath === '/api/mihomo/stop' && method === 'POST') {
try {
await execCommand('systemctl stop mihomo');
return sendJson(res, { ok: true, message: 'Mihomo stopped' });
} catch (e) {
return sendError(res, 'Failed to stop: ' + e.message);
}
}
// POST /api/mihomo/restart - Restart mihomo
if (apiPath === '/api/mihomo/restart' && method === 'POST') {
try {
await execCommand('systemctl restart mihomo');
return sendJson(res, { ok: true, message: 'Mihomo restarted' });
} catch (e) {
return sendError(res, 'Failed to restart: ' + e.message);
}
}
// GET /api/mihomo/logs - Get mihomo service logs
if (apiPath === '/api/mihomo/logs' && method === 'GET') {
try {
const logs = await execCommand('journalctl -u mihomo --no-pager -n 50');
return sendJson(res, { logs });
} catch (e) {
return sendError(res, e.message);
}
}
// GET /api/config - Get current config
if (apiPath === '/api/config' && method === 'GET') {
try {