panrouter 1.1.0 → 1.1.1
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/package.json +1 -1
- package/tray-manager.ps1 +179 -193
package/package.json
CHANGED
package/tray-manager.ps1
CHANGED
|
@@ -6,232 +6,218 @@
|
|
|
6
6
|
右键菜单: 状态, 开机自启动开关, 退出
|
|
7
7
|
|
|
8
8
|
用法:
|
|
9
|
-
powershell -STA -File tray-manager.ps1 -ServerPath "server.mjs"
|
|
9
|
+
powershell -ExecutionPolicy Bypass -STA -File tray-manager.ps1 -ServerPath "server.mjs"
|
|
10
10
|
#>
|
|
11
11
|
|
|
12
12
|
param(
|
|
13
13
|
[string]$ServerPath
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
-
# ───
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"Pan Router 需要 STA 模式运行。`n是否自动以 STA 模式重新启动?",
|
|
20
|
-
"Pan Router",
|
|
21
|
-
"YesNo",
|
|
22
|
-
"Warning"
|
|
23
|
-
)
|
|
24
|
-
if ($r -eq "Yes") {
|
|
25
|
-
powershell -STA -File $MyInvocation.MyCommand.Path -ServerPath $ServerPath
|
|
26
|
-
}
|
|
27
|
-
exit 1
|
|
28
|
-
}
|
|
16
|
+
# ─── 日志辅助 ──────────────────────────────────
|
|
17
|
+
$logFile = "$env:TEMP\panrouter-tray.log"
|
|
18
|
+
function Write-Log { param([string]$m) "$([DateTime]::Now.ToString('HH:mm:ss')) $m" | Out-File -Append -Encoding utf8 $logFile }
|
|
29
19
|
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
try {
|
|
21
|
+
Write-Log "==== Pan Router Tray Started ===="
|
|
22
|
+
Write-Log "ServerPath: $ServerPath"
|
|
23
|
+
Write-Log "PSVersion: $($PSVersionTable.PSVersion)"
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
$autostartName = "PanRouter"
|
|
36
|
-
$runKeyPath = "Software\Microsoft\Windows\CurrentVersion\Run"
|
|
25
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
26
|
+
Add-Type -AssemblyName System.Drawing
|
|
37
27
|
|
|
38
|
-
#
|
|
39
|
-
$
|
|
40
|
-
$
|
|
41
|
-
$
|
|
28
|
+
# ─── 常量 ──────────────────────────────────
|
|
29
|
+
$scriptPath = $MyInvocation.MyCommand.Path
|
|
30
|
+
$autostartName = "PanRouter"
|
|
31
|
+
$runKeyPath = "Software\Microsoft\Windows\CurrentVersion\Run"
|
|
42
32
|
|
|
43
|
-
# ───
|
|
44
|
-
function New-TrayIcon {
|
|
33
|
+
# ─── 生成图标 ──────────────────────────────
|
|
45
34
|
$bmp = New-Object System.Drawing.Bitmap(16, 16)
|
|
46
35
|
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
47
36
|
$g.SmoothingMode = 'HighQuality'
|
|
48
37
|
$g.Clear([System.Drawing.Color]::Transparent)
|
|
49
|
-
|
|
50
|
-
# 蓝色圆底
|
|
51
38
|
$brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0, 120, 215))
|
|
52
39
|
$g.FillEllipse($brush, 0, 0, 15, 15)
|
|
53
|
-
|
|
54
|
-
# 白色 "P" 字
|
|
55
40
|
$font = New-Object System.Drawing.Font("Segoe UI", 8.5, [System.Drawing.FontStyle]::Bold)
|
|
56
41
|
$fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
|
|
57
42
|
$g.DrawString("P", $font, $fg, 3, 1.5)
|
|
58
|
-
$font.Dispose()
|
|
59
|
-
$fg.Dispose()
|
|
60
|
-
$brush.Dispose()
|
|
61
|
-
$g.Dispose()
|
|
62
|
-
|
|
43
|
+
$font.Dispose(); $fg.Dispose(); $brush.Dispose(); $g.Dispose()
|
|
63
44
|
$hIcon = $bmp.GetHicon()
|
|
64
45
|
$icon = [System.Drawing.Icon]::FromHandle($hIcon)
|
|
65
46
|
$bmp.Dispose()
|
|
66
|
-
|
|
67
|
-
|
|
47
|
+
Write-Log "Icon created"
|
|
48
|
+
|
|
49
|
+
# ─── NotifyIcon ────────────────────────────
|
|
50
|
+
$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
51
|
+
$notifyIcon.Icon = $icon
|
|
52
|
+
$notifyIcon.Text = "Pan Router`n端口 50816 | 运行中"
|
|
53
|
+
$notifyIcon.Visible = $true
|
|
54
|
+
Write-Log "NotifyIcon created and visible"
|
|
55
|
+
|
|
56
|
+
# ─── 用 UseShellExecute 启动服务器(避免输出缓冲死锁) ──
|
|
57
|
+
function Start-ServerNohup {
|
|
58
|
+
param([string]$ServerPath)
|
|
59
|
+
$serverDir = Split-Path $ServerPath -Parent
|
|
60
|
+
try {
|
|
61
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
62
|
+
$psi.FileName = "node"
|
|
63
|
+
$psi.Arguments = "`"$ServerPath`""
|
|
64
|
+
$psi.WorkingDirectory = $serverDir
|
|
65
|
+
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
|
|
66
|
+
$psi.CreateNoWindow = $true
|
|
67
|
+
# UseShellExecute=$true 避免重定向缓冲死锁
|
|
68
|
+
$psi.UseShellExecute = $true
|
|
69
|
+
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
70
|
+
Write-Log "Server started PID=$($proc.Id)"
|
|
71
|
+
return $proc
|
|
72
|
+
} catch {
|
|
73
|
+
Write-Log "Server start FAILED: $_"
|
|
74
|
+
return $null
|
|
75
|
+
}
|
|
76
|
+
}
|
|
68
77
|
|
|
69
|
-
# ───
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
$psi.CreateNoWindow = $true
|
|
80
|
-
$psi.UseShellExecute = $false
|
|
81
|
-
$psi.RedirectStandardOutput = $true
|
|
82
|
-
$psi.RedirectStandardError = $true
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
86
|
-
return $proc
|
|
87
|
-
} catch {
|
|
88
|
-
return $null
|
|
78
|
+
# ─── 杀掉旧进程 (wmic 兼容 PS5.1) ──────────
|
|
79
|
+
function Stop-OldServer {
|
|
80
|
+
try {
|
|
81
|
+
$old = Get-WmiObject Win32_Process -Filter "Name='node.exe'" -ErrorAction Stop |
|
|
82
|
+
Where-Object { $_.CommandLine -match "server\.mjs" }
|
|
83
|
+
foreach ($p in $old) {
|
|
84
|
+
Write-Log "Killing old server PID=$($p.ProcessId)"
|
|
85
|
+
Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
|
|
86
|
+
}
|
|
87
|
+
} catch { Write-Log "Stop-OldServer: $_" }
|
|
89
88
|
}
|
|
90
|
-
}
|
|
91
89
|
|
|
92
|
-
# ───
|
|
93
|
-
function Test-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return $
|
|
101
|
-
} catch {
|
|
102
|
-
return $false
|
|
90
|
+
# ─── 健康检查 ──────────────────────────────
|
|
91
|
+
function Test-Online {
|
|
92
|
+
try {
|
|
93
|
+
$req = [System.Net.WebRequest]::Create("http://127.0.0.1:50816/health")
|
|
94
|
+
$req.Timeout = 1500
|
|
95
|
+
$resp = $req.GetResponse()
|
|
96
|
+
$resp.Close()
|
|
97
|
+
return $true
|
|
98
|
+
} catch { return $false }
|
|
103
99
|
}
|
|
104
|
-
}
|
|
105
100
|
|
|
106
|
-
# ───
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
$existing = Get-CimInstance -ClassName Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
|
|
110
|
-
Where-Object { $_.CommandLine -match "server\.mjs" }
|
|
111
|
-
foreach ($p in $existing) {
|
|
112
|
-
$pid = $p.ProcessId
|
|
113
|
-
Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue
|
|
114
|
-
}
|
|
115
|
-
} catch {}
|
|
116
|
-
}
|
|
101
|
+
# ─── 启动流程 ──────────────────────────────
|
|
102
|
+
Stop-OldServer
|
|
103
|
+
$serverProcess = Start-ServerNohup -ServerPath $ServerPath
|
|
117
104
|
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Stop-OldServer
|
|
124
|
-
|
|
125
|
-
# 2. 生成图标
|
|
126
|
-
$icon, $hIcon = New-TrayIcon
|
|
127
|
-
|
|
128
|
-
# 3. 创建 NotifyIcon
|
|
129
|
-
$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
130
|
-
$notifyIcon.Icon = $icon
|
|
131
|
-
$notifyIcon.Text = $tooltipText
|
|
132
|
-
$notifyIcon.Visible = $true
|
|
133
|
-
|
|
134
|
-
# 4. 启动隐藏服务器进程
|
|
135
|
-
$serverProcess = Start-ServerHidden -ServerPath $ServerPath
|
|
136
|
-
|
|
137
|
-
# 5. 等待服务器就绪(后台检查,不阻塞 UI)
|
|
138
|
-
$checkTimer = New-Object System.Windows.Forms.Timer
|
|
139
|
-
$checkTimer.Interval = 500
|
|
140
|
-
$checkCount = 0
|
|
141
|
-
$checkTimer.Add_Tick({
|
|
142
|
-
$checkCount++
|
|
143
|
-
if (Test-ServerOnline) {
|
|
144
|
-
$checkTimer.Stop()
|
|
145
|
-
$notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已就绪 (端口 50816)", "Info")
|
|
146
|
-
} elseif ($checkCount -ge 10) {
|
|
147
|
-
$checkTimer.Stop()
|
|
148
|
-
$notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器启动可能较慢,尝试访问中...", "Warning")
|
|
105
|
+
# 前台等待服务器就绪(超时 10 秒)
|
|
106
|
+
$ready = $false
|
|
107
|
+
for ($i = 0; $i -lt 20; $i++) {
|
|
108
|
+
Start-Sleep -Milliseconds 500
|
|
109
|
+
if (Test-Online) { $ready = $true; break }
|
|
149
110
|
}
|
|
150
|
-
|
|
151
|
-
$
|
|
152
|
-
|
|
153
|
-
# 6. 左键点击: 弹气泡提示状态
|
|
154
|
-
$notifyIcon.Add_MouseClick({
|
|
155
|
-
param($sender, $e)
|
|
156
|
-
if ($e.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
|
|
157
|
-
$online = Test-ServerOnline
|
|
158
|
-
if ($online) {
|
|
159
|
-
$notifyIcon.ShowBalloonTip(3000, "Pan Router", "运行正常 ✓ (端口 50816)", "Info")
|
|
160
|
-
} else {
|
|
161
|
-
$notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器未响应 ⚠", "Error")
|
|
162
|
-
}
|
|
111
|
+
Write-Log "Server ready=$ready"
|
|
112
|
+
if ($ready) {
|
|
113
|
+
$notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已就绪 (端口 50816)", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
163
114
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
$
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
$
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
$
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
$
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
$
|
|
115
|
+
|
|
116
|
+
# ─── 后台定时健康检查 ──────────────────────
|
|
117
|
+
$healthTimer = New-Object System.Windows.Forms.Timer
|
|
118
|
+
$healthTimer.Interval = 30000
|
|
119
|
+
$healthTimer.Add_Tick({
|
|
120
|
+
if (-not (Test-Online)) {
|
|
121
|
+
Write-Log "Health check FAILED, restarting..."
|
|
122
|
+
Stop-OldServer
|
|
123
|
+
$script:serverProcess = Start-ServerNohup -ServerPath $ServerPath
|
|
124
|
+
Start-Sleep -Seconds 3
|
|
125
|
+
if (Test-Online) {
|
|
126
|
+
$notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已自动重启", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
$healthTimer.Start()
|
|
131
|
+
Write-Log "Health timer started"
|
|
132
|
+
|
|
133
|
+
# ─── 左键: 状态气泡 ────────────────────────
|
|
134
|
+
$notifyIcon.Add_MouseClick({
|
|
135
|
+
if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
|
|
136
|
+
$online = Test-Online
|
|
137
|
+
if ($online) {
|
|
138
|
+
$notifyIcon.ShowBalloonTip(2000, "Pan Router", "运行正常 ✓ (端口 50816)", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
139
|
+
} else {
|
|
140
|
+
$notifyIcon.ShowBalloonTip(2000, "Pan Router", "服务器未响应 ⚠", [System.Windows.Forms.ToolTipIcon]::Error)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
# ─── 右键菜单 ──────────────────────────────
|
|
146
|
+
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
147
|
+
|
|
148
|
+
$titleItem = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
149
|
+
$titleItem.Text = "Pan Router - :50816"
|
|
150
|
+
$titleItem.Enabled = $false
|
|
151
|
+
$titleItem.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
|
|
152
|
+
$menu.Items.Add($titleItem)
|
|
153
|
+
$menu.Items.Add("-")
|
|
154
|
+
|
|
155
|
+
$autoStartItem = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
156
|
+
$autoStartItem.Text = "开机自启动"
|
|
184
157
|
$rk = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($runKeyPath, $true)
|
|
185
|
-
|
|
186
|
-
$rk.DeleteValue($autostartName, $false)
|
|
187
|
-
$autoStartItem.Checked = $false
|
|
188
|
-
$notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已关闭", "Info")
|
|
189
|
-
} else {
|
|
190
|
-
$cmd = "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -STA -File `"$scriptPath`" -ServerPath `"$ServerPath`""
|
|
191
|
-
$rk.SetValue($autostartName, $cmd)
|
|
192
|
-
$autoStartItem.Checked = $true
|
|
193
|
-
$notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已开启 ✓", "Info")
|
|
194
|
-
}
|
|
158
|
+
$autoStartItem.Checked = ($rk.GetValue($autostartName) -ne $null)
|
|
195
159
|
$rk.Close()
|
|
196
|
-
|
|
197
|
-
$
|
|
198
|
-
|
|
199
|
-
$
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
$
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
$notifyIcon.ContextMenuStrip = $menu
|
|
211
|
-
|
|
212
|
-
# 8. 退出清理
|
|
213
|
-
$cleanup = {
|
|
214
|
-
try {
|
|
215
|
-
if ($checkTimer) { $checkTimer.Stop(); $checkTimer.Dispose() }
|
|
216
|
-
if ($serverProcess -and !$serverProcess.HasExited) {
|
|
217
|
-
$serverProcess.Kill()
|
|
218
|
-
$serverProcess.WaitForExit(3000)
|
|
219
|
-
$serverProcess.Dispose()
|
|
160
|
+
$autoStartItem.Add_Click({
|
|
161
|
+
$rk2 = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($runKeyPath, $true)
|
|
162
|
+
if ($autoStartItem.Checked) {
|
|
163
|
+
$rk2.DeleteValue($autostartName, $false)
|
|
164
|
+
$autoStartItem.Checked = $false
|
|
165
|
+
$notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已关闭", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
166
|
+
Write-Log "Autostart OFF"
|
|
167
|
+
} else {
|
|
168
|
+
$cmd = "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -STA -File `"$scriptPath`" -ServerPath `"$ServerPath`""
|
|
169
|
+
$rk2.SetValue($autostartName, $cmd)
|
|
170
|
+
$autoStartItem.Checked = $true
|
|
171
|
+
$notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已开启 ✓", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
172
|
+
Write-Log "Autostart ON"
|
|
220
173
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
174
|
+
$rk2.Close()
|
|
175
|
+
})
|
|
176
|
+
$menu.Items.Add($autoStartItem)
|
|
177
|
+
$menu.Items.Add("-")
|
|
178
|
+
|
|
179
|
+
$exitItem = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
180
|
+
$exitItem.Text = "退出"
|
|
181
|
+
$exitItem.Add_Click({
|
|
182
|
+
Write-Log "Exit clicked"
|
|
183
|
+
$notifyIcon.Visible = $false
|
|
184
|
+
[System.Windows.Forms.Application]::Exit()
|
|
185
|
+
})
|
|
186
|
+
$menu.Items.Add($exitItem)
|
|
187
|
+
|
|
188
|
+
$notifyIcon.ContextMenuStrip = $menu
|
|
189
|
+
|
|
190
|
+
# ─── 退出清理 ──────────────────────────────
|
|
191
|
+
$cleanup = {
|
|
192
|
+
try {
|
|
193
|
+
Write-Log "Cleanup started"
|
|
194
|
+
$healthTimer.Stop()
|
|
195
|
+
$healthTimer.Dispose()
|
|
196
|
+
if (-not $serverProcess.HasExited) {
|
|
197
|
+
$serverProcess.Kill()
|
|
198
|
+
$serverProcess.WaitForExit(3000)
|
|
199
|
+
$serverProcess.Dispose()
|
|
200
|
+
Write-Log "Server killed"
|
|
201
|
+
}
|
|
202
|
+
Stop-OldServer
|
|
203
|
+
Write-Log "Cleanup done"
|
|
204
|
+
} catch { Write-Log "Cleanup error: $_" }
|
|
205
|
+
}
|
|
225
206
|
|
|
226
|
-
[System.Windows.Forms.Application]::ApplicationExit += {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
207
|
+
[System.Windows.Forms.Application]::ApplicationExit += {
|
|
208
|
+
& $cleanup
|
|
209
|
+
$notifyIcon.Dispose()
|
|
210
|
+
[System.Runtime.InteropServices.Marshal]::DestroyIcon($hIcon)
|
|
211
|
+
$icon.Dispose()
|
|
212
|
+
}
|
|
232
213
|
|
|
233
|
-
|
|
234
|
-
[System.Windows.Forms.Application]::Run()
|
|
214
|
+
Write-Log "Entering message loop"
|
|
215
|
+
[System.Windows.Forms.Application]::Run()
|
|
216
|
+
Write-Log "Message loop exited"
|
|
217
|
+
& $cleanup
|
|
235
218
|
|
|
236
|
-
|
|
237
|
-
|
|
219
|
+
} catch {
|
|
220
|
+
$errMsg = "UNHANDLED ERROR: $_"
|
|
221
|
+
Write-Log $errMsg
|
|
222
|
+
try { [System.Windows.Forms.MessageBox]::Show($errMsg, "Pan Router Error") } catch {}
|
|
223
|
+
}
|