mirror of
https://github.com/unraid/api.git
synced 2026-02-12 11:08:31 -06:00
151 lines
5.6 KiB
Vue
151 lines
5.6 KiB
Vue
<script setup lang="ts">
|
|
import type { BackupJobsQuery } from '~/composables/gql/graphql';
|
|
import { RCloneJobStatus } from '~/composables/gql/graphql';
|
|
import { useFragment } from '~/composables/gql/fragment-masking';
|
|
import { BACKUP_STATS_FRAGMENT } from './backup-jobs.query';
|
|
import { computed } from 'vue';
|
|
|
|
interface Props {
|
|
job: NonNullable<BackupJobsQuery['backup']>['jobs'][0];
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
const stats = useFragment(BACKUP_STATS_FRAGMENT, props.job.stats);
|
|
|
|
// Calculate percentage if it's null but we have bytes and totalBytes
|
|
const calculatedPercentage = computed(() => {
|
|
if (stats?.percentage !== null) {
|
|
return stats?.percentage;
|
|
}
|
|
if (stats?.bytes && stats?.totalBytes) {
|
|
return Math.round((stats.bytes / stats.totalBytes) * 100);
|
|
}
|
|
return null;
|
|
});
|
|
|
|
// Determine job status based on job properties
|
|
const jobStatus = computed(() => {
|
|
if (props.job.status) {
|
|
return props.job.status;
|
|
}
|
|
if (props.job.error) return RCloneJobStatus.ERROR;
|
|
if (props.job.finished && props.job.success) return RCloneJobStatus.COMPLETED;
|
|
if (props.job.finished && !props.job.success) return RCloneJobStatus.ERROR;
|
|
return RCloneJobStatus.RUNNING;
|
|
});
|
|
|
|
const statusColor = computed(() => {
|
|
switch (jobStatus.value) {
|
|
case RCloneJobStatus.ERROR:
|
|
case RCloneJobStatus.CANCELLED:
|
|
return 'red';
|
|
case RCloneJobStatus.COMPLETED:
|
|
return 'green';
|
|
case RCloneJobStatus.RUNNING:
|
|
default:
|
|
return 'blue';
|
|
}
|
|
});
|
|
|
|
const statusText = computed(() => {
|
|
switch (jobStatus.value) {
|
|
case RCloneJobStatus.ERROR:
|
|
return 'Error';
|
|
case RCloneJobStatus.CANCELLED:
|
|
return 'Cancelled';
|
|
case RCloneJobStatus.COMPLETED:
|
|
return 'Completed';
|
|
case RCloneJobStatus.RUNNING:
|
|
default:
|
|
return 'Running';
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-6 shadow-sm">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="flex items-center space-x-3">
|
|
<div class="flex-shrink-0">
|
|
<div
|
|
:class="[
|
|
'w-3 h-3 rounded-full',
|
|
statusColor === 'green' ? 'bg-green-400' : statusColor === 'red' ? 'bg-red-400' : 'bg-blue-400',
|
|
jobStatus === RCloneJobStatus.RUNNING ? 'animate-pulse' : ''
|
|
]"
|
|
></div>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
|
Backup Job
|
|
</h3>
|
|
<div class="text-sm text-gray-500 dark:text-gray-400 space-y-1">
|
|
<p>Job ID: {{ job.id }}</p>
|
|
<p v-if="job.configId">Config ID: {{ job.configId }}</p>
|
|
<p v-if="job.group">Group: {{ job.group }}</p>
|
|
<p>Status: {{ statusText }}</p>
|
|
<p v-if="job.error" class="text-red-600 dark:text-red-400">Error: {{ job.error }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<span
|
|
:class="`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${statusColor === 'green' ? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400' : statusColor === 'red' ? 'bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400' : 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400'}`"
|
|
>
|
|
{{ statusText }}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div v-if="stats?.formattedBytes" class="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Bytes Transferred</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{{ stats.formattedBytes }}
|
|
</dd>
|
|
</div>
|
|
|
|
<div v-if="stats?.transfers" class="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Files Transferred</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{{ stats.transfers }}
|
|
</dd>
|
|
</div>
|
|
|
|
<div v-if="stats?.formattedSpeed" class="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Transfer Speed</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">{{ stats.formattedSpeed }}</dd>
|
|
</div>
|
|
|
|
<div v-if="stats?.formattedElapsedTime" class="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Elapsed Time</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{{ stats.formattedElapsedTime }}
|
|
</dd>
|
|
</div>
|
|
|
|
<div v-if="stats?.formattedEta" class="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">ETA</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{{ stats.formattedEta }}
|
|
</dd>
|
|
</div>
|
|
|
|
<div v-if="calculatedPercentage" class="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Progress</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">{{ calculatedPercentage }}%</dd>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="calculatedPercentage" class="mt-4">
|
|
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
|
|
<div
|
|
:class="[
|
|
'h-2 rounded-full transition-all duration-300',
|
|
statusColor === 'green' ? 'bg-green-600' : statusColor === 'red' ? 'bg-red-600' : 'bg-blue-600'
|
|
]"
|
|
:style="{ width: `${calculatedPercentage}%` }"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template> |