Skip to content

Commit 267e244

Browse files
夏一飞夏一飞
夏一飞
authored and
夏一飞
committed
feat: add update check and export/import UI
1 parent b68b61d commit 267e244

File tree

6 files changed

+189
-88
lines changed

6 files changed

+189
-88
lines changed

app/components/DatabaseBackup.tsx

+41-60
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,13 @@
11
"use client";
22

33
import { message, Upload, Modal } from "antd";
4-
import { ImportOutlined, ExportOutlined } from "@ant-design/icons";
54
import type { UploadProps } from "antd";
65

76
interface DatabaseBackupProps {
87
open: boolean;
98
onClose: () => void;
109
}
1110

12-
interface MigrationCardProps {
13-
type: "export" | "import";
14-
onClick?: () => void;
15-
uploadProps?: UploadProps;
16-
}
17-
18-
const MigrationCard: React.FC<MigrationCardProps> = ({
19-
type,
20-
onClick,
21-
uploadProps,
22-
}) => {
23-
const config = {
24-
export: {
25-
icon: <ExportOutlined className="text-base text-blue-600" />,
26-
title: "导出",
27-
color: "border-blue-100 hover:border-blue-200 bg-blue-50/40",
28-
iconBg: "bg-blue-50",
29-
},
30-
import: {
31-
icon: <ImportOutlined className="text-base text-purple-600" />,
32-
title: "导入",
33-
color: "border-purple-100 hover:border-purple-200 bg-purple-50/40",
34-
iconBg: "bg-purple-50",
35-
},
36-
}[type];
37-
38-
const card = (
39-
<div
40-
className={`w-full flex items-center gap-2 p-2 rounded-lg border transition-all duration-300 ${config.color}`}
41-
>
42-
<div className={`p-2 rounded ${config.iconBg}`}>{config.icon}</div>
43-
<span className="text-sm font-medium text-gray-900">{config.title}</span>
44-
</div>
45-
);
46-
47-
if (type === "import" && uploadProps) {
48-
return (
49-
<Upload {...uploadProps} className="w-full">
50-
<div className="w-full cursor-pointer">{card}</div>
51-
</Upload>
52-
);
53-
}
54-
55-
return (
56-
<div className="w-full cursor-pointer" onClick={onClick}>
57-
{card}
58-
</div>
59-
);
60-
};
61-
6211
const DatabaseBackup: React.FC<DatabaseBackupProps> = ({ open, onClose }) => {
6312
const handleExport = async () => {
6413
try {
@@ -133,16 +82,48 @@ const DatabaseBackup: React.FC<DatabaseBackupProps> = ({ open, onClose }) => {
13382
footer={null}
13483
width={360}
13584
centered
136-
className="rounded-xl"
13785
>
138-
<div className="py-4">
139-
<p className="text-xs text-gray-500 text-center mb-4">
140-
导出或导入系统数据
141-
</p>
142-
<div className="flex flex-col gap-2">
143-
<MigrationCard type="export" onClick={handleExport} />
144-
<MigrationCard type="import" uploadProps={uploadProps} />
145-
</div>
86+
<div className="flex flex-row justify-between gap-3 py-4">
87+
<button
88+
onClick={handleExport}
89+
className="flex items-center justify-center gap-2 w-[150px] px-4 py-3 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition-all duration-200"
90+
>
91+
<svg
92+
className="w-5 h-5"
93+
fill="none"
94+
stroke="currentColor"
95+
viewBox="0 0 24 24"
96+
xmlns="http://www.w3.org/2000/svg"
97+
>
98+
<path
99+
strokeLinecap="round"
100+
strokeLinejoin="round"
101+
strokeWidth={2}
102+
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
103+
/>
104+
</svg>
105+
导出数据
106+
</button>
107+
108+
<Upload {...uploadProps}>
109+
<button className="flex items-center justify-center gap-2 w-[150px] px-4 py-3 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition-all duration-200">
110+
<svg
111+
className="w-5 h-5"
112+
fill="none"
113+
stroke="currentColor"
114+
viewBox="0 0 24 24"
115+
xmlns="http://www.w3.org/2000/svg"
116+
>
117+
<path
118+
strokeLinecap="round"
119+
strokeLinejoin="round"
120+
strokeWidth={2}
121+
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"
122+
/>
123+
</svg>
124+
导入数据
125+
</button>
126+
</Upload>
146127
</div>
147128
</Modal>
148129
);

app/components/Header.tsx

+84
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ import {
99
LogoutOutlined,
1010
SettingOutlined,
1111
DatabaseOutlined,
12+
GithubOutlined,
1213
} from "@ant-design/icons";
1314
import { message } from "antd";
1415
import DatabaseBackup from "./DatabaseBackup";
16+
import { APP_VERSION } from "@/lib/version";
1517

1618
export default function Header() {
1719
const [apiKey, setApiKey] = useState("加载中...");
1820
const [isBackupModalOpen, setIsBackupModalOpen] = useState(false);
21+
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
1922

2023
useEffect(() => {
2124
const token = document.cookie
@@ -51,6 +54,75 @@ export default function Header() {
5154
window.location.href = "/token";
5255
};
5356

57+
const checkUpdate = async () => {
58+
setIsCheckingUpdate(true);
59+
try {
60+
const response = await fetch(
61+
"https://api.github.com/repos/variantconst/openwebui-monitor/releases/latest"
62+
);
63+
const data = await response.json();
64+
const latestVersion = data.tag_name;
65+
66+
if (!latestVersion) {
67+
throw new Error("获取版本号失败");
68+
}
69+
70+
const currentVer = APP_VERSION.replace(/^v/, "");
71+
const latestVer = latestVersion.replace(/^v/, "");
72+
73+
if (currentVer === latestVer) {
74+
message.success(`当前已是最新版本 v${APP_VERSION}`);
75+
} else {
76+
Modal.confirm({
77+
icon: null,
78+
title: (
79+
<div className="flex items-center gap-3 mb-4">
80+
<div className="flex items-center justify-center w-10 h-10 rounded-full bg-blue-50">
81+
<GithubOutlined className="text-lg text-blue-500" />
82+
</div>
83+
<div className="flex flex-col">
84+
<div className="text-lg font-medium text-gray-800">
85+
发现新版本
86+
</div>
87+
</div>
88+
</div>
89+
),
90+
content: (
91+
<div className="flex flex-col gap-4 py-2">
92+
<div className="flex justify-between items-center">
93+
<span className="text-gray-500">当前版本</span>
94+
<span className="font-mono text-gray-800">v{APP_VERSION}</span>
95+
</div>
96+
<div className="flex justify-between items-center">
97+
<span className="text-gray-500">最新版本</span>
98+
<span className="font-mono text-blue-600">{latestVersion}</span>
99+
</div>
100+
</div>
101+
),
102+
centered: true,
103+
width: 400,
104+
okText: "前往更新",
105+
cancelText: "暂不更新",
106+
className: "update-modal",
107+
okButtonProps: {
108+
className: "bg-blue-500 hover:bg-blue-600",
109+
},
110+
onOk: () => {
111+
window.open(
112+
"https://github.com/VariantConst/OpenWebUI-Monitor/releases/latest",
113+
"_blank"
114+
);
115+
},
116+
});
117+
}
118+
} catch (error) {
119+
message.error("检查更新失败");
120+
console.error("检查更新失败:", error);
121+
} finally {
122+
setIsCheckingUpdate(false);
123+
}
124+
};
125+
54126
const items: MenuProps["items"] = [
55127
{
56128
key: "1",
@@ -78,6 +150,18 @@ export default function Header() {
78150
},
79151
{
80152
key: "3",
153+
label: (
154+
<div
155+
className="flex items-center gap-2 px-2 py-1.5 text-gray-600 hover:text-gray-900"
156+
onClick={checkUpdate}
157+
>
158+
<GithubOutlined className="text-base" spin={isCheckingUpdate} />
159+
<span>检查更新</span>
160+
</div>
161+
),
162+
},
163+
{
164+
key: "4",
81165
label: (
82166
<div
83167
className="flex items-center gap-2 px-2 py-1.5 text-gray-600 hover:text-red-500"

app/globals.css

+42
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,45 @@ body {
7171
.custom-tooltip-simple .ant-tooltip-arrow {
7272
display: none;
7373
}
74+
75+
/* 更新模态框样式 */
76+
.update-modal .ant-modal-content {
77+
padding: 24px;
78+
border-radius: 16px;
79+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
80+
}
81+
82+
.update-modal .ant-modal-footer {
83+
margin-top: 24px;
84+
border-top: none;
85+
padding: 0;
86+
}
87+
88+
.update-modal .ant-modal-footer .ant-btn {
89+
height: 38px;
90+
padding: 0 24px;
91+
border-radius: 8px;
92+
font-weight: 500;
93+
}
94+
95+
.update-modal .ant-modal-footer .ant-btn-primary {
96+
background: rgb(59 130 246);
97+
border-color: rgb(59 130 246);
98+
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
99+
}
100+
101+
.update-modal .ant-modal-footer .ant-btn-primary:hover {
102+
background: rgb(37 99 235);
103+
border-color: rgb(37 99 235);
104+
}
105+
106+
.update-modal .ant-modal-footer .ant-btn-default {
107+
border-color: #e5e7eb;
108+
color: #6b7280;
109+
}
110+
111+
.update-modal .ant-modal-footer .ant-btn-default:hover {
112+
border-color: #d1d5db;
113+
color: #4b5563;
114+
background: #f9fafb;
115+
}

app/models/page.tsx

+15-28
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ export default function ModelsPage() {
482482
</p>
483483
</div>
484484

485-
<div className="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
485+
<div className="mb-6 flex items-center gap-6">
486486
<div className="flex items-center gap-2">
487487
<Tooltip title="批量测试所有模型的可用性">
488488
<Button
@@ -540,33 +540,20 @@ export default function ModelsPage() {
540540
</Tooltip>
541541
</div>
542542

543-
<div className="flex items-center space-x-3">
544-
<Tooltip title="导出当前模型价格配置">
545-
<Button
546-
icon={<DownloadOutlined />}
547-
onClick={handleExportPrices}
548-
size="middle"
549-
className="bg-white hover:bg-gray-50 border-gray-200 shadow-sm flex items-center"
550-
>
551-
<span className="hidden sm:inline ml-1">导出价格</span>
552-
</Button>
553-
</Tooltip>
554-
555-
<Tooltip title="导入模型价格配置">
556-
<Upload
557-
accept=".json"
558-
showUploadList={false}
559-
beforeUpload={handleImportPrices}
560-
>
561-
<Button
562-
icon={<UploadOutlined />}
563-
size="middle"
564-
className="bg-white hover:bg-gray-50 border-gray-200 shadow-sm flex items-center"
565-
>
566-
<span className="hidden sm:inline ml-1">导入价格</span>
567-
</Button>
568-
</Upload>
569-
</Tooltip>
543+
<div className="flex items-center gap-4 text-sm text-gray-500">
544+
<span
545+
className="cursor-pointer hover:text-gray-700"
546+
onClick={handleExportPrices}
547+
>
548+
导出
549+
</span>
550+
<Upload
551+
accept=".json"
552+
showUploadList={false}
553+
beforeUpload={handleImportPrices}
554+
>
555+
<span className="cursor-pointer hover:text-gray-700">导入</span>
556+
</Upload>
570557
</div>
571558
</div>
572559

lib/version.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// 从环境变量获取版本号
2+
export const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.1";

next.config.js

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
/** @type {import('next').NextConfig} */
2+
const packageJson = require("./package.json");
3+
24
const nextConfig = {
35
reactStrictMode: true,
6+
env: {
7+
NEXT_PUBLIC_APP_VERSION: packageJson.version,
8+
},
49
};
510

611
module.exports = nextConfig;

0 commit comments

Comments
 (0)