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:
@@ -9,6 +9,7 @@
|
||||
import { Link, usePathname } from "@/i18n/navigation"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { useEffect, useState } from "react"
|
||||
import { sidebarItems } from "@/components/DocSidebar"
|
||||
|
||||
interface DocNavigationProps {
|
||||
@@ -55,6 +56,26 @@ export function DocNavigation({ className }: DocNavigationProps) {
|
||||
const tNav = useTranslations("docNav")
|
||||
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) => {
|
||||
if (!i18nKey) return fallback
|
||||
try {
|
||||
@@ -102,9 +123,30 @@ export function DocNavigation({ className }: DocNavigationProps) {
|
||||
// bar showed "Next: Introduction" everywhere regardless of the route.
|
||||
const stripTrailingSlash = (s: string) => (s !== "/" ? s.replace(/\/+$/, "") : s)
|
||||
const normalizedPathname = stripTrailingSlash(pathname)
|
||||
const currentPageIndex = allPages.findIndex(
|
||||
(page) => stripTrailingSlash(page.href) === normalizedPathname,
|
||||
)
|
||||
|
||||
// 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,
|
||||
)
|
||||
}
|
||||
|
||||
const prevPage = currentPageIndex > 0 ? allPages[currentPageIndex - 1] : null
|
||||
const nextPage = currentPageIndex < allPages.length - 1 ? allPages[currentPageIndex + 1] : null
|
||||
|
||||
Reference in New Issue
Block a user