Added Windows Service Monitor & changed local to systen
This commit is contained in:
parent
fe50adb061
commit
0f951ef123
@ -1,55 +0,0 @@
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const { execFile } = require("child_process");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class LocalServiceMonitorType extends MonitorType {
|
||||
name = "local-service";
|
||||
description = "Checks if a local service is running by executing a command.";
|
||||
|
||||
/**
|
||||
* Check a local systemd service status.
|
||||
* Uses `systemctl is-running` to determine if the service is active.
|
||||
* @param {object} monitor The monitor object containing monitor.local_service_name.
|
||||
* @param {object} heartbeat The heartbeat object to update.
|
||||
* @param {object} server The server object (unused in this specific check).
|
||||
* @returns {Promise<object>} A promise that resolves with the updated heartbeat.
|
||||
* @throws {Error} If the monitor.local_service_name is invalid or the command execution fails.
|
||||
*/
|
||||
async check(monitor, heartbeat, server) {
|
||||
// Basic sanitization to prevent argument injection.
|
||||
// This regex allows for standard service names, including those with instances like "sshd@.service".
|
||||
if (!monitor.local_service_name || !/^[a-zA-Z0-9._\-@]+$/.test(monitor.local_service_name)) {
|
||||
heartbeat.status = DOWN;
|
||||
heartbeat.msg = "Invalid service name provided.";
|
||||
throw new Error(heartbeat.msg);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("systemctl", [ "is-active", monitor.local_service_name ], (error, stdout, stderr) => {
|
||||
// systemctl is-active exits with 0 if the service is active,
|
||||
// and a non-zero code if it is inactive, failed, or not found.
|
||||
// stderr often contains useful info like "service not found"
|
||||
let output = (stderr || stdout || "").toString().trim();
|
||||
|
||||
if (output.length > 200) {
|
||||
output = output.substring(0, 200) + "...";
|
||||
}
|
||||
|
||||
if (error) {
|
||||
heartbeat.msg = stderr || stdout || `Service '${monitor.local_service_name}' is not running.`;
|
||||
reject(new Error(heartbeat.msg));
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's no error, the service is running.
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = `Service '${monitor.local_service_name}' is running.`;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
LocalServiceMonitorType,
|
||||
};
|
||||
110
server/monitor-types/system-service.js
Normal file
110
server/monitor-types/system-service.js
Normal file
@ -0,0 +1,110 @@
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const { execFile } = require("child_process");
|
||||
const process = require("process");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class SystemServiceMonitorType extends MonitorType {
|
||||
name = "system-service";
|
||||
description = "Checks if a system service is running (systemd on Linux, Service Manager on Windows).";
|
||||
|
||||
/**
|
||||
* Check the system service status.
|
||||
* Detects OS and dispatches to the appropriate check method.
|
||||
* @param {object} monitor The monitor object containing monitor.system_service_name.
|
||||
* @param {object} heartbeat The heartbeat object to update.
|
||||
* @returns {Promise<void>} Resolves when check is complete.
|
||||
*/
|
||||
async check(monitor, heartbeat) {
|
||||
// Use the new variable name 'system_service_name' to match the monitor type change
|
||||
const serviceName = monitor.system_service_name;
|
||||
|
||||
// Basic sanitization.
|
||||
// We do not allow spaces to ensure users use the "Service Name" (wuauserv) and not "Display Name".
|
||||
if (!serviceName || !/^[a-zA-Z0-9._\-@]+$/.test(serviceName)) {
|
||||
heartbeat.status = DOWN;
|
||||
heartbeat.msg = "Invalid service name. Please use the internal Service Name (no spaces).";
|
||||
throw new Error(heartbeat.msg);
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
return this.checkWindows(serviceName, heartbeat);
|
||||
} else {
|
||||
return this.checkLinux(serviceName, heartbeat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Linux Check (Systemd)
|
||||
* @param {string} serviceName The name of the service to check.
|
||||
* @param {object} heartbeat The heartbeat object.
|
||||
* @returns {Promise<void>} Resolves on success, rejects on error.
|
||||
*/
|
||||
async checkLinux(serviceName, heartbeat) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Linter requires spaces inside array brackets: [ "arg1", "arg2" ]
|
||||
execFile("systemctl", [ "is-active", serviceName ], (error, stdout, stderr) => {
|
||||
// Combine output and truncate to ~200 chars to prevent DB bloat
|
||||
let output = (stderr || stdout || "").toString().trim();
|
||||
if (output.length > 200) {
|
||||
output = output.substring(0, 200) + "...";
|
||||
}
|
||||
|
||||
if (error) {
|
||||
heartbeat.msg = output || `Service '${serviceName}' is not running.`;
|
||||
reject(new Error(heartbeat.msg));
|
||||
return;
|
||||
}
|
||||
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = `Service '${serviceName}' is running.`;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows Check (PowerShell)
|
||||
* @param {string} serviceName The name of the service to check.
|
||||
* @param {object} heartbeat The heartbeat object.
|
||||
* @returns {Promise<void>} Resolves on success, rejects on error.
|
||||
*/
|
||||
async checkWindows(serviceName, heartbeat) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cmd = "powershell";
|
||||
// -NoProfile: Faster startup, -NonInteractive: No prompts
|
||||
const args = [
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
`(Get-Service -Name "${serviceName}").Status`
|
||||
];
|
||||
|
||||
execFile(cmd, args, (error, stdout, stderr) => {
|
||||
let output = (stderr || stdout || "").toString().trim();
|
||||
if (output.length > 200) {
|
||||
output = output.substring(0, 200) + "...";
|
||||
}
|
||||
|
||||
// PowerShell writes to stderr if the service is not found
|
||||
if (error || stderr) {
|
||||
heartbeat.msg = output || `Service '${serviceName}' is not running/found.`;
|
||||
reject(new Error(heartbeat.msg));
|
||||
return;
|
||||
}
|
||||
|
||||
if (output === "Running") {
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = `Service '${serviceName}' is running.`;
|
||||
resolve();
|
||||
} else {
|
||||
heartbeat.msg = `Service '${serviceName}' is ${output}.`;
|
||||
reject(new Error(heartbeat.msg));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SystemServiceMonitorType,
|
||||
};
|
||||
@ -124,7 +124,7 @@ class UptimeKumaServer {
|
||||
UptimeKumaServer.monitorTypeList["port"] = new TCPMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["manual"] = new ManualMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["redis"] = new RedisMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["local-service"] = new LocalServiceMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["system-service"] = new SystemServiceMonitorType();
|
||||
|
||||
// Allow all CORS origins (polling) in development
|
||||
let cors = undefined;
|
||||
@ -571,6 +571,6 @@ const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");
|
||||
const { TCPMonitorType } = require("./monitor-types/tcp.js");
|
||||
const { ManualMonitorType } = require("./monitor-types/manual");
|
||||
const { RedisMonitorType } = require("./monitor-types/redis");
|
||||
const { LocalServiceMonitorType } = require("./monitor-types/local-service");
|
||||
const { SystemServiceMonitorType } = require("./monitor-types/system-service");
|
||||
const Monitor = require("./model/monitor");
|
||||
|
||||
|
||||
@ -1024,10 +1024,10 @@
|
||||
"useRemoteBrowser": "Use a Remote Browser",
|
||||
"deleteRemoteBrowserMessage": "Are you sure want to delete this Remote Browser for all monitors?",
|
||||
"GrafanaOncallUrl": "Grafana Oncall URL",
|
||||
"Local Service": "Local Service",
|
||||
"System Service": "System Service",
|
||||
"Service Name": "Service Name",
|
||||
"localServiceDescription": "The internal service name used by the OS (systemd for Linux, Service Name for Windows).",
|
||||
"localServiceDebugHelp": "To debug, run this on your server: {linuxCommand} (Linux) or {windowsCommand} (Windows PowerShell). The command must return 'active' or 'Running'.",
|
||||
"systemServiceDescription": "The internal service name used by the OS (systemd for Linux, Service Name for Windows).",
|
||||
"systemServiceDebugHelp": "To debug, run this on your server: {linuxCommand} (Linux) or {windowsCommand} (Windows PowerShell). The command must return 'active' or 'Running'.",
|
||||
"Browser Screenshot": "Browser Screenshot",
|
||||
"Command": "Command",
|
||||
"Expected Output": "Expected Output",
|
||||
|
||||
@ -45,8 +45,8 @@
|
||||
<option value="docker">
|
||||
{{ $t("Docker Container") }}
|
||||
</option>
|
||||
<option value="local-service">
|
||||
{{ $t("Local Service") }}
|
||||
<option value="system-service">
|
||||
{{ $t("System Service") }}
|
||||
</option>
|
||||
<option value="real-browser">
|
||||
HTTP(s) - Browser Engine (Chrome/Chromium) (Beta)
|
||||
@ -665,15 +665,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="monitor.type === 'local-service'">
|
||||
<template v-if="monitor.type === 'system-service'">
|
||||
<div class="my-3">
|
||||
<label for="local-service-name" class="form-label">{{ $t("Service Name") }}</label>
|
||||
<input id="local-service-name" v-model="monitor.local_service_name" type="text" class="form-control" required placeholder="nginx.service">
|
||||
<label for="system-service-name" class="form-label">{{ $t("Service Name") }}</label>
|
||||
<input id="system-service-name" v-model="monitor.system_service_name" type="text" class="form-control" required placeholder="nginx.service">
|
||||
<div class="form-text">
|
||||
{{ $t("localServiceDescription") }}
|
||||
{{ $t("systemServiceDescription") }}
|
||||
|
||||
<div class="mt-2">
|
||||
<i18n-t keypath="localServiceDebugHelp" tag="span">
|
||||
<i18n-t keypath="systemServiceDebugHelp" tag="span">
|
||||
<template #linuxCommand>
|
||||
<code>systemctl is-active <service></code>
|
||||
</template>
|
||||
@ -1383,7 +1383,7 @@ const monitorDefaults = {
|
||||
rabbitmqUsername: "",
|
||||
rabbitmqPassword: "",
|
||||
conditions: [],
|
||||
local_service_name: "",
|
||||
system_service_name: "",
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user