Skip to content

feat(system-service): add PM2 picker and platform selection#7114

Draft
KraoESPfan1n wants to merge 13 commits intolouislam:masterfrom
KraoESPfan1n:Krao/pm2-system-service-platform
Draft

feat(system-service): add PM2 picker and platform selection#7114
KraoESPfan1n wants to merge 13 commits intolouislam:masterfrom
KraoESPfan1n:Krao/pm2-system-service-platform

Conversation

@KraoESPfan1n
Copy link
Copy Markdown

@KraoESPfan1n KraoESPfan1n commented Mar 7, 2026

Summary

In this pull request, the following changes are made:

  • Add explicit PM2 support to the system-service monitor, 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_name values.

  • 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)
  • If there are Breaking change (a fix or feature that alters existing functionality in a way that could cause issues) I have called them out
  • I have disclosed any use of LLMs/AI in this contribution and reviewed all generated content.
    I understand that I am responsible for and able to explain every line of code I submit.
  • Any UI changes adhere to visual style of this project.
  • I have self-reviewed and self-tested my code to ensure it works as expected.
  • I have commented my code, especially in hard-to-understand areas (e.g., using JSDoc for methods).
  • I added or updated automated tests where appropriate.
  • Documentation updates are included (if applicable).
  • Dependency updates are listed and explained.
  • CI passes and is green.

Screenshots for Visual Changes

  • UI Modifications: System Service monitor now supports explicit platform selection and PM2 process dropdown with refresh.
  • Before & After: Not attached in this CLI submission.
Event Before After
UP N/A N/A
DOWN N/A N/A
Certificate-expiry N/A N/A
Testing N/A N/A

@CommanderStorm CommanderStorm requested a review from Copilot March 8, 2026 05:54
@CommanderStorm CommanderStorm added the pr:needs review this PR needs a review by maintainers or other community members label Mar 8, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-service monitor type to support encoded targets (svc:<platform>:<name> and pm2:<nameOrId>) and add PM2 status checking via pm2 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.

Comment on lines +1226 to +1244
<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">
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
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) => {
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
this.execFile(command, args, { timeout: 5000 }, (error, stdout, stderr) => {
this.execFile(command, args, { timeout: 5000, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +86
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.",
});
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1190 to +1198
<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>
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1201 to +1208
<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>
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<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>

Copilot uses AI. Check for mistakes.
@KraoESPfan1n
Copy link
Copy Markdown
Author

@CommanderStorm I have finished making the requested updates

Copy link
Copy Markdown
Collaborator

@CommanderStorm CommanderStorm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can "Linux" be meaningfully translated?
Same applies to Windows server..

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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";
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ComSpec?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are all of these options? Are there longer form options that are self-documenting?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +1229 to +1236
<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>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not be one of the options that is selectable.
When would you want to select windows when on linux?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. There is no platform selector anymore; the system-service monitor now follows the actual host platform only.

Comment on lines +1216 to +1223
<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>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. PM2 now lives in a dedicated pm2 monitor, and system-service is back to host-native service checks only.

Comment on lines +11 to +17
/**
* Initialize monitor type dependencies.
*/
constructor() {
super();
this.execFile = execFile;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you doing this?
If it is only for tests, please mock or spy the method instead.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. system-service no longer injects execFile, and the PM2 tests now stub getProcessList() directly instead of wiring child-process behavior through the constructor.

Comment on lines +37 to +46
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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is gone with the platform-selector removal. The monitor no longer supports cross-platform selection, so there is no mismatch branch to justify.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you take this design decision?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this code exist twice in the codebase?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file does not exist

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, I forgot to commit the changes.

Copy link
Copy Markdown
Collaborator

@CommanderStorm CommanderStorm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better, but still quite weird in a few places.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please review changes to this file

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are there changes to this in the first place?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +3641 to +3657
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();
}
}
},
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please explain

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what legacy normalisation ???

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 CommanderStorm marked this pull request as draft March 23, 2026 03:39
@CommanderStorm CommanderStorm added pr:please address review comments this PR needs a bit more work to be mergable and removed pr:needs review this PR needs a review by maintainers or other community members labels Mar 23, 2026
@KraoESPfan1n
Copy link
Copy Markdown
Author

@CommanderStorm I’ve already replied to all the comments. Is there any other one I still need to respond to?

@CommanderStorm
Copy link
Copy Markdown
Collaborator

Yes, there are un-resolved PR review comments.
Please resolve them and mark the PR as ready for review once you are done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:please address review comments this PR needs a bit more work to be mergable

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants