refactor(quality): Q12 前端巨型组件拆分 — CloudServerImport 699L→404L (-42%), 提取 StepIndicator + 4 个向导步骤组件 + types, any[]→具体接口类型, 移除文件级 eslint-disable
This commit is contained in:
1 parent
76aa8817c0
commit
b2e8fbbfc0
10 files changed
+660
-394
No files matched your search
@@ -0,0 +1,176 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertCircle, CheckCircle, MapPin, RefreshCw, Server } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import type { CloudRegion, CloudServer, ImportResult } from './types';
|
||||
|
||||
interface DiscoverImportStepProps {
|
||||
/** 导入结果(非空时显示结果页) */
|
||||
importResult: ImportResult | null;
|
||||
selectedRegions: string[];
|
||||
regions: CloudRegion[];
|
||||
cloudServers: CloudServer[];
|
||||
selectedServers: string[];
|
||||
isDiscovering: boolean;
|
||||
isImporting: boolean;
|
||||
discoverErrors: string[];
|
||||
/** 导入凭证设置面板(Linux SSH / Windows RDP),由父组件渲染后传入 */
|
||||
importSettingsPanel: ReactNode;
|
||||
onDiscover: () => void;
|
||||
onToggleServer: (instanceId: string) => void;
|
||||
onSelectAllServers: () => void;
|
||||
}
|
||||
|
||||
/** Step 4: 发现并导入云主机(结果页 / 发现按钮 / 区域标签 / 错误提示 / 主机列表 + 凭证设置) */
|
||||
export function DiscoverImportStep({
|
||||
importResult,
|
||||
selectedRegions,
|
||||
regions,
|
||||
cloudServers,
|
||||
selectedServers,
|
||||
isDiscovering,
|
||||
isImporting,
|
||||
discoverErrors,
|
||||
importSettingsPanel,
|
||||
onDiscover,
|
||||
onToggleServer,
|
||||
onSelectAllServers,
|
||||
}: DiscoverImportStepProps) {
|
||||
const { t } = useTranslation();
|
||||
const allServersSelected = selectedServers.length === cloudServers.length && cloudServers.length > 0;
|
||||
|
||||
// 导入完成 — 结果页
|
||||
if (importResult) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
<CheckCircle className="h-12 w-12 mx-auto mb-3 dg-text-green" />
|
||||
<p className="text-lg font-medium text-[var(--dg-text-primary)]">{t('server.importComplete')}</p>
|
||||
<div className="flex items-center justify-center gap-6 mt-4">
|
||||
<Badge className="bg-emerald-500/20 dg-text-green border-0 text-base px-4 py-1">
|
||||
{t('server.importedCount', { count: importResult.imported })}
|
||||
</Badge>
|
||||
{importResult.skipped > 0 && (
|
||||
<Badge className="bg-amber-500/20 dg-text-yellow border-0 text-base px-4 py-1">
|
||||
{t('server.skippedCount', { count: importResult.skipped })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 发现 + 导入流程
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-[var(--dg-text-secondary)] text-sm">
|
||||
{t('server.selectedRegionsCount', { count: selectedRegions.length })}
|
||||
{cloudServers.length > 0 && (
|
||||
<span className="ml-2">· {t('server.discoveredHosts', { count: cloudServers.length })}</span>
|
||||
)}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
{cloudServers.length > 0 && (
|
||||
<>
|
||||
<Button variant="outline" size="sm" onClick={onSelectAllServers} className="border-[var(--dg-border)]">
|
||||
{allServersSelected ? t('common.deselectAll') : t('common.selectAll')}
|
||||
</Button>
|
||||
<Badge className="bg-blue-500/20 dg-text-blue border-0">
|
||||
{t('server.selectedServersCount', { count: selectedServers.length })}
|
||||
</Badge>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onDiscover}
|
||||
disabled={isDiscovering || selectedRegions.length === 0}
|
||||
className="border-[var(--dg-border)] dg-text-blue"
|
||||
>
|
||||
<RefreshCw className={`h-3 w-3 mr-1 ${isDiscovering ? 'animate-spin' : ''}`} />
|
||||
{isDiscovering ? t('server.discovering') : cloudServers.length > 0 ? t('server.rediscover') : t('server.discoverHosts')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 区域标签 */}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{selectedRegions.map((r) => {
|
||||
const region = regions.find((reg) => reg.code === r);
|
||||
return (
|
||||
<Badge key={r} className="bg-[var(--dg-bg-secondary)] text-[var(--dg-text-muted)] border border-[var(--dg-border)] text-xs">
|
||||
<MapPin className="h-3 w-3 mr-1" />
|
||||
{region?.name || r}
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 发现错误提示 */}
|
||||
{discoverErrors.length > 0 && (
|
||||
<div className="flex items-start gap-2 p-3 rounded-lg bg-amber-500/10 border border-amber-500/30 dg-text-yellow text-sm">
|
||||
<AlertCircle className="h-4 w-4 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium">{t('server.partialRegionFailed')}</p>
|
||||
{discoverErrors.map((err, i) => (
|
||||
<p key={i} className="text-xs mt-1 opacity-80">{err}</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 主机列表 */}
|
||||
{isDiscovering ? (
|
||||
<div className="text-center py-12">
|
||||
<RefreshCw className="h-8 w-8 mx-auto mb-3 animate-spin dg-text-blue" />
|
||||
<p className="text-[var(--dg-text-secondary)]">{t('server.discoveringCloudServers')}</p>
|
||||
<p className="text-xs text-[var(--dg-text-muted)] mt-1">{t('server.scanningRegions')}</p>
|
||||
</div>
|
||||
) : cloudServers.length === 0 ? (
|
||||
<div className="text-center py-8 text-[var(--dg-text-muted)]">
|
||||
<Server className="h-10 w-10 mx-auto mb-3 opacity-50" />
|
||||
<p>{t('server.clickDiscoverHint')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="border rounded-lg border-[var(--dg-border)] overflow-hidden max-h-[300px] overflow-y-auto">
|
||||
<table className="w-full">
|
||||
<thead className="bg-[var(--dg-bg-secondary)] sticky top-0">
|
||||
<tr>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)] w-10">
|
||||
<Checkbox checked={allServersSelected} onCheckedChange={onSelectAllServers} />
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">{t('server.excelHostname')}</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">{t('server.excelIpAddress')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[var(--dg-border)]">
|
||||
{cloudServers.map((server) => {
|
||||
const checked = selectedServers.includes(server.instance_id);
|
||||
return (
|
||||
<tr
|
||||
key={server.instance_id}
|
||||
className={`cursor-pointer hover:bg-[var(--dg-bg-hover)] ${checked ? 'bg-blue-500/10' : ''}`}
|
||||
onClick={() => onToggleServer(server.instance_id)}
|
||||
>
|
||||
<td className="px-3 py-2">
|
||||
<Checkbox checked={checked} />
|
||||
</td>
|
||||
<td className="px-3 py-2 text-sm text-[var(--dg-text-primary)]">{server.instance_name || server.hostname || '-'}</td>
|
||||
<td className="px-3 py-2 text-sm text-[var(--dg-text-secondary)]">
|
||||
{server.private_ip || server.public_ip || '-'}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 导入中时禁用,由父组件 Footer 按钮触发 onImport */}
|
||||
{isImporting ? null : importSettingsPanel}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user