From 814369da3840c663288a50075e17e551f9552aed Mon Sep 17 00:00:00 2001 From: Kuzz007 Date: Sun, 26 Jul 2026 12:42:20 +0300 Subject: [PATCH] fix(panel): recognize this fork's -awg.N tags in version comparison internal/config/version was never bumped past 3.5.0 when v3.5.0-awg.1 was tagged, so a freshly-updated panel kept reporting its own version as the plain upstream base. On top of that, parseVersionParts (Go and its TypeScript mirror) required exactly 3 dot-separated numeric parts, so it rejected the -awg.N suffix entirely and fell back to a raw string inequality that reports "update available" any time the strings merely differ -- which they always do here, even when already on the latest tag. Bump the embedded version to 3.5.0-awg.1 and extend both parsers to treat "-awg.N" as an optional 4th, lower-priority component (defaulting to 0 for a plain tag), so e.g. 3.5.0-awg.2 > 3.5.0-awg.1 > 3.5.0 and a matching tag compares equal instead of always looking outdated. Co-Authored-By: Claude Sonnet 5 --- frontend/src/lib/panel-version.ts | 26 +++++++++++------------- frontend/src/test/panel-version.test.ts | 12 +++++++++++ internal/config/config.go | 7 +++++++ internal/config/version | 2 +- internal/web/service/panel/panel.go | 19 ++++++++++++----- internal/web/service/panel/panel_test.go | 25 +++++++++++++++++++++++ 6 files changed, 71 insertions(+), 20 deletions(-) diff --git a/frontend/src/lib/panel-version.ts b/frontend/src/lib/panel-version.ts index 598a1e978..76745bff2 100644 --- a/frontend/src/lib/panel-version.ts +++ b/frontend/src/lib/panel-version.ts @@ -1,17 +1,15 @@ // Mirror of web/service/panel.go isNewerVersion: parse a vMAJOR.MINOR.PATCH tag -// and report whether `latest` is ahead of `current`. When either side isn't a -// clean three-part numeric tag, fall back to a normalized string inequality — -// the same heuristic the Go side uses so the node "update available" badge -// agrees with what the server would decide. -function parseVersionParts(version: string): [number, number, number] | null { - const parts = version.trim().replace(/^v/, '').split('.'); - if (parts.length !== 3) return null; - const out: number[] = []; - for (const part of parts) { - if (!/^\d+$/.test(part)) return null; - out.push(Number(part)); - } - return [out[0], out[1], out[2]]; +// (or this fork's own "vMAJOR.MINOR.PATCH-awg.N" release tag, see +// internal/config/version) and report whether `latest` is ahead of `current`. +// When either side isn't a recognized tag, fall back to a normalized string +// inequality — the same heuristic the Go side uses so the node "update +// available" badge agrees with what the server would decide. +const VERSION_PARTS_PATTERN = /^v?(\d+)\.(\d+)\.(\d+)(?:-awg\.(\d+))?$/; + +function parseVersionParts(version: string): [number, number, number, number] | null { + const match = VERSION_PARTS_PATTERN.exec(version.trim()); + if (!match) return null; + return [Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4] || 0)]; } // Format a panel version for display. Dev builds report a "dev+" @@ -33,7 +31,7 @@ export function isPanelUpdateAvailable(latest: string, current: string): boolean if (!a || !b) { return latest.trim().replace(/^v/, '') !== current.trim().replace(/^v/, ''); } - for (let i = 0; i < 3; i++) { + for (let i = 0; i < 4; i++) { if (a[i] > b[i]) return true; if (a[i] < b[i]) return false; } diff --git a/frontend/src/test/panel-version.test.ts b/frontend/src/test/panel-version.test.ts index 5f628968f..52d489e84 100644 --- a/frontend/src/test/panel-version.test.ts +++ b/frontend/src/test/panel-version.test.ts @@ -30,6 +30,18 @@ describe('isPanelUpdateAvailable', () => { expect(isPanelUpdateAvailable('nightly-2', 'nightly-1')).toBe(true); expect(isPanelUpdateAvailable('nightly-1', 'nightly-1')).toBe(false); }); + + // Parity with web/service/panel.go TestIsNewerVersionAwgSuffix -- this + // fork's own "-awg.N" release tags (see internal/config/version). + it('handles this fork\'s -awg.N release tags', () => { + expect(isPanelUpdateAvailable('v3.5.0-awg.1', '3.5.0-awg.1')).toBe(false); + expect(isPanelUpdateAvailable('v3.5.0-awg.2', '3.5.0-awg.1')).toBe(true); + expect(isPanelUpdateAvailable('v3.5.0-awg.1', '3.5.0-awg.2')).toBe(false); + expect(isPanelUpdateAvailable('v3.5.0-awg.1', '3.5.0')).toBe(true); + expect(isPanelUpdateAvailable('v3.5.0', '3.5.0-awg.1')).toBe(false); + expect(isPanelUpdateAvailable('v3.6.0', '3.5.0-awg.1')).toBe(true); + expect(isPanelUpdateAvailable('v3.5.0-awg.1', '3.6.0')).toBe(false); + }); }); describe('formatPanelVersion', () => { diff --git a/internal/config/config.go b/internal/config/config.go index 0f6817b23..d8d0830d8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,6 +14,13 @@ import ( "testing" ) +// version must be bumped to match the exact tag BEFORE it's pushed -- unlike +// buildCommit/buildDate below, nothing stamps this automatically for a +// tagged release build, so a forgotten bump here makes the panel misreport +// its own version and permanently show a bogus "update available" (the +// already-installed tag never matches this stale string). See +// isNewerVersion/parseVersionParts in internal/web/service/panel/panel.go. +// //go:embed version var version string diff --git a/internal/config/version b/internal/config/version index e5b820341..f20ad3460 100644 --- a/internal/config/version +++ b/internal/config/version @@ -1 +1 @@ -3.5.0 \ No newline at end of file +3.5.0-awg.1 \ No newline at end of file diff --git a/internal/web/service/panel/panel.go b/internal/web/service/panel/panel.go index 746c03f93..5f1b9f26e 100644 --- a/internal/web/service/panel/panel.go +++ b/internal/web/service/panel/panel.go @@ -529,13 +529,22 @@ func compareVersionStrings(a string, b string) (int, bool) { return 0, true } -func parseVersionParts(version string) ([3]int, bool) { - var result [3]int - parts := strings.Split(normalizeVersionTag(version), ".") - if len(parts) != 3 { +// versionPartsPattern matches a plain "X.Y.Z" tag as well as this fork's own +// "X.Y.Z-awg.N" release tags (see internal/config/version). The awg build +// number is treated as a 4th, lower-priority component that defaults to 0 for +// a plain upstream-style tag, so e.g. "3.5.0-awg.2" > "3.5.0-awg.1" > "3.5.0". +var versionPartsPattern = regexp.MustCompile(`^v?(\d+)\.(\d+)\.(\d+)(?:-awg\.(\d+))?$`) + +func parseVersionParts(version string) ([4]int, bool) { + var result [4]int + matches := versionPartsPattern.FindStringSubmatch(strings.TrimSpace(version)) + if matches == nil { return result, false } - for i, part := range parts { + for i, part := range matches[1:] { + if part == "" { + continue // awg build number omitted -- defaults to 0 + } n, err := strconv.Atoi(part) if err != nil { return result, false diff --git a/internal/web/service/panel/panel_test.go b/internal/web/service/panel/panel_test.go index cd5e642bf..b0b770b88 100644 --- a/internal/web/service/panel/panel_test.go +++ b/internal/web/service/panel/panel_test.go @@ -42,6 +42,31 @@ func TestCompareVersionStringsRejectsUnexpectedFormats(t *testing.T) { } } +// TestIsNewerVersionAwgSuffix covers this fork's own "-awg.N" release tags +// (see internal/config/version), which must compare correctly against both +// plain upstream-style tags and other awg builds of the same base version. +func TestIsNewerVersionAwgSuffix(t *testing.T) { + cases := []struct { + latest string + current string + want bool + }{ + {"v3.5.0-awg.1", "3.5.0-awg.1", false}, + {"v3.5.0-awg.2", "3.5.0-awg.1", true}, + {"v3.5.0-awg.1", "3.5.0-awg.2", false}, + {"v3.5.0-awg.1", "3.5.0", true}, + {"v3.5.0", "3.5.0-awg.1", false}, + {"v3.6.0", "3.5.0-awg.1", true}, + {"v3.5.0-awg.1", "3.6.0", false}, + } + + for _, tc := range cases { + if got := isNewerVersion(tc.latest, tc.current); got != tc.want { + t.Fatalf("isNewerVersion(%q, %q) = %v, want %v", tc.latest, tc.current, got, tc.want) + } + } +} + func TestShellQuote(t *testing.T) { if got := shellQuote("/usr/bin/curl"); got != "'/usr/bin/curl'" { t.Fatalf("unexpected quote result: %s", got)