diff --git a/atsf_server/web/components/layout/dashboard-topbar.tsx b/atsf_server/web/components/layout/dashboard-topbar.tsx index 9fb188a4..d25705f6 100644 --- a/atsf_server/web/components/layout/dashboard-topbar.tsx +++ b/atsf_server/web/components/layout/dashboard-topbar.tsx @@ -186,20 +186,20 @@ export function DashboardTopbar() { upgradeMutation.mutate(selectedReleaseChannel); }; - const handleCheckStableRelease = () => { - setSelectedReleaseChannel('stable'); + const handleCheckRelease = () => { setVersionFeedback(null); if (isRoot) { - void stableReleaseQuery.refetch(); + if (selectedReleaseChannel === 'preview') { + void previewReleaseQuery.refetch(); + } else { + void stableReleaseQuery.refetch(); + } } }; - const handleCheckPreviewRelease = () => { - setSelectedReleaseChannel('preview'); + const handleReleaseChannelChange = (channel: ReleaseChannel) => { + setSelectedReleaseChannel(channel); setVersionFeedback(null); - if (isRoot) { - void previewReleaseQuery.refetch(); - } }; const handleUploadBinary = (binary: File) => { @@ -347,8 +347,8 @@ export function DashboardTopbar() { isUpgrading={upgradeMutation.isPending} isUploadingBinary={uploadBinaryMutation.isPending} isConfirmingManualUpgrade={confirmManualUpgradeMutation.isPending} - onCheckStable={handleCheckStableRelease} - onCheckPreview={handleCheckPreviewRelease} + onChannelChange={handleReleaseChannelChange} + onCheck={handleCheckRelease} onUpgrade={handleUpgrade} onUploadBinary={handleUploadBinary} onConfirmManualUpgrade={handleConfirmManualUpgrade} diff --git a/atsf_server/web/components/ui/status-badge.tsx b/atsf_server/web/components/ui/status-badge.tsx index 8f19c770..09cff43b 100644 --- a/atsf_server/web/components/ui/status-badge.tsx +++ b/atsf_server/web/components/ui/status-badge.tsx @@ -1,36 +1,51 @@ -import { cn } from '@/lib/utils/cn'; - -const variantClasses = { - success: - 'border-[var(--status-success-border)] bg-[var(--status-success-soft)] text-[var(--status-success-foreground)]', - warning: - 'border-[var(--status-warning-border)] bg-[var(--status-warning-soft)] text-[var(--status-warning-foreground)]', - danger: - 'border-[var(--status-danger-border)] bg-[var(--status-danger-soft)] text-[var(--status-danger-foreground)]', - info: - 'border-[var(--status-info-border)] bg-[var(--status-info-soft)] text-[var(--status-info-foreground)]', -} as const; - -interface StatusBadgeProps { - label: string; - variant?: keyof typeof variantClasses; - className?: string; -} - -export function StatusBadge({ - label, - variant = 'info', - className, -}: StatusBadgeProps) { - return ( - - {label} - - ); -} +import { cn } from '@/lib/utils/cn'; + +const variantClasses = { + success: + 'border-[var(--status-success-border)] bg-[var(--status-success-soft)] text-[var(--status-success-foreground)]', + warning: + 'border-[var(--status-warning-border)] bg-[var(--status-warning-soft)] text-[var(--status-warning-foreground)]', + danger: + 'border-[var(--status-danger-border)] bg-[var(--status-danger-soft)] text-[var(--status-danger-foreground)]', + info: 'border-[var(--status-info-border)] bg-[var(--status-info-soft)] text-[var(--status-info-foreground)]', +} as const; + +interface StatusBadgeProps { + label: string; + variant?: keyof typeof variantClasses; + className?: string; + onClick?: () => void; + disabled?: boolean; +} + +export function StatusBadge({ + label, + variant = 'info', + className, + onClick, + disabled = false, +}: StatusBadgeProps) { + const badgeClassName = cn( + 'inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-medium tracking-wide', + variantClasses[variant], + onClick + ? 'cursor-pointer transition hover:opacity-85 disabled:cursor-not-allowed disabled:opacity-60' + : undefined, + className, + ); + + if (onClick) { + return ( + + ); + } + + return {label}; +} diff --git a/atsf_server/web/features/update/components/version-upgrade-modal.tsx b/atsf_server/web/features/update/components/version-upgrade-modal.tsx index 9c83fa5c..f0224f7e 100644 --- a/atsf_server/web/features/update/components/version-upgrade-modal.tsx +++ b/atsf_server/web/features/update/components/version-upgrade-modal.tsx @@ -40,8 +40,8 @@ interface VersionUpgradeModalProps { isUpgrading: boolean; isUploadingBinary: boolean; isConfirmingManualUpgrade: boolean; - onCheckStable: () => void; - onCheckPreview: () => void; + onChannelChange: (channel: ReleaseChannel) => void; + onCheck: () => void; onUpgrade: () => void; onUploadBinary: (file: File) => void; onConfirmManualUpgrade: () => void; @@ -78,8 +78,8 @@ export function VersionUpgradeModal({ isUpgrading, isUploadingBinary, isConfirmingManualUpgrade, - onCheckStable, - onCheckPreview, + onChannelChange, + onCheck, onUpgrade, onUploadBinary, onConfirmManualUpgrade, @@ -88,6 +88,13 @@ export function VersionUpgradeModal({ const [selectedBinary, setSelectedBinary] = useState(null); const selectedChannelLabel = selectedChannel === 'preview' ? '预览版' : '正式版'; + const toggleChannel = () => { + onChannelChange(selectedChannel === 'preview' ? 'stable' : 'preview'); + }; + const canConfirmManualUpgrade = Boolean( + uploadedBinary?.ready_to_upgrade && uploadedBinary.upload_token, + ); + const showConfirmManualUpgradeAction = canConfirmManualUpgrade; useEffect(() => { if (!isOpen) { @@ -102,78 +109,6 @@ export function VersionUpgradeModal({ title="版本" description="默认检查正式版更新;你也可以手动检查 preview 发布并选择升级,或上传 Server 二进制确认升级。升级开始后服务会短暂重启。" size="lg" - footer={ - canUpgrade ? ( -
- - {isChecking && selectedChannel === 'stable' - ? '检查中...' - : '检查正式版'} - - - {isChecking && selectedChannel === 'preview' - ? '检查中...' - : '检查预览版'} - - { - if (selectedBinary) { - onUploadBinary(selectedBinary); - } - }} - disabled={ - !selectedBinary || - isUploadingBinary || - isConfirmingManualUpgrade - } - > - {isUploadingBinary ? '上传检查中...' : '上传并检查'} - - - {isConfirmingManualUpgrade ? '升级中...' : '确认手动升级'} - - - {isUpgrading - ? '升级中...' - : release?.in_progress - ? '升级中...' - : selectedChannel === 'preview' - ? '升级到预览版' - : '升级到正式版'} - -
- ) : undefined - } >
@@ -194,21 +129,43 @@ export function VersionUpgradeModal({
-
-

- {release?.tag_name || '未检查'} -

- +
+
+

+ {release?.tag_name || '未检查'} +

+ +
+ {canUpgrade ? ( +
+
+ + 点击标签切换通道 + + +
+ + {isChecking ? '检查中...' : '检查'} + +
+ ) : null}
- -

- {startTime ? formatDateTime(new Date(startTime * 1000)) : '未知'} -

-
{isLoading ? : null} @@ -267,6 +224,30 @@ export function VersionUpgradeModal({ > 查看发布详情 + {canUpgrade ? ( +
+ + {isUpgrading + ? '升级中...' + : release.in_progress + ? '升级中...' + : selectedChannel === 'preview' + ? '升级到预览版' + : '升级到正式版'} + +
+ ) : null}
) : null} @@ -291,6 +272,39 @@ export function VersionUpgradeModal({ /> +
+ {showConfirmManualUpgradeAction ? ( + + {isConfirmingManualUpgrade ? '升级中...' : '升级'} + + ) : ( + { + if (selectedBinary) { + onUploadBinary(selectedBinary); + } + }} + disabled={ + !selectedBinary || + isUploadingBinary || + isConfirmingManualUpgrade + } + > + {isUploadingBinary ? '上传检查中...' : '上传并检查'} + + )} +
+ {manualErrorMessage ? (