snapshot: AnsibleManager拆分前快照

This commit is contained in:
attacker committed 2026-06-15 10:01:24 +08:00
1 parent 16205fdb2b
commit e6d9f364e5
13 files changed
+660 -103

No files matched your search

@@ -3,6 +3,7 @@ import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Checkbox } from '@/components/ui/checkbox';
import {
Dialog,
DialogContent,
@@ -59,6 +60,8 @@ export function CronJobManager() {
const [executionsLoading, setExecutionsLoading] = useState(false);
const [expandedExec, setExpandedExec] = useState<number | null>(null);
const [executingId, setExecutingId] = useState<number | null>(null);
// 批量选择(批量删除) // Logan.Li
const [selectedIds, setSelectedIds] = useState<number[]>([]);
// editingId === null 表示新建模式;非 null 表示编辑已有任务 // Logan.Li
const [editingId, setEditingId] = useState<number | null>(null);
const [formJob, setFormJob] = useState({
@@ -250,6 +253,42 @@ export function CronJobManager() {
}
};
// 批量选择相关 // Logan.Li
const toggleSelect = (id: number) => {
setSelectedIds((prev) =>
prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]
);
};
const toggleSelectAll = () => {
if (selectedIds.length === filteredJobs.length && filteredJobs.length > 0) {
setSelectedIds([]);
} else {
setSelectedIds(filteredJobs.map((j) => j.id));
}
};
// 批量删除任务(带确认框) // Logan.Li
const handleBatchDelete = async () => {
if (selectedIds.length === 0) return;
const ok = await _confirm({
title: '批量删除定时任务',
description: `确定删除选中的 ${selectedIds.length} 个任务吗?此操作不可恢复。`,
confirmText: '批量删除',
variant: 'destructive',
});
if (!ok) return;
try {
await cronJobApi.batchDelete(selectedIds);
toast.success(`已删除 ${selectedIds.length} 个任务`);
setSelectedIds([]);
refreshData();
} catch (error) {
console.error('Failed to batch delete jobs:', error);
toast.error('批量删除失败');
}
};
return (
<div className="space-y-6">
{/* Header */}
@@ -318,16 +357,44 @@ export function CronJobManager() {
</div>
{/* Filters */}
<div className="flex items-center gap-4">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--dg-text-secondary)]" />
<Input
placeholder="搜索任务..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"
/>
<div className="flex items-center justify-between flex-wrap gap-4">
<div className="flex items-center gap-3 flex-wrap">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--dg-text-secondary)]" />
<Input
placeholder="搜索任务..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"
/>
</div>
{filteredJobs.length > 0 && (
<label className="flex items-center gap-2 text-sm text-[var(--dg-text-secondary)] cursor-pointer select-none">
<Checkbox
checked={
selectedIds.length === filteredJobs.length && filteredJobs.length > 0
}
onCheckedChange={toggleSelectAll}
className="border-[var(--dg-border)]"
/>
全选
</label>
)}
</div>
{selectedIds.length > 0 && (
<div className="flex items-center gap-2">
<span className="text-sm text-[var(--dg-text-secondary)]">已选择 {selectedIds.length} 项</span>
<Button
variant="outline"
size="sm"
className="border-red-500/30 text-red-400 hover:bg-red-500/10"
onClick={handleBatchDelete}
>
<Trash2 className="h-4 w-4 mr-2" />
批量删除
</Button>
</div>
)}
</div>
{initialLoading ? (
@@ -346,7 +413,14 @@ export function CronJobManager() {
{filteredJobs.map((job) => (
<Card key={job.id} className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4">
<div className="flex items-start justify-between">
<div className="flex items-start justify-between gap-3">
<div className="flex items-center pt-1">
<Checkbox
checked={selectedIds.includes(job.id)}
onCheckedChange={() => toggleSelect(job.id)}
className="border-[var(--dg-border)]"
/>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 flex-wrap">
<h3 className="font-medium text-[var(--dg-text-primary)]">{job.name}</h3>