fix: agent workdir error

This commit is contained in:
engigu
2025-12-29 19:52:39 +08:00
parent 78c3707cce
commit 6c666d998c
6 changed files with 84 additions and 13 deletions
+2 -1
View File
@@ -55,11 +55,12 @@ export const api = {
request('/auth/register', { method: 'POST', body: JSON.stringify(data) })
},
tasks: {
list: (params?: { page?: number; page_size?: number; name?: string }) => {
list: (params?: { page?: number; page_size?: number; name?: string; agent_id?: number }) => {
const query = new URLSearchParams()
if (params?.page) query.set('page', String(params.page))
if (params?.page_size) query.set('page_size', String(params.page_size))
if (params?.name) query.set('name', params.name)
if (params?.agent_id) query.set('agent_id', String(params.agent_id))
return request<TaskListResponse>(`/tasks?${query}`)
},
create: (data: Partial<Task>) => request<Task>('/tasks', { method: 'POST', body: JSON.stringify(data) }),
+13 -3
View File
@@ -6,11 +6,14 @@ import { Label } from '@/components/ui/label'
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'
import { RefreshCw, Trash2, Edit, Copy, Server, Search, Download, RotateCw, Plus, Ticket, Power, PowerOff } from 'lucide-vue-next'
import { RefreshCw, Trash2, Edit, Copy, Server, Search, Download, RotateCw, Plus, Ticket, Power, PowerOff, ListTodo } from 'lucide-vue-next'
import { api, type Agent, type AgentRegCode } from '@/api'
import { toast } from 'vue-sonner'
import { useRouter } from 'vue-router'
import TextOverflow from '@/components/TextOverflow.vue'
const router = useRouter()
const agents = ref<Agent[]>([])
const regCodes = ref<AgentRegCode[]>([])
const loading = ref(false)
@@ -121,6 +124,10 @@ async function forceUpdate(agent: Agent) {
}
}
function viewTasks(agent: Agent) {
router.push({ path: '/tasks', query: { agent_id: String(agent.id) } })
}
function copyRegCode(code: string) {
navigator.clipboard.writeText(code)
toast.success('已复制')
@@ -222,7 +229,7 @@ onUnmounted(() => {
<span class="w-28">构建时间</span>
<span class="w-36">心跳时间</span>
<span class="flex-1">描述</span>
<span class="w-28 text-center">操作</span>
<span class="w-32 text-center">操作</span>
</div>
<div class="divide-y min-w-[900px]">
<div v-if="filteredAgents.length === 0" class="text-center py-8 text-muted-foreground">
@@ -248,11 +255,14 @@ onUnmounted(() => {
<span class="flex-1 text-sm text-muted-foreground truncate">
<TextOverflow :text="agent.description || '-'" title="描述" />
</span>
<span class="w-28 flex justify-center gap-1">
<span class="w-32 flex justify-center gap-1">
<Button variant="ghost" size="icon" class="h-7 w-7" @click="toggleEnabled(agent)" :title="agent.enabled ? '禁用' : '启用'">
<Power v-if="agent.enabled" class="h-3.5 w-3.5 text-green-600" />
<PowerOff v-else class="h-3.5 w-3.5 text-gray-400" />
</Button>
<Button variant="ghost" size="icon" class="h-7 w-7" @click="viewTasks(agent)" title="查看任务">
<ListTodo class="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" class="h-7 w-7" @click="forceUpdate(agent)" title="强制更新">
<RotateCw class="h-3.5 w-3.5" />
</Button>
+8 -1
View File
@@ -98,6 +98,13 @@ watch(() => props.open, async (val) => {
}
})
// 切换执行位置时,如果从本地切换到 Agent,清空工作目录
watch(selectedAgentId, (newVal, oldVal) => {
if (oldVal === 'local' && newVal !== 'local') {
form.value.work_dir = ''
}
})
async function loadData() {
try {
const [envs, agents] = await Promise.all([
@@ -159,7 +166,7 @@ async function save() {
<Label class="sm:text-right text-sm">工作目录</Label>
<div class="sm:col-span-3">
<DirTreeSelect v-if="selectedAgentId === 'local'" :model-value="form.work_dir || ''" @update:model-value="v => form.work_dir = v" />
<Input v-else v-model="form.work_dir" placeholder="Agent 上的工作目录(可选)" class="h-8 text-sm" />
<Input v-else v-model="form.work_dir" placeholder="工作目录(可选)" class="h-8 text-sm" />
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-3">
+47 -6
View File
@@ -1,19 +1,20 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { ref, onMounted, computed, watch } from 'vue'
import { Button } from '@/components/ui/button'
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'
import { Input } from '@/components/ui/input'
import Pagination from '@/components/Pagination.vue'
import TaskDialog from './TaskDialog.vue'
import RepoDialog from './RepoDialog.vue'
import { Plus, Play, Pencil, Trash2, Search, ScrollText, GitBranch, Terminal, Server, Monitor } from 'lucide-vue-next'
import { Plus, Play, Pencil, Trash2, Search, ScrollText, GitBranch, Terminal, Server, Monitor, X } from 'lucide-vue-next'
import { api, type Task, type Agent } from '@/api'
import { toast } from 'vue-sonner'
import { useSiteSettings } from '@/composables/useSiteSettings'
import { useRouter } from 'vue-router'
import { useRouter, useRoute } from 'vue-router'
import TextOverflow from '@/components/TextOverflow.vue'
const router = useRouter()
const route = useRoute()
const { pageSize } = useSiteSettings()
const tasks = ref<Task[]>([])
@@ -26,6 +27,7 @@ const showDeleteDialog = ref(false)
const deleteTaskId = ref<number | null>(null)
const filterName = ref('')
const filterAgentId = ref<number | null>(null)
const currentPage = ref(1)
const total = ref(0)
let searchTimer: ReturnType<typeof setTimeout> | null = null
@@ -37,6 +39,13 @@ const agentMap = computed(() => {
return map
})
// 当前筛选的 Agent 名称
const filterAgentName = computed(() => {
if (!filterAgentId.value) return ''
const agent = agentMap.value[filterAgentId.value]
return agent ? agent.name : `Agent #${filterAgentId.value}`
})
// 获取任务执行位置名称
function getExecutorName(task: Task): string {
if (!task.agent_id) return '本地'
@@ -53,7 +62,12 @@ function getExecutorStatus(task: Task): 'local' | 'online' | 'offline' {
async function loadTasks() {
try {
const res = await api.tasks.list({ page: currentPage.value, page_size: pageSize.value, name: filterName.value || undefined })
const res = await api.tasks.list({
page: currentPage.value,
page_size: pageSize.value,
name: filterName.value || undefined,
agent_id: filterAgentId.value || undefined
})
tasks.value = res.data
total.value = res.total
} catch { toast.error('加载任务失败') }
@@ -78,6 +92,13 @@ function handlePageChange(page: number) {
loadTasks()
}
function clearAgentFilter() {
filterAgentId.value = null
router.replace({ query: {} })
currentPage.value = 1
loadTasks()
}
function openCreate() {
editingTask.value = { name: '', command: '', type: 'task', schedule: '0 * * * * *', timeout: 30, work_dir: '', enabled: true, clean_config: '', envs: '' }
isEdit.value = false
@@ -136,9 +157,24 @@ function getTaskTypeTitle(type: string) {
return type === 'repo' ? '仓库同步' : '普通任务'
}
onMounted(() => {
onMounted(async () => {
// 先加载 agents,再处理 URL 参数
await loadAgents()
// 从 URL 参数读取 agent_id
const agentIdParam = route.query.agent_id
if (agentIdParam) {
filterAgentId.value = Number(agentIdParam)
}
loadTasks()
})
// 监听路由参数变化
watch(() => route.query.agent_id, (newVal) => {
filterAgentId.value = newVal ? Number(newVal) : null
currentPage.value = 1
loadTasks()
loadAgents()
})
</script>
@@ -154,6 +190,11 @@ onMounted(() => {
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input v-model="filterName" placeholder="搜索任务..." class="h-9 pl-9 w-full sm:w-56 text-sm" @input="handleSearch" />
</div>
<div v-if="filterAgentId" class="flex items-center gap-1 px-2 py-1 bg-primary/10 text-primary rounded-md text-sm">
<Server class="h-3.5 w-3.5" />
<span>{{ filterAgentName }}</span>
<X class="h-3.5 w-3.5 cursor-pointer hover:text-destructive" @click="clearAgentFilter" />
</div>
<Button variant="outline" @click="openCreateRepo" class="shrink-0">
<GitBranch class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline">仓库同步</span>
</Button>