feat: 服务器导入弹窗主题化——CloudServerImport+ExcelServerImport全量替换硬编码slate色为CSS变量+补DialogDescription消除Radix警告
This commit is contained in:
1 parent
bc7323e687
commit
c111be7610
2 files changed
+113
-111
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import {
|
import {
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import {
|
import {
|
||||||
@@ -82,8 +83,8 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleServerToggle = (serverId: string) => {
|
const handleServerToggle = (serverId: string) => {
|
||||||
setSelectedServers(prev =>
|
setSelectedServers(prev =>
|
||||||
prev.includes(serverId)
|
prev.includes(serverId)
|
||||||
? prev.filter(id => id !== serverId)
|
? prev.filter(id => id !== serverId)
|
||||||
: [...prev, serverId]
|
: [...prev, serverId]
|
||||||
);
|
);
|
||||||
@@ -98,7 +99,7 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleImport = () => {
|
const handleImport = () => {
|
||||||
// Mock import action
|
// 云主机发现功能尚未实现,暂以空操作占位
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onClose();
|
onClose();
|
||||||
setStep(1);
|
setStep(1);
|
||||||
@@ -124,9 +125,10 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={handleClose}>
|
<Dialog open={open} onOpenChange={handleClose}>
|
||||||
<DialogContent className="max-w-3xl bg-slate-900 border-slate-700 text-slate-100">
|
<DialogContent className="max-w-3xl bg-[var(--dg-bg-primary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-xl">导入云主机</DialogTitle>
|
<DialogTitle className="text-xl">导入云主机</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">从云平台批量导入主机资产</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{/* Step Indicator */}
|
{/* Step Indicator */}
|
||||||
@@ -134,18 +136,18 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
{steps.map((s, index) => (
|
{steps.map((s, index) => (
|
||||||
<div key={s.id} className="flex items-center">
|
<div key={s.id} className="flex items-center">
|
||||||
<div className={`flex items-center gap-2 px-4 py-2 rounded-lg ${
|
<div className={`flex items-center gap-2 px-4 py-2 rounded-lg ${
|
||||||
step === s.id
|
step === s.id
|
||||||
? 'bg-blue-600 text-white'
|
? 'bg-blue-600 text-white'
|
||||||
: step > s.id
|
: step > s.id
|
||||||
? 'bg-emerald-600/20 text-emerald-400'
|
? 'bg-emerald-600/20 text-emerald-400'
|
||||||
: 'bg-slate-800 text-slate-500'
|
: 'bg-[var(--dg-bg-secondary)] text-[var(--dg-text-muted)]'
|
||||||
}`}>
|
}`}>
|
||||||
<s.icon className="h-4 w-4" />
|
<s.icon className="h-4 w-4" />
|
||||||
<span className="text-sm font-medium">{s.title}</span>
|
<span className="text-sm font-medium">{s.title}</span>
|
||||||
{step > s.id && <Check className="h-4 w-4 ml-1" />}
|
{step > s.id && <Check className="h-4 w-4 ml-1" />}
|
||||||
</div>
|
</div>
|
||||||
{index < steps.length - 1 && (
|
{index < steps.length - 1 && (
|
||||||
<ChevronRight className="h-5 w-5 text-slate-600 mx-2" />
|
<ChevronRight className="h-5 w-5 text-[var(--dg-text-muted)] mx-2" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -154,15 +156,15 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
{/* Step 1: Select Cloud Provider */}
|
{/* Step 1: Select Cloud Provider */}
|
||||||
{step === 1 && (
|
{step === 1 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-slate-400 text-sm">请选择要导入云主机的云平台</p>
|
<p className="text-[var(--dg-text-secondary)] text-sm">请选择要导入云主机的云平台</p>
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
{cloudProviders.map((provider) => (
|
{cloudProviders.map((provider) => (
|
||||||
<Card
|
<Card
|
||||||
key={provider.id}
|
key={provider.id}
|
||||||
className={`cursor-pointer transition-all ${
|
className={`cursor-pointer transition-all ${
|
||||||
selectedProvider === provider.id
|
selectedProvider === provider.id
|
||||||
? 'border-blue-500 ring-2 ring-blue-500/30 bg-slate-800'
|
? 'border-blue-500 ring-2 ring-blue-500/30 bg-[var(--dg-bg-secondary)]'
|
||||||
: 'border-slate-700 hover:border-slate-600 bg-slate-800/50'
|
: 'border-[var(--dg-border)] hover:border-blue-500 bg-[var(--dg-bg-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setSelectedProvider(provider.id)}
|
onClick={() => setSelectedProvider(provider.id)}
|
||||||
>
|
>
|
||||||
@@ -170,7 +172,7 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
<div className={`w-10 h-10 rounded-lg ${provider.color} flex items-center justify-center text-white font-bold text-sm`}>
|
<div className={`w-10 h-10 rounded-lg ${provider.color} flex items-center justify-center text-white font-bold text-sm`}>
|
||||||
{provider.icon}
|
{provider.icon}
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium text-slate-200">{provider.name}</span>
|
<span className="font-medium text-[var(--dg-text-primary)]">{provider.name}</span>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
@@ -182,13 +184,13 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
{step === 2 && (
|
{step === 2 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-slate-400 text-sm">请选择访问凭据(AccessKey)</p>
|
<p className="text-[var(--dg-text-secondary)] text-sm">请选择访问凭据(AccessKey)</p>
|
||||||
<Button variant="outline" size="sm" className="border-slate-700 text-slate-300">
|
<Button variant="outline" size="sm" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">
|
||||||
<RefreshCw className="h-3 w-3 mr-1" />
|
<RefreshCw className="h-3 w-3 mr-1" />
|
||||||
刷新
|
刷新
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{filteredAccounts.length > 0 ? (
|
{filteredAccounts.length > 0 ? (
|
||||||
<div className="space-y-2 max-h-[300px] overflow-y-auto">
|
<div className="space-y-2 max-h-[300px] overflow-y-auto">
|
||||||
{filteredAccounts.map((account) => (
|
{filteredAccounts.map((account) => (
|
||||||
@@ -196,8 +198,8 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
key={account.id}
|
key={account.id}
|
||||||
className={`cursor-pointer transition-all ${
|
className={`cursor-pointer transition-all ${
|
||||||
selectedAccount === account.id
|
selectedAccount === account.id
|
||||||
? 'border-blue-500 ring-1 ring-blue-500/30 bg-slate-800'
|
? 'border-blue-500 ring-1 ring-blue-500/30 bg-[var(--dg-bg-secondary)]'
|
||||||
: 'border-slate-700 hover:border-slate-600 bg-slate-800/50'
|
: 'border-[var(--dg-border)] hover:border-blue-500 bg-[var(--dg-bg-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setSelectedAccount(account.id)}
|
onClick={() => setSelectedAccount(account.id)}
|
||||||
>
|
>
|
||||||
@@ -205,25 +207,25 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Key className="h-5 w-5 text-blue-400" />
|
<Key className="h-5 w-5 text-blue-400" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-slate-200">{account.name}</p>
|
<p className="font-medium text-[var(--dg-text-primary)]">{account.name}</p>
|
||||||
<p className="text-xs text-slate-500">{(account.access_key || account.accessKey || '').slice(0, 20)}...</p>
|
<p className="text-xs text-[var(--dg-text-muted)]">{(account.access_key || account.accessKey || '').slice(0, 20)}...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<Badge className="bg-emerald-500/20 text-emerald-400 border-0">
|
<Badge className="bg-emerald-500/20 text-emerald-400 border-0">
|
||||||
{account.region_count ?? account.regionCount ?? 0} 区域
|
{account.region_count ?? account.regionCount ?? 0} 区域
|
||||||
</Badge>
|
</Badge>
|
||||||
<p className="text-xs text-slate-500 mt-1">{account.server_count ?? account.serverCount ?? 0} 服务器</p>
|
<p className="text-xs text-[var(--dg-text-muted)] mt-1">{account.server_count ?? account.serverCount ?? 0} 服务器</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 text-slate-500">
|
<div className="text-center py-8 text-[var(--dg-text-muted)]">
|
||||||
<Key className="h-12 w-12 mx-auto mb-3 opacity-50" />
|
<Key className="h-12 w-12 mx-auto mb-3 opacity-50" />
|
||||||
<p>该云平台暂无有效的访问凭据</p>
|
<p>该云平台暂无有效的访问凭据</p>
|
||||||
<Button variant="outline" className="mt-3 border-slate-700 text-slate-300">
|
<Button variant="outline" className="mt-3 border-[var(--dg-border)] text-[var(--dg-text-secondary)]">
|
||||||
添加访问凭据
|
添加访问凭据
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -235,9 +237,9 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
{step === 3 && (
|
{step === 3 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-slate-400 text-sm">请选择要导入的云主机</p>
|
<p className="text-[var(--dg-text-secondary)] text-sm">请选择要导入的云主机</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button variant="outline" size="sm" className="border-slate-700 text-slate-300" onClick={handleSelectAll} disabled={cloudServers.length === 0}>
|
<Button variant="outline" size="sm" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]" onClick={handleSelectAll} disabled={cloudServers.length === 0}>
|
||||||
{selectedServers.length === cloudServers.length && cloudServers.length > 0 ? '取消全选' : '全选'}
|
{selectedServers.length === cloudServers.length && cloudServers.length > 0 ? '取消全选' : '全选'}
|
||||||
</Button>
|
</Button>
|
||||||
<Badge className="bg-blue-500/20 text-blue-400 border-0">
|
<Badge className="bg-blue-500/20 text-blue-400 border-0">
|
||||||
@@ -249,41 +251,41 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<RefreshCw className="h-8 w-8 mx-auto mb-3 animate-spin text-blue-400" />
|
<RefreshCw className="h-8 w-8 mx-auto mb-3 animate-spin text-blue-400" />
|
||||||
<p className="text-slate-400">正在获取云主机列表...</p>
|
<p className="text-[var(--dg-text-secondary)]">正在获取云主机列表...</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="border rounded-lg border-slate-700 overflow-hidden max-h-[350px] overflow-y-auto">
|
<div className="border rounded-lg border-[var(--dg-border)] overflow-hidden max-h-[350px] overflow-y-auto">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-slate-800 sticky top-0">
|
<thead className="bg-[var(--dg-bg-secondary)] sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400 w-10">
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)] w-10">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedServers.length === cloudServers.length && cloudServers.length > 0}
|
checked={selectedServers.length === cloudServers.length && cloudServers.length > 0}
|
||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">实例ID</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">实例ID</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">主机名</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">主机名</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">IP地址</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">IP地址</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">区域</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">区域</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">规格</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">规格</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">状态</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">状态</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-slate-800">
|
<tbody className="divide-y divide-[var(--dg-border)]">
|
||||||
{cloudServers.length === 0 ? (
|
{cloudServers.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7} className="px-3 py-12 text-center">
|
<td colSpan={7} className="px-3 py-12 text-center">
|
||||||
<Server className="h-10 w-10 mx-auto mb-3 text-slate-600" />
|
<Server className="h-10 w-10 mx-auto mb-3 text-[var(--dg-text-muted)]" />
|
||||||
<p className="text-slate-500">暂无云主机</p>
|
<p className="text-[var(--dg-text-muted)]">暂无云主机</p>
|
||||||
<p className="text-xs text-slate-600 mt-1">云主机发现功能尚未实现,请手动添加服务器</p>
|
<p className="text-xs text-[var(--dg-text-muted)] mt-1">云主机发现功能尚未实现,请手动添加服务器</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
cloudServers.map((server) => (
|
cloudServers.map((server) => (
|
||||||
<tr
|
<tr
|
||||||
key={server.id}
|
key={server.id}
|
||||||
className={`hover:bg-slate-800/50 ${selectedServers.includes(server.id) ? 'bg-blue-500/10' : ''}`}
|
className={`hover:bg-[var(--dg-bg-hover)] ${selectedServers.includes(server.id) ? 'bg-blue-500/10' : ''}`}
|
||||||
>
|
>
|
||||||
<td className="px-3 py-2">
|
<td className="px-3 py-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -291,11 +293,11 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
onCheckedChange={() => handleServerToggle(server.id)}
|
onCheckedChange={() => handleServerToggle(server.id)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm text-slate-300">{server.name}</td>
|
<td className="px-3 py-2 text-sm text-[var(--dg-text-secondary)]">{server.name}</td>
|
||||||
<td className="px-3 py-2 text-sm text-slate-200">{server.hostname}</td>
|
<td className="px-3 py-2 text-sm text-[var(--dg-text-primary)]">{server.hostname}</td>
|
||||||
<td className="px-3 py-2 text-sm text-slate-400">{server.ip}</td>
|
<td className="px-3 py-2 text-sm text-[var(--dg-text-secondary)]">{server.ip}</td>
|
||||||
<td className="px-3 py-2 text-sm text-slate-400">{server.region}</td>
|
<td className="px-3 py-2 text-sm text-[var(--dg-text-secondary)]">{server.region}</td>
|
||||||
<td className="px-3 py-2 text-sm text-slate-400">{server.spec}</td>
|
<td className="px-3 py-2 text-sm text-[var(--dg-text-secondary)]">{server.spec}</td>
|
||||||
<td className="px-3 py-2">
|
<td className="px-3 py-2">
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -321,19 +323,19 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
<DialogFooter className="flex justify-between items-center">
|
<DialogFooter className="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
{step > 1 && (
|
{step > 1 && (
|
||||||
<Button variant="outline" onClick={handleBack} className="border-slate-700 text-slate-300">
|
<Button variant="outline" onClick={handleBack} className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">
|
||||||
<ChevronLeft className="h-4 w-4 mr-1" />
|
<ChevronLeft className="h-4 w-4 mr-1" />
|
||||||
上一步
|
上一步
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="outline" onClick={handleClose} className="border-slate-700 text-slate-300">
|
<Button variant="outline" onClick={handleClose} className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
{step < 3 ? (
|
{step < 3 ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
disabled={(step === 1 && !selectedProvider) || (step === 2 && !selectedAccount)}
|
disabled={(step === 1 && !selectedProvider) || (step === 2 && !selectedAccount)}
|
||||||
className="bg-blue-600 hover:bg-blue-700"
|
className="bg-blue-600 hover:bg-blue-700"
|
||||||
>
|
>
|
||||||
@@ -341,7 +343,7 @@ export function CloudServerImport({ open, onClose }: CloudServerImportProps) {
|
|||||||
<ChevronRight className="h-4 w-4 ml-1" />
|
<ChevronRight className="h-4 w-4 ml-1" />
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleImport}
|
onClick={handleImport}
|
||||||
disabled={selectedServers.length === 0}
|
disabled={selectedServers.length === 0}
|
||||||
className="bg-emerald-600 hover:bg-emerald-700"
|
className="bg-emerald-600 hover:bg-emerald-700"
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import {
|
import {
|
||||||
Download,
|
Download,
|
||||||
Upload,
|
Upload,
|
||||||
FileSpreadsheet,
|
FileSpreadsheet,
|
||||||
@@ -271,7 +271,7 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={handleClose}>
|
<Dialog open={open} onOpenChange={handleClose}>
|
||||||
<DialogContent className="max-w-3xl bg-slate-900 border-slate-700 text-slate-100">
|
<DialogContent className="max-w-3xl bg-[var(--dg-bg-primary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-xl">Excel导入服务器</DialogTitle>
|
<DialogTitle className="text-xl">Excel导入服务器</DialogTitle>
|
||||||
<DialogDescription className="sr-only">通过 CSV 文件批量导入服务器资产</DialogDescription>
|
<DialogDescription className="sr-only">通过 CSV 文件批量导入服务器资产</DialogDescription>
|
||||||
@@ -282,19 +282,19 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
{steps.map((s, index) => (
|
{steps.map((s, index) => (
|
||||||
<div key={s.id} className="flex items-center">
|
<div key={s.id} className="flex items-center">
|
||||||
<div className={`flex items-center justify-center w-8 h-8 rounded-full ${
|
<div className={`flex items-center justify-center w-8 h-8 rounded-full ${
|
||||||
step === s.id
|
step === s.id
|
||||||
? 'bg-blue-600 text-white'
|
? 'bg-blue-600 text-white'
|
||||||
: step > s.id
|
: step > s.id
|
||||||
? 'bg-emerald-600 text-white'
|
? 'bg-emerald-600 text-white'
|
||||||
: 'bg-slate-800 text-slate-500'
|
: 'bg-[var(--dg-bg-secondary)] text-[var(--dg-text-muted)]'
|
||||||
}`}>
|
}`}>
|
||||||
{step > s.id ? <Check className="h-4 w-4" /> : s.id}
|
{step > s.id ? <Check className="h-4 w-4" /> : s.id}
|
||||||
</div>
|
</div>
|
||||||
<span className={`ml-2 text-sm ${step === s.id ? 'text-slate-200' : 'text-slate-500'}`}>
|
<span className={`ml-2 text-sm ${step === s.id ? 'text-[var(--dg-text-primary)]' : 'text-[var(--dg-text-muted)]'}`}>
|
||||||
{s.title}
|
{s.title}
|
||||||
</span>
|
</span>
|
||||||
{index < steps.length - 1 && (
|
{index < steps.length - 1 && (
|
||||||
<div className="w-12 h-0.5 bg-slate-700 mx-3" />
|
<div className="w-12 h-0.5 bg-[var(--dg-border)] mx-3" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -304,7 +304,7 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
{step === 1 && (
|
{step === 1 && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Download Template */}
|
{/* Download Template */}
|
||||||
<Card className="bg-slate-800 border-slate-700">
|
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@@ -312,11 +312,11 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
<FileSpreadsheet className="h-5 w-5 text-emerald-400" />
|
<FileSpreadsheet className="h-5 w-5 text-emerald-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-slate-200">主机导入模板</p>
|
<p className="font-medium text-[var(--dg-text-primary)]">主机导入模板</p>
|
||||||
<p className="text-xs text-slate-500">下载Excel模板,填写服务器信息后上传</p>
|
<p className="text-xs text-[var(--dg-text-muted)]">下载Excel模板,填写服务器信息后上传</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" className="border-slate-700 text-slate-300" onClick={downloadTemplate}>
|
<Button variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]" onClick={downloadTemplate}>
|
||||||
<Download className="h-4 w-4 mr-2" />
|
<Download className="h-4 w-4 mr-2" />
|
||||||
下载模板
|
下载模板
|
||||||
</Button>
|
</Button>
|
||||||
@@ -326,11 +326,11 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
|
|
||||||
{/* Region Selection */}
|
{/* Region Selection */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-300 mb-2">选择区域</label>
|
<label className="block text-sm font-medium text-[var(--dg-text-secondary)] mb-2">选择区域</label>
|
||||||
<select
|
<select
|
||||||
value={selectedRegion}
|
value={selectedRegion}
|
||||||
onChange={(e) => setSelectedRegion(e.target.value)}
|
onChange={(e) => setSelectedRegion(e.target.value)}
|
||||||
className="w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded-md text-slate-100 text-sm"
|
className="w-full px-3 py-2 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-md text-[var(--dg-text-primary)] text-sm"
|
||||||
>
|
>
|
||||||
<option value="">请选择区域</option>
|
<option value="">请选择区域</option>
|
||||||
{regions.map((region) => (
|
{regions.map((region) => (
|
||||||
@@ -343,21 +343,21 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
|
|
||||||
{/* Connection Method */}
|
{/* Connection Method */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-300 mb-2">连接方式</label>
|
<label className="block text-sm font-medium text-[var(--dg-text-secondary)] mb-2">连接方式</label>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
{connectionMethods.map((method) => (
|
{connectionMethods.map((method) => (
|
||||||
<Card
|
<Card
|
||||||
key={method.id}
|
key={method.id}
|
||||||
className={`cursor-pointer transition-all ${
|
className={`cursor-pointer transition-all ${
|
||||||
connectionMethod === method.id
|
connectionMethod === method.id
|
||||||
? 'border-blue-500 ring-1 ring-blue-500/30 bg-slate-800'
|
? 'border-blue-500 ring-1 ring-blue-500/30 bg-[var(--dg-bg-secondary)]'
|
||||||
: 'border-slate-700 hover:border-slate-600 bg-slate-800/50'
|
: 'border-[var(--dg-border)] hover:border-blue-500 bg-[var(--dg-bg-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setConnectionMethod(method.id)}
|
onClick={() => setConnectionMethod(method.id)}
|
||||||
>
|
>
|
||||||
<CardContent className="p-3">
|
<CardContent className="p-3">
|
||||||
<p className="font-medium text-slate-200">{method.name}</p>
|
<p className="font-medium text-[var(--dg-text-primary)]">{method.name}</p>
|
||||||
<p className="text-xs text-slate-500">{method.description}</p>
|
<p className="text-xs text-[var(--dg-text-muted)]">{method.description}</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
@@ -369,12 +369,12 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
{/* Step 2: Upload */}
|
{/* Step 2: Upload */}
|
||||||
{step === 2 && (
|
{step === 2 && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div
|
<div
|
||||||
className="border-2 border-dashed border-slate-700 rounded-lg p-8 text-center hover:border-blue-500/50 transition-colors"
|
className="border-2 border-dashed border-[var(--dg-border)] rounded-lg p-8 text-center hover:border-blue-500/50 transition-colors"
|
||||||
>
|
>
|
||||||
<Upload className="h-12 w-12 mx-auto mb-4 text-slate-500" />
|
<Upload className="h-12 w-12 mx-auto mb-4 text-[var(--dg-text-muted)]" />
|
||||||
<p className="text-slate-300 mb-2">点击或拖拽文件到此处上传</p>
|
<p className="text-[var(--dg-text-secondary)] mb-2">点击或拖拽文件到此处上传</p>
|
||||||
<p className="text-xs text-slate-500 mb-4">支持 CSV 格式文件(可点击左侧"下载模板"获取)</p>
|
<p className="text-xs text-[var(--dg-text-muted)] mb-4">支持 CSV 格式文件(可点击左侧"下载模板"获取)</p>
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
accept=".csv,text/csv"
|
accept=".csv,text/csv"
|
||||||
@@ -383,26 +383,26 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
id="excel-upload"
|
id="excel-upload"
|
||||||
/>
|
/>
|
||||||
<label htmlFor="excel-upload">
|
<label htmlFor="excel-upload">
|
||||||
<Button variant="outline" className="border-slate-700 text-slate-300" asChild>
|
<Button variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]" asChild>
|
||||||
<span>选择文件</span>
|
<span>选择文件</span>
|
||||||
</Button>
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedFile && (
|
{selectedFile && (
|
||||||
<Card className="bg-slate-800 border-slate-700">
|
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||||
<CardContent className="p-3 flex items-center justify-between">
|
<CardContent className="p-3 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<FileSpreadsheet className="h-8 w-8 text-emerald-400" />
|
<FileSpreadsheet className="h-8 w-8 text-emerald-400" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-slate-200">{selectedFile.name}</p>
|
<p className="text-sm text-[var(--dg-text-primary)]">{selectedFile.name}</p>
|
||||||
<p className="text-xs text-slate-500">{(selectedFile.size / 1024).toFixed(2)} KB</p>
|
<p className="text-xs text-[var(--dg-text-muted)]">{(selectedFile.size / 1024).toFixed(2)} KB</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="text-slate-400 hover:text-red-400"
|
className="text-[var(--dg-text-muted)] hover:text-red-400"
|
||||||
onClick={() => setSelectedFile(null)}
|
onClick={() => setSelectedFile(null)}
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
@@ -414,7 +414,7 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
{isUploading && (
|
{isUploading && (
|
||||||
<div className="text-center py-4">
|
<div className="text-center py-4">
|
||||||
<div className="w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-2" />
|
<div className="w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-2" />
|
||||||
<p className="text-slate-400 text-sm">正在解析文件...</p>
|
<p className="text-[var(--dg-text-secondary)] text-sm">正在解析文件...</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -424,7 +424,7 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
{step === 3 && (
|
{step === 3 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-slate-400 text-sm">数据预览</p>
|
<p className="text-[var(--dg-text-secondary)] text-sm">数据预览</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge className="bg-emerald-500/20 text-emerald-400 border-0">
|
<Badge className="bg-emerald-500/20 text-emerald-400 border-0">
|
||||||
<Check className="h-3 w-3 mr-1" />
|
<Check className="h-3 w-3 mr-1" />
|
||||||
@@ -445,26 +445,26 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border rounded-lg border-slate-700 overflow-hidden max-h-[350px] overflow-y-auto">
|
<div className="border rounded-lg border-[var(--dg-border)] overflow-hidden max-h-[350px] overflow-y-auto">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-slate-800 sticky top-0">
|
<thead className="bg-[var(--dg-bg-secondary)] sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">行号</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">行号</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">主机名</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">主机名</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">IP地址</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">IP地址</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">端口</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">端口</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">用户名</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">用户名</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-slate-400">状态</th>
|
<th className="px-3 py-2 text-left text-xs font-medium text-[var(--dg-text-secondary)]">状态</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-slate-800">
|
<tbody className="divide-y divide-[var(--dg-border)]">
|
||||||
{previewData.map((row) => (
|
{previewData.map((row) => (
|
||||||
<tr key={row.row} className="hover:bg-slate-800/50">
|
<tr key={row.row} className="hover:bg-[var(--dg-bg-hover)]">
|
||||||
<td className="px-3 py-2 text-sm text-slate-500">{row.row}</td>
|
<td className="px-3 py-2 text-sm text-[var(--dg-text-muted)]">{row.row}</td>
|
||||||
<td className="px-3 py-2 text-sm text-slate-200">{row.hostname || '-'}</td>
|
<td className="px-3 py-2 text-sm text-[var(--dg-text-primary)]">{row.hostname || '-'}</td>
|
||||||
<td className="px-3 py-2 text-sm text-slate-300">{row.ip}</td>
|
<td className="px-3 py-2 text-sm text-[var(--dg-text-secondary)]">{row.ip}</td>
|
||||||
<td className="px-3 py-2 text-sm text-slate-300">{row.port}</td>
|
<td className="px-3 py-2 text-sm text-[var(--dg-text-secondary)]">{row.port}</td>
|
||||||
<td className="px-3 py-2 text-sm text-slate-300">{row.username}</td>
|
<td className="px-3 py-2 text-sm text-[var(--dg-text-secondary)]">{row.username}</td>
|
||||||
<td className="px-3 py-2">
|
<td className="px-3 py-2">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{row.status === 'valid' && (
|
{row.status === 'valid' && (
|
||||||
@@ -508,19 +508,19 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
<DialogFooter className="flex justify-between items-center">
|
<DialogFooter className="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
{step > 1 && (
|
{step > 1 && (
|
||||||
<Button variant="outline" onClick={handleBack} className="border-slate-700 text-slate-300">
|
<Button variant="outline" onClick={handleBack} className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">
|
||||||
<ChevronLeft className="h-4 w-4 mr-1" />
|
<ChevronLeft className="h-4 w-4 mr-1" />
|
||||||
上一步
|
上一步
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="outline" onClick={handleClose} className="border-slate-700 text-slate-300">
|
<Button variant="outline" onClick={handleClose} className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
{step < 3 ? (
|
{step < 3 ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
disabled={(step === 1 && !selectedRegion) || (step === 2 && !selectedFile)}
|
disabled={(step === 1 && !selectedRegion) || (step === 2 && !selectedFile)}
|
||||||
className="bg-blue-600 hover:bg-blue-700"
|
className="bg-blue-600 hover:bg-blue-700"
|
||||||
>
|
>
|
||||||
@@ -528,7 +528,7 @@ export function ExcelServerImport({ open, onClose }: ExcelServerImportProps) {
|
|||||||
<ChevronRight className="h-4 w-4 ml-1" />
|
<ChevronRight className="h-4 w-4 ml-1" />
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleImport}
|
onClick={handleImport}
|
||||||
disabled={isImporting || errorCount > 0 || validCount === 0}
|
disabled={isImporting || errorCount > 0 || validCount === 0}
|
||||||
className="bg-emerald-600 hover:bg-emerald-700"
|
className="bg-emerald-600 hover:bg-emerald-700"
|
||||||
|
|||||||
Reference in new issue
Block a user