- Revamp decypharr arch \n

- Add callback_ur, download_folder to addContent API \n
- Fix few bugs \n
- More declarative UI keywords
- Speed up repairs
- Few other improvements/bug fixes
This commit is contained in:
Mukhtar Akere
2025-06-02 12:57:36 +01:00
parent 1cd09239f9
commit 9c6c44d785
67 changed files with 1726 additions and 1464 deletions

View File

@@ -143,6 +143,9 @@
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="processJobBtn">Process Items</button>
<button type="button" class="btn btn-warning d-none" id="stopJobBtn">
<i class="bi bi-stop-fill me-1"></i>Stop Job
</button>
</div>
</div>
</div>
@@ -218,6 +221,27 @@
}
}
// Return status text and class based on job status
function getStatus(status) {
switch (status) {
case 'started':
return {text: 'In Progress', class: 'text-primary'};
case 'failed':
return {text: 'Failed', class: 'text-danger'};
case 'completed':
return {text: 'Completed', class: 'text-success'};
case 'pending':
return {text: 'Pending', class: 'text-warning'};
case 'cancelled':
return {text: 'Cancelled', class: 'text-secondary'};
case 'processing':
return {text: 'Processing', class: 'text-info'};
default:
// Return status in title case if unknown
return {text: status.charAt(0).toUpperCase() + status.slice(1), class: 'text-secondary'};
}
}
// Render jobs table with pagination
function renderJobsTable(page) {
const tableBody = document.getElementById('jobsTableBody');
@@ -254,24 +278,10 @@
const formattedDate = startedDate.toLocaleString();
// Determine status
let status = 'In Progress';
let statusClass = 'text-primary';
let status = getStatus(job.status);
let canDelete = job.status !== "started";
let totalItems = job.broken_items ? Object.values(job.broken_items).reduce((sum, arr) => sum + arr.length, 0) : 0;
if (job.status === 'failed') {
status = 'Failed';
statusClass = 'text-danger';
} else if (job.status === 'completed') {
status = 'Completed';
statusClass = 'text-success';
} else if (job.status === 'pending') {
status = 'Pending';
statusClass = 'text-warning';
} else if (job.status === "processing") {
status = 'Processing';
statusClass = 'text-info';
}
row.innerHTML = `
<td>
@@ -283,25 +293,31 @@
<td><a href="#" class="text-link view-job" data-id="${job.id}"><small>${job.id.substring(0, 8)}</small></a></td>
<td>${job.arrs.join(', ')}</td>
<td><small>${formattedDate}</small></td>
<td><span class="${statusClass}">${status}</span></td>
<td><span class="${status.class}">${status.text}</span></td>
<td>${totalItems}</td>
<td>
${job.status === "pending" ?
`<button class="btn btn-sm btn-primary process-job" data-id="${job.id}">
<i class="bi bi-play-fill"></i> Process
`<button class="btn btn-sm btn-primary process-job" data-id="${job.id}">
<i class="bi bi-play-fill"></i> Process
</button>` :
`<button class="btn btn-sm btn-primary" disabled>
<i class="bi bi-eye"></i> Process
`<button class="btn btn-sm btn-primary" disabled>
<i class="bi bi-eye"></i> Process
</button>`
}
}
${(job.status === "started" || job.status === "processing") ?
`<button class="btn btn-sm btn-warning stop-job" data-id="${job.id}">
<i class="bi bi-stop-fill"></i> Stop
</button>` :
''
}
${canDelete ?
`<button class="btn btn-sm btn-danger delete-job" data-id="${job.id}">
<i class="bi bi-trash"></i>
</button>` :
`<button class="btn btn-sm btn-danger" disabled>
<i class="bi bi-trash"></i>
</button>`
}
`<button class="btn btn-sm btn-danger delete-job" data-id="${job.id}">
<i class="bi bi-trash"></i>
</button>` :
`<button class="btn btn-sm btn-danger" disabled>
<i class="bi bi-trash"></i>
</button>`
}
</td>
`;
@@ -370,6 +386,13 @@
viewJobDetails(jobId);
});
});
document.querySelectorAll('.stop-job').forEach(button => {
button.addEventListener('click', (e) => {
const jobId = e.currentTarget.dataset.id;
stopJob(jobId);
});
});
}
document.getElementById('selectAllJobs').addEventListener('change', function() {
@@ -456,6 +479,25 @@
}
}
async function stopJob(jobId) {
if (confirm('Are you sure you want to stop this job?')) {
try {
const response = await fetcher(`/api/repair/jobs/${jobId}/stop`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
});
if (!response.ok) throw new Error(await response.text());
createToast('Job stop requested successfully');
await loadJobs(currentPage); // Refresh the jobs list
} catch (error) {
createToast(`Error stopping job: ${error.message}`, 'error');
}
}
}
// View job details function
function viewJobDetails(jobId) {
// Find the job
@@ -477,24 +519,9 @@
}
// Set status with color
let status = 'In Progress';
let statusClass = 'text-primary';
let status = getStatus(job.status);
if (job.status === 'failed') {
status = 'Failed';
statusClass = 'text-danger';
} else if (job.status === 'completed') {
status = 'Completed';
statusClass = 'text-success';
} else if (job.status === 'pending') {
status = 'Pending';
statusClass = 'text-warning';
} else if (job.status === "processing") {
status = 'Processing';
statusClass = 'text-info';
}
document.getElementById('modalJobStatus').innerHTML = `<span class="${statusClass}">${status}</span>`;
document.getElementById('modalJobStatus').innerHTML = `<span class="${status.class}">${status.text}</span>`;
// Set other job details
document.getElementById('modalJobArrs').textContent = job.arrs.join(', ');
@@ -524,6 +551,19 @@
processBtn.classList.add('d-none');
}
// Stop button visibility
const stopBtn = document.getElementById('stopJobBtn'); // You'll need to add this button to the HTML
if (job.status === 'started' || job.status === 'processing') {
stopBtn.classList.remove('d-none');
stopBtn.onclick = () => {
stopJob(job.id);
const modal = bootstrap.Modal.getInstance(document.getElementById('jobDetailsModal'));
modal.hide();
};
} else {
stopBtn.classList.add('d-none');
}
// Populate broken items table
const brokenItemsTableBody = document.getElementById('brokenItemsTableBody');
const noBrokenItemsMessage = document.getElementById('noBrokenItemsMessage');