mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 05:30:46 +08:00
Release 0.4.7: ProgramData ACL fix and tray menu polish.
Some checks failed
Release Windows / build (push) Has been cancelled
Some checks failed
Release Windows / build (push) Has been cancelled
Allow non-admin reconfigure of .env, harden setup/tray flows, and refresh the context menu with hover selection. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
5429c07342
commit
4e582dc4a5
6 changed files with 583 additions and 131 deletions
|
|
@ -6,8 +6,7 @@
|
|||
[switch]$AutoUpdate = $false
|
||||
)
|
||||
|
||||
# Simple system-tray controller for NetX (Windows packaged installs).
|
||||
# Start / Stop / Open UI / Check update / Exit
|
||||
# System-tray controller for NetX (Windows packaged installs).
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
. "$PSScriptRoot\_common.ps1"
|
||||
|
|
@ -15,6 +14,74 @@ $ErrorActionPreference = "Stop"
|
|||
Add-Type -AssemblyName System.Windows.Forms
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
# Custom tray menu: hover/selected highlight + flat light chrome (default OS menu looks dead).
|
||||
if (-not ("NetxMenuRenderer" -as [type])) {
|
||||
Add-Type -ReferencedAssemblies System.Windows.Forms, System.Drawing -TypeDefinition @"
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Windows.Forms;
|
||||
|
||||
public sealed class NetxMenuColorTable : ProfessionalColorTable {
|
||||
static readonly Color Hover = Color.FromArgb(232, 240, 252);
|
||||
static readonly Color Press = Color.FromArgb(214, 228, 248);
|
||||
static readonly Color Border = Color.FromArgb(170, 198, 240);
|
||||
static readonly Color Edge = Color.FromArgb(210, 214, 220);
|
||||
static readonly Color Sep = Color.FromArgb(228, 230, 235);
|
||||
static readonly Color Bg = Color.FromArgb(252, 252, 253);
|
||||
|
||||
public override Color MenuItemSelected { get { return Hover; } }
|
||||
public override Color MenuItemSelectedGradientBegin { get { return Hover; } }
|
||||
public override Color MenuItemSelectedGradientEnd { get { return Hover; } }
|
||||
public override Color MenuItemBorder { get { return Border; } }
|
||||
public override Color MenuItemPressedGradientBegin { get { return Press; } }
|
||||
public override Color MenuItemPressedGradientMiddle { get { return Press; } }
|
||||
public override Color MenuItemPressedGradientEnd { get { return Press; } }
|
||||
public override Color MenuBorder { get { return Edge; } }
|
||||
public override Color ToolStripDropDownBackground { get { return Bg; } }
|
||||
public override Color ImageMarginGradientBegin { get { return Bg; } }
|
||||
public override Color ImageMarginGradientMiddle { get { return Bg; } }
|
||||
public override Color ImageMarginGradientEnd { get { return Bg; } }
|
||||
public override Color SeparatorDark { get { return Sep; } }
|
||||
public override Color SeparatorLight { get { return Sep; } }
|
||||
}
|
||||
|
||||
public sealed class NetxMenuRenderer : ToolStripProfessionalRenderer {
|
||||
public NetxMenuRenderer() : base(new NetxMenuColorTable()) {
|
||||
RoundedEdges = false;
|
||||
}
|
||||
|
||||
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) {
|
||||
ToolStripMenuItem item = e.Item as ToolStripMenuItem;
|
||||
if (item == null || !item.Selected || !item.Enabled) {
|
||||
base.OnRenderMenuItemBackground(e);
|
||||
return;
|
||||
}
|
||||
Rectangle r = new Rectangle(2, 0, e.Item.Width - 4, e.Item.Height);
|
||||
using (SolidBrush brush = new SolidBrush(Color.FromArgb(232, 240, 252)))
|
||||
using (Pen pen = new Pen(Color.FromArgb(170, 198, 240))) {
|
||||
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
e.Graphics.FillRectangle(brush, r);
|
||||
e.Graphics.DrawRectangle(pen, r.X, r.Y, r.Width - 1, r.Height - 1);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) {
|
||||
Rectangle r = new Rectangle(0, 0, e.AffectedBounds.Width - 1, e.AffectedBounds.Height - 1);
|
||||
using (Pen pen = new Pen(Color.FromArgb(210, 214, 220))) {
|
||||
e.Graphics.DrawRectangle(pen, r);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) {
|
||||
int y = e.Item.Height / 2;
|
||||
using (Pen pen = new Pen(Color.FromArgb(228, 230, 235))) {
|
||||
e.Graphics.DrawLine(pen, 10, y, e.Item.Width - 10, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
$prog = Get-NetxProgramRoot -Override $ProgramRoot
|
||||
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot
|
||||
$hostBind = "127.0.0.1"
|
||||
|
|
@ -35,6 +102,12 @@ if ($autoVal -match '^(1|true|yes|on)$') {
|
|||
}
|
||||
$uiUrl = "http://${hostBind}:${port}/"
|
||||
$ver = Get-NetxVersion -ProgramRoot $prog
|
||||
$dbMode = Get-DbMode -EnvMap $map
|
||||
$dbModeLabel = if ($dbMode -eq "bundled") {
|
||||
if (([cultureinfo]::CurrentUICulture.Name -match '^(zh|zh-)')) { "内置库" } else { "built-in DB" }
|
||||
} else {
|
||||
if (([cultureinfo]::CurrentUICulture.Name -match '^(zh|zh-)')) { "外置库" } else { "external DB" }
|
||||
}
|
||||
$zh = ([cultureinfo]::CurrentUICulture.Name -match '^(zh|zh-)')
|
||||
function T([string]$En, [string]$Zh) {
|
||||
if ($zh) { return $Zh } else { return $En }
|
||||
|
|
@ -74,6 +147,7 @@ try {
|
|||
$startPs1 = Join-Path $PSScriptRoot "start_netx_app.ps1"
|
||||
$stopPs1 = Join-Path $PSScriptRoot "stop_netx_app.ps1"
|
||||
$checkPs1 = Join-Path $PSScriptRoot "check_update.ps1"
|
||||
$setupPs1 = Join-Path $PSScriptRoot "setup_first_run.ps1"
|
||||
|
||||
function Invoke-NetxScript {
|
||||
param(
|
||||
|
|
@ -85,6 +159,52 @@ function Invoke-NetxScript {
|
|||
-WorkingDirectory $prog -WindowStyle Hidden -PassThru -Wait:$Wait
|
||||
}
|
||||
|
||||
function Update-NetxTrayTip {
|
||||
$running = Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 1
|
||||
$state = if ($running) {
|
||||
(T "Running" "运行中")
|
||||
} else {
|
||||
(T "Stopped" "已停止")
|
||||
}
|
||||
$tip = "NetX $ver · $state · $dbModeLabel`n$uiUrl"
|
||||
if ($tip.Length -gt 63) {
|
||||
# NotifyIcon.Text max ~63 chars on older Windows.
|
||||
$tip = "NetX $ver · $state · $dbModeLabel"
|
||||
}
|
||||
$notify.Text = $tip
|
||||
}
|
||||
|
||||
function Close-NetxTrayMenu {
|
||||
# ContextMenuStrip stays modal if Click handlers block; close before dialogs/waits.
|
||||
try { $menu.Close() } catch {}
|
||||
try { [System.Windows.Forms.Application]::DoEvents() } catch {}
|
||||
}
|
||||
|
||||
function Start-NetxTrayDeferred {
|
||||
param([scriptblock]$Work)
|
||||
Close-NetxTrayMenu
|
||||
$timer = New-Object System.Windows.Forms.Timer
|
||||
$timer.Interval = 50
|
||||
$script:netxTrayDeferredWork = $Work
|
||||
$timer.add_Tick({
|
||||
$t = $script:netxTrayDeferredTimer
|
||||
try { if ($t) { $t.Stop(); $t.Dispose() } } catch {}
|
||||
$script:netxTrayDeferredTimer = $null
|
||||
$w = $script:netxTrayDeferredWork
|
||||
$script:netxTrayDeferredWork = $null
|
||||
if ($w) { & $w }
|
||||
})
|
||||
$script:netxTrayDeferredTimer = $timer
|
||||
$timer.Start()
|
||||
}
|
||||
|
||||
function Test-NetxSetupCancelled {
|
||||
param([AllowNull()][Nullable[int]]$ExitCode)
|
||||
if ($null -eq $ExitCode) { return $false }
|
||||
# STATUS_CONTROL_C_EXIT (0xC000013A): console closed / Ctrl+C — not a setup failure.
|
||||
return ([int]$ExitCode -eq -1073741510)
|
||||
}
|
||||
|
||||
function Open-NetxUiWhenReady {
|
||||
param([int]$TimeoutSec = 180)
|
||||
$notify.ShowBalloonTip(
|
||||
|
|
@ -95,14 +215,16 @@ function Open-NetxUiWhenReady {
|
|||
)
|
||||
if (Wait-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec $TimeoutSec) {
|
||||
try { Start-Process $uiUrl } catch {}
|
||||
Update-NetxTrayTip
|
||||
return
|
||||
}
|
||||
$notify.ShowBalloonTip(
|
||||
8000,
|
||||
"NetX",
|
||||
(T "UI not ready yet. Use tray → Open UI when the icon appears, or check data\runtime\netx.err.log." "界面尚未就绪。就绪后请用托盘「打开界面」,或查看 data\runtime\netx.err.log。"),
|
||||
(T "UI not ready yet. Use tray → Open UI when ready, or check data\runtime\netx.err.log." "界面尚未就绪。就绪后请用托盘「打开界面」,或查看 data\runtime\netx.err.log。"),
|
||||
[System.Windows.Forms.ToolTipIcon]::Warning
|
||||
)
|
||||
Update-NetxTrayTip
|
||||
}
|
||||
|
||||
$form = New-Object System.Windows.Forms.Form
|
||||
|
|
@ -113,7 +235,6 @@ $form.Visible = $false
|
|||
$form.Size = New-Object System.Drawing.Size(0, 0)
|
||||
|
||||
$notify = New-Object System.Windows.Forms.NotifyIcon
|
||||
$notify.Text = "NetX $ver"
|
||||
$notify.Visible = $true
|
||||
$iconPath = Join-Path $PSScriptRoot "assets\netx.ico"
|
||||
try {
|
||||
|
|
@ -125,68 +246,286 @@ try {
|
|||
} catch {
|
||||
try { $notify.Icon = [System.Drawing.SystemIcons]::Application } catch {}
|
||||
}
|
||||
Update-NetxTrayTip
|
||||
|
||||
function New-NetxMenuItem {
|
||||
param(
|
||||
[string]$Text,
|
||||
[switch]$Header,
|
||||
[switch]$Primary,
|
||||
[switch]$Muted
|
||||
)
|
||||
$item = New-Object System.Windows.Forms.ToolStripMenuItem
|
||||
$item.Text = $Text
|
||||
$item.AutoSize = $true
|
||||
$item.Padding = New-Object System.Windows.Forms.Padding(10, 5, 28, 5)
|
||||
$item.Margin = New-Object System.Windows.Forms.Padding(0)
|
||||
if ($Header) {
|
||||
$item.Enabled = $false
|
||||
$item.Font = New-Object System.Drawing.Font("Segoe UI", 9.0, [System.Drawing.FontStyle]::Bold)
|
||||
$item.ForeColor = [System.Drawing.Color]::FromArgb(55, 65, 80)
|
||||
$item.Padding = New-Object System.Windows.Forms.Padding(10, 6, 28, 2)
|
||||
} elseif ($Muted) {
|
||||
$item.Enabled = $false
|
||||
$item.Font = New-Object System.Drawing.Font("Segoe UI", 8.25)
|
||||
$item.ForeColor = [System.Drawing.Color]::FromArgb(120, 128, 140)
|
||||
$item.Padding = New-Object System.Windows.Forms.Padding(10, 1, 28, 6)
|
||||
} elseif ($Primary) {
|
||||
$item.Font = New-Object System.Drawing.Font("Segoe UI", 9.0, [System.Drawing.FontStyle]::Bold)
|
||||
$item.ForeColor = [System.Drawing.Color]::FromArgb(20, 40, 70)
|
||||
} else {
|
||||
$item.Font = New-Object System.Drawing.Font("Segoe UI", 9.0)
|
||||
$item.ForeColor = [System.Drawing.Color]::FromArgb(35, 40, 48)
|
||||
}
|
||||
return $item
|
||||
}
|
||||
|
||||
function New-NetxMenuSeparator {
|
||||
$sep = New-Object System.Windows.Forms.ToolStripSeparator
|
||||
$sep.Margin = New-Object System.Windows.Forms.Padding(0, 3, 0, 3)
|
||||
$sep.Padding = New-Object System.Windows.Forms.Padding(0)
|
||||
return $sep
|
||||
}
|
||||
|
||||
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
||||
$menu.ShowImageMargin = $false
|
||||
$menu.ShowCheckMargin = $false
|
||||
$menu.Font = New-Object System.Drawing.Font("Segoe UI", 9.0)
|
||||
$menu.BackColor = [System.Drawing.Color]::FromArgb(252, 252, 253)
|
||||
$menu.ForeColor = [System.Drawing.Color]::FromArgb(35, 40, 48)
|
||||
$menu.Padding = New-Object System.Windows.Forms.Padding(4, 4, 4, 4)
|
||||
$menu.Renderer = New-Object NetxMenuRenderer
|
||||
|
||||
$miOpen = $menu.Items.Add((T "Open UI ($uiUrl)" "打开界面 ($uiUrl)"))
|
||||
$miOpen.add_Click({ Start-Process $uiUrl })
|
||||
# --- header (info only) ---
|
||||
$miHeader = New-NetxMenuItem -Text "NetX $ver" -Header
|
||||
[void]$menu.Items.Add($miHeader)
|
||||
|
||||
$miStart = $menu.Items.Add((T "Start NetX" "启动 NetX"))
|
||||
$miSub = New-NetxMenuItem -Text "$dbModeLabel · ${hostBind}:$port" -Muted
|
||||
[void]$menu.Items.Add($miSub)
|
||||
|
||||
[void]$menu.Items.Add((New-NetxMenuSeparator))
|
||||
|
||||
# --- primary ---
|
||||
$miOpen = New-NetxMenuItem -Text (T "Open UI" "打开界面") -Primary
|
||||
$miOpen.add_Click({
|
||||
Start-NetxTrayDeferred {
|
||||
if (Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 2) {
|
||||
Start-Process $uiUrl
|
||||
} else {
|
||||
$notify.ShowBalloonTip(4000, "NetX", (T "NetX is not running. Starting…" "NetX 未运行,正在启动…"), [System.Windows.Forms.ToolTipIcon]::Info)
|
||||
Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null
|
||||
Open-NetxUiWhenReady
|
||||
}
|
||||
}
|
||||
})
|
||||
[void]$menu.Items.Add($miOpen)
|
||||
|
||||
[void]$menu.Items.Add((New-NetxMenuSeparator))
|
||||
|
||||
# --- service control ---
|
||||
$miStart = New-NetxMenuItem -Text (T "Start" "启动")
|
||||
$miStart.add_Click({
|
||||
Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null
|
||||
Open-NetxUiWhenReady
|
||||
Start-NetxTrayDeferred {
|
||||
Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null
|
||||
Open-NetxUiWhenReady
|
||||
}
|
||||
})
|
||||
[void]$menu.Items.Add($miStart)
|
||||
|
||||
$miStop = $menu.Items.Add((T "Stop NetX (keep tray)" "停止 NetX(保留托盘)"))
|
||||
$miStop = New-NetxMenuItem -Text (T "Stop" "停止")
|
||||
$miStop.add_Click({
|
||||
$notify.ShowBalloonTip(3000, "NetX", (T "Stopping…" "正在停止…"), [System.Windows.Forms.ToolTipIcon]::Info)
|
||||
Invoke-NetxScript -File $stopPs1 | Out-Null
|
||||
Start-NetxTrayDeferred {
|
||||
$notify.ShowBalloonTip(3000, "NetX", (T "Stopping…" "正在停止…"), [System.Windows.Forms.ToolTipIcon]::Info)
|
||||
Invoke-NetxScript -File $stopPs1 | Out-Null
|
||||
Update-NetxTrayTip
|
||||
}
|
||||
})
|
||||
[void]$menu.Items.Add($miStop)
|
||||
|
||||
$miStopExit = $menu.Items.Add((T "Stop NetX and exit tray" "停止 NetX 并退出托盘"))
|
||||
$miStopExit.add_Click({
|
||||
$notify.ShowBalloonTip(3000, "NetX", (T "Stopping…" "正在停止…"), [System.Windows.Forms.ToolTipIcon]::Info)
|
||||
Invoke-NetxScript -File $stopPs1 -Wait | Out-Null
|
||||
[void]$menu.Items.Add((New-NetxMenuSeparator))
|
||||
|
||||
# --- setup / update ---
|
||||
$miReconfig = New-NetxMenuItem -Text (T "Reconfigure database…" "重新配置数据库…")
|
||||
$miReconfig.add_Click({
|
||||
Start-NetxTrayDeferred {
|
||||
$ans = [System.Windows.Forms.MessageBox]::Show(
|
||||
(T `
|
||||
"NetX will stop while you reconfigure the database.`n`nChoose built-in or external PostgreSQL in the console window.`nContinue?" `
|
||||
"重新配置数据库时会先停止 NetX。`n`n请在随后的控制台窗口中选择内置或外置 PostgreSQL。`n是否继续?"),
|
||||
(T "Reconfigure database" "重新配置数据库"),
|
||||
[System.Windows.Forms.MessageBoxButtons]::YesNo,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Question
|
||||
)
|
||||
if ($ans -ne [System.Windows.Forms.DialogResult]::Yes) { return }
|
||||
|
||||
$notify.ShowBalloonTip(3000, "NetX", (T "Stopping…" "正在停止…"), [System.Windows.Forms.ToolTipIcon]::Info)
|
||||
Invoke-NetxScript -File $stopPs1 -Wait | Out-Null
|
||||
|
||||
# ProgramData\.env is often admin-owned after Setup; repair ACL before reconfigure.
|
||||
$null = Ensure-NetxDataAcl -DataRoot $data -Quiet
|
||||
|
||||
try {
|
||||
$p = Start-NetxPowerShell -File $setupPs1 `
|
||||
-Arguments @("-ProgramRoot", $prog, "-DataRoot", $data) `
|
||||
-WorkingDirectory $prog -WindowStyle Normal -Wait -PassThru
|
||||
if (Test-NetxSetupCancelled -ExitCode $p.ExitCode) {
|
||||
$notify.ShowBalloonTip(
|
||||
4000, "NetX",
|
||||
(T "Database setup cancelled." "已取消数据库配置。"),
|
||||
[System.Windows.Forms.ToolTipIcon]::Info
|
||||
)
|
||||
Update-NetxTrayTip
|
||||
return
|
||||
}
|
||||
if ($null -ne $p.ExitCode -and $p.ExitCode -ne 0) {
|
||||
# Access denied → offer elevated re-run (UAC).
|
||||
$elev = [System.Windows.Forms.MessageBox]::Show(
|
||||
(T `
|
||||
"Database setup failed (exit $($p.ExitCode)).`n`nIf this was a permission error on %ProgramData%\NetX\.env, click Yes to retry as Administrator." `
|
||||
"数据库配置失败(退出码 $($p.ExitCode))。`n`n若是 %ProgramData%\NetX\.env 权限问题,点「是」将以管理员身份重试。"),
|
||||
"NetX",
|
||||
[System.Windows.Forms.MessageBoxButtons]::YesNo,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Warning
|
||||
)
|
||||
if ($elev -eq [System.Windows.Forms.DialogResult]::Yes) {
|
||||
$arg = "-NoProfile -ExecutionPolicy Bypass -File `"$setupPs1`" -ProgramRoot `"$prog`" -DataRoot `"$data`""
|
||||
$ep = Start-Process -FilePath "powershell.exe" -ArgumentList $arg `
|
||||
-WorkingDirectory $prog -Verb RunAs -Wait -PassThru
|
||||
if (Test-NetxSetupCancelled -ExitCode $ep.ExitCode) {
|
||||
Update-NetxTrayTip
|
||||
return
|
||||
}
|
||||
if ($null -ne $ep.ExitCode -and $ep.ExitCode -ne 0) {
|
||||
[System.Windows.Forms.MessageBox]::Show(
|
||||
(T "Elevated database setup still failed (exit $($ep.ExitCode))." "管理员配置仍失败(退出码 $($ep.ExitCode))。"),
|
||||
"NetX",
|
||||
[System.Windows.Forms.MessageBoxButtons]::OK,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Error
|
||||
)
|
||||
Update-NetxTrayTip
|
||||
return
|
||||
}
|
||||
} else {
|
||||
Update-NetxTrayTip
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
[System.Windows.Forms.MessageBox]::Show(
|
||||
(T "Database setup failed: $($_.Exception.Message)" "数据库配置失败:$($_.Exception.Message)"),
|
||||
"NetX",
|
||||
[System.Windows.Forms.MessageBoxButtons]::OK,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Error
|
||||
)
|
||||
Update-NetxTrayTip
|
||||
return
|
||||
}
|
||||
|
||||
# Reload .env after reconfigure (host/port/mode may change).
|
||||
if (Test-Path $envPath) {
|
||||
$map = Read-DotEnv -Path $envPath
|
||||
if ($map["NETX_HOST"]) { $hostBind = $map["NETX_HOST"] }
|
||||
if ($map["NETX_PORT"]) { try { $port = [int]$map["NETX_PORT"] } catch {} }
|
||||
$uiUrl = "http://${hostBind}:${port}/"
|
||||
$dbMode = Get-DbMode -EnvMap $map
|
||||
$dbModeLabel = if ($dbMode -eq "bundled") {
|
||||
(T "built-in DB" "内置库")
|
||||
} else {
|
||||
(T "external DB" "外置库")
|
||||
}
|
||||
$miSub.Text = "$dbModeLabel · ${hostBind}:$port"
|
||||
}
|
||||
|
||||
$restart = [System.Windows.Forms.MessageBox]::Show(
|
||||
(T "Database configuration saved.`nStart NetX now?" "数据库配置已保存。`n是否立即启动 NetX?"),
|
||||
"NetX",
|
||||
[System.Windows.Forms.MessageBoxButtons]::YesNo,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Information
|
||||
)
|
||||
if ($restart -eq [System.Windows.Forms.DialogResult]::Yes) {
|
||||
Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null
|
||||
Open-NetxUiWhenReady
|
||||
} else {
|
||||
Update-NetxTrayTip
|
||||
}
|
||||
}
|
||||
})
|
||||
[void]$menu.Items.Add($miReconfig)
|
||||
|
||||
$miUpdate = New-NetxMenuItem -Text (T "Check for updates…" "检查更新…")
|
||||
$miUpdate.add_Click({
|
||||
Start-NetxTrayDeferred {
|
||||
try {
|
||||
$p = Start-NetxPowerShell -File $checkPs1 -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data) `
|
||||
-WorkingDirectory $prog -WindowStyle Normal -Wait -PassThru
|
||||
if (Test-NetxSetupCancelled -ExitCode $p.ExitCode) { return }
|
||||
if ($p.ExitCode -eq 10) {
|
||||
$ans = [System.Windows.Forms.MessageBox]::Show(
|
||||
(T "A newer NetX is available. Download and apply now?" "发现新版本,是否立即下载并更新?"),
|
||||
(T "NetX update" "NetX 更新"),
|
||||
[System.Windows.Forms.MessageBoxButtons]::YesNo,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Question
|
||||
)
|
||||
if ($ans -eq [System.Windows.Forms.DialogResult]::Yes) {
|
||||
Start-NetxPowerShell -File $checkPs1 -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data, "-Apply") `
|
||||
-WorkingDirectory $prog -WindowStyle Normal | Out-Null
|
||||
}
|
||||
} elseif ($p.ExitCode -eq 0) {
|
||||
[System.Windows.Forms.MessageBox]::Show(
|
||||
(T "NetX is up to date ($ver)." "已是最新版本 ($ver)。"),
|
||||
(T "NetX update" "NetX 更新")
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
[System.Windows.Forms.MessageBox]::Show(
|
||||
(T "Update check failed: $($_.Exception.Message)" "检查更新失败:$($_.Exception.Message)"),
|
||||
"NetX"
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
[void]$menu.Items.Add($miUpdate)
|
||||
|
||||
[void]$menu.Items.Add((New-NetxMenuSeparator))
|
||||
|
||||
# --- exit ---
|
||||
$miExit = New-NetxMenuItem -Text (T "Exit tray" "退出托盘")
|
||||
$miExit.ToolTipText = (T "Leave NetX services running" "NetX 服务继续运行")
|
||||
$miExit.add_Click({
|
||||
Close-NetxTrayMenu
|
||||
$notify.Visible = $false
|
||||
$form.Close()
|
||||
[System.Windows.Forms.Application]::Exit()
|
||||
})
|
||||
[void]$menu.Items.Add($miExit)
|
||||
|
||||
$miUpdate = $menu.Items.Add((T "Check for updates" "检查更新"))
|
||||
$miUpdate.add_Click({
|
||||
try {
|
||||
$p = Start-NetxPowerShell -File $checkPs1 -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data) `
|
||||
-WorkingDirectory $prog -WindowStyle Normal -Wait -PassThru
|
||||
if ($p.ExitCode -eq 10) {
|
||||
$ans = [System.Windows.Forms.MessageBox]::Show(
|
||||
(T "A newer NetX is available. Download and apply now?" "发现新版本,是否立即下载并更新?"),
|
||||
(T "NetX update" "NetX 更新"),
|
||||
[System.Windows.Forms.MessageBoxButtons]::YesNo,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Question
|
||||
)
|
||||
if ($ans -eq [System.Windows.Forms.DialogResult]::Yes) {
|
||||
Start-NetxPowerShell -File $checkPs1 -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data, "-Apply") `
|
||||
-WorkingDirectory $prog -WindowStyle Normal | Out-Null
|
||||
}
|
||||
} elseif ($p.ExitCode -eq 0) {
|
||||
[System.Windows.Forms.MessageBox]::Show((T "NetX is up to date ($ver)." "已是最新版本 ($ver)。"), (T "NetX update" "NetX 更新"))
|
||||
}
|
||||
} catch {
|
||||
[System.Windows.Forms.MessageBox]::Show((T "Update check failed: $($_.Exception.Message)" "检查更新失败:$($_.Exception.Message)"), "NetX")
|
||||
$miStopExit = New-NetxMenuItem -Text (T "Stop and exit" "停止并退出")
|
||||
$miStopExit.add_Click({
|
||||
Start-NetxTrayDeferred {
|
||||
$notify.ShowBalloonTip(3000, "NetX", (T "Stopping…" "正在停止…"), [System.Windows.Forms.ToolTipIcon]::Info)
|
||||
Invoke-NetxScript -File $stopPs1 -Wait | Out-Null
|
||||
$notify.Visible = $false
|
||||
$form.Close()
|
||||
[System.Windows.Forms.Application]::Exit()
|
||||
}
|
||||
})
|
||||
[void]$menu.Items.Add($miStopExit)
|
||||
|
||||
$notify.ContextMenuStrip = $menu
|
||||
$notify.add_DoubleClick({
|
||||
if (Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 2) {
|
||||
Start-Process $uiUrl
|
||||
} else {
|
||||
Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null
|
||||
Open-NetxUiWhenReady
|
||||
}
|
||||
})
|
||||
|
||||
[void]$menu.Items.Add("-")
|
||||
|
||||
$miExit = $menu.Items.Add((T "Exit tray (services keep running)" "退出托盘(服务继续运行)"))
|
||||
$miExit.add_Click({
|
||||
$notify.Visible = $false
|
||||
$form.Close()
|
||||
[System.Windows.Forms.Application]::Exit()
|
||||
})
|
||||
|
||||
$notify.ContextMenuStrip = $menu
|
||||
$notify.add_DoubleClick({ Start-Process $uiUrl })
|
||||
# Refresh tooltip periodically (running / stopped).
|
||||
$script:tipTimer = New-Object System.Windows.Forms.Timer
|
||||
$script:tipTimer.Interval = 5000
|
||||
$script:tipTimer.add_Tick({ Update-NetxTrayTip })
|
||||
$script:tipTimer.Start()
|
||||
|
||||
$form.add_Shown({
|
||||
if ($AutoUpdate) {
|
||||
|
|
@ -200,7 +539,6 @@ $form.add_Shown({
|
|||
-WorkingDirectory $prog -WindowStyle Hidden | Out-Null
|
||||
}
|
||||
if ($StartOnLaunch) {
|
||||
# Detach start so the tray message pump stays alive; poll /health before browser.
|
||||
Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null
|
||||
$script:netxUiWaitTicks = 0
|
||||
$script:netxUiWaitMax = 360 # 360 * 500ms = 180s
|
||||
|
|
@ -212,6 +550,7 @@ $form.add_Shown({
|
|||
$script:netxUiTimer.Stop()
|
||||
$script:netxUiTimer.Dispose()
|
||||
try { Start-Process $uiUrl } catch {}
|
||||
Update-NetxTrayTip
|
||||
return
|
||||
}
|
||||
if ($script:netxUiWaitTicks -eq 1) {
|
||||
|
|
@ -231,6 +570,7 @@ $form.add_Shown({
|
|||
(T "UI not ready yet. Use tray → Open UI later, or check data\runtime\netx.err.log." "界面尚未就绪。请稍后用托盘「打开界面」,或查看 data\runtime\netx.err.log。"),
|
||||
[System.Windows.Forms.ToolTipIcon]::Warning
|
||||
)
|
||||
Update-NetxTrayTip
|
||||
}
|
||||
})
|
||||
$script:netxUiTimer.Start()
|
||||
|
|
@ -238,6 +578,7 @@ $form.add_Shown({
|
|||
})
|
||||
|
||||
$form.add_FormClosing({
|
||||
try { $script:tipTimer.Stop(); $script:tipTimer.Dispose() } catch {}
|
||||
$notify.Visible = $false
|
||||
$notify.Dispose()
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue