feat(system-service): add PM2 picker and platform selection#7114
feat(system-service): add PM2 picker and platform selection#7114KraoESPfan1n wants to merge 13 commits intolouislam:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds explicit PM2 monitoring support to the existing system-service monitor type, including a UI workflow to choose between system services and PM2 processes, plus platform selection for service checks. This fits into Uptime Kuma’s existing monitor-type architecture by extending the system-service monitor type and wiring a new socket event for PM2 process discovery in the monitor edit UI.
Changes:
- Extend backend
system-servicemonitor type to support encoded targets (svc:<platform>:<name>andpm2:<nameOrId>) and add PM2 status checking viapm2 jlist. - Add a new socket event (
getPM2ProcessList) to provide a refreshable PM2 process list to the frontend. - Update the monitor edit UI to expose “Target Type” (service vs PM2), platform selection, and a PM2 process picker; add backend unit tests for PM2 mapping and platform routing.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
server/monitor-types/system-service.js |
Adds target parsing, platform enforcement, and PM2 status checking using pm2 jlist. |
server/socket-handlers/general-socket-handler.js |
Adds getPM2ProcessList socket handler that executes pm2 jlist and returns parsed process metadata. |
src/pages/EditMonitor.vue |
Adds UI controls for target type/platform and a socket-driven PM2 process dropdown + refresh. |
test/backend-test/test-system-service-pm2.js |
Adds unit tests for PM2 state mapping (online => UP, stopped/errored => DOWN via rejection). |
test/backend-test/test-system-service-platform.js |
Adds unit tests for platform selection routing and mismatch errors. |
src/pages/EditMonitor.vue
Outdated
| <label for="pm2-process-name" class="form-label mb-0">PM2 Process</label> | ||
| <button | ||
| class="btn btn-outline-secondary btn-sm" | ||
| type="button" | ||
| :disabled="pm2ProcessLoading" | ||
| @click="loadPM2ProcessList" | ||
| > | ||
| {{ pm2ProcessLoading ? "Loading..." : "Refresh" }} | ||
| </button> | ||
| </div> | ||
| <select | ||
| v-if="pm2ProcessOptions.length > 0" | ||
| id="pm2-process-name" | ||
| v-model="systemServiceNameInput" | ||
| class="form-select mt-2" | ||
| required | ||
| > | ||
| <option disabled value="">Select PM2 process</option> | ||
| <option v-for="item in pm2ProcessOptions" :key="item.id" :value="item.id"> |
There was a problem hiding this comment.
PM2 picker UI strings ("PM2 Process", "Loading...", "Refresh", "Select PM2 process") are hard-coded. Please convert these to i18n keys and use $t so they are translated like the rest of the monitor edit form.
| const command = isWindows ? process.env.ComSpec || "cmd.exe" : "pm2"; | ||
| const args = isWindows ? ["/d", "/s", "/c", "pm2 jlist"] : ["jlist"]; | ||
|
|
||
| this.execFile(command, args, { timeout: 5000 }, (error, stdout, stderr) => { |
There was a problem hiding this comment.
pm2 jlist output can become large on hosts with many processes. Using execFile without maxBuffer can cause the call to fail with a buffer overflow error, making PM2 monitors flap DOWN unexpectedly. Consider setting an explicit maxBuffer (and potentially a smaller/streaming approach) to make behavior predictable.
| this.execFile(command, args, { timeout: 5000 }, (error, stdout, stderr) => { | |
| this.execFile(command, args, { timeout: 5000, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => { |
| const isWindows = process.platform === "win32"; | ||
| const command = isWindows ? process.env.ComSpec || "cmd.exe" : "pm2"; | ||
| const args = isWindows ? ["/d", "/s", "/c", "pm2 jlist"] : ["jlist"]; | ||
|
|
||
| execFile(command, args, { timeout: 5000 }, (error, stdout, stderr) => { | ||
| if (error) { | ||
| callback({ | ||
| ok: false, | ||
| msg: "Unable to query PM2 process list.", | ||
| }); |
There was a problem hiding this comment.
Same concern here: pm2 jlist can exceed execFile’s default maxBuffer on busy servers. Please set maxBuffer explicitly and consider returning a truncated stderr/stdout detail in the error response so users can diagnose missing-PM2/PATH/buffer issues.
src/pages/EditMonitor.vue
Outdated
| <label for="system-service-mode" class="form-label">Target Type</label> | ||
| <select | ||
| id="system-service-mode" | ||
| v-model="systemServiceMode" | ||
| class="form-select mb-3" | ||
| > | ||
| <option value="service">System Service</option> | ||
| <option value="pm2">PM2 Process</option> | ||
| </select> |
There was a problem hiding this comment.
New user-facing strings here (e.g. "System Service") are hard-coded instead of using i18n ($t / i18n-t). This will leave these labels untranslated in non-English locales; please add translation keys (en.json) and reference them via $t for consistency with the existing system-service strings in this section.
src/pages/EditMonitor.vue
Outdated
| <label for="system-service-platform" class="form-label">Platform</label> | ||
| <select | ||
| id="system-service-platform" | ||
| v-model="systemServicePlatform" | ||
| class="form-select mb-3" | ||
| > | ||
| <option value="linux">Linux</option> | ||
| <option value="win32">Windows Server</option> |
There was a problem hiding this comment.
Platform selector labels ("Platform", "Linux", "Windows Server") are hard-coded. Please move these to i18n keys and use $t so the system-service form remains translatable.
| <label for="system-service-platform" class="form-label">Platform</label> | |
| <select | |
| id="system-service-platform" | |
| v-model="systemServicePlatform" | |
| class="form-select mb-3" | |
| > | |
| <option value="linux">Linux</option> | |
| <option value="win32">Windows Server</option> | |
| <label for="system-service-platform" class="form-label">{{ $t("Platform") }}</label> | |
| <select | |
| id="system-service-platform" | |
| v-model="systemServicePlatform" | |
| class="form-select mb-3" | |
| > | |
| <option value="linux">{{ $t("Linux") }}</option> | |
| <option value="win32">{{ $t("Windows Server") }}</option> |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@CommanderStorm I have finished making the requested updates |
CommanderStorm
left a comment
There was a problem hiding this comment.
I did a partial review and the code is just super strange.
Please explain your design descisions...
src/lang/en.json
Outdated
| "System Service / PM2": "System Service / PM2", | ||
| "Target Type": "Target Type", | ||
| "PM2 Process": "PM2 Process", | ||
| "Linux": "Linux", |
There was a problem hiding this comment.
Can "Linux" be meaningfully translated?
Same applies to Windows server..
There was a problem hiding this comment.
Removed with the selector refactor. There are no translatable Linux/Windows option labels in the form anymore.
| checkLogin(socket); | ||
|
|
||
| const isWindows = process.platform === "win32"; | ||
| const command = isWindows ? process.env.ComSpec || "cmd.exe" : "pm2"; |
There was a problem hiding this comment.
Removed. The shared PM2 helper now runs pm2.cmd directly on Windows instead of going through ComSpec.
|
|
||
| const isWindows = process.platform === "win32"; | ||
| const command = isWindows ? process.env.ComSpec || "cmd.exe" : "pm2"; | ||
| const args = isWindows ? ["/d", "/s", "/c", "pm2 jlist"] : ["jlist"]; |
There was a problem hiding this comment.
what are all of these options? Are there longer form options that are self-documenting?
There was a problem hiding this comment.
Also removed. The Windows path no longer shells out through cmd /d /s /c; the helper executes pm2.cmd directly, so the options disappeared with it.
src/pages/EditMonitor.vue
Outdated
| <select | ||
| id="system-service-platform" | ||
| v-model="systemServicePlatform" | ||
| class="form-select mb-3" | ||
| > | ||
| <option value="linux">{{ $t("Linux") }}</option> | ||
| <option value="win32">{{ $t("Windows Server") }}</option> | ||
| </select> |
There was a problem hiding this comment.
this should not be one of the options that is selectable.
When would you want to select windows when on linux?
There was a problem hiding this comment.
Removed. There is no platform selector anymore; the system-service monitor now follows the actual host platform only.
src/pages/EditMonitor.vue
Outdated
| <select | ||
| id="system-service-mode" | ||
| v-model="systemServiceMode" | ||
| class="form-select mb-3" | ||
| > | ||
| <option value="service">{{ $t("System Service") }}</option> | ||
| <option value="pm2">{{ $t("PM2 Process") }}</option> | ||
| </select> |
There was a problem hiding this comment.
please move this to a new monitor.
I feel like at the point when you need to add service discovery and the rest of the code, this is not really a systemd like service any longer..
At least, the code is much more complex, so I think graduating it to be a first level option likely makes sense.
There was a problem hiding this comment.
Done. PM2 now lives in a dedicated pm2 monitor, and system-service is back to host-native service checks only.
| /** | ||
| * Initialize monitor type dependencies. | ||
| */ | ||
| constructor() { | ||
| super(); | ||
| this.execFile = execFile; | ||
| } |
There was a problem hiding this comment.
why are you doing this?
If it is only for tests, please mock or spy the method instead.
There was a problem hiding this comment.
Removed. system-service no longer injects execFile, and the PM2 tests now stub getProcessList() directly instead of wiring child-process behavior through the constructor.
| if (target.platform === "win32") { | ||
| if (process.platform !== "win32") { | ||
| throw new Error("Selected platform Windows Server is not supported on this host."); | ||
| } | ||
| return this.checkWindows(target.name, heartbeat); | ||
| } else if (target.platform === "linux") { | ||
| if (process.platform !== "linux") { | ||
| throw new Error("Selected platform Linux is not supported on this host."); | ||
| } | ||
| return this.checkLinux(target.name, heartbeat); |
There was a problem hiding this comment.
This is gone with the platform-selector removal. The monitor no longer supports cross-platform selection, so there is no mismatch branch to justify.
There was a problem hiding this comment.
Why did you take this design decision?
There was a problem hiding this comment.
That design decision was ours in this PR, not inherited from the previous implementation. I originally chose it because I was testing on a Windows machine and tried to keep PM2 support and cross-platform service selection inside one monitor/field so I could validate both paths quickly from the same UI. After your review I agree that was the wrong tradeoff: it made system-service carry PM2-specific logic and added mismatch branches that only existed because of that abstraction. I have now simplified it so system-service is host-native again and PM2 is split into its own first-class monitor. I am finishing/refining this from Linux now, but the original reason for the combined design was the earlier Windows-based testing setup.
There was a problem hiding this comment.
Small correction to my previous reply: I had mixed up what came from the earlier implementation vs. what we introduced in this PR. The combined PM2/platform-selection design was ours.
|
|
||
| const isWindows = process.platform === "win32"; | ||
| const command = isWindows ? process.env.ComSpec || "cmd.exe" : "pm2"; | ||
| const args = isWindows ? ["/d", "/s", "/c", "pm2 jlist"] : ["jlist"]; |
There was a problem hiding this comment.
why does this code exist twice in the codebase?
There was a problem hiding this comment.
Deduplicated. pm2 jlist execution and parsing now live in server/util/pm2.js, and both the socket handler and the new PM2 monitor reuse that helper.
There was a problem hiding this comment.
file does not exist
There was a problem hiding this comment.
My bad, I forgot to commit the changes.
CommanderStorm
left a comment
There was a problem hiding this comment.
Much better, but still quite weird in a few places.
There was a problem hiding this comment.
please review changes to this file
There was a problem hiding this comment.
Reviewed and simplified. The follow-up changes leave system-service minimal again; the PM2-specific normalization/validation was moved out so this file only keeps the host-native service check logic plus legacy read compatibility.
There was a problem hiding this comment.
why are there changes to this in the first place?
There was a problem hiding this comment.
Those changes were only there to read the intermediate svc:<platform>:<name> values I had introduced earlier in this PR. Since that format was never meant to survive in the final result, I removed the compatibility path again. system-service is back to just using the stored service name directly.
src/pages/EditMonitor.vue
Outdated
| normalizeLegacyMonitorFields() { | ||
| if (!this.monitor.system_service_name) { | ||
| return; | ||
| } | ||
|
|
||
| if (this.monitor.type === "pm2" && this.monitor.system_service_name.toLowerCase().startsWith("pm2:")) { | ||
| this.monitor.system_service_name = this.monitor.system_service_name.slice(4).trim(); | ||
| return; | ||
| } | ||
|
|
||
| if (this.monitor.type === "system-service") { | ||
| const serviceWithPlatform = this.monitor.system_service_name.match(/^svc:(linux|win32):([\s\S]+)$/i); | ||
| if (serviceWithPlatform) { | ||
| this.monitor.system_service_name = serviceWithPlatform[2].trim(); | ||
| } | ||
| } | ||
| }, |
There was a problem hiding this comment.
Moved out of the frontend. The legacy normalization now happens in server/model/monitor.js when monitors are serialized and validated, so the Vue form only deals with the current shape instead of carrying compatibility logic.
There was a problem hiding this comment.
what legacy normalisation ???
There was a problem hiding this comment.
That was my mistake. The "legacy" values were only intermediate branch-local formats from earlier iterations of this PR while I was testing the split, not a real compatibility requirement for the final change. I removed that normalization in the latest push so the frontend no longer carries branch-only compatibility logic.
|
@CommanderStorm I’ve already replied to all the comments. Is there any other one I still need to respond to? |
|
Yes, there are un-resolved PR review comments. |
Summary
In this pull request, the following changes are made:
Add explicit PM2 support to the
system-servicemonitor, including Windows-compatible PM2 execution.Add configurable platform selection for system services (
Linux/Windows Server) in monitor UI.Add PM2 process picker in monitor UI (socket-driven process list + refresh).
Keep backward compatibility with existing
system_service_namevalues.Add backend tests for PM2 state mapping and platform selection routing.
Relates to Monitor pm2 #3011
Related discussion: Monitor pm2 #3011 (comment)
Please follow this checklist to avoid unnecessary back and forth (click to expand)
I understand that I am responsible for and able to explain every line of code I submit.
Screenshots for Visual Changes
UPDOWN