feat: implement agent custom scheduler config and queue model #101
This commit is contained in:
+10
-1
@@ -279,7 +279,7 @@ export const api = {
|
||||
agents: {
|
||||
list: () => request<Agent[]>('/agents'),
|
||||
getVersion: () => request<{ version: string; platforms: { os: string; arch: string; filename: string }[] }>('/agents/version'),
|
||||
update: (id: string, data: { name: string; description?: string; enabled: boolean }) =>
|
||||
update: (id: string, data: { name: string; description?: string; enabled: boolean; scheduler_config: SchedulerConfig | null }) =>
|
||||
request('/agents/' + id, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
delete: (id: string) => request('/agents/' + id, { method: 'DELETE' }),
|
||||
forceUpdate: (id: string) => request('/agents/' + id + '/update', { method: 'POST' }),
|
||||
@@ -582,10 +582,19 @@ export interface Agent {
|
||||
os: string
|
||||
arch: string
|
||||
enabled: boolean
|
||||
scheduler_config: SchedulerConfig | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface SchedulerConfig {
|
||||
worker_count: number
|
||||
queue_size: number
|
||||
rate_interval: number
|
||||
verbose: boolean
|
||||
strict_queue: boolean
|
||||
}
|
||||
|
||||
export interface AgentToken {
|
||||
id: string
|
||||
token: string
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ref, onMounted, computed, onUnmounted } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog'
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
@@ -38,7 +39,18 @@ const showDownloadDialog = ref(false)
|
||||
const showTokenDialog = ref(false)
|
||||
const showEditTokenDialog = ref(false)
|
||||
const showDetailDialog = ref(false)
|
||||
const formData = ref({ name: '', description: '' })
|
||||
const customScheduler = ref(false)
|
||||
const formData = ref({
|
||||
name: '',
|
||||
description: '',
|
||||
scheduler_config: {
|
||||
worker_count: 1,
|
||||
queue_size: 100,
|
||||
rate_interval: 200,
|
||||
verbose: false,
|
||||
strict_queue: false
|
||||
}
|
||||
})
|
||||
const tokenForm = ref({ remark: '', max_uses: 0, expires_at: '' })
|
||||
const editingToken = ref<AgentToken | null>(null)
|
||||
const editTokenForm = ref({ remark: '', max_uses: 0, expires_at: '' })
|
||||
@@ -89,14 +101,37 @@ function viewDetail(agent: Agent) {
|
||||
function openEditDialog(agent: Agent) {
|
||||
;(document.activeElement as HTMLElement)?.blur()
|
||||
editingAgent.value = agent
|
||||
formData.value = { name: agent.name, description: agent.description }
|
||||
customScheduler.value = !!agent.scheduler_config
|
||||
formData.value = {
|
||||
name: agent.name,
|
||||
description: agent.description,
|
||||
scheduler_config: agent.scheduler_config ? {
|
||||
worker_count: agent.scheduler_config.worker_count,
|
||||
queue_size: agent.scheduler_config.queue_size,
|
||||
rate_interval: agent.scheduler_config.rate_interval,
|
||||
verbose: agent.scheduler_config.verbose,
|
||||
strict_queue: agent.scheduler_config.strict_queue
|
||||
} : {
|
||||
worker_count: 1,
|
||||
queue_size: 100,
|
||||
rate_interval: 200,
|
||||
verbose: false,
|
||||
strict_queue: false
|
||||
}
|
||||
}
|
||||
showEditDialog.value = true
|
||||
}
|
||||
|
||||
async function updateAgent() {
|
||||
if (!editingAgent.value || !formData.value.name.trim()) return
|
||||
try {
|
||||
await api.agents.update(editingAgent.value.id, { ...formData.value, enabled: editingAgent.value.enabled })
|
||||
const payload = {
|
||||
name: formData.value.name,
|
||||
description: formData.value.description,
|
||||
enabled: editingAgent.value.enabled,
|
||||
scheduler_config: customScheduler.value ? formData.value.scheduler_config : null
|
||||
}
|
||||
await api.agents.update(editingAgent.value.id, payload)
|
||||
showEditDialog.value = false
|
||||
await loadAgents()
|
||||
toast.success('更新成功')
|
||||
@@ -108,7 +143,12 @@ async function updateAgent() {
|
||||
async function toggleEnabled(agent: Agent) {
|
||||
try {
|
||||
const newEnabled = !agent.enabled
|
||||
await api.agents.update(agent.id, { name: agent.name, description: agent.description, enabled: newEnabled })
|
||||
await api.agents.update(agent.id, {
|
||||
name: agent.name,
|
||||
description: agent.description,
|
||||
enabled: newEnabled,
|
||||
scheduler_config: agent.scheduler_config
|
||||
})
|
||||
await loadAgents()
|
||||
toast.success(`${agent.name} 已${newEnabled ? '启用' : '禁用'}`)
|
||||
} catch (e: unknown) {
|
||||
@@ -612,6 +652,12 @@ onUnmounted(() => {
|
||||
<Label class="text-muted-foreground text-xs">注册时间</Label>
|
||||
<div class="text-sm">{{ viewingAgent.created_at || '-' }}</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between sm:block">
|
||||
<Label class="text-muted-foreground text-xs">任务排队</Label>
|
||||
<div class="text-sm">
|
||||
{{ viewingAgent.scheduler_config ? `自定义 (并发: ${viewingAgent.scheduler_config.worker_count}, 队列: ${viewingAgent.scheduler_config.queue_size}, 严格排队: ${viewingAgent.scheduler_config.strict_queue ? '是' : '否'})` : '继承全局' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="viewingAgent.description" class="pt-2 border-t">
|
||||
<Label class="text-muted-foreground text-xs">描述</Label>
|
||||
@@ -629,13 +675,55 @@ onUnmounted(() => {
|
||||
<DialogDescription class="sr-only">修改 Agent 的名称和描述信息</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<Label>名称</Label>
|
||||
<Input v-model="formData.name" placeholder="Agent 名称" />
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-medium text-foreground">名称</Label>
|
||||
<Input v-model="formData.name" placeholder="Agent 名称" class="h-9" />
|
||||
</div>
|
||||
<div>
|
||||
<Label>描述</Label>
|
||||
<Input v-model="formData.description" placeholder="描述信息(可选)" />
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-medium text-foreground">描述</Label>
|
||||
<Input v-model="formData.description" placeholder="描述信息(可选)" class="h-9" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-sm font-medium">自定义调度配置</Label>
|
||||
<div class="text-xs text-muted-foreground">开启后可独立配置该 Agent 的并发限制与任务排队参数</div>
|
||||
</div>
|
||||
<Switch v-model="customScheduler" />
|
||||
</div>
|
||||
<div v-if="customScheduler" class="p-4 rounded-lg border border-border bg-muted/20 space-y-4 mt-2">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-medium text-foreground">并发限制数 (Workers)</Label>
|
||||
<Input type="number" v-model.number="formData.scheduler_config.worker_count" :min="1" class="h-9" />
|
||||
<p class="text-[10px] text-muted-foreground">同一时间最大并行任务数</p>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-medium text-foreground">最大队列数 (Queue Size)</Label>
|
||||
<Input type="number" v-model.number="formData.scheduler_config.queue_size" :min="1" class="h-9" />
|
||||
<p class="text-[10px] text-muted-foreground">并发满时等待排队的任务数</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-medium text-foreground">调度频率限制 (Rate Interval)</Label>
|
||||
<div class="relative">
|
||||
<Input type="number" v-model.number="formData.scheduler_config.rate_interval" :min="0" class="h-9 pr-10" />
|
||||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">ms</span>
|
||||
</div>
|
||||
<p class="text-[10px] text-muted-foreground">两次调度启动的最小间隔时间</p>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-medium text-foreground">执行降级策略</Label>
|
||||
<div class="flex items-center justify-between rounded-md border border-input bg-card px-3 h-9 shadow-sm">
|
||||
<span class="text-xs text-muted-foreground">队列满时拒绝执行</span>
|
||||
<Switch v-model="formData.scheduler_config.strict_queue" class="scale-90" />
|
||||
</div>
|
||||
<p class="text-[10px] text-muted-foreground">开启后拒绝执行;关闭则同步直接执行</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-md bg-yellow-500/10 border border-yellow-500/20 p-2.5 text-[10px] text-yellow-600 dark:text-yellow-400 leading-relaxed">
|
||||
<strong>提示:</strong>开启严格排队后,服务端将不再拦截此 Agent 上已运行任务的并行触发,而是交由 Agent 本地队列调度。若要严格保证任务不并行,请将 <strong>并发限制数 (Workers)</strong> 设为 1。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
|
||||
Reference in New Issue
Block a user