fix: agent workdir error
This commit is contained in:
@@ -96,8 +96,17 @@ func (tc *TaskController) CreateTask(c *gin.Context) {
|
||||
func (tc *TaskController) GetTasks(c *gin.Context) {
|
||||
p := utils.ParsePagination(c)
|
||||
name := c.DefaultQuery("name", "")
|
||||
agentIDStr := c.DefaultQuery("agent_id", "")
|
||||
|
||||
var agentID *uint
|
||||
if agentIDStr != "" {
|
||||
if id, err := strconv.ParseUint(agentIDStr, 10, 32); err == nil {
|
||||
uid := uint(id)
|
||||
agentID = &uid
|
||||
}
|
||||
}
|
||||
|
||||
tasks, total := tc.taskService.GetTasksWithPagination(p.Page, p.PageSize, name)
|
||||
tasks, total := tc.taskService.GetTasksWithPagination(p.Page, p.PageSize, name, agentID)
|
||||
utils.PaginatedResponse(c, tasks, total, p)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ func (ts *TaskService) GetTasks() []models.Task {
|
||||
}
|
||||
|
||||
// GetTasksWithPagination 分页获取任务列表
|
||||
func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string) ([]models.Task, int64) {
|
||||
func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string, agentID *uint) ([]models.Task, int64) {
|
||||
var tasks []models.Task
|
||||
var total int64
|
||||
|
||||
@@ -47,6 +47,9 @@ func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string) (
|
||||
if name != "" {
|
||||
query = query.Where("name LIKE ?", "%"+name+"%")
|
||||
}
|
||||
if agentID != nil {
|
||||
query = query.Where("agent_id = ?", *agentID)
|
||||
}
|
||||
|
||||
query.Count(&total)
|
||||
query.Order("id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&tasks)
|
||||
|
||||
@@ -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) }),
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user