mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-06-03 13:54:41 +00:00
Bump Next.js to 15.1.9 + doc nav handles in-page anchors + help_info_menu
Three changes that fold into the v1.2.2 release PR: 1. AppImage: bump Next.js 15.1.6 -> 15.1.9 (CVE-2025-55182) GHSA-9qr9-h5gf-34mp / React2Shell is a pre-auth RCE in React Server Components when Server Functions deserialize attacker payloads. The ProxMenux Monitor ships Next.js in `output: "export"` mode behind Flask on :8008, so there is no runtime Next.js server and no "use server" directive in the source tree — the exploitable path is not reachable. Bumping to 15.1.9 anyway because OpenVAS and similar scanners flag the version string from the JS bundle regardless of architecture; raising the floor removes false-positive noise across every install. Reported by @rost43 in #219. 2. web/components/ui/doc-navigation.tsx: handle sidebar entries that point to in-page anchors. The Storage Share Manager sidebar has entries for `/docs/storage-share#host` and `/docs/storage-share#lxc-net` as section headers, but usePathname() does not include the hash so every visit collapsed to the parent page. As a result Next/Previous on /docs/storage-share stayed stuck at #host, and Next from .../lxc-mount-points/ pointed back at #host instead of #lxc-net. Read window.location.hash on mount (and on hashchange) and try the pathname+hash match before falling back to the pathname-only lookup. SSR hydrates with an empty hash and refreshes once mounted — brief render before hydration is the same as the previous behaviour, so no regression. 3. scripts/help_info_menu.sh: user-side improvement (mirrored from develop). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -1 +1 @@
|
|||||||
097e2344675d4b21f1dd18c531c956c299a6507fbc3d0c9695418063581ba2b0
|
149acc8644e7830ddccda97faa452f36f9a820c507e2c6b54fc9a7e51b9b4297
|
||||||
|
|||||||
Generated
+4429
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,7 @@
|
|||||||
"geist": "^1.3.1",
|
"geist": "^1.3.1",
|
||||||
"input-otp": "1.4.1",
|
"input-otp": "1.4.1",
|
||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"next": "15.1.6",
|
"next": "15.1.9",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"react-day-picker": "9.8.0",
|
"react-day-picker": "9.8.0",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import { Link, usePathname } from "@/i18n/navigation"
|
import { Link, usePathname } from "@/i18n/navigation"
|
||||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||||
import { useTranslations } from "next-intl"
|
import { useTranslations } from "next-intl"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
import { sidebarItems } from "@/components/DocSidebar"
|
import { sidebarItems } from "@/components/DocSidebar"
|
||||||
|
|
||||||
interface DocNavigationProps {
|
interface DocNavigationProps {
|
||||||
@@ -55,6 +56,26 @@ export function DocNavigation({ className }: DocNavigationProps) {
|
|||||||
const tNav = useTranslations("docNav")
|
const tNav = useTranslations("docNav")
|
||||||
const tSidebar = useTranslations("docSidebar")
|
const tSidebar = useTranslations("docSidebar")
|
||||||
|
|
||||||
|
// Capture the URL hash (`#host`, `#lxc-net`, …) on the client so we
|
||||||
|
// can disambiguate Previous/Next when a single doc page hosts several
|
||||||
|
// sidebar entries via in-page anchors (Storage Share Manager is the
|
||||||
|
// canonical case: /docs/storage-share + /docs/storage-share#host +
|
||||||
|
// /docs/storage-share#lxc-net are three distinct sidebar items but a
|
||||||
|
// single physical page; usePathname() returns the same string for
|
||||||
|
// all three because the fragment is not part of the path).
|
||||||
|
//
|
||||||
|
// SSR can't see the hash, so we hydrate with an empty string and
|
||||||
|
// refresh on mount + on hashchange. The brief render before
|
||||||
|
// hydration just shows the navigation as if the user were at the
|
||||||
|
// parent page — same behaviour as before this fix, so no regression.
|
||||||
|
const [hash, setHash] = useState("")
|
||||||
|
useEffect(() => {
|
||||||
|
const sync = () => setHash(window.location.hash || "")
|
||||||
|
sync()
|
||||||
|
window.addEventListener("hashchange", sync)
|
||||||
|
return () => window.removeEventListener("hashchange", sync)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const tItem = (i18nKey: string | undefined, fallback: string) => {
|
const tItem = (i18nKey: string | undefined, fallback: string) => {
|
||||||
if (!i18nKey) return fallback
|
if (!i18nKey) return fallback
|
||||||
try {
|
try {
|
||||||
@@ -102,9 +123,30 @@ export function DocNavigation({ className }: DocNavigationProps) {
|
|||||||
// bar showed "Next: Introduction" everywhere regardless of the route.
|
// bar showed "Next: Introduction" everywhere regardless of the route.
|
||||||
const stripTrailingSlash = (s: string) => (s !== "/" ? s.replace(/\/+$/, "") : s)
|
const stripTrailingSlash = (s: string) => (s !== "/" ? s.replace(/\/+$/, "") : s)
|
||||||
const normalizedPathname = stripTrailingSlash(pathname)
|
const normalizedPathname = stripTrailingSlash(pathname)
|
||||||
const currentPageIndex = allPages.findIndex(
|
|
||||||
|
// Match attempt order:
|
||||||
|
// 1. pathname + hash (e.g. /docs/storage-share#host) — exact match
|
||||||
|
// against sidebar items that intentionally point to an in-page
|
||||||
|
// anchor as the "current location" for navigation purposes.
|
||||||
|
// 2. pathname alone — the regular case, no anchor in the URL.
|
||||||
|
//
|
||||||
|
// Without step 1, every anchor visit collapsed to the parent page
|
||||||
|
// and Next/Previous walked from there — so on /docs/storage-share#host
|
||||||
|
// the bottom bar offered the same #host as Next (no movement) and on
|
||||||
|
// /docs/storage-share/lxc-mount-points/ Next pointed back at #host
|
||||||
|
// because the entire flat list got indexed from position 0.
|
||||||
|
const effectivePath = normalizedPathname + hash
|
||||||
|
let currentPageIndex = -1
|
||||||
|
if (hash) {
|
||||||
|
currentPageIndex = allPages.findIndex(
|
||||||
|
(page) => stripTrailingSlash(page.href) === effectivePath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (currentPageIndex === -1) {
|
||||||
|
currentPageIndex = allPages.findIndex(
|
||||||
(page) => stripTrailingSlash(page.href) === normalizedPathname,
|
(page) => stripTrailingSlash(page.href) === normalizedPathname,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const prevPage = currentPageIndex > 0 ? allPages[currentPageIndex - 1] : null
|
const prevPage = currentPageIndex > 0 ? allPages[currentPageIndex - 1] : null
|
||||||
const nextPage = currentPageIndex < allPages.length - 1 ? allPages[currentPageIndex + 1] : null
|
const nextPage = currentPageIndex < allPages.length - 1 ? allPages[currentPageIndex + 1] : null
|
||||||
|
|||||||
Reference in New Issue
Block a user