@woopsy/mcpanel 1.0.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/commandRouter.js +20 -0
- package/dist/index.js +49 -2
- package/dist/managers/playitManager.js +40 -5
- package/dist/managers/trayManager.js +335 -0
- package/dist/services/updateChecker.js +140 -0
- package/dist/utils/helpers.js +148 -0
- package/package.json +3 -2
|
@@ -42,6 +42,7 @@ const path = __importStar(require("path"));
|
|
|
42
42
|
const colors = __importStar(require("../utils/colors"));
|
|
43
43
|
const helpers_1 = require("../utils/helpers");
|
|
44
44
|
const downloadService_1 = require("../services/downloadService");
|
|
45
|
+
const updateChecker_1 = require("../services/updateChecker");
|
|
45
46
|
const pidusage_1 = __importDefault(require("pidusage"));
|
|
46
47
|
class CommandRouter {
|
|
47
48
|
configManager;
|
|
@@ -95,7 +96,10 @@ class CommandRouter {
|
|
|
95
96
|
' /stats - System stats + CPU/RAM/disk of the server',
|
|
96
97
|
' /java [path] - Show/list Java runtimes, or set the one used to launch',
|
|
97
98
|
' /folder - Open the server folder in the file explorer',
|
|
99
|
+
' /tray - Run in background, minimize console to system tray',
|
|
100
|
+
' /background - Synonym for /tray',
|
|
98
101
|
' /clear - Clear the screen, scrollback and command history',
|
|
102
|
+
' /update - Check npm for a newer version of MCPANEL',
|
|
99
103
|
' /config - View active application config.json',
|
|
100
104
|
' /exit - Close MCPANEL server manager',
|
|
101
105
|
colors.gray('──────────────────────────────────────────────\n')
|
|
@@ -465,6 +469,22 @@ class CommandRouter {
|
|
|
465
469
|
this.configManager.updateSettings({ defaultJavaPath: cleanPath });
|
|
466
470
|
return colors.success(`Java set to "${cleanPath}" (version ${info.version}). It will be used on the next /start.`);
|
|
467
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Executes /update — checks npm for a newer version and prints how to update.
|
|
474
|
+
*/
|
|
475
|
+
async executeUpdate() {
|
|
476
|
+
const info = await (0, updateChecker_1.checkForUpdate)(true);
|
|
477
|
+
if (!info) {
|
|
478
|
+
return colors.warning('Could not check for updates (no network connection?).');
|
|
479
|
+
}
|
|
480
|
+
if (info.updateAvailable) {
|
|
481
|
+
return [
|
|
482
|
+
colors.warning(`Update available: ${colors.bold(info.current)} → ${colors.bold(colors.green(info.latest))}`),
|
|
483
|
+
colors.gray('Update with: ') + colors.cyan(`npm i -g ${info.name}@latest`),
|
|
484
|
+
].join('\n');
|
|
485
|
+
}
|
|
486
|
+
return colors.success(`You're on the latest version (${info.current}).`);
|
|
487
|
+
}
|
|
468
488
|
/**
|
|
469
489
|
* Executes /config
|
|
470
490
|
*/
|
package/dist/index.js
CHANGED
|
@@ -50,7 +50,9 @@ const playitManager_1 = require("./managers/playitManager");
|
|
|
50
50
|
const commandRouter_1 = require("./commands/commandRouter");
|
|
51
51
|
const colors = __importStar(require("./utils/colors"));
|
|
52
52
|
const helpers_1 = require("./utils/helpers");
|
|
53
|
+
const updateChecker_1 = require("./services/updateChecker");
|
|
53
54
|
const logger_1 = require("./utils/logger");
|
|
55
|
+
const trayManager_1 = require("./managers/trayManager");
|
|
54
56
|
// Initialize managers
|
|
55
57
|
const configManager = new configManager_1.ConfigManager();
|
|
56
58
|
configManager.initialize();
|
|
@@ -58,6 +60,7 @@ const processManager = new processManager_1.ProcessManager();
|
|
|
58
60
|
const serverManager = new serverManager_1.ServerManager(configManager);
|
|
59
61
|
const backupManager = new backupManager_1.BackupManager(configManager);
|
|
60
62
|
const playitManager = new playitManager_1.PlayitManager(configManager);
|
|
63
|
+
const trayManager = new trayManager_1.TrayManager(configManager, processManager, playitManager);
|
|
61
64
|
const router = new commandRouter_1.CommandRouter(configManager, processManager, serverManager, backupManager, playitManager);
|
|
62
65
|
const HISTORY_PATH = path.join(configManager_1.APP_ROOT, 'logs', '.history');
|
|
63
66
|
let currentState = 'COMMAND';
|
|
@@ -70,6 +73,12 @@ let consoleActiveServer = '';
|
|
|
70
73
|
let logViewServer = '';
|
|
71
74
|
// Readline interface
|
|
72
75
|
let rl;
|
|
76
|
+
let CLI_VERSION = '1.0.3';
|
|
77
|
+
try {
|
|
78
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(configManager_1.APP_ROOT, 'package.json'), 'utf-8'));
|
|
79
|
+
CLI_VERSION = pkg.version || '1.0.3';
|
|
80
|
+
}
|
|
81
|
+
catch { /* ignore */ }
|
|
73
82
|
/**
|
|
74
83
|
* Renders the figlet "MCPANEL" ASCII banner with a chalk gradient.
|
|
75
84
|
*/
|
|
@@ -79,7 +88,7 @@ function renderBanner() {
|
|
|
79
88
|
const tints = [chalk_1.default.cyanBright, chalk_1.default.cyan, chalk_1.default.greenBright, chalk_1.default.green, chalk_1.default.green];
|
|
80
89
|
console.log();
|
|
81
90
|
lines.forEach((line, i) => console.log((tints[i] || chalk_1.default.green)(line)));
|
|
82
|
-
console.log(chalk_1.default.greenBright.bold(' Minecraft Server Manager') + chalk_1.default.gray(
|
|
91
|
+
console.log(chalk_1.default.greenBright.bold(' Minecraft Server Manager') + chalk_1.default.gray(` v${CLI_VERSION}`));
|
|
83
92
|
}
|
|
84
93
|
/**
|
|
85
94
|
* Renders the neofetch / Arch-Linux-style info block for the synced server.
|
|
@@ -161,7 +170,7 @@ const COMMAND_LIST = [
|
|
|
161
170
|
'/plugins list', '/plugins install', '/plugins remove',
|
|
162
171
|
'/setup',
|
|
163
172
|
'/tunnel java', '/tunnel bedrock', '/tunnel status', '/tunnel stop', '/tunnel reset',
|
|
164
|
-
'/config', '/clear', '/exit'
|
|
173
|
+
'/config', '/clear', '/update', '/tray', '/background', '/exit'
|
|
165
174
|
];
|
|
166
175
|
// Subcommands offered once "<command> " has been typed.
|
|
167
176
|
const SUBCOMMANDS = {
|
|
@@ -238,6 +247,11 @@ function getStatusBar() {
|
|
|
238
247
|
const running = server ? !!processManager.getActiveServer(server.name) : false;
|
|
239
248
|
const backupsCount = backupManager.listBackups().length;
|
|
240
249
|
const tunnelStatus = playitManager.getStatus().status;
|
|
250
|
+
// Sync menu state to the system tray
|
|
251
|
+
try {
|
|
252
|
+
trayManager.updateMenu();
|
|
253
|
+
}
|
|
254
|
+
catch { /* ignore */ }
|
|
241
255
|
const serverStr = !server
|
|
242
256
|
? colors.gray('none')
|
|
243
257
|
: running ? colors.green('Running') : colors.red('Offline');
|
|
@@ -554,6 +568,16 @@ async function handleCommandState(line) {
|
|
|
554
568
|
console.log(colors.failure(`Failed to clear: ${err.message}`));
|
|
555
569
|
}
|
|
556
570
|
break;
|
|
571
|
+
case '/tray':
|
|
572
|
+
case '/background': {
|
|
573
|
+
console.log(colors.info('\nPutting MCPANEL in the background...'));
|
|
574
|
+
console.log(colors.gray('The terminal window will be hidden. Use the system tray icon to restore it.'));
|
|
575
|
+
const success = trayManager.hideConsole();
|
|
576
|
+
if (!success) {
|
|
577
|
+
console.log(colors.failure('Failed to hide console window. Ensure you are running in a supported GUI environment.'));
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
557
581
|
case '/exit':
|
|
558
582
|
logger_1.logger.info('Exiting MCPANEL manager.');
|
|
559
583
|
playitManager.stopTunnel();
|
|
@@ -609,6 +633,9 @@ async function handleCommandState(line) {
|
|
|
609
633
|
case '/java':
|
|
610
634
|
console.log(router.executeJava(args.length ? args.join(' ') : undefined));
|
|
611
635
|
break;
|
|
636
|
+
case '/update':
|
|
637
|
+
console.log(await router.executeUpdate());
|
|
638
|
+
break;
|
|
612
639
|
case '/config':
|
|
613
640
|
console.log(router.executeConfig());
|
|
614
641
|
break;
|
|
@@ -720,11 +747,31 @@ async function finishStartup() {
|
|
|
720
747
|
currentState = 'COMMAND';
|
|
721
748
|
promptUser();
|
|
722
749
|
}
|
|
750
|
+
/**
|
|
751
|
+
* Prints a one-time-per-launch notice if a newer version is on npm. Fail-silent
|
|
752
|
+
* and cached, so it never slows down or blocks startup.
|
|
753
|
+
*/
|
|
754
|
+
async function showUpdateNotice() {
|
|
755
|
+
try {
|
|
756
|
+
const info = await (0, updateChecker_1.checkForUpdate)();
|
|
757
|
+
if (info && info.updateAvailable) {
|
|
758
|
+
console.log();
|
|
759
|
+
console.log(chalk_1.default.yellow(' ⚡ Update available: ') + chalk_1.default.gray(info.current) + chalk_1.default.gray(' → ') + chalk_1.default.greenBright.bold(info.latest));
|
|
760
|
+
console.log(chalk_1.default.gray(' Update with: ') + chalk_1.default.cyan(`npm i -g ${info.name}@latest`));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
catch {
|
|
764
|
+
// Never let an update check break startup.
|
|
765
|
+
}
|
|
766
|
+
}
|
|
723
767
|
/**
|
|
724
768
|
* Main application setup
|
|
725
769
|
*/
|
|
726
770
|
async function main() {
|
|
727
771
|
renderBanner();
|
|
772
|
+
await showUpdateNotice();
|
|
773
|
+
// Start the background system tray loop
|
|
774
|
+
await trayManager.start();
|
|
728
775
|
rl = readline.createInterface({
|
|
729
776
|
input: process.stdin,
|
|
730
777
|
output: process.stdout,
|
|
@@ -362,20 +362,28 @@ class PlayitManager {
|
|
|
362
362
|
// Full automated setup
|
|
363
363
|
// ---------------------------------------------------------------------------
|
|
364
364
|
/**
|
|
365
|
-
* One-call entry point: ensures binary + secret,
|
|
366
|
-
*
|
|
365
|
+
* One-call entry point: ensures binary + secret, starts the relay daemon (so
|
|
366
|
+
* the agent registers its version with playit), creates the tunnel via the
|
|
367
|
+
* API, and returns the live tunnel status.
|
|
368
|
+
*
|
|
369
|
+
* The relay MUST be started before creating a tunnel — playit rejects
|
|
370
|
+
* /tunnels/create with "AgentVersionTooOld" until a current agent has
|
|
371
|
+
* connected and reported its version.
|
|
367
372
|
*/
|
|
368
373
|
async setupAndStart(type, callbacks = {}) {
|
|
369
374
|
await this.ensureBinary();
|
|
370
375
|
const secret = await this.ensureSecret(callbacks);
|
|
371
376
|
this.tunnelStatus.status = 'Connecting';
|
|
372
377
|
this.tunnelStatus.type = type;
|
|
378
|
+
// Start the relay first so the agent connects and registers its version.
|
|
379
|
+
callbacks.onStatus?.('Starting tunnel agent...');
|
|
380
|
+
await this.startAgent(secret);
|
|
373
381
|
callbacks.onStatus?.('Checking your playit account for an existing tunnel...');
|
|
374
382
|
let rd = await this.getRunData(secret);
|
|
375
383
|
let tunnel = this.findTunnel(rd, type);
|
|
376
384
|
if (!tunnel) {
|
|
377
385
|
callbacks.onStatus?.(`Creating ${type} tunnel...`);
|
|
378
|
-
await this.
|
|
386
|
+
await this.createTunnelWithRetry(type, rd.agent_id, secret, callbacks);
|
|
379
387
|
// Poll until the tunnel leaves "pending" and gets a public address.
|
|
380
388
|
for (let i = 0; i < 15 && !tunnel; i++) {
|
|
381
389
|
await this.sleep(3000);
|
|
@@ -390,11 +398,38 @@ class PlayitManager {
|
|
|
390
398
|
this.tunnelStatus.address = address;
|
|
391
399
|
this.tunnelStatus.port = port;
|
|
392
400
|
this.configManager.updatePlayitTunnel({ tunnelAddress: address, tunnelPort: Number(port) });
|
|
393
|
-
callbacks.onStatus?.('Starting tunnel relay...');
|
|
394
|
-
await this.startAgent(secret);
|
|
395
401
|
this.tunnelStatus.status = 'Online';
|
|
396
402
|
return this.tunnelStatus;
|
|
397
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Creates a tunnel, retrying on "AgentVersionTooOld" — that error means the
|
|
406
|
+
* freshly-started agent hasn't finished registering its version yet, so we
|
|
407
|
+
* wait and retry (refreshing the agent_id) a few times.
|
|
408
|
+
*/
|
|
409
|
+
async createTunnelWithRetry(type, agentId, secret, callbacks) {
|
|
410
|
+
let lastErr;
|
|
411
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
412
|
+
try {
|
|
413
|
+
await this.createApiTunnel(type, agentId, secret);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
catch (err) {
|
|
417
|
+
lastErr = err;
|
|
418
|
+
if (!/AgentVersionTooOld/i.test(err.message || ''))
|
|
419
|
+
throw err;
|
|
420
|
+
callbacks.onStatus?.('Waiting for the agent to finish registering with playit...');
|
|
421
|
+
await this.sleep(4000);
|
|
422
|
+
try {
|
|
423
|
+
const rd = await this.getRunData(secret);
|
|
424
|
+
if (rd?.agent_id)
|
|
425
|
+
agentId = rd.agent_id;
|
|
426
|
+
}
|
|
427
|
+
catch { /* keep previous agentId */ }
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
throw new Error(`${lastErr?.message || 'AgentVersionTooOld'} — the playit agent did not register in time. ` +
|
|
431
|
+
`Make sure the server can reach playit.gg, then try /tunnel again.`);
|
|
432
|
+
}
|
|
398
433
|
/** Spawns the long-running daemon that relays tunnel traffic. */
|
|
399
434
|
startAgent(secret) {
|
|
400
435
|
return new Promise((resolve) => {
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.TrayManager = void 0;
|
|
40
|
+
const systray2_1 = __importDefault(require("systray2"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const helpers_1 = require("../utils/helpers");
|
|
44
|
+
const configManager_1 = require("../config/configManager");
|
|
45
|
+
const logger_1 = require("../utils/logger");
|
|
46
|
+
const SysTrayBase = systray2_1.default;
|
|
47
|
+
class WSLSysTray extends SysTrayBase {
|
|
48
|
+
constructor(conf) {
|
|
49
|
+
super(conf);
|
|
50
|
+
}
|
|
51
|
+
async init() {
|
|
52
|
+
const osType = (0, helpers_1.detectOS)();
|
|
53
|
+
if (osType === 'WSL') {
|
|
54
|
+
const binName = "tray_windows_release.exe";
|
|
55
|
+
const nodeModulesBin = path.join(configManager_1.APP_ROOT, 'node_modules', 'systray2', 'traybin', binName);
|
|
56
|
+
this._binPath = nodeModulesBin;
|
|
57
|
+
// Auto chmod +x to ensure the binary is executable from WSL
|
|
58
|
+
try {
|
|
59
|
+
fs.chmodSync(nodeModulesBin, 0o755);
|
|
60
|
+
}
|
|
61
|
+
catch { /* ignore */ }
|
|
62
|
+
return new Promise(async (resolve, reject) => {
|
|
63
|
+
try {
|
|
64
|
+
const child = require('child_process');
|
|
65
|
+
const readline = require('readline');
|
|
66
|
+
this._process = child.spawn(this._binPath, [], {
|
|
67
|
+
windowsHide: true
|
|
68
|
+
});
|
|
69
|
+
this._process.on('error', reject);
|
|
70
|
+
this._rl = readline.createInterface({
|
|
71
|
+
input: this._process.stdout
|
|
72
|
+
});
|
|
73
|
+
const internalIdMap = this.internalIdMap;
|
|
74
|
+
const counter = { id: 1 };
|
|
75
|
+
const addInternalId = (item) => {
|
|
76
|
+
const id = counter.id++;
|
|
77
|
+
internalIdMap.set(id, item);
|
|
78
|
+
if (item.items) {
|
|
79
|
+
item.items.forEach(addInternalId);
|
|
80
|
+
}
|
|
81
|
+
item.__id = id;
|
|
82
|
+
};
|
|
83
|
+
this._conf.menu.items.forEach(addInternalId);
|
|
84
|
+
const loadIcon = async (fileName) => {
|
|
85
|
+
const buffer = await fs.promises.readFile(fileName);
|
|
86
|
+
return buffer.toString('base64');
|
|
87
|
+
};
|
|
88
|
+
const resolveIcon = async (item) => {
|
|
89
|
+
if (item.icon && fs.existsSync(item.icon)) {
|
|
90
|
+
item.icon = await loadIcon(item.icon);
|
|
91
|
+
}
|
|
92
|
+
if (item.items) {
|
|
93
|
+
await Promise.all(item.items.map((sub) => resolveIcon(sub)));
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
await resolveIcon(this._conf.menu);
|
|
97
|
+
this.onReady(() => {
|
|
98
|
+
const itemTrimmer = (item) => ({
|
|
99
|
+
title: item.title,
|
|
100
|
+
tooltip: item.tooltip,
|
|
101
|
+
checked: item.checked,
|
|
102
|
+
enabled: item.enabled === undefined ? true : item.enabled,
|
|
103
|
+
hidden: item.hidden,
|
|
104
|
+
items: item.items,
|
|
105
|
+
icon: item.icon,
|
|
106
|
+
isTemplateIcon: item.isTemplateIcon,
|
|
107
|
+
__id: item.__id
|
|
108
|
+
});
|
|
109
|
+
const menuTrimmer = (menu) => ({
|
|
110
|
+
icon: menu.icon,
|
|
111
|
+
title: menu.title,
|
|
112
|
+
tooltip: menu.tooltip,
|
|
113
|
+
items: menu.items.map(itemTrimmer),
|
|
114
|
+
isTemplateIcon: menu.isTemplateIcon
|
|
115
|
+
});
|
|
116
|
+
this.writeLine(JSON.stringify(menuTrimmer(this._conf.menu)));
|
|
117
|
+
resolve();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
reject(err);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
return super.init();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
class TrayManager {
|
|
131
|
+
configManager;
|
|
132
|
+
processManager;
|
|
133
|
+
playitManager;
|
|
134
|
+
systray = null;
|
|
135
|
+
activeHandle = null;
|
|
136
|
+
isInitialized = false;
|
|
137
|
+
// Menu item state tracking
|
|
138
|
+
itemShow = { title: 'Open Console', tooltip: 'Restore terminal window', enabled: true };
|
|
139
|
+
itemHide = { title: 'Hide Console', tooltip: 'Hide terminal window from taskbar', enabled: true };
|
|
140
|
+
itemServerStatus = { title: 'Server: Checking...', tooltip: 'Current Minecraft server status', enabled: false };
|
|
141
|
+
itemServerToggle = { title: 'Start Server', tooltip: 'Toggle server state', enabled: true };
|
|
142
|
+
itemTunnelStatus = { title: 'Tunnel: Checking...', tooltip: 'Current Playit tunnel status', enabled: false };
|
|
143
|
+
itemTunnelToggle = { title: 'Start Tunnel', tooltip: 'Toggle tunnel state', enabled: true };
|
|
144
|
+
itemExit = { title: 'Exit', tooltip: 'Stop server/tunnel and exit', enabled: true };
|
|
145
|
+
constructor(configManager, processManager, playitManager) {
|
|
146
|
+
this.configManager = configManager;
|
|
147
|
+
this.processManager = processManager;
|
|
148
|
+
this.playitManager = playitManager;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Initializes and starts the system tray icon loop
|
|
152
|
+
*/
|
|
153
|
+
async start() {
|
|
154
|
+
if (this.isInitialized)
|
|
155
|
+
return true;
|
|
156
|
+
const osType = (0, helpers_1.detectOS)();
|
|
157
|
+
const iconFile = osType === 'Windows' || osType === 'WSL'
|
|
158
|
+
? path.join(configManager_1.APP_ROOT, 'assets', 'logo.ico')
|
|
159
|
+
: path.join(configManager_1.APP_ROOT, 'assets', 'logo.png');
|
|
160
|
+
try {
|
|
161
|
+
this.systray = new WSLSysTray({
|
|
162
|
+
menu: {
|
|
163
|
+
icon: iconFile,
|
|
164
|
+
title: 'MCPANEL',
|
|
165
|
+
tooltip: 'MCPANEL Server Manager',
|
|
166
|
+
items: [
|
|
167
|
+
this.itemShow,
|
|
168
|
+
this.itemHide,
|
|
169
|
+
systray2_1.default.separator,
|
|
170
|
+
this.itemServerStatus,
|
|
171
|
+
this.itemServerToggle,
|
|
172
|
+
systray2_1.default.separator,
|
|
173
|
+
this.itemTunnelStatus,
|
|
174
|
+
this.itemTunnelToggle,
|
|
175
|
+
systray2_1.default.separator,
|
|
176
|
+
this.itemExit
|
|
177
|
+
]
|
|
178
|
+
},
|
|
179
|
+
debug: false
|
|
180
|
+
});
|
|
181
|
+
this.systray.onClick((event) => {
|
|
182
|
+
this.handleTrayClick(event).catch((err) => {
|
|
183
|
+
logger_1.logger.error('Error handling tray click event', err);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
await this.systray.ready();
|
|
187
|
+
this.isInitialized = true;
|
|
188
|
+
logger_1.logger.info('System tray initialized successfully.');
|
|
189
|
+
this.updateMenu();
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
this.systray = null;
|
|
194
|
+
logger_1.logger.error('Failed to initialize system tray', err);
|
|
195
|
+
// Fail silently for user prompt, fall back to CLI-only mode gracefully.
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Dynamically updates the titles and states of the tray menu items
|
|
201
|
+
*/
|
|
202
|
+
updateMenu() {
|
|
203
|
+
const tray = this.systray;
|
|
204
|
+
if (!tray || !this.isInitialized)
|
|
205
|
+
return;
|
|
206
|
+
const server = this.configManager.getServer();
|
|
207
|
+
const running = server ? !!this.processManager.getActiveServer(server.name) : false;
|
|
208
|
+
const tunnel = this.playitManager.getStatus();
|
|
209
|
+
// Update server status & toggle label
|
|
210
|
+
this.itemServerStatus.title = `Server: ${running ? 'Running' : 'Offline'}`;
|
|
211
|
+
this.itemServerToggle.title = running ? 'Stop Server' : 'Start Server';
|
|
212
|
+
this.itemServerToggle.enabled = !!server;
|
|
213
|
+
// Update tunnel status & toggle label
|
|
214
|
+
this.itemTunnelStatus.title = `Tunnel: ${tunnel.status}`;
|
|
215
|
+
this.itemTunnelToggle.title = tunnel.status === 'Online' || tunnel.status === 'Connecting' ? 'Stop Tunnel' : 'Start Tunnel';
|
|
216
|
+
// Push updates to the native helper
|
|
217
|
+
tray.sendAction({ type: 'update-item', item: this.itemServerStatus });
|
|
218
|
+
tray.sendAction({ type: 'update-item', item: this.itemServerToggle });
|
|
219
|
+
tray.sendAction({ type: 'update-item', item: this.itemTunnelStatus });
|
|
220
|
+
tray.sendAction({ type: 'update-item', item: this.itemTunnelToggle });
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Hides the active terminal window to the background
|
|
224
|
+
*/
|
|
225
|
+
hideConsole() {
|
|
226
|
+
const handle = (0, helpers_1.getActiveWindowHandle)();
|
|
227
|
+
if (!handle) {
|
|
228
|
+
logger_1.logger.warn('Could not retrieve active console window handle.');
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
this.activeHandle = handle;
|
|
232
|
+
const success = (0, helpers_1.hideConsoleWindow)(handle);
|
|
233
|
+
if (success) {
|
|
234
|
+
logger_1.logger.info(`Console window (${handle}) hidden to background.`);
|
|
235
|
+
}
|
|
236
|
+
return success;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Restores the hidden console window back to foreground
|
|
240
|
+
*/
|
|
241
|
+
showConsole() {
|
|
242
|
+
const handle = this.activeHandle;
|
|
243
|
+
if (!handle) {
|
|
244
|
+
logger_1.logger.warn('No saved console window handle to restore.');
|
|
245
|
+
// Fallback: try to retrieve current active handle (best effort if not saved)
|
|
246
|
+
const curHandle = (0, helpers_1.getActiveWindowHandle)();
|
|
247
|
+
if (curHandle) {
|
|
248
|
+
return (0, helpers_1.showConsoleWindow)(curHandle);
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const success = (0, helpers_1.showConsoleWindow)(handle);
|
|
253
|
+
if (success) {
|
|
254
|
+
logger_1.logger.info(`Console window (${handle}) restored to foreground.`);
|
|
255
|
+
}
|
|
256
|
+
return success;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Handles individual tray menu click actions
|
|
260
|
+
*/
|
|
261
|
+
async handleTrayClick(event) {
|
|
262
|
+
const title = event.item.title;
|
|
263
|
+
if (title === 'Open Console') {
|
|
264
|
+
this.showConsole();
|
|
265
|
+
}
|
|
266
|
+
else if (title === 'Hide Console') {
|
|
267
|
+
this.hideConsole();
|
|
268
|
+
}
|
|
269
|
+
else if (title === 'Start Server') {
|
|
270
|
+
const server = this.configManager.getServer();
|
|
271
|
+
if (!server)
|
|
272
|
+
return;
|
|
273
|
+
const jarPath = path.join(server.path, 'server.jar');
|
|
274
|
+
let resolvedJar = jarPath;
|
|
275
|
+
if (!fs.existsSync(jarPath)) {
|
|
276
|
+
const jarFiles = fs.readdirSync(server.path).filter(f => f.endsWith('.jar'));
|
|
277
|
+
if (jarFiles.length > 0) {
|
|
278
|
+
resolvedJar = path.join(server.path, jarFiles[0]);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
logger_1.logger.info(`Starting Minecraft server "${server.name}" from tray menu...`);
|
|
283
|
+
await this.processManager.startServer(server.name, server.path, resolvedJar, server.ram, this.configManager.getConfig().defaultJavaPath);
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
logger_1.logger.error('Failed to start server from tray', err);
|
|
287
|
+
}
|
|
288
|
+
this.updateMenu();
|
|
289
|
+
}
|
|
290
|
+
else if (title === 'Stop Server') {
|
|
291
|
+
const server = this.configManager.getServer();
|
|
292
|
+
if (!server)
|
|
293
|
+
return;
|
|
294
|
+
logger_1.logger.info(`Stopping Minecraft server "${server.name}" from tray menu gracefully...`);
|
|
295
|
+
await this.processManager.stopServer(server.name);
|
|
296
|
+
this.updateMenu();
|
|
297
|
+
}
|
|
298
|
+
else if (title === 'Start Tunnel') {
|
|
299
|
+
logger_1.logger.info('Starting playit tunnel agent from tray menu...');
|
|
300
|
+
try {
|
|
301
|
+
const savedSettings = this.configManager.getConfig().playitSettings;
|
|
302
|
+
const type = (savedSettings.tunnelAddress ? 'java' : 'java'); // default to java
|
|
303
|
+
await this.playitManager.setupAndStart(type);
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
logger_1.logger.error('Failed to start tunnel from tray', err);
|
|
307
|
+
}
|
|
308
|
+
this.updateMenu();
|
|
309
|
+
}
|
|
310
|
+
else if (title === 'Stop Tunnel') {
|
|
311
|
+
logger_1.logger.info('Stopping playit tunnel agent from tray menu...');
|
|
312
|
+
this.playitManager.stopTunnel();
|
|
313
|
+
this.updateMenu();
|
|
314
|
+
}
|
|
315
|
+
else if (title === 'Exit') {
|
|
316
|
+
logger_1.logger.info('Exit requested from system tray. Shutting down MCPANEL...');
|
|
317
|
+
// Stop server
|
|
318
|
+
const server = this.configManager.getServer();
|
|
319
|
+
if (server) {
|
|
320
|
+
await this.processManager.stopServer(server.name);
|
|
321
|
+
}
|
|
322
|
+
// Stop tunnel
|
|
323
|
+
this.playitManager.stopTunnel();
|
|
324
|
+
// Clean exit
|
|
325
|
+
const tray = this.systray;
|
|
326
|
+
if (tray) {
|
|
327
|
+
await tray.kill(true);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
process.exit(0);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
exports.TrayManager = TrayManager;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isNewer = isNewer;
|
|
37
|
+
exports.checkForUpdate = checkForUpdate;
|
|
38
|
+
const https = __importStar(require("https"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const configManager_1 = require("../config/configManager");
|
|
42
|
+
/**
|
|
43
|
+
* Lightweight "is there a newer version on npm?" checker.
|
|
44
|
+
*
|
|
45
|
+
* - Reads the installed name/version from the package's own package.json.
|
|
46
|
+
* - Asks the npm registry's dist-tags endpoint for the latest version.
|
|
47
|
+
* - Caches the result (logs/.update-check.json) so we only hit the network
|
|
48
|
+
* every few hours, keeping startup fast.
|
|
49
|
+
* - Fully fail-silent: no network / offline / parse error => returns null.
|
|
50
|
+
*/
|
|
51
|
+
const CACHE_FILE = path.join(configManager_1.APP_ROOT, 'logs', '.update-check.json');
|
|
52
|
+
const CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // re-check at most every 6h
|
|
53
|
+
const FETCH_TIMEOUT_MS = 2500;
|
|
54
|
+
function readPkg() {
|
|
55
|
+
try {
|
|
56
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(configManager_1.APP_ROOT, 'package.json'), 'utf-8'));
|
|
57
|
+
if (pkg && pkg.name && pkg.version)
|
|
58
|
+
return { name: pkg.name, version: pkg.version };
|
|
59
|
+
}
|
|
60
|
+
catch { /* ignore */ }
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function parseVer(v) {
|
|
64
|
+
// Strip any pre-release/build suffix, then split into numeric parts.
|
|
65
|
+
return v.split('-')[0].split('.').map((n) => parseInt(n, 10) || 0);
|
|
66
|
+
}
|
|
67
|
+
/** True if `latest` is a higher semver than `current`. */
|
|
68
|
+
function isNewer(latest, current) {
|
|
69
|
+
const a = parseVer(latest);
|
|
70
|
+
const b = parseVer(current);
|
|
71
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
72
|
+
const x = a[i] || 0;
|
|
73
|
+
const y = b[i] || 0;
|
|
74
|
+
if (x > y)
|
|
75
|
+
return true;
|
|
76
|
+
if (x < y)
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
function fetchLatest(name) {
|
|
82
|
+
// dist-tags is a tiny payload: {"latest":"1.2.3", ...}
|
|
83
|
+
const url = `https://registry.npmjs.org/-/package/${name.replace('/', '%2F')}/dist-tags`;
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
const req = https.get(url, { timeout: FETCH_TIMEOUT_MS }, (res) => {
|
|
86
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
87
|
+
res.resume();
|
|
88
|
+
resolve(null);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
let data = '';
|
|
92
|
+
res.on('data', (c) => { data += c; });
|
|
93
|
+
res.on('end', () => {
|
|
94
|
+
try {
|
|
95
|
+
resolve(JSON.parse(data).latest || null);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
resolve(null);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
req.on('error', () => resolve(null));
|
|
103
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function readCache() {
|
|
107
|
+
try {
|
|
108
|
+
const c = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
109
|
+
if (c && typeof c.latest === 'string' && typeof c.checkedAt === 'number')
|
|
110
|
+
return c;
|
|
111
|
+
}
|
|
112
|
+
catch { /* ignore */ }
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function writeCache(latest) {
|
|
116
|
+
try {
|
|
117
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify({ latest, checkedAt: Date.now() }), 'utf-8');
|
|
118
|
+
}
|
|
119
|
+
catch { /* ignore */ }
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Returns update info, or null if it couldn't be determined.
|
|
123
|
+
* Pass force=true (e.g. for an explicit /update command) to bypass the cache.
|
|
124
|
+
*/
|
|
125
|
+
async function checkForUpdate(force = false) {
|
|
126
|
+
const pkg = readPkg();
|
|
127
|
+
if (!pkg)
|
|
128
|
+
return null;
|
|
129
|
+
if (!force) {
|
|
130
|
+
const cached = readCache();
|
|
131
|
+
if (cached && Date.now() - cached.checkedAt < CHECK_INTERVAL_MS) {
|
|
132
|
+
return { name: pkg.name, current: pkg.version, latest: cached.latest, updateAvailable: isNewer(cached.latest, pkg.version) };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const latest = await fetchLatest(pkg.name);
|
|
136
|
+
if (!latest)
|
|
137
|
+
return null;
|
|
138
|
+
writeCache(latest);
|
|
139
|
+
return { name: pkg.name, current: pkg.version, latest, updateAvailable: isNewer(latest, pkg.version) };
|
|
140
|
+
}
|
package/dist/utils/helpers.js
CHANGED
|
@@ -42,9 +42,14 @@ exports.getDirSize = getDirSize;
|
|
|
42
42
|
exports.checkJava = checkJava;
|
|
43
43
|
exports.findInstalledJavas = findInstalledJavas;
|
|
44
44
|
exports.getSystemStats = getSystemStats;
|
|
45
|
+
exports.checkForUpdates = checkForUpdates;
|
|
46
|
+
exports.getActiveWindowHandle = getActiveWindowHandle;
|
|
47
|
+
exports.hideConsoleWindow = hideConsoleWindow;
|
|
48
|
+
exports.showConsoleWindow = showConsoleWindow;
|
|
45
49
|
const fs = __importStar(require("fs"));
|
|
46
50
|
const os = __importStar(require("os"));
|
|
47
51
|
const child_process_1 = require("child_process");
|
|
52
|
+
const https = __importStar(require("https"));
|
|
48
53
|
/**
|
|
49
54
|
* Detects the runtime OS environment: Windows, WSL, or Linux
|
|
50
55
|
*/
|
|
@@ -363,3 +368,146 @@ function getSystemStats() {
|
|
|
363
368
|
uptimeSeconds: Math.floor(os.uptime()),
|
|
364
369
|
};
|
|
365
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Checks npm registry for a newer version of the CLI package.
|
|
373
|
+
* Returns the latest version string if a newer version is available, or null otherwise.
|
|
374
|
+
*/
|
|
375
|
+
function checkForUpdates(currentVersion) {
|
|
376
|
+
return new Promise((resolve) => {
|
|
377
|
+
const options = {
|
|
378
|
+
hostname: 'registry.npmjs.org',
|
|
379
|
+
path: '/@woopsy/mcpanel/latest',
|
|
380
|
+
method: 'GET',
|
|
381
|
+
timeout: 2000,
|
|
382
|
+
headers: {
|
|
383
|
+
'User-Agent': 'mcpanel-cli',
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
const req = https.get(options, (res) => {
|
|
387
|
+
if (res.statusCode !== 200) {
|
|
388
|
+
resolve(null);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
let data = '';
|
|
392
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
393
|
+
res.on('end', () => {
|
|
394
|
+
try {
|
|
395
|
+
const parsed = JSON.parse(data);
|
|
396
|
+
const latest = parsed.version;
|
|
397
|
+
if (latest && isNewerVersion(currentVersion, latest)) {
|
|
398
|
+
resolve(latest);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
resolve(null);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
resolve(null);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
req.on('timeout', () => {
|
|
410
|
+
req.destroy();
|
|
411
|
+
resolve(null);
|
|
412
|
+
});
|
|
413
|
+
req.on('error', () => {
|
|
414
|
+
resolve(null);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Basic semver comparison (a < b)
|
|
420
|
+
*/
|
|
421
|
+
function isNewerVersion(current, latest) {
|
|
422
|
+
const cParts = current.split('.').map(Number);
|
|
423
|
+
const lParts = latest.split('.').map(Number);
|
|
424
|
+
for (let i = 0; i < 3; i++) {
|
|
425
|
+
const c = cParts[i] || 0;
|
|
426
|
+
const l = lParts[i] || 0;
|
|
427
|
+
if (l > c)
|
|
428
|
+
return true;
|
|
429
|
+
if (c > l)
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Gets the handle/ID of the active console window.
|
|
436
|
+
* Returns null if not supported or fails.
|
|
437
|
+
*/
|
|
438
|
+
function getActiveWindowHandle() {
|
|
439
|
+
const osType = detectOS();
|
|
440
|
+
try {
|
|
441
|
+
if (osType === 'Windows') {
|
|
442
|
+
const cmd = `powershell -NoProfile -Command "Add-Type -MemberDefinition '[DllImport(\\"user32.dll\\")] public static extern IntPtr GetForegroundWindow();' -Name Win32Util -Namespace Win32 -PassThru | Out-Null; [Win32.Win32Util]::GetForegroundWindow()"`;
|
|
443
|
+
return (0, child_process_1.execSync)(cmd).toString().trim();
|
|
444
|
+
}
|
|
445
|
+
else if (osType === 'WSL') {
|
|
446
|
+
const cmd = `powershell.exe -NoProfile -Command "Add-Type -MemberDefinition '[DllImport(\\"user32.dll\\")] public static extern IntPtr GetForegroundWindow();' -Name Win32Util -Namespace Win32 -PassThru | Out-Null; [Win32.Win32Util]::GetForegroundWindow()"`;
|
|
447
|
+
return (0, child_process_1.execSync)(cmd).toString().trim();
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
// Native Linux
|
|
451
|
+
return (0, child_process_1.execSync)('xdotool getactivewindow 2>/dev/null').toString().trim() || null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Hides the console window using its handle/ID.
|
|
460
|
+
*/
|
|
461
|
+
function hideConsoleWindow(handle) {
|
|
462
|
+
if (!handle)
|
|
463
|
+
return false;
|
|
464
|
+
const osType = detectOS();
|
|
465
|
+
try {
|
|
466
|
+
if (osType === 'Windows') {
|
|
467
|
+
const cmd = `powershell -NoProfile -Command "Add-Type -MemberDefinition '[DllImport(\\"user32.dll\\")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);' -Name Win32Util -Namespace Win32; [Win32.Win32Util]::ShowWindowAsync([IntPtr]${handle}, 0)"`;
|
|
468
|
+
(0, child_process_1.execSync)(cmd);
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
else if (osType === 'WSL') {
|
|
472
|
+
const cmd = `powershell.exe -NoProfile -Command "Add-Type -MemberDefinition '[DllImport(\\"user32.dll\\")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);' -Name Win32Util -Namespace Win32; [Win32.Win32Util]::ShowWindowAsync([IntPtr]${handle}, 0)"`;
|
|
473
|
+
(0, child_process_1.execSync)(cmd);
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
// Native Linux
|
|
478
|
+
(0, child_process_1.execSync)(`xdotool windowunmap ${handle} 2>/dev/null`);
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Restores and focuses the console window using its handle/ID.
|
|
488
|
+
*/
|
|
489
|
+
function showConsoleWindow(handle) {
|
|
490
|
+
if (!handle)
|
|
491
|
+
return false;
|
|
492
|
+
const osType = detectOS();
|
|
493
|
+
try {
|
|
494
|
+
if (osType === 'Windows') {
|
|
495
|
+
const cmd = `powershell -NoProfile -Command "Add-Type -MemberDefinition '[DllImport(\\"user32.dll\\")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); [DllImport(\\"user32.dll\\")] public static extern bool SetForegroundWindow(IntPtr hWnd);' -Name Win32Util -Namespace Win32; [Win32.Win32Util]::ShowWindowAsync([IntPtr]${handle}, 9); [Win32.Win32Util]::SetForegroundWindow([IntPtr]${handle})"`;
|
|
496
|
+
(0, child_process_1.execSync)(cmd);
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
else if (osType === 'WSL') {
|
|
500
|
+
const cmd = `powershell.exe -NoProfile -Command "Add-Type -MemberDefinition '[DllImport(\\"user32.dll\\")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); [DllImport(\\"user32.dll\\")] public static extern bool SetForegroundWindow(IntPtr hWnd);' -Name Win32Util -Namespace Win32; [Win32.Win32Util]::ShowWindowAsync([IntPtr]${handle}, 9); [Win32.Win32Util]::SetForegroundWindow([IntPtr]${handle})"`;
|
|
501
|
+
(0, child_process_1.execSync)(cmd);
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
// Native Linux
|
|
506
|
+
(0, child_process_1.execSync)(`xdotool windowmap ${handle} 2>/dev/null && xdotool windowactivate ${handle} 2>/dev/null`);
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@woopsy/mcpanel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "MCPANEL — a terminal-based, single-server Minecraft server manager with an Arch/neofetch-style UI, live logs, backups, plugins and Playit.gg tunnels.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"adm-zip": "^0.5.12",
|
|
49
49
|
"chalk": "^4.1.2",
|
|
50
50
|
"figlet": "^1.11.0",
|
|
51
|
-
"pidusage": "^4.0.1"
|
|
51
|
+
"pidusage": "^4.0.1",
|
|
52
|
+
"systray2": "^2.1.4"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@types/adm-zip": "^0.5.5",
|