188 lines
4.7 KiB
Vue
188 lines
4.7 KiB
Vue
<template>
|
|
<div>
|
|
<LayoutsBreadcrumb />
|
|
|
|
<!-- Header Section -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-monitor"></Icon>
|
|
<h1 class="text-xl font-bold text-primary">Queue Monitor</h1>
|
|
</div>
|
|
<rs-button variant="outline" size="sm" @click="refreshData">
|
|
<Icon class="mr-1" name="ic:outline-refresh"></Icon>
|
|
Refresh
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<p class="text-gray-600">Monitor current notification queues and job statuses.</p>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Basic Stats -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
|
<rs-card>
|
|
<div class="p-4 text-center">
|
|
<h3 class="text-2xl font-bold text-blue-600">{{ stats.pending }}</h3>
|
|
<p class="text-sm text-gray-600">Pending</p>
|
|
</div>
|
|
</rs-card>
|
|
<rs-card>
|
|
<div class="p-4 text-center">
|
|
<h3 class="text-2xl font-bold text-yellow-600">
|
|
{{ stats.processing }}
|
|
</h3>
|
|
<p class="text-sm text-gray-600">Processing</p>
|
|
</div>
|
|
</rs-card>
|
|
<rs-card>
|
|
<div class="p-4 text-center">
|
|
<h3 class="text-2xl font-bold text-green-600">
|
|
{{ stats.completed }}
|
|
</h3>
|
|
<p class="text-sm text-gray-600">Completed</p>
|
|
</div>
|
|
</rs-card>
|
|
<rs-card>
|
|
<div class="p-4 text-center">
|
|
<h3 class="text-2xl font-bold text-red-600">{{ stats.failed }}</h3>
|
|
<p class="text-sm text-gray-600">Failed</p>
|
|
</div>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Job List -->
|
|
<rs-card>
|
|
<template #header>
|
|
<h3 class="text-lg font-semibold text-primary">Recent Jobs</h3>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-3">
|
|
<div
|
|
v-for="(job, index) in jobs"
|
|
:key="index"
|
|
class="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
|
|
>
|
|
<div class="flex items-center">
|
|
<div
|
|
class="w-3 h-3 rounded-full mr-3"
|
|
:class="getStatusColor(job.status)"
|
|
></div>
|
|
<div>
|
|
<p class="font-medium">{{ job.type }} - {{ job.id }}</p>
|
|
<p class="text-sm text-gray-600">{{ job.description }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="text-sm font-medium capitalize">{{ job.status }}</p>
|
|
<p class="text-xs text-gray-500">{{ job.time }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
definePageMeta({
|
|
title: "Queue Monitor",
|
|
middleware: ["auth"],
|
|
requiresAuth: true,
|
|
breadcrumb: [
|
|
{
|
|
name: "Notification",
|
|
path: "/notification",
|
|
},
|
|
{
|
|
name: "Queue",
|
|
path: "/notification/queue",
|
|
},
|
|
{
|
|
name: "Monitor",
|
|
path: "/notification/queue/monitor",
|
|
},
|
|
],
|
|
});
|
|
|
|
// Stats data
|
|
const stats = ref({
|
|
pending: 0,
|
|
processing: 0,
|
|
completed: 0,
|
|
failed: 0,
|
|
});
|
|
|
|
// Jobs data with pagination
|
|
const jobs = ref([]);
|
|
const pagination = ref({
|
|
page: 1,
|
|
totalPages: 1,
|
|
totalItems: 0,
|
|
hasMore: false,
|
|
});
|
|
|
|
// Fetch stats from API
|
|
const fetchStats = async () => {
|
|
try {
|
|
const { data } = await useFetch("/api/notifications/queue/stats");
|
|
if (data.value?.success) {
|
|
stats.value = data.value.data;
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching stats:", error);
|
|
}
|
|
};
|
|
|
|
// Fetch jobs from API
|
|
const fetchJobs = async () => {
|
|
try {
|
|
const { data } = await useFetch("/api/notifications/queue/jobs", {
|
|
query: {
|
|
page: pagination.value.page,
|
|
limit: 10,
|
|
},
|
|
});
|
|
if (data.value?.success) {
|
|
jobs.value = data.value.data.jobs;
|
|
pagination.value = data.value.data.pagination;
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching jobs:", error);
|
|
}
|
|
};
|
|
|
|
// Status color mapping
|
|
const getStatusColor = (status) => {
|
|
const colors = {
|
|
queued: "bg-blue-500",
|
|
processing: "bg-yellow-500",
|
|
sent: "bg-green-500",
|
|
failed: "bg-red-500",
|
|
};
|
|
return colors[status] || "bg-gray-500";
|
|
};
|
|
|
|
// Refresh data
|
|
const refreshData = async () => {
|
|
await Promise.all([fetchStats(), fetchJobs()]);
|
|
};
|
|
|
|
// Auto-refresh every 30 seconds
|
|
let refreshInterval;
|
|
onMounted(() => {
|
|
refreshData();
|
|
refreshInterval = setInterval(refreshData, 30000);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
if (refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped></style>
|