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:
parent
bae4342af1
commit
e075be6b10
3 changed files with 69 additions and 30 deletions
|
|
@ -11,12 +11,10 @@ import {
|
||||||
IconSparkles,
|
IconSparkles,
|
||||||
IconTools,
|
IconTools,
|
||||||
} from "@tabler/icons-react"
|
} from "@tabler/icons-react"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
|
||||||
import { Link, useRouterState } from "@tanstack/react-router"
|
import { Link, useRouterState } from "@tanstack/react-router"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
import { getSystemVersionInfo } from "@/api/system"
|
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
|
|
@ -25,7 +23,6 @@ import {
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
SidebarFooter,
|
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
SidebarGroupContent,
|
SidebarGroupContent,
|
||||||
SidebarGroupLabel,
|
SidebarGroupLabel,
|
||||||
|
|
@ -84,13 +81,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||||
language: (i18n.resolvedLanguage ?? i18n.language ?? "").toLowerCase(),
|
language: (i18n.resolvedLanguage ?? i18n.language ?? "").toLowerCase(),
|
||||||
t,
|
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(() => {
|
const handleNavItemClick = React.useCallback(() => {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
setOpenMobile(false)
|
setOpenMobile(false)
|
||||||
|
|
@ -263,26 +254,6 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
))}
|
))}
|
||||||
</SidebarContent>
|
</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 />
|
<SidebarRail />
|
||||||
</Sidebar>
|
</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 { useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { Link } from "@tanstack/react-router"
|
import { Link } from "@tanstack/react-router"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
|
@ -10,6 +10,7 @@ import { launcherFetch } from "@/api/http"
|
||||||
import {
|
import {
|
||||||
getAutoStartStatus,
|
getAutoStartStatus,
|
||||||
getLauncherConfig,
|
getLauncherConfig,
|
||||||
|
getSystemVersionInfo,
|
||||||
setAutoStartEnabled as updateAutoStartEnabled,
|
setAutoStartEnabled as updateAutoStartEnabled,
|
||||||
setLauncherConfig as updateLauncherConfig,
|
setLauncherConfig as updateLauncherConfig,
|
||||||
} from "@/api/system"
|
} from "@/api/system"
|
||||||
|
|
@ -32,6 +33,7 @@ import {
|
||||||
parseMultilineList,
|
parseMultilineList,
|
||||||
} from "@/components/config/form-model"
|
} from "@/components/config/form-model"
|
||||||
import { PageHeader } from "@/components/page-header"
|
import { PageHeader } from "@/components/page-header"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { refreshGatewayState } from "@/store/gateway"
|
import { refreshGatewayState } from "@/store/gateway"
|
||||||
|
|
||||||
|
|
@ -64,6 +66,12 @@ export function ConfigPage() {
|
||||||
queryFn: getLauncherConfig,
|
queryFn: getLauncherConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { data: versionInfo } = useQuery({
|
||||||
|
queryKey: ["system", "version"],
|
||||||
|
queryFn: getSystemVersionInfo,
|
||||||
|
staleTime: 5 * 60 * 1000,
|
||||||
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: autoStartStatus,
|
data: autoStartStatus,
|
||||||
isLoading: isAutoStartLoading,
|
isLoading: isAutoStartLoading,
|
||||||
|
|
@ -297,6 +305,17 @@ export function ConfigPage() {
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={t("navigation.config")}
|
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={
|
children={
|
||||||
<Button variant="outline" asChild>
|
<Button variant="outline" asChild>
|
||||||
<Link to="/config/raw">
|
<Link to="/config/raw">
|
||||||
|
|
|
||||||
49
web/frontend/src/components/ui/badge.tsx
Normal file
49
web/frontend/src/components/ui/badge.tsx
Normal 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 }
|
||||||
Loading…
Add table
Reference in a new issue