@woopsy/mcpanel 2.1.0 → 2.1.2
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/assets/banner.svg +52 -0
- package/assets/logo.ico +0 -0
- package/assets/logo.png +0 -0
- package/dist/config/configManager.js +41 -1
- package/dist/managers/trayManager.js +41 -2
- package/package.json +2 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<svg width="1200" height="320" viewBox="0 0 1200 320" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="MCPANEL - Terminal Minecraft Server Manager">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#0d1117"/>
|
|
5
|
+
<stop offset="1" stop-color="#10231a"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="title" x1="0" y1="0" x2="1" y2="0">
|
|
8
|
+
<stop offset="0" stop-color="#5eead4"/>
|
|
9
|
+
<stop offset="0.5" stop-color="#34d399"/>
|
|
10
|
+
<stop offset="1" stop-color="#22c55e"/>
|
|
11
|
+
</linearGradient>
|
|
12
|
+
</defs>
|
|
13
|
+
|
|
14
|
+
<!-- backdrop -->
|
|
15
|
+
<rect width="1200" height="320" rx="18" fill="url(#bg)"/>
|
|
16
|
+
<rect x="1" y="1" width="1198" height="318" rx="17" fill="none" stroke="#1f6f4d" stroke-opacity="0.5"/>
|
|
17
|
+
|
|
18
|
+
<!-- terminal traffic lights -->
|
|
19
|
+
<circle cx="42" cy="40" r="8" fill="#ff5f56"/>
|
|
20
|
+
<circle cx="68" cy="40" r="8" fill="#ffbd2e"/>
|
|
21
|
+
<circle cx="94" cy="40" r="8" fill="#27c93f"/>
|
|
22
|
+
<text x="120" y="46" font-family="'JetBrains Mono','Fira Code',Consolas,monospace" font-size="16" fill="#7d8590">mcpanel — minecraft server manager</text>
|
|
23
|
+
|
|
24
|
+
<!-- creeper face accent -->
|
|
25
|
+
<g transform="translate(70,110)">
|
|
26
|
+
<rect width="150" height="150" rx="8" fill="#22c55e"/>
|
|
27
|
+
<rect width="150" height="150" rx="8" fill="#000000" opacity="0.08"/>
|
|
28
|
+
<rect x="28" y="36" width="34" height="34" fill="#0d1117"/>
|
|
29
|
+
<rect x="88" y="36" width="34" height="34" fill="#0d1117"/>
|
|
30
|
+
<rect x="58" y="74" width="34" height="40" fill="#0d1117"/>
|
|
31
|
+
<rect x="28" y="100" width="30" height="34" fill="#0d1117"/>
|
|
32
|
+
<rect x="92" y="100" width="30" height="34" fill="#0d1117"/>
|
|
33
|
+
</g>
|
|
34
|
+
|
|
35
|
+
<!-- title -->
|
|
36
|
+
<text x="270" y="170" font-family="'Segoe UI',Arial,sans-serif" font-size="96" font-weight="800" letter-spacing="6" fill="url(#title)">MCPANEL</text>
|
|
37
|
+
<text x="274" y="212" font-family="'JetBrains Mono','Fira Code',Consolas,monospace" font-size="24" fill="#9ca3af">Terminal Minecraft Server Manager</text>
|
|
38
|
+
|
|
39
|
+
<!-- prompt line -->
|
|
40
|
+
<text x="274" y="262" font-family="'JetBrains Mono','Fira Code',Consolas,monospace" font-size="22" fill="#34d399">mcpanel> <tspan fill="#e5e7eb">/start</tspan> <tspan fill="#6b7280">·</tspan> <tspan fill="#e5e7eb">/log</tspan> <tspan fill="#6b7280">·</tspan> <tspan fill="#e5e7eb">/backup</tspan> <tspan fill="#6b7280">·</tspan> <tspan fill="#e5e7eb">/tunnel</tspan></text>
|
|
41
|
+
|
|
42
|
+
<!-- neofetch palette strip -->
|
|
43
|
+
<g transform="translate(274,286)">
|
|
44
|
+
<rect x="0" y="0" width="34" height="16" fill="#ef4444"/>
|
|
45
|
+
<rect x="34" y="0" width="34" height="16" fill="#eab308"/>
|
|
46
|
+
<rect x="68" y="0" width="34" height="16" fill="#22c55e"/>
|
|
47
|
+
<rect x="102" y="0" width="34" height="16" fill="#06b6d4"/>
|
|
48
|
+
<rect x="136" y="0" width="34" height="16" fill="#3b82f6"/>
|
|
49
|
+
<rect x="170" y="0" width="34" height="16" fill="#a855f7"/>
|
|
50
|
+
<rect x="204" y="0" width="34" height="16" fill="#e5e7eb"/>
|
|
51
|
+
</g>
|
|
52
|
+
</svg>
|
package/assets/logo.ico
ADDED
|
Binary file
|
package/assets/logo.png
ADDED
|
Binary file
|
|
@@ -80,7 +80,8 @@ class ConfigManager {
|
|
|
80
80
|
*/
|
|
81
81
|
load() {
|
|
82
82
|
if (!fs.existsSync(CONFIG_PATH)) {
|
|
83
|
-
this.config = { ...DEFAULT_CONFIG };
|
|
83
|
+
this.config = { ...DEFAULT_CONFIG, playitSettings: {} };
|
|
84
|
+
this.migrateLegacyConfig();
|
|
84
85
|
this.save();
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
@@ -121,6 +122,9 @@ class ConfigManager {
|
|
|
121
122
|
server,
|
|
122
123
|
externalBackups: parsed.externalBackups || [],
|
|
123
124
|
};
|
|
125
|
+
// Recover an already-claimed playit secret from the pre-2.0 config
|
|
126
|
+
// location before persisting (no-op once a secret is present here).
|
|
127
|
+
this.migrateLegacyConfig();
|
|
124
128
|
// Persist the migrated shape so the legacy keys are cleaned up on disk.
|
|
125
129
|
this.save();
|
|
126
130
|
}
|
|
@@ -131,6 +135,42 @@ class ConfigManager {
|
|
|
131
135
|
this.save();
|
|
132
136
|
}
|
|
133
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Recovers settings written by versions <2.0, which stored config.json next
|
|
140
|
+
* to the app (`APP_ROOT/config.json`) instead of in `~/.mcpanel`. Without
|
|
141
|
+
* this, an already-claimed playit agent secret is invisible to the new
|
|
142
|
+
* location, so the agent gets re-claimed in the browser on every launch.
|
|
143
|
+
*
|
|
144
|
+
* Only runs when the current config has no playit secret, and never
|
|
145
|
+
* overwrites values already present in the new location.
|
|
146
|
+
*/
|
|
147
|
+
migrateLegacyConfig() {
|
|
148
|
+
const legacyPath = path.join(exports.APP_ROOT, 'config.json');
|
|
149
|
+
if (legacyPath === CONFIG_PATH)
|
|
150
|
+
return; // same file — nothing to migrate
|
|
151
|
+
if (this.config.playitSettings.secret)
|
|
152
|
+
return; // already linked
|
|
153
|
+
if (!fs.existsSync(legacyPath))
|
|
154
|
+
return;
|
|
155
|
+
try {
|
|
156
|
+
const legacy = JSON.parse(fs.readFileSync(legacyPath, 'utf-8'));
|
|
157
|
+
if (legacy?.playitSettings?.secret) {
|
|
158
|
+
// Bring the secret + last-known tunnel forward so the existing agent
|
|
159
|
+
// and tunnel are reused instead of re-claimed/recreated.
|
|
160
|
+
this.config.playitSettings = {
|
|
161
|
+
...legacy.playitSettings,
|
|
162
|
+
...this.config.playitSettings,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// Adopt the legacy server only if none is synced in the new location.
|
|
166
|
+
if (!this.config.server && legacy.server && typeof legacy.server === 'object') {
|
|
167
|
+
this.config.server = legacy.server;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Unreadable/corrupt legacy config — ignore and continue with defaults.
|
|
172
|
+
}
|
|
173
|
+
}
|
|
134
174
|
/**
|
|
135
175
|
* Saves the current config memory state to disk
|
|
136
176
|
*/
|
|
@@ -134,6 +134,10 @@ class TrayManager {
|
|
|
134
134
|
systray = null;
|
|
135
135
|
activeHandle = null;
|
|
136
136
|
isInitialized = false;
|
|
137
|
+
// The tray runs a separate native helper over a stdin pipe. If that helper
|
|
138
|
+
// dies, writes to its pipe raise an async EPIPE. The tray is best-effort, so
|
|
139
|
+
// this flag lets us stop writing and degrade to CLI-only instead of crashing.
|
|
140
|
+
trayAlive = false;
|
|
137
141
|
// Menu item state tracking
|
|
138
142
|
itemShow = { title: 'Open Console', tooltip: 'Restore terminal window', enabled: true };
|
|
139
143
|
itemHide = { title: 'Hide Console', tooltip: 'Hide terminal window from taskbar', enabled: true };
|
|
@@ -154,12 +158,22 @@ class TrayManager {
|
|
|
154
158
|
if (this.isInitialized)
|
|
155
159
|
return true;
|
|
156
160
|
const osType = (0, helpers_1.detectOS)();
|
|
157
|
-
const
|
|
161
|
+
const iconCandidate = osType === 'Windows' || osType === 'WSL'
|
|
158
162
|
? path.join(configManager_1.APP_ROOT, 'assets', 'logo.ico')
|
|
159
163
|
: path.join(configManager_1.APP_ROOT, 'assets', 'logo.png');
|
|
164
|
+
// systray2 only base64-encodes the icon if the file exists; otherwise it
|
|
165
|
+
// ships the raw path, which the native helper can't decode and then exits.
|
|
166
|
+
// Omit a missing icon so the helper stays alive (CLI works without one).
|
|
167
|
+
const iconFile = fs.existsSync(iconCandidate) ? iconCandidate : undefined;
|
|
168
|
+
if (!iconFile) {
|
|
169
|
+
logger_1.logger.warn(`Tray icon not found at ${iconCandidate}; starting tray without an icon.`);
|
|
170
|
+
}
|
|
160
171
|
try {
|
|
161
172
|
this.systray = new WSLSysTray({
|
|
162
173
|
menu: {
|
|
174
|
+
// May be undefined when the icon file is absent; systray2 tolerates
|
|
175
|
+
// this at runtime (it pathExists-checks before encoding) — the cast
|
|
176
|
+
// just satisfies the `Conf` type, which declares icon as required.
|
|
163
177
|
icon: iconFile,
|
|
164
178
|
title: 'MCPANEL',
|
|
165
179
|
tooltip: 'MCPANEL Server Manager',
|
|
@@ -185,6 +199,8 @@ class TrayManager {
|
|
|
185
199
|
});
|
|
186
200
|
await this.systray.ready();
|
|
187
201
|
this.isInitialized = true;
|
|
202
|
+
this.trayAlive = true;
|
|
203
|
+
this.attachTrayGuards();
|
|
188
204
|
logger_1.logger.info('System tray initialized successfully.');
|
|
189
205
|
this.updateMenu();
|
|
190
206
|
return true;
|
|
@@ -196,12 +212,35 @@ class TrayManager {
|
|
|
196
212
|
return false;
|
|
197
213
|
}
|
|
198
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Attaches error/exit listeners to the native tray helper so a dead helper
|
|
217
|
+
* (e.g. EPIPE when writing to its closed stdin) degrades to CLI-only mode
|
|
218
|
+
* instead of throwing an unhandled 'error' event that crashes the process.
|
|
219
|
+
*/
|
|
220
|
+
attachTrayGuards() {
|
|
221
|
+
const proc = this.systray?._process;
|
|
222
|
+
if (!proc)
|
|
223
|
+
return;
|
|
224
|
+
const onDead = (err) => {
|
|
225
|
+
if (this.trayAlive) {
|
|
226
|
+
const detail = err ? ` (${err.code || err.message})` : '';
|
|
227
|
+
logger_1.logger.warn(`System tray helper stopped; continuing in CLI-only mode.${detail}`);
|
|
228
|
+
}
|
|
229
|
+
this.trayAlive = false;
|
|
230
|
+
};
|
|
231
|
+
// The load-bearing handler: without an 'error' listener, an EPIPE on the
|
|
232
|
+
// helper's stdin is emitted as an unhandled 'error' event and crashes Node.
|
|
233
|
+
proc.stdin?.on('error', onDead);
|
|
234
|
+
proc.on('error', onDead);
|
|
235
|
+
proc.on('exit', () => onDead());
|
|
236
|
+
proc.on('close', () => onDead());
|
|
237
|
+
}
|
|
199
238
|
/**
|
|
200
239
|
* Dynamically updates the titles and states of the tray menu items
|
|
201
240
|
*/
|
|
202
241
|
updateMenu() {
|
|
203
242
|
const tray = this.systray;
|
|
204
|
-
if (!tray || !this.isInitialized)
|
|
243
|
+
if (!tray || !this.isInitialized || !this.trayAlive)
|
|
205
244
|
return;
|
|
206
245
|
const server = this.configManager.getServer();
|
|
207
246
|
const running = server ? !!this.processManager.getActiveServer(server.name) : false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@woopsy/mcpanel",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
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": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
+
"assets",
|
|
11
12
|
"README.md",
|
|
12
13
|
"LICENSE"
|
|
13
14
|
],
|