系统控制台完成更新

This commit is contained in:
ryan
2026-06-12 14:12:03 +08:00
parent 407c1edf74
commit c916f566d9
34 changed files with 1297 additions and 40 deletions
+137 -17
View File
@@ -1,10 +1,25 @@
"use client"
import {Info, Server} from "lucide-react"
import packageJson from "../../../package.json"
import {APP_VERSION, APP_BUILD_DATE} from "@/lib/app-info"
import {apiConfig} from "@/lib/services"
import {useMutation, useQuery} from "@tanstack/react-query"
import {ExternalLink, RefreshCw, Server, Sparkles} from "lucide-react"
import {toast} from "sonner"
import {AdminService, apiConfig} from "@/lib/services"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger
} from "@/components/ui/alert-dialog"
import {Badge} from "@/components/ui/badge"
import {Button} from "@/components/ui/button"
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card"
import {Spinner} from "@/components/ui/spinner"
function InfoRow({ label, value }: { label: string; value: React.ReactNode }) {
return (
@@ -21,26 +36,131 @@ interface InfoTabProps {
}
export function InfoTab({ systemConfigsLength, authSourcesLength }: InfoTabProps) {
const updateQuery = useQuery({
queryKey: ["admin", "update"],
queryFn: () => AdminService.getUpdateStatus(),
refetchInterval: 30 * 60 * 1000,
staleTime: 5 * 60 * 1000,
})
const applyUpdateMutation = useMutation({
mutationFn: () => AdminService.applyUpdate(),
onSuccess: () => {
toast.success("升级包已校验完成,服务正在重启")
},
onError: (error: Error) => {
toast.error(error.message || "应用升级失败")
},
})
const update = updateQuery.data
return (
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
<Card className="border border-dashed shadow-sm">
<CardHeader className="border-b border-dashed pb-4">
<div className="flex items-center gap-2">
<div className="p-1.5 rounded-lg bg-muted text-muted-foreground">
<Info className="size-4" />
</div>
<div>
<CardTitle className="text-base font-semibold">应用信息</CardTitle>
<CardDescription className="text-xs">当前前端应用的版本与构建信息</CardDescription>
<div className="flex items-start justify-between gap-4">
<div className="flex items-center gap-2">
<div className="rounded-lg bg-muted p-1.5 text-muted-foreground">
<Sparkles className="size-4" />
</div>
<div>
<CardTitle className="text-base font-semibold">应用更新</CardTitle>
<CardDescription className="text-xs">
检查上游 GitHub Actions Release 并升级当前服务
</CardDescription>
</div>
</div>
{update?.update_available ? (
<Badge>发现新版本</Badge>
) : update ? (
<Badge variant="secondary">已是最新</Badge>
) : null}
</div>
</CardHeader>
<CardContent className="pt-4">
<InfoRow label="应用名称" value={packageJson.name} />
<InfoRow label="版本号" value={APP_VERSION} />
{APP_BUILD_DATE && <InfoRow label="构建时间" value={APP_BUILD_DATE} />}
<InfoRow label="Next.js" value={(packageJson as { dependencies?: Record<string, string> }).dependencies?.next} />
<InfoRow label="React" value={(packageJson as { dependencies?: Record<string, string> }).dependencies?.react} />
<CardContent className="flex flex-col gap-4 pt-4">
{updateQuery.isLoading ? (
<div className="flex min-h-32 items-center justify-center">
<Spinner />
</div>
) : updateQuery.isError ? (
<div className="flex min-h-32 flex-col items-center justify-center gap-3 text-center">
<p className="text-xs text-muted-foreground">
{updateQuery.error.message || "无法获取上游版本信息"}
</p>
<Button type="button" variant="outline" size="sm" onClick={() => updateQuery.refetch()}>
<RefreshCw data-icon="inline-start" />
重新检查
</Button>
</div>
) : update ? (
<>
<div>
<InfoRow label="当前版本" value={update.current_version} />
<InfoRow label="最新版本" value={update.latest_version} />
{update.build_time && <InfoRow label="构建时间" value={update.build_time} />}
<InfoRow label="运行平台" value={update.platform} />
<InfoRow label="上游仓库" value={update.upstream_repository} />
<InfoRow label="Release 资产" value={update.asset_name} />
</div>
{update.release_notes && (
<div className="flex flex-col gap-2 rounded-md border border-dashed p-3">
<p className="text-xs font-medium">更新说明</p>
<p className="max-h-40 overflow-y-auto whitespace-pre-wrap text-xs leading-relaxed text-muted-foreground">
{update.release_notes}
</p>
</div>
)}
<div className="flex flex-wrap justify-end gap-2">
<Button type="button" variant="ghost" size="sm" onClick={() => updateQuery.refetch()} disabled={updateQuery.isFetching}>
{updateQuery.isFetching ? <Spinner data-icon="inline-start" /> : <RefreshCw data-icon="inline-start" />}
检查更新
</Button>
{update.release_url && (
<Button asChild variant="outline" size="sm">
<a href={update.release_url} target="_blank" rel="noreferrer">
<ExternalLink data-icon="inline-start" />
查看 Release
</a>
</Button>
)}
<AlertDialog>
<AlertDialogTrigger asChild>
<Button type="button" size="sm" disabled={!update.can_upgrade || applyUpdateMutation.isPending}>
{applyUpdateMutation.isPending && <Spinner data-icon="inline-start" />}
立即升级
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>升级到 {update.latest_version}?</AlertDialogTitle>
<AlertDialogDescription>
服务将下载并校验 {update.asset_name},随后替换当前二进制并重启。请确保安装目录可写,且服务允许原地重启。
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction onClick={() => applyUpdateMutation.mutate()}>
确认升级
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
{!update.can_upgrade && (
<p className="text-xs text-muted-foreground">
{update.current_version === "dev"
? "开发构建没有可比较的 Release 版本,不能执行自动升级。"
: update.update_available
? "当前平台暂不支持自动替换二进制,请从 Release 页面手动升级。"
: "当前版本无需升级。"}
</p>
)}
</>
) : null}
</CardContent>
</Card>