feat(web): move version display to the config page header (#2273)

- remove version details from the sidebar footer
- show the current app version as a badge in the config page header
- add a reusable Badge UI component for the new version label
This commit is contained in:
wenjie
2026-04-02 19:09:27 +08:00
committed by GitHub
parent bae4342af1
commit e075be6b10
3 changed files with 69 additions and 30 deletions
@@ -11,12 +11,10 @@ import {
IconSparkles,
IconTools,
} from "@tabler/icons-react"
import { useQuery } from "@tanstack/react-query"
import { Link, useRouterState } from "@tanstack/react-router"
import * as React from "react"
import { useTranslation } from "react-i18next"
import { getSystemVersionInfo } from "@/api/system"
import {
Collapsible,
CollapsibleContent,
@@ -25,7 +23,6 @@ import {
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
@@ -84,13 +81,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
language: (i18n.resolvedLanguage ?? i18n.language ?? "").toLowerCase(),
t,
})
const { data: versionInfo } = useQuery({
queryKey: ["system", "version"],
queryFn: getSystemVersionInfo,
staleTime: 5 * 60 * 1000,
})
const versionText = versionInfo?.version ?? t("footer.version_unknown")
const handleNavItemClick = React.useCallback(() => {
if (isMobile) {
setOpenMobile(false)
@@ -263,26 +254,6 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</Collapsible>
))}
</SidebarContent>
<SidebarFooter className="border-t-border/30 border-t px-3 py-2 group-data-[collapsible=icon]:hidden">
<div className="text-muted-foreground flex flex-col gap-0.5 text-[11px] leading-4">
<div className="truncate" title={versionText}>
<span className="text-foreground/80">{t("footer.version")}:</span>{" "}
{versionText}
</div>
{versionInfo?.git_commit && (
<div className="truncate" title={versionInfo.git_commit}>
<span className="text-foreground/80">{t("footer.commit")}:</span>{" "}
{versionInfo.git_commit}
</div>
)}
{versionInfo?.build_time && (
<div className="truncate" title={versionInfo.build_time}>
<span className="text-foreground/80">{t("footer.build")}:</span>{" "}
{versionInfo.build_time}
</div>
)}
</div>
</SidebarFooter>
<SidebarRail />
</Sidebar>
)
@@ -1,4 +1,4 @@
import { IconCode, IconDeviceFloppy } from "@tabler/icons-react"
import { IconCode, IconDeviceFloppy, IconTag } from "@tabler/icons-react"
import { useQuery, useQueryClient } from "@tanstack/react-query"
import { Link } from "@tanstack/react-router"
import { useEffect, useState } from "react"
@@ -10,6 +10,7 @@ import { launcherFetch } from "@/api/http"
import {
getAutoStartStatus,
getLauncherConfig,
getSystemVersionInfo,
setAutoStartEnabled as updateAutoStartEnabled,
setLauncherConfig as updateLauncherConfig,
} from "@/api/system"
@@ -32,6 +33,7 @@ import {
parseMultilineList,
} from "@/components/config/form-model"
import { PageHeader } from "@/components/page-header"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { refreshGatewayState } from "@/store/gateway"
@@ -64,6 +66,12 @@ export function ConfigPage() {
queryFn: getLauncherConfig,
})
const { data: versionInfo } = useQuery({
queryKey: ["system", "version"],
queryFn: getSystemVersionInfo,
staleTime: 5 * 60 * 1000,
})
const {
data: autoStartStatus,
isLoading: isAutoStartLoading,
@@ -297,6 +305,17 @@ export function ConfigPage() {
<div className="flex h-full flex-col">
<PageHeader
title={t("navigation.config")}
titleExtra={
versionInfo && (
<Badge
variant="secondary"
className="gap-1 font-mono text-[11px] font-normal opacity-80"
>
<IconTag className="size-3 opacity-70" />
{versionInfo.version}
</Badge>
)
}
children={
<Button variant="outline" asChild>
<Link to="/config/raw">
+49
View File
@@ -0,0 +1,49 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
secondary:
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive:
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
outline:
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant = "default",
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span"
return (
<Comp
data-slot="badge"
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }