You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
6.0 KiB
178 lines
6.0 KiB
<script setup lang="ts">
|
|
interface TaskRow {
|
|
id: string
|
|
name: string
|
|
cronExpression: string
|
|
type: string
|
|
enabled: number
|
|
}
|
|
|
|
const { data, refresh } = await useHttpFetch("/api/scheduler/tasks")
|
|
const stats = await useHttpFetch("/api/scheduler/stats")
|
|
|
|
const taskList = computed<TaskRow[]>(() => (data.value as any)?.list ?? [])
|
|
const registeredFunctions = computed<string[]>(() => (data.value as any)?.registeredFunctions ?? [])
|
|
const statsData = computed(() => (stats.data.value ?? {}) as {
|
|
totalTasks: number
|
|
enabledTasks: number
|
|
activeJobs: number
|
|
last24hExecutions: number
|
|
})
|
|
|
|
const showCreateModal = ref(false)
|
|
const editingTask = ref<TaskRow | null>(null)
|
|
|
|
function statusBadge(enabled: number) {
|
|
return enabled
|
|
? { label: "Active", class: "bg-green-100 text-green-800" }
|
|
: { label: "Paused", class: "bg-gray-100 text-gray-800" }
|
|
}
|
|
|
|
async function handleDelete(id: string) {
|
|
if (!confirm("Delete this task?")) return
|
|
await $fetch(`/api/scheduler/tasks/${id}`, { method: "DELETE" })
|
|
refresh()
|
|
}
|
|
|
|
async function handleToggle(id: string, enabled: boolean) {
|
|
await $fetch(`/api/scheduler/tasks/${id}/toggle`, {
|
|
method: "POST",
|
|
body: { enabled },
|
|
})
|
|
refresh()
|
|
}
|
|
|
|
async function handleTrigger(id: string) {
|
|
await $fetch(`/api/scheduler/tasks/${id}/trigger`, { method: "POST" })
|
|
}
|
|
|
|
function openEdit(task: any) {
|
|
editingTask.value = task
|
|
showCreateModal.value = true
|
|
}
|
|
|
|
function openCreate() {
|
|
editingTask.value = null
|
|
showCreateModal.value = true
|
|
}
|
|
|
|
function onModalClose() {
|
|
showCreateModal.value = false
|
|
editingTask.value = null
|
|
refresh()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="p-6 max-w-6xl mx-auto">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h1 class="text-2xl font-bold text-gray-900">Scheduled Tasks</h1>
|
|
<button
|
|
class="px-4 py-2 bg-[#cc785c] text-white rounded-lg text-sm font-medium hover:bg-[#a9583e] transition-colors"
|
|
@click="openCreate"
|
|
>
|
|
Create Task
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Stats bar -->
|
|
<div class="grid grid-cols-4 gap-4 mb-6">
|
|
<div class="rounded-lg border p-4">
|
|
<div class="text-sm text-gray-500">Total</div>
|
|
<div class="text-2xl font-bold">{{ statsData.totalTasks ?? 0 }}</div>
|
|
</div>
|
|
<div class="rounded-lg border p-4">
|
|
<div class="text-sm text-gray-500">Active</div>
|
|
<div class="text-2xl font-bold">{{ statsData.enabledTasks ?? 0 }}</div>
|
|
</div>
|
|
<div class="rounded-lg border p-4">
|
|
<div class="text-sm text-gray-500">Jobs Running</div>
|
|
<div class="text-2xl font-bold">{{ statsData.activeJobs ?? 0 }}</div>
|
|
</div>
|
|
<div class="rounded-lg border p-4">
|
|
<div class="text-sm text-gray-500">24h Executions</div>
|
|
<div class="text-2xl font-bold">{{ statsData.last24hExecutions ?? 0 }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Task table -->
|
|
<div class="rounded-lg border overflow-hidden">
|
|
<table class="w-full">
|
|
<thead>
|
|
<tr class="bg-gray-50 border-b">
|
|
<th class="text-left px-4 py-3 text-sm font-medium text-gray-500">Name</th>
|
|
<th class="text-left px-4 py-3 text-sm font-medium text-gray-500">Cron</th>
|
|
<th class="text-left px-4 py-3 text-sm font-medium text-gray-500">Type</th>
|
|
<th class="text-left px-4 py-3 text-sm font-medium text-gray-500">Status</th>
|
|
<th class="text-right px-4 py-3 text-sm font-medium text-gray-500">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-if="taskList.length === 0">
|
|
<td colspan="5" class="text-center px-4 py-8 text-sm text-gray-400">No tasks yet</td>
|
|
</tr>
|
|
<tr
|
|
v-for="t in taskList"
|
|
:key="t.id"
|
|
class="border-b last:border-b-0 hover:bg-gray-50"
|
|
>
|
|
<td class="px-4 py-3 text-sm">{{ t.name }}</td>
|
|
<td class="px-4 py-3 text-sm font-mono">{{ t.cronExpression }}</td>
|
|
<td class="px-4 py-3 text-sm">
|
|
<span :class="['px-2 py-1 rounded text-xs font-medium', t.type === 'function' ? 'bg-blue-100 text-blue-800' : 'bg-purple-100 text-purple-800']">
|
|
{{ t.type }}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<span :class="['px-2 py-1 rounded text-xs font-medium', statusBadge(t.enabled).class]">
|
|
{{ statusBadge(t.enabled).label }}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<div class="flex gap-1 justify-end">
|
|
<button
|
|
class="px-2 py-1 text-xs text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded"
|
|
@click="handleTrigger(t.id)"
|
|
>
|
|
Trigger
|
|
</button>
|
|
<button
|
|
class="px-2 py-1 text-xs text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded"
|
|
@click="handleToggle(t.id, !t.enabled)"
|
|
>
|
|
{{ t.enabled ? 'Pause' : 'Resume' }}
|
|
</button>
|
|
<NuxtLink
|
|
:to="`/admin/scheduler/${t.id}`"
|
|
class="px-2 py-1 text-xs text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded"
|
|
>
|
|
Detail
|
|
</NuxtLink>
|
|
<button
|
|
class="px-2 py-1 text-xs text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded"
|
|
@click="openEdit(t)"
|
|
>
|
|
Edit
|
|
</button>
|
|
<button
|
|
class="px-2 py-1 text-xs text-red-600 hover:text-red-900 hover:bg-red-50 rounded"
|
|
@click="handleDelete(t.id)"
|
|
>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Create/Edit Modal -->
|
|
<SchedulerTaskModal
|
|
v-if="showCreateModal"
|
|
:task="editingTask"
|
|
:registered-functions="registeredFunctions"
|
|
@close="onModalClose"
|
|
/>
|
|
</div>
|
|
</template>
|