fix: skip version check for preview channel and add upload progress bar

Co-authored-by: Rain-kl <63696351+Rain-kl@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-12 12:40:20 +00:00
parent 8cc409ce68
commit 63219e4802
5 changed files with 121 additions and 7 deletions
@@ -45,6 +45,7 @@ export function DashboardTopbar() {
);
const [uploadedBinary, setUploadedBinary] =
useState<UploadedServerBinaryInfo | null>(null);
const [uploadProgress, setUploadProgress] = useState(0);
const menuRef = useRef<HTMLDivElement | null>(null);
const isRoot = (user?.role ?? 0) >= 100;
const upgradeStatusPollInterval = 3000;
@@ -102,14 +103,19 @@ export function DashboardTopbar() {
});
const uploadBinaryMutation = useMutation({
mutationFn: uploadServerBinary,
mutationFn: (binary: File) =>
uploadServerBinary(binary, (progress) => {
setUploadProgress(progress);
}),
onSuccess: (candidate) => {
setUploadProgress(0);
setVersionFeedback(null);
setManualUpgradeError(null);
setUploadedBinary(candidate);
setManualUpgradeStatus(candidate.comparison_message);
},
onError: (error) => {
setUploadProgress(0);
setUploadedBinary(null);
setManualUpgradeStatus(null);
setManualUpgradeError(
@@ -217,6 +223,7 @@ export function DashboardTopbar() {
};
const handleUploadBinary = (binary: File) => {
setUploadProgress(0);
setManualUpgradeStatus(null);
setManualUpgradeError(null);
uploadBinaryMutation.mutate(binary);
@@ -359,6 +366,7 @@ export function DashboardTopbar() {
}
isUpgrading={upgradeMutation.isPending}
isUploadingBinary={uploadBinaryMutation.isPending}
uploadProgress={uploadProgress}
isConfirmingManualUpgrade={confirmManualUpgradeMutation.isPending}
onChannelChange={handleReleaseChannelChange}
onCheck={handleCheckRelease}
+56 -5
View File
@@ -1,4 +1,5 @@
import { apiRequest } from '@/lib/api/client';
import { ApiError, apiRequest, getApiUrl } from '@/lib/api/client';
import type { ApiEnvelope } from '@/types/api';
import type {
LatestReleaseInfo,
@@ -19,13 +20,63 @@ export function upgradeServer(channel: ReleaseChannel = 'stable') {
});
}
export function uploadServerBinary(binary: File) {
export function uploadServerBinary(
binary: File,
onProgress?: (progress: number) => void,
): Promise<UploadedServerBinaryInfo> {
const formData = new FormData();
formData.append('binary', binary);
return apiRequest<UploadedServerBinaryInfo>('/update/manual-upload', {
method: 'POST',
body: formData,
if (!onProgress) {
return apiRequest<UploadedServerBinaryInfo>('/update/manual-upload', {
method: 'POST',
body: formData,
});
}
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', getApiUrl('/update/manual-upload'));
xhr.withCredentials = true;
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
onProgress(Math.round((event.loaded / event.total) * 100));
}
});
xhr.addEventListener('load', () => {
let payload: ApiEnvelope<UploadedServerBinaryInfo> | null = null;
try {
payload = JSON.parse(xhr.responseText) as ApiEnvelope<UploadedServerBinaryInfo>;
} catch {
payload = null;
}
if (xhr.status < 200 || xhr.status >= 300) {
reject(
new ApiError(
payload?.message || `请求失败(${xhr.status})`,
xhr.status,
),
);
return;
}
if (!payload) {
reject(new ApiError('响应格式无效', xhr.status));
return;
}
if (!payload.success) {
reject(new ApiError(payload.message || '请求失败', xhr.status));
return;
}
resolve(payload.data);
});
xhr.addEventListener('error', () => {
reject(new ApiError('上传过程中网络连接中断,请检查网络后重试', 0));
});
xhr.send(formData);
});
}
@@ -34,6 +34,7 @@ interface VersionUpgradeModalProps {
isChecking: boolean;
isUpgrading: boolean;
isUploadingBinary: boolean;
uploadProgress: number;
isConfirmingManualUpgrade: boolean;
onChannelChange: (channel: ReleaseChannel) => void;
onCheck: () => void;
@@ -71,6 +72,7 @@ export function VersionUpgradeModal({
isChecking,
isUpgrading,
isUploadingBinary,
uploadProgress,
isConfirmingManualUpgrade,
onChannelChange,
onCheck,
@@ -290,6 +292,21 @@ export function VersionUpgradeModal({
)}
</div>
{isUploadingBinary ? (
<div className="space-y-1">
<div className="flex items-center justify-between text-xs text-[var(--foreground-secondary)]">
<span>上传中...</span>
<span>{uploadProgress}%</span>
</div>
<div className="h-2 w-full overflow-hidden rounded-full bg-[var(--border-default)]">
<div
className="h-full rounded-full bg-[var(--brand-primary)] transition-all duration-200"
style={{width: `${uploadProgress}%`}}
/>
</div>
</div>
) : null}
{manualErrorMessage ? (
<ErrorState
title="手动升级检查失败"