From c7669c39c3ecb9dd08820ae5f67d038a8a973a6b Mon Sep 17 00:00:00 2001 From: "CanbiZ (MickLesk)" <47820557+MickLesk@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:29:26 +0100 Subject: [PATCH] Frontend: use github-versions.json for version display (#11281) * fix(frontend): use github-versions.json for version display - Update AppVersion type to match new format with slug field - Switch from versions.json to github-versions.json API - Simplify version matching by direct slug comparison - Remove 'Loading versions...' text - show nothing if no version found * feat(frontend): show tooltip for pinned versions * fix(api): add github-versions endpoint and fix legacy versions route --- frontend/src/app/api/github-versions/route.ts | 36 +++++++++++++++++++ frontend/src/app/api/versions/route.ts | 10 ++++-- .../app/scripts/_components/script-item.tsx | 27 +++++++++----- frontend/src/hooks/use-versions.ts | 12 ++----- frontend/src/lib/data.ts | 2 +- frontend/src/lib/types.ts | 11 ++++-- 6 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 frontend/src/app/api/github-versions/route.ts diff --git a/frontend/src/app/api/github-versions/route.ts b/frontend/src/app/api/github-versions/route.ts new file mode 100644 index 000000000..b24327607 --- /dev/null +++ b/frontend/src/app/api/github-versions/route.ts @@ -0,0 +1,36 @@ +import { NextResponse } from "next/server"; +import { promises as fs } from "node:fs"; +import path from "node:path"; + +import type { GitHubVersionsResponse } from "@/lib/types"; + +export const dynamic = "force-static"; + +const jsonDir = "public/json"; +const versionsFileName = "github-versions.json"; +const encoding = "utf-8"; + +async function getVersions(): Promise { + const filePath = path.resolve(jsonDir, versionsFileName); + const fileContent = await fs.readFile(filePath, encoding); + const data: GitHubVersionsResponse = JSON.parse(fileContent); + return data; +} + +export async function GET() { + try { + const versions = await getVersions(); + return NextResponse.json(versions); + } + catch (error) { + console.error(error); + const err = error as globalThis.Error; + return NextResponse.json({ + generated: "", + versions: [], + error: err.message || "An unexpected error occurred", + }, { + status: 500, + }); + } +} diff --git a/frontend/src/app/api/versions/route.ts b/frontend/src/app/api/versions/route.ts index 1d2807a55..ca9e19758 100644 --- a/frontend/src/app/api/versions/route.ts +++ b/frontend/src/app/api/versions/route.ts @@ -3,18 +3,22 @@ import { NextResponse } from "next/server"; import { promises as fs } from "node:fs"; import path from "node:path"; -import type { AppVersion } from "@/lib/types"; - export const dynamic = "force-static"; const jsonDir = "public/json"; const versionsFileName = "versions.json"; const encoding = "utf-8"; +interface LegacyVersion { + name: string; + version: string; + date: string; +} + async function getVersions() { const filePath = path.resolve(jsonDir, versionsFileName); const fileContent = await fs.readFile(filePath, encoding); - const versions: AppVersion[] = JSON.parse(fileContent); + const versions: LegacyVersion[] = JSON.parse(fileContent); const modifiedVersions = versions.map((version) => { let newName = version.name; diff --git a/frontend/src/app/scripts/_components/script-item.tsx b/frontend/src/app/scripts/_components/script-item.tsx index 90ac3190e..affb3af6d 100644 --- a/frontend/src/app/scripts/_components/script-item.tsx +++ b/frontend/src/app/scripts/_components/script-item.tsx @@ -1,13 +1,13 @@ "use client"; -import { X } from "lucide-react"; +import { X, HelpCircle } from "lucide-react"; import { Suspense } from "react"; import Image from "next/image"; import type { AppVersion, Script } from "@/lib/types"; -import { cleanSlug } from "@/lib/utils/resource-utils"; import { Separator } from "@/components/ui/separator"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useVersions } from "@/hooks/use-versions"; import { basePath } from "@/config/site-config"; import { extractDate } from "@/lib/time"; @@ -108,18 +108,29 @@ function VersionInfo({ item }: { item: Script }) { const { data: versions = [], isLoading } = useVersions(); if (isLoading || versions.length === 0) { - return

Loading versions...

; + return null; } - const matchedVersion = versions.find((v: AppVersion) => { - const cleanName = v.name.replace(/[^a-z0-9]/gi, "").toLowerCase(); - return cleanName === cleanSlug(item.slug) || cleanName.includes(cleanSlug(item.slug)); - }); + const matchedVersion = versions.find((v: AppVersion) => v.slug === item.slug); if (!matchedVersion) return null; - return {matchedVersion.version}; + return ( + + {matchedVersion.version} + {matchedVersion.pinned && ( + + + + + +

This version is pinned. We test each update for breaking changes before releasing new versions.

+
+
+ )} +
+ ); } export function ScriptItem({ item, setSelectedScript }: ScriptItemProps) { diff --git a/frontend/src/hooks/use-versions.ts b/frontend/src/hooks/use-versions.ts index 566dc5834..31de4d20d 100644 --- a/frontend/src/hooks/use-versions.ts +++ b/frontend/src/hooks/use-versions.ts @@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query"; -import type { AppVersion } from "@/lib/types"; +import type { AppVersion, GitHubVersionsResponse } from "@/lib/types"; import { fetchVersions } from "@/lib/data"; @@ -10,14 +10,8 @@ export function useVersions() { return useQuery({ queryKey: ["versions"], queryFn: async () => { - const fetchedVersions = await fetchVersions(); - if (Array.isArray(fetchedVersions)) { - return fetchedVersions; - } - if (fetchedVersions && typeof fetchedVersions === "object") { - return [fetchedVersions]; - } - return []; + const response: GitHubVersionsResponse = await fetchVersions(); + return response.versions ?? []; }, }); } diff --git a/frontend/src/lib/data.ts b/frontend/src/lib/data.ts index 9119f5dfc..bd437fe02 100644 --- a/frontend/src/lib/data.ts +++ b/frontend/src/lib/data.ts @@ -10,7 +10,7 @@ export async function fetchCategories() { } export async function fetchVersions() { - const response = await fetch(`/ProxmoxVE/api/versions`); + const response = await fetch(`/ProxmoxVE/api/github-versions`); if (!response.ok) { throw new Error(`Failed to fetch versions: ${response.statusText}`); } diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index e0c32adac..c20f1f5e4 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -63,7 +63,14 @@ export type OperatingSystem = { }; export type AppVersion = { - name: string; + slug: string; + repo: string; version: string; - date: Date; + pinned: boolean; + date: string; +}; + +export type GitHubVersionsResponse = { + generated: string; + versions: AppVersion[]; };