@woopsy/mcpanel 1.1.0 → 2.1.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 +2 -0
- package/dist/config/configManager.js +8 -3
- package/dist/index.js +21 -2
- package/dist/managers/backupManager.js +2 -2
- package/dist/managers/playitManager.js +4 -4
- package/dist/managers/trayManager.js +335 -0
- package/dist/services/downloadService.js +1 -1
- package/dist/services/updateChecker.js +1 -1
- package/dist/utils/helpers.js +83 -0
- package/dist/utils/logger.js +1 -1
- package/package.json +3 -2
|
@@ -96,6 +96,8 @@ class CommandRouter {
|
|
|
96
96
|
' /stats - System stats + CPU/RAM/disk of the server',
|
|
97
97
|
' /java [path] - Show/list Java runtimes, or set the one used to launch',
|
|
98
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',
|
|
99
101
|
' /clear - Clear the screen, scrollback and command history',
|
|
100
102
|
' /update - Check npm for a newer version of MCPANEL',
|
|
101
103
|
' /config - View active application config.json',
|
|
@@ -33,14 +33,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.ConfigManager = exports.APP_ROOT = void 0;
|
|
36
|
+
exports.ConfigManager = exports.APP_DATA_DIR = exports.APP_ROOT = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
39
40
|
// Dynamically resolve application root folder
|
|
40
41
|
exports.APP_ROOT = fs.existsSync(path.join(__dirname, '..', '..', 'package.json'))
|
|
41
42
|
? path.resolve(__dirname, '..', '..')
|
|
42
43
|
: path.resolve(__dirname, '..');
|
|
43
|
-
|
|
44
|
+
exports.APP_DATA_DIR = path.join(os.homedir(), '.mcpanel');
|
|
45
|
+
const CONFIG_PATH = path.join(exports.APP_DATA_DIR, 'config.json');
|
|
44
46
|
const DEFAULT_CONFIG = {
|
|
45
47
|
defaultJavaPath: 'java',
|
|
46
48
|
defaultRam: '4G',
|
|
@@ -61,9 +63,12 @@ class ConfigManager {
|
|
|
61
63
|
* Initializes folders and configuration file
|
|
62
64
|
*/
|
|
63
65
|
initialize() {
|
|
66
|
+
if (!fs.existsSync(exports.APP_DATA_DIR)) {
|
|
67
|
+
fs.mkdirSync(exports.APP_DATA_DIR, { recursive: true });
|
|
68
|
+
}
|
|
64
69
|
const requiredDirs = ['backups', 'downloads', 'logs', 'playit'];
|
|
65
70
|
for (const dir of requiredDirs) {
|
|
66
|
-
const dirPath = path.join(exports.
|
|
71
|
+
const dirPath = path.join(exports.APP_DATA_DIR, dir);
|
|
67
72
|
if (!fs.existsSync(dirPath)) {
|
|
68
73
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
69
74
|
}
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,7 @@ const colors = __importStar(require("./utils/colors"));
|
|
|
52
52
|
const helpers_1 = require("./utils/helpers");
|
|
53
53
|
const updateChecker_1 = require("./services/updateChecker");
|
|
54
54
|
const logger_1 = require("./utils/logger");
|
|
55
|
+
const trayManager_1 = require("./managers/trayManager");
|
|
55
56
|
// Initialize managers
|
|
56
57
|
const configManager = new configManager_1.ConfigManager();
|
|
57
58
|
configManager.initialize();
|
|
@@ -59,8 +60,9 @@ const processManager = new processManager_1.ProcessManager();
|
|
|
59
60
|
const serverManager = new serverManager_1.ServerManager(configManager);
|
|
60
61
|
const backupManager = new backupManager_1.BackupManager(configManager);
|
|
61
62
|
const playitManager = new playitManager_1.PlayitManager(configManager);
|
|
63
|
+
const trayManager = new trayManager_1.TrayManager(configManager, processManager, playitManager);
|
|
62
64
|
const router = new commandRouter_1.CommandRouter(configManager, processManager, serverManager, backupManager, playitManager);
|
|
63
|
-
const HISTORY_PATH = path.join(configManager_1.
|
|
65
|
+
const HISTORY_PATH = path.join(configManager_1.APP_DATA_DIR, 'logs', '.history');
|
|
64
66
|
let currentState = 'COMMAND';
|
|
65
67
|
const propertiesContext = {
|
|
66
68
|
properties: {},
|
|
@@ -168,7 +170,7 @@ const COMMAND_LIST = [
|
|
|
168
170
|
'/plugins list', '/plugins install', '/plugins remove',
|
|
169
171
|
'/setup',
|
|
170
172
|
'/tunnel java', '/tunnel bedrock', '/tunnel status', '/tunnel stop', '/tunnel reset',
|
|
171
|
-
'/config', '/clear', '/update', '/exit'
|
|
173
|
+
'/config', '/clear', '/update', '/tray', '/background', '/exit'
|
|
172
174
|
];
|
|
173
175
|
// Subcommands offered once "<command> " has been typed.
|
|
174
176
|
const SUBCOMMANDS = {
|
|
@@ -245,6 +247,11 @@ function getStatusBar() {
|
|
|
245
247
|
const running = server ? !!processManager.getActiveServer(server.name) : false;
|
|
246
248
|
const backupsCount = backupManager.listBackups().length;
|
|
247
249
|
const tunnelStatus = playitManager.getStatus().status;
|
|
250
|
+
// Sync menu state to the system tray
|
|
251
|
+
try {
|
|
252
|
+
trayManager.updateMenu();
|
|
253
|
+
}
|
|
254
|
+
catch { /* ignore */ }
|
|
248
255
|
const serverStr = !server
|
|
249
256
|
? colors.gray('none')
|
|
250
257
|
: running ? colors.green('Running') : colors.red('Offline');
|
|
@@ -561,6 +568,16 @@ async function handleCommandState(line) {
|
|
|
561
568
|
console.log(colors.failure(`Failed to clear: ${err.message}`));
|
|
562
569
|
}
|
|
563
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
|
+
}
|
|
564
581
|
case '/exit':
|
|
565
582
|
logger_1.logger.info('Exiting MCPANEL manager.');
|
|
566
583
|
playitManager.stopTunnel();
|
|
@@ -753,6 +770,8 @@ async function showUpdateNotice() {
|
|
|
753
770
|
async function main() {
|
|
754
771
|
renderBanner();
|
|
755
772
|
await showUpdateNotice();
|
|
773
|
+
// Start the background system tray loop
|
|
774
|
+
await trayManager.start();
|
|
756
775
|
rl = readline.createInterface({
|
|
757
776
|
input: process.stdin,
|
|
758
777
|
output: process.stdout,
|
|
@@ -61,7 +61,7 @@ class BackupManager {
|
|
|
61
61
|
}
|
|
62
62
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
63
63
|
const backupFileName = `${server.name}_backup_${timestamp}.zip`;
|
|
64
|
-
const backupDestDir = path.join(configManager_1.
|
|
64
|
+
const backupDestDir = path.join(configManager_1.APP_DATA_DIR, 'backups');
|
|
65
65
|
if (!fs.existsSync(backupDestDir)) {
|
|
66
66
|
fs.mkdirSync(backupDestDir, { recursive: true });
|
|
67
67
|
}
|
|
@@ -88,7 +88,7 @@ class BackupManager {
|
|
|
88
88
|
listBackups() {
|
|
89
89
|
const backups = [];
|
|
90
90
|
const scanDirs = [
|
|
91
|
-
path.join(configManager_1.
|
|
91
|
+
path.join(configManager_1.APP_DATA_DIR, 'backups'),
|
|
92
92
|
...this.configManager.getConfig().externalBackups
|
|
93
93
|
];
|
|
94
94
|
for (const dir of scanDirs) {
|
|
@@ -79,7 +79,7 @@ class PlayitManager {
|
|
|
79
79
|
getExecutablePath() {
|
|
80
80
|
const osType = (0, helpers_1.detectOS)();
|
|
81
81
|
const binName = osType === 'Windows' ? 'playit.exe' : 'playit';
|
|
82
|
-
return path.join(configManager_1.
|
|
82
|
+
return path.join(configManager_1.APP_DATA_DIR, 'playit', binName);
|
|
83
83
|
}
|
|
84
84
|
/** Path to the control cli (used for the one-time claim flow). */
|
|
85
85
|
getCliPath() {
|
|
@@ -87,17 +87,17 @@ class PlayitManager {
|
|
|
87
87
|
// v1.0.8 only ships a separate cli for Linux; on Windows the main exe is used.
|
|
88
88
|
if (osType === 'Windows')
|
|
89
89
|
return this.getExecutablePath();
|
|
90
|
-
return path.join(configManager_1.
|
|
90
|
+
return path.join(configManager_1.APP_DATA_DIR, 'playit', 'playit-cli');
|
|
91
91
|
}
|
|
92
92
|
/** IPC socket / named pipe the daemon binds to (its default location may not exist). */
|
|
93
93
|
getSocketPath() {
|
|
94
94
|
const osType = (0, helpers_1.detectOS)();
|
|
95
95
|
if (osType === 'Windows')
|
|
96
96
|
return '\\\\.\\pipe\\mcpanel-playit';
|
|
97
|
-
return path.join(configManager_1.
|
|
97
|
+
return path.join(configManager_1.APP_DATA_DIR, 'playit', 'agent.sock');
|
|
98
98
|
}
|
|
99
99
|
getVersionSentinel() {
|
|
100
|
-
return path.join(configManager_1.
|
|
100
|
+
return path.join(configManager_1.APP_DATA_DIR, 'playit', '.version');
|
|
101
101
|
}
|
|
102
102
|
downloadUrls() {
|
|
103
103
|
const base = `https://github.com/playit-cloud/playit-agent/releases/download/v${PLAYIT_VERSION}`;
|
|
@@ -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;
|
|
@@ -40,7 +40,7 @@ const path = __importStar(require("path"));
|
|
|
40
40
|
const https = __importStar(require("https"));
|
|
41
41
|
const configManager_1 = require("../config/configManager");
|
|
42
42
|
const logger_1 = require("../utils/logger");
|
|
43
|
-
const DOWNLOADS_DIR = path.join(configManager_1.
|
|
43
|
+
const DOWNLOADS_DIR = path.join(configManager_1.APP_DATA_DIR, 'downloads');
|
|
44
44
|
/**
|
|
45
45
|
* Downloads a file from a URL with redirection support and reports progress.
|
|
46
46
|
*/
|
|
@@ -48,7 +48,7 @@ const configManager_1 = require("../config/configManager");
|
|
|
48
48
|
* every few hours, keeping startup fast.
|
|
49
49
|
* - Fully fail-silent: no network / offline / parse error => returns null.
|
|
50
50
|
*/
|
|
51
|
-
const CACHE_FILE = path.join(configManager_1.
|
|
51
|
+
const CACHE_FILE = path.join(configManager_1.APP_DATA_DIR, 'logs', '.update-check.json');
|
|
52
52
|
const CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // re-check at most every 6h
|
|
53
53
|
const FETCH_TIMEOUT_MS = 2500;
|
|
54
54
|
function readPkg() {
|
package/dist/utils/helpers.js
CHANGED
|
@@ -43,6 +43,9 @@ exports.checkJava = checkJava;
|
|
|
43
43
|
exports.findInstalledJavas = findInstalledJavas;
|
|
44
44
|
exports.getSystemStats = getSystemStats;
|
|
45
45
|
exports.checkForUpdates = checkForUpdates;
|
|
46
|
+
exports.getActiveWindowHandle = getActiveWindowHandle;
|
|
47
|
+
exports.hideConsoleWindow = hideConsoleWindow;
|
|
48
|
+
exports.showConsoleWindow = showConsoleWindow;
|
|
46
49
|
const fs = __importStar(require("fs"));
|
|
47
50
|
const os = __importStar(require("os"));
|
|
48
51
|
const child_process_1 = require("child_process");
|
|
@@ -428,3 +431,83 @@ function isNewerVersion(current, latest) {
|
|
|
428
431
|
}
|
|
429
432
|
return false;
|
|
430
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/dist/utils/logger.js
CHANGED
|
@@ -37,7 +37,7 @@ exports.logger = void 0;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const configManager_1 = require("../config/configManager");
|
|
40
|
-
const LOGS_DIR = path.join(configManager_1.
|
|
40
|
+
const LOGS_DIR = path.join(configManager_1.APP_DATA_DIR, 'logs');
|
|
41
41
|
function ensureLogsDirExists() {
|
|
42
42
|
if (!fs.existsSync(LOGS_DIR)) {
|
|
43
43
|
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@woopsy/mcpanel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.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",
|