corrad-af-2024/components/JobDetailModal.vue

180 lines
6.6 KiB
Vue

<script setup>
import { defineProps, defineEmits, computed } from 'vue';
const props = defineProps({
job: {
type: Object,
required: true,
},
show: {
type: Boolean,
required: true,
}
});
const emit = defineEmits(['close']);
function formatDate(timestamp) {
if (!timestamp) return 'N/A';
const date = new Date(Number(timestamp));
if (isNaN(date.getTime())) return 'Invalid Date';
return date.toLocaleString();
}
const jobDuration = computed(() => {
if (props.job.duration) {
return (props.job.duration / 1000).toFixed(2) + 's';
}
if (props.job.processedOn && props.job.finishedOn) {
return ((props.job.finishedOn - props.job.processedOn) / 1000).toFixed(2) + 's';
}
return 'N/A';
});
const dataEntries = computed(() => {
if (props.job.data && typeof props.job.data === 'object') {
return Object.entries(props.job.data);
}
return [];
});
</script>
<template>
<Teleport to="body">
<transition name="modal-fade">
<div v-if="show"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60 backdrop-blur-sm p-4"
@click.self="emit('close')">
<rs-card class="w-full max-w-2xl bg-white shadow-xl rounded-lg max-h-[90vh] flex flex-col">
<template #header>
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
<h3 class="text-xl font-semibold text-gray-800">
Job Details: <span class="text-primary">#{{ job.id }}</span>
</h3>
<button @click="emit('close')" class="text-gray-400 hover:text-gray-600 transition-colors">
<Icon name="mdi:close" size="24" />
</button>
</div>
</template>
<template #body>
<div class="p-6 space-y-5 overflow-y-auto flex-grow">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4 text-sm">
<div>
<p class="text-gray-500">Name</p>
<p class="font-medium text-gray-800">{{ job.name || 'N/A' }}</p>
</div>
<div>
<p class="text-gray-500">Queue</p>
<p class="font-medium text-gray-800">{{ job.queue }}</p>
</div>
<div>
<p class="text-gray-500">Status</p>
<rs-badge
:variant="job.state === 'completed' ? 'success' :
job.state === 'failed' ? 'danger' :
job.state === 'active' ? 'primary' : 'info'"
class="text-xs font-semibold"
>
{{ job.state }}
</rs-badge>
</div>
<div>
<p class="text-gray-500">Priority</p>
<p class="font-medium text-gray-800">{{ job.priority }}</p>
</div>
<div>
<p class="text-gray-500">Attempts Made</p>
<p class="font-medium text-gray-800">{{ job.attemptsMade }}</p>
</div>
<div>
<p class="text-gray-500">Duration</p>
<p class="font-medium text-gray-800">{{ jobDuration }}</p>
</div>
</div>
<div class="border-t border-gray-200 pt-4">
<h4 class="text-md font-semibold text-gray-700 mb-2">Timestamps</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-2 text-sm">
<div>
<p class="text-gray-500">Created At</p>
<p class="font-medium text-gray-800">{{ formatDate(job.timestamp) }}</p>
</div>
<div>
<p class="text-gray-500">Processed At</p>
<p class="font-medium text-gray-800">{{ formatDate(job.processedOn) }}</p>
</div>
<div>
<p class="text-gray-500">Finished At</p>
<p class="font-medium text-gray-800">{{ formatDate(job.finishedOn) }}</p>
</div>
</div>
</div>
<div v-if="job.state === 'active' && job.progress > 0" class="border-t border-gray-200 pt-4">
<h4 class="text-md font-semibold text-gray-700 mb-1">Progress</h4>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-blue-600 h-2.5 rounded-full transition-all duration-300 ease-out"
:style="{ width: job.progress + '%' }">
</div>
</div>
<p class="text-xs text-gray-600 mt-1">{{ job.progress }}% complete</p>
</div>
<div v-if="job.state === 'failed' && job.failedReason" class="border-t border-gray-200 pt-4">
<h4 class="text-md font-semibold text-red-600 mb-1">Failure Reason</h4>
<div class="p-3 bg-red-50 border border-red-200 rounded-md text-sm text-red-700 whitespace-pre-wrap">
{{ job.failedReason }}
</div>
</div>
<div v-if="dataEntries.length > 0" class="border-t border-gray-200 pt-4">
<h4 class="text-md font-semibold text-gray-700 mb-2">Job Data</h4>
<div class="p-3 bg-gray-50 border border-gray-200 rounded-md max-h-60 overflow-y-auto text-xs">
<pre class="whitespace-pre-wrap break-all">{{ JSON.stringify(job.data, null, 2) }}</pre>
</div>
</div>
<div v-else-if="job.data" class="border-t border-gray-200 pt-4">
<h4 class="text-md font-semibold text-gray-700 mb-2">Job Data</h4>
<div class="p-3 bg-gray-50 border border-gray-200 rounded-md text-xs text-gray-500">
No data or data is not an object.
</div>
</div>
</div>
</template>
<template #footer>
<div class="px-6 py-3 bg-gray-50 border-t border-gray-200 flex justify-end">
<rs-button variant="secondary" @click="emit('close')">Close</rs-button>
</div>
</template>
</rs-card>
</div>
</transition>
</Teleport>
</template>
<style scoped>
.modal-fade-enter-active,
.modal-fade-leave-active {
transition: opacity 0.3s ease;
}
.modal-fade-enter-from,
.modal-fade-leave-to {
opacity: 0;
}
pre::-webkit-scrollbar {
width: 6px;
height: 6px;
}
pre::-webkit-scrollbar-thumb {
background: #cbd5e1; /* cool-gray-300 */
border-radius: 3px;
}
pre::-webkit-scrollbar-thumb:hover {
background: #94a3b8; /* cool-gray-400 */
}
</style>