194 lines
7.4 KiB
JavaScript
194 lines
7.4 KiB
JavaScript
import { state as appState } from "/assets/js/state.js";
|
|
import { pluginText as text, $, 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" ? text("运行中", "Running") : s.active === "inactive" || s.active === "failed" ? text("已停止", "Stopped") : text("未确认", "Unconfirmed"))}</span></span><span class="row-actions"><span class="badge semantic-status" data-semantic="${s.monitored && !s.paused ? "enabled" : "neutral"}">${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 semantic-status" data-semantic="${s.paused ? "neutral" : s.active === "active" ? "running" : s.active === "inactive" || s.active === "failed" ? "error" : "unknown"}">${s.paused ? text("监控已暂停", "Monitor paused") : s.active === "active" ? text("运行中", "Running") : s.active === "inactive" || s.active === "failed" ? text("已停止", "Stopped") : text("未确认", "Unconfirmed")}</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);
|
|
}
|
|
}
|