feat: 定时任务编辑入口全栈打通 + 修复ApprovalFlow编译错误 + 完成大文件拆分重构

- CronJobManager: 编辑按钮接通 onClick → openEdit(job),弹出编辑对话框预填数据,
  复用新建/编辑共用 Dialog(editingId 区分模式),标题/按钮文案动态切换,
  保存时根据 editingId 调用 createJob 或 updateJob — 消除占位按钮
- ApprovalFlow: 修复 API 方法名不匹配导致 tsc -b 编译失败
  (getInstances→getProcesses, getMyApprovals→getProcesses({status:'pending'}),
   approveInstance→approveProcess, rejectInstance→rejectProcess)
  + 添加 ApprovalFlowManager 导出别名兼容 App.tsx 导入
- 合并已完成的大文件拆分重构: automation.go→automation_cron/script/playbook,
  ldap.go→ldap_ou/ldap_users, server.go→cloud_accounts/domain/platform/region,
  user.go→user_admin; 前端 Dashboard/K8s/Server/Approval/Monitor 子组件拆分
  // Logan.Li
This commit is contained in:
attacker committed 2026-06-15 01:09:49 +08:00
1 parent 8be63ac168
commit a7e2351ad2
37 files changed
+7374 -4891

No files matched your search

+12 -277
View File
@@ -3,15 +3,8 @@ import { cn } from '@/lib/utils';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Badge } from '@/components/ui/badge';
import { Checkbox } from '@/components/ui/checkbox';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import {
DropdownMenu,
DropdownMenuContent,
@@ -28,14 +21,6 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
@@ -59,49 +44,12 @@ import {
Cloud,
FileSpreadsheet,
RefreshCw,
ChevronDown,
Loader2,
Save,
} from 'lucide-react';
import { serverApi } from '@/services/api';
import type { Server as ServerType } from '@/types';
import { CloudServerImport } from './CloudServerImport';
import { ExcelServerImport } from './ExcelServerImport';
// 表单数据结构 — 与后端 CreateServerRequest/UpdateServerRequest 字段对齐
interface ServerFormData {
hostname: string;
ip: string;
public_ip: string;
port: string;
username: string;
auth_type: string; // password | key
password: string;
os: string;
cpu: string;
memory: string; // GB (表单) → MB (后端)
disk: string;
status: string;
tags: string;
description: string;
}
const EMPTY_FORM: ServerFormData = {
hostname: '',
ip: '',
public_ip: '',
port: '22',
username: 'root',
auth_type: 'password',
password: '',
os: '',
cpu: '',
memory: '',
disk: '',
status: 'running',
tags: '',
description: '',
};
import { ServerForm, EMPTY_FORM, type ServerFormData } from './ServerForm';
export function ServerManager() {
const [servers, setServers] = useState<any[]>([]);
@@ -660,230 +608,17 @@ export function ServerManager() {
</Card>
{/* 创建/编辑服务器 Dialog */}
<Dialog open={formOpen} onOpenChange={setFormOpen}>
<DialogContent className="max-w-2xl bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
<Server className="h-5 w-5 text-[var(--dg-blue)]" />
{editingId ? '编辑服务器' : '添加服务器'}
</DialogTitle>
<DialogDescription className="text-[var(--dg-text-muted)]">
{editingId ? '修改服务器资产信息' : '录入新的服务器资产到 CMDB'}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-2">
{/* 基础信息 */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">
主机名 <span className="text-red-400">*</span>
</Label>
<Input
placeholder="web-server-01"
value={formData.hostname}
onChange={(e) => setFormData({ ...formData, hostname: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">
IP地址 <span className="text-red-400">*</span>
</Label>
<Input
placeholder="10.0.0.1"
value={formData.ip}
onChange={(e) => setFormData({ ...formData, ip: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
</div>
{/* SSH 连接配置 */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">公网IP</Label>
<Input
placeholder="如 203.0.113.10"
value={formData.public_ip}
onChange={(e) => setFormData({ ...formData, public_ip: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">SSH端口</Label>
<Input
type="number"
placeholder="22"
value={formData.port}
onChange={(e) => setFormData({ ...formData, port: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">登录用户</Label>
<Input
placeholder="root"
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">认证方式</Label>
<Select
value={formData.auth_type}
onValueChange={(v) => setFormData({ ...formData, auth_type: v })}
>
<SelectTrigger className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]">
<SelectItem value="password">密码认证</SelectItem>
<SelectItem value="key">密钥认证</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">
{formData.auth_type === 'key' ? 'SSH 私钥' : '登录密码'}
{!editingId && <span className="text-red-400 ml-1">*</span>}
{editingId && <span className="text-[var(--dg-text-muted)] ml-1 text-xs">(留空则不修改)</span>}
</Label>
<Textarea
placeholder={formData.auth_type === 'key' ? '粘贴 SSH 私钥内容...' : '输入登录密码...'}
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)] min-h-[50px] font-mono text-sm"
/>
</div>
{/* 更多详情 - 可折叠区域 */}
<Collapsible open={detailsOpen} onOpenChange={setDetailsOpen}>
<CollapsibleTrigger asChild>
<Button
variant="ghost"
size="sm"
className="w-full justify-between text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)]"
>
<span className="flex items-center gap-2">
<Server className="h-4 w-4" />
更多详情(OS版本 / 运行状态 / CPU / 内存 / 磁盘 / 标签 / 描述)
</span>
<ChevronDown className={cn('h-4 w-4 transition-transform', detailsOpen && 'rotate-180')} />
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="space-y-4 pt-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">操作系统</Label>
<Input
placeholder="如 CentOS 7.9"
value={formData.os}
onChange={(e) => setFormData({ ...formData, os: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">运行状态</Label>
<Select
value={formData.status}
onValueChange={(v) => setFormData({ ...formData, status: v })}
>
<SelectTrigger className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]">
<SelectItem value="running">运行中</SelectItem>
<SelectItem value="stopped">已停止</SelectItem>
<SelectItem value="maintenance">维护中</SelectItem>
<SelectItem value="offline">离线</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">CPU核数</Label>
<Input
type="number"
placeholder="4"
value={formData.cpu}
onChange={(e) => setFormData({ ...formData, cpu: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">内存 (GB)</Label>
<Input
type="number"
placeholder="8"
value={formData.memory}
onChange={(e) => setFormData({ ...formData, memory: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">磁盘 (GB)</Label>
<Input
type="number"
placeholder="100"
value={formData.disk}
onChange={(e) => setFormData({ ...formData, disk: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">标签</Label>
<Input
placeholder='JSON 格式,如 {"project":"web","team":"ops"}'
value={formData.tags}
onChange={(e) => setFormData({ ...formData, tags: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)] font-mono text-sm"
/>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]">描述</Label>
<Textarea
placeholder="补充说明..."
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)] min-h-[60px]"
/>
</div>
</CollapsibleContent>
</Collapsible>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setFormOpen(false)}
className="border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]"
>
取消
</Button>
<Button
onClick={handleSave}
disabled={!formData.hostname || !formData.ip || formSaving}
className="bg-[var(--dg-blue)] hover:opacity-90 text-white"
>
{formSaving ? (
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
) : (
<Save className="h-4 w-4 mr-2" />
)}
{editingId ? '保存修改' : '创建'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<ServerForm
open={formOpen}
onOpenChange={setFormOpen}
editingId={editingId}
formData={formData}
setFormData={setFormData}
detailsOpen={detailsOpen}
setDetailsOpen={setDetailsOpen}
onSave={handleSave}
formSaving={formSaving}
/>
{/* Import Dialogs */}
<CloudServerImport