Files

194 lines
7.0 KiB
JavaScript

import { state as appState } from "/assets/js/state.js";
import { $, esc } from "/assets/js/shared.js";
import { get, rows } from "/assets/js/navigation.js";
import { loadConfig } from "/assets/js/settings.js";
export function tokenHeaders() {
return { "Content-Type": "application/json" };
}
export async function loadServices() {
if (!$("systemServices")) return;
try {
appState.serviceReadOnly = false;
const d = await get(
`/api/v1/services?host=${encodeURIComponent(appState.currentConfigHost)}`,
);
appState.serviceCache = d.services;
renderServiceLists();
} catch (e) {
$("systemServices").innerHTML =
`<div class="error">${esc(e.message)}</div>`;
}
}
export function renderServiceLists() {
if (!$("systemServices")) return;
const q = $("serviceSearch").value.trim().toLowerCase(),
filtered = appState.serviceCache.filter(
(s) =>
!q || `${s.unit} ${s.description} ${s.note}`.toLowerCase().includes(q),
);
rows(
"systemServices",
filtered.map(
(s) =>
`<div class="row"><span><b>${esc(s.unit)}</b><br><span class="muted">${esc(s.description || "暂无服务说明")} · ${esc(s.active === "active" ? "正在运行" : "未运行")}</span></span><span class="row-actions"><span class="badge ${s.monitored && !s.paused ? "good" : ""}">${s.monitored ? (s.paused ? "已暂停" : "已监控") : "未监控"}</span><button data-select-service="${esc(s.unit)}">${s.monitored ? "编辑监控" : "加入监控"}</button></span></div>`,
),
"没有匹配的服务",
);
const monitored = appState.serviceCache.filter((s) => s.monitored);
rows(
"monitoredServices",
monitored.map(
(s) =>
`<div class="row"><span><b>${esc(s.monitor_name)}</b> <span class="badge ${s.paused ? "" : s.active === "active" ? "good" : "bad"}">${s.paused ? "监控已暂停" : s.active === "active" ? "运行中" : "服务已停止"}</span><br><span class="muted">${esc(s.unit)}</span><br><span class="muted">备注:${esc(s.note || "未填写")}</span></span><span class="row-actions"><button data-monitor-action="${s.paused ? "resume" : "pause"}" data-unit="${esc(s.unit)}">${s.paused ? "恢复监控" : "暂停监控"}</button><button data-service-action="${s.active === "active" ? "stop" : "start"}" data-unit="${esc(s.unit)}" ${s.protected && s.active === "active" ? 'disabled title="API 服务不能停止自身"' : ""}>${s.active === "active" ? "停止服务" : "启动服务"}</button><button data-select-service="${esc(s.unit)}">编辑</button><button class="danger" data-delete-monitor="${esc(s.unit)}">删除监控</button></span></div>`,
),
"尚未监控任何服务",
);
if (appState.serviceReadOnly)
document
.querySelectorAll("#configRows button")
.forEach((b) => (b.disabled = true));
document
.querySelectorAll("[data-select-service]")
.forEach((b) => (b.onclick = () => selectService(b.dataset.selectService)));
document
.querySelectorAll("[data-monitor-action]")
.forEach(
(b) =>
(b.onclick = () =>
monitorAction(b.dataset.unit, b.dataset.monitorAction)),
);
document
.querySelectorAll("[data-service-action]")
.forEach(
(b) =>
(b.onclick = () =>
serviceAction(b.dataset.unit, b.dataset.serviceAction)),
);
document
.querySelectorAll("[data-delete-monitor]")
.forEach((b) => (b.onclick = () => deleteMonitor(b.dataset.deleteMonitor)));
}
export function selectService(unit) {
const s = appState.serviceCache.find((x) => x.unit === unit);
if (!s) return;
appState.editingServiceName = s.monitor_name || "";
$("monitorTarget").value = s.unit;
$("monitorName").value =
s.monitor_name ||
s.unit
.replace(/\.service$/, "")
.replace(/[^A-Za-z0-9_.-]/g, "_")
.slice(0, 16)
.toUpperCase();
$("monitorNote").value = s.note || s.description || "";
$("serviceFormTitle").textContent = s.monitored
? "编辑服务监控"
: "新建服务监控";
$("serviceMessage").textContent = "已自动填写,可继续修改。";
$("monitorName").scrollIntoView({ behavior: "smooth", block: "center" });
}
export function resetMonitorForm() {
appState.editingServiceName = "";
$("monitorName").value = "";
$("monitorTarget").value = "";
$("monitorNote").value = "";
$("serviceFormTitle").textContent = "新建服务监控";
$("serviceMessage").textContent = "";
$("serviceMessage").className = "muted";
}
export async function saveMonitor() {
const body = {
host: appState.currentConfigHost,
operation: "save",
name: $("monitorName").value.trim(),
target: $("monitorTarget").value.trim(),
note: $("monitorNote").value.trim(),
previous_name: appState.editingServiceName,
};
try {
$("serviceMessage").textContent = "正在保存…";
await get("/api/v1/services", {
method: "PUT",
headers: tokenHeaders(),
body: JSON.stringify(body),
});
resetMonitorForm();
await loadServices();
$("serviceMessage").textContent = "保存成功";
$("serviceMessage").className = "success";
} catch (e) {
$("serviceMessage").textContent = e.message;
$("serviceMessage").className = "error";
}
}
export async function deleteMonitor(unit) {
const s = appState.serviceCache.find((x) => x.unit === unit);
if (
!s ||
!confirm(
appState.LANG === "zh"
? `确定删除 ${s.monitor_name} 的监控吗?\n不会删除或停止 ${unit}。`
: `Remove monitoring for ${s.monitor_name}?\nThis will not remove or stop ${unit}.`,
)
)
return;
try {
await get("/api/v1/services", {
method: "PUT",
headers: tokenHeaders(),
body: JSON.stringify({
host: appState.currentConfigHost,
operation: "delete",
name: s.monitor_name,
target: s.unit,
note: "",
}),
});
await loadConfig();
document.querySelector('[data-config-section="services"]')?.click();
} catch (e) {
alert(e.message);
}
}
export async function monitorAction(unit, action) {
const s = appState.serviceCache.find((x) => x.unit === unit);
if (!s) return;
try {
await get("/api/v1/services", {
method: "PUT",
headers: tokenHeaders(),
body: JSON.stringify({
host: appState.currentConfigHost,
operation: action,
name: s.monitor_name,
target: s.unit,
note: s.note || "",
}),
});
await loadServices();
} catch (e) {
alert(e.message);
}
}
export async function serviceAction(unit, action) {
if (
!confirm(
appState.LANG === "zh"
? `确定${action === "start" ? "启动服务" : "停止服务"} ${unit} 吗?\n这会直接执行 systemctl ${action}。`
: `${action === "start" ? "Start" : "Stop"} ${unit}?\nThis runs systemctl ${action} directly.`,
)
)
return;
try {
await get("/api/v1/services/control", {
method: "PUT",
headers: tokenHeaders(),
body: JSON.stringify({ host: appState.currentConfigHost, unit, action }),
});
setTimeout(loadServices, 700);
} catch (e) {
alert(e.message);
}
}