1 line
23 KiB
JavaScript
1 line
23 KiB
JavaScript
class RepairManager{constructor(){this.state={jobs:[],currentJob:null,allBrokenItems:[],filteredItems:[],selectedItems:new Set,currentPage:1,currentItemsPage:1,itemsPerPage:10,itemsPerModalPage:20,searchTerm:"",arrFilter:"",pathFilter:"",sortBy:"created_at",sortDirection:"desc"},this.refs={repairForm:document.getElementById("repairForm"),arrSelect:document.getElementById("arrSelect"),mediaIds:document.getElementById("mediaIds"),isAsync:document.getElementById("isAsync"),autoProcess:document.getElementById("autoProcess"),submitBtn:document.getElementById("submitRepair"),jobsTable:document.getElementById("jobsTable"),jobsTableBody:document.getElementById("jobsTableBody"),jobsPagination:document.getElementById("jobsPagination"),noJobsMessage:document.getElementById("noJobsMessage"),refreshJobs:document.getElementById("refreshJobs"),deleteSelectedJobs:document.getElementById("deleteSelectedJobs"),selectAllJobs:document.getElementById("selectAllJobs"),jobDetailsModal:document.getElementById("jobDetailsModal"),modalJobId:document.getElementById("modalJobId"),modalJobStatus:document.getElementById("modalJobStatus"),modalJobStarted:document.getElementById("modalJobStarted"),modalJobCompleted:document.getElementById("modalJobCompleted"),modalJobArrs:document.getElementById("modalJobArrs"),modalJobMediaIds:document.getElementById("modalJobMediaIds"),modalJobAutoProcess:document.getElementById("modalJobAutoProcess"),modalJobError:document.getElementById("modalJobError"),errorContainer:document.getElementById("errorContainer"),brokenItemsTableBody:document.getElementById("brokenItemsTableBody"),itemsPagination:document.getElementById("itemsPagination"),noBrokenItemsMessage:document.getElementById("noBrokenItemsMessage"),noFilteredItemsMessage:document.getElementById("noFilteredItemsMessage"),totalItemsCount:document.getElementById("totalItemsCount"),modalFooterStats:document.getElementById("modalFooterStats"),itemSearchInput:document.getElementById("itemSearchInput"),arrFilterSelect:document.getElementById("arrFilterSelect"),pathFilterSelect:document.getElementById("pathFilterSelect"),clearFiltersBtn:document.getElementById("clearFiltersBtn"),processJobBtn:document.getElementById("processJobBtn"),stopJobBtn:document.getElementById("stopJobBtn")},this.init()}init(){this.bindEvents(),this.loadArrInstances(),this.loadJobs(),this.startAutoRefresh()}bindEvents(){this.refs.repairForm.addEventListener("submit",e=>this.handleFormSubmit(e)),this.refs.refreshJobs.addEventListener("click",()=>this.loadJobs()),this.refs.deleteSelectedJobs.addEventListener("click",()=>this.deleteSelectedJobs()),this.refs.selectAllJobs.addEventListener("change",e=>this.toggleSelectAllJobs(e.target.checked)),this.refs.processJobBtn.addEventListener("click",()=>this.processCurrentJob()),this.refs.stopJobBtn.addEventListener("click",()=>this.stopCurrentJob()),this.refs.itemSearchInput.addEventListener("input",window.decypharrUtils.debounce(()=>this.applyFilters(),300)),this.refs.arrFilterSelect.addEventListener("change",()=>this.applyFilters()),this.refs.pathFilterSelect.addEventListener("change",()=>this.applyFilters()),this.refs.clearFiltersBtn.addEventListener("click",()=>this.clearFilters()),this.refs.jobsTableBody.addEventListener("click",e=>this.handleJobTableClick(e)),this.refs.brokenItemsTableBody.addEventListener("click",e=>this.handleItemTableClick(e))}async loadArrInstances(){try{const e=await window.decypharrUtils.fetcher("/api/arrs");if(!e.ok)throw new Error("Failed to load Arr instances");const t=await e.json();this.refs.arrSelect.innerHTML='<option value="">Select an Arr instance</option>',t.forEach(e=>{const t=document.createElement("option");t.value=e.name,t.textContent=`${e.name} (${e.host})`,this.refs.arrSelect.appendChild(t)})}catch(e){console.error("Error loading Arr instances:",e),window.decypharrUtils.createToast("Failed to load Arr instances","error")}}async handleFormSubmit(e){e.preventDefault();const t=this.refs.arrSelect.value,s=this.refs.mediaIds.value.trim(),r=s?s.split(",").map(e=>e.trim()).filter(Boolean):[];try{window.decypharrUtils.setButtonLoading(this.refs.submitBtn,!0);const e=await window.decypharrUtils.fetcher("/api/repair",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({arr:t,mediaIds:r.length>0?r:null,async:this.refs.isAsync.checked,autoProcess:this.refs.autoProcess.checked})});if(!e.ok){const t=await e.text();throw new Error(t||"Failed to start repair")}const s=await e.json();window.decypharrUtils.createToast(`Repair job started successfully! Job ID: ${s.job_id?.substring(0,8)||"Unknown"}`,"success"),this.refs.mediaIds.value="",await this.loadJobs()}catch(e){console.error("Error starting repair:",e),window.decypharrUtils.createToast(`Error starting repair: ${e.message}`,"error")}finally{window.decypharrUtils.setButtonLoading(this.refs.submitBtn,!1)}}async loadJobs(){try{const e=await window.decypharrUtils.fetcher("/api/repair/jobs");if(!e.ok)throw new Error("Failed to fetch jobs");this.state.jobs=await e.json(),this.renderJobsTable()}catch(e){console.error("Error loading jobs:",e),window.decypharrUtils.createToast("Error loading repair jobs","error")}}renderJobsTable(){const e=this.getSortedJobs(),t=Math.ceil(e.length/this.state.itemsPerPage),s=(this.state.currentPage-1)*this.state.itemsPerPage,r=Math.min(s+this.state.itemsPerPage,e.length),a=e.slice(s,r);this.refs.jobsTableBody.innerHTML="",this.refs.jobsPagination.innerHTML="",this.refs.selectAllJobs.checked=!1,this.refs.deleteSelectedJobs.disabled=!0,0!==e.length?(this.refs.noJobsMessage.classList.add("hidden"),a.forEach(e=>{const t=this.createJobRow(e);this.refs.jobsTableBody.appendChild(t)}),this.renderJobsPagination(t),this.updateJobSelectionState()):this.refs.noJobsMessage.classList.remove("hidden")}createJobRow(e){const t=document.createElement("tr");t.className="hover:bg-base-200 transition-colors",t.dataset.jobId=e.id;const s=this.getJobStatus(e.status),r=new Date(e.created_at).toLocaleString(),a=e.broken_items?Object.values(e.broken_items).reduce((e,t)=>e+t.length,0):0,n=!["started","processing"].includes(e.status);return t.innerHTML=`\n <td>\n <label class="cursor-pointer">\n <input type="checkbox" class="checkbox checkbox-sm job-checkbox" \n value="${e.id}" ${n?"":"disabled"}>\n </label>\n </td>\n <td>\n <button class="link link-primary text-sm view-job" data-job-id="${e.id}">\n ${e.id.substring(0,8)}...\n </button>\n </td>\n <td>\n <div class="flex flex-wrap gap-1">\n ${e.arrs.map(e=>`<div class="badge badge-secondary badge-xs">${e}</div>`).join("")}\n </div>\n </td>\n <td>\n <time class="text-sm" datetime="${e.created_at}">${r}</time>\n </td>\n <td>\n <div class="badge ${s.class} badge-sm">${s.text}</div>\n </td>\n <td>\n <span class="font-mono text-sm">${a}</span>\n </td>\n <td>\n <div class="flex gap-1">\n ${"pending"===e.status?`\n <button class="btn btn-primary btn-xs process-job" data-job-id="${e.id}">\n <i class="bi bi-play-fill"></i>\n </button>\n `:""}\n ${["started","processing"].includes(e.status)?`\n <button class="btn btn-warning btn-xs stop-job" data-job-id="${e.id}">\n <i class="bi bi-stop-fill"></i>\n </button>\n `:""}\n ${n?`\n <button class="btn btn-error btn-xs delete-job" data-job-id="${e.id}">\n <i class="bi bi-trash"></i>\n </button>\n `:'\n <button class="btn btn-error btn-xs" disabled>\n <i class="bi bi-trash"></i>\n </button>\n '}\n </div>\n </td>\n `,t}getJobStatus(e){return{pending:{text:"Pending",class:"badge-warning"},started:{text:"Running",class:"badge-primary"},processing:{text:"Processing",class:"badge-info"},completed:{text:"Completed",class:"badge-success"},failed:{text:"Failed",class:"badge-error"},cancelled:{text:"Cancelled",class:"badge-ghost"}}[e]||{text:e,class:"badge-ghost"}}getSortedJobs(){const e=[...this.state.jobs];return e.sort((e,t)=>{let s,r;switch(this.state.sortBy){case"created_at":s=new Date(e.created_at).getTime(),r=new Date(t.created_at).getTime();break;case"status":s=e.status,r=t.status;break;case"arrs":s=e.arrs.join(","),r=t.arrs.join(",");break;default:s=e[this.state.sortBy]||"",r=t[this.state.sortBy]||""}return"string"==typeof s?"asc"===this.state.sortDirection?s.localeCompare(r):r.localeCompare(s):"asc"===this.state.sortDirection?s-r:r-s}),e}renderJobsPagination(e){if(e<=1)return;const t=document.createElement("div");t.className="join";const s=document.createElement("button");s.className="join-item btn btn-sm "+(1===this.state.currentPage?"btn-disabled":""),s.innerHTML='<i class="bi bi-chevron-left"></i>',s.disabled=1===this.state.currentPage,this.state.currentPage>1&&s.addEventListener("click",()=>{this.state.currentPage--,this.renderJobsTable()}),t.appendChild(s);let r=Math.max(1,this.state.currentPage-Math.floor(2.5)),a=Math.min(e,r+5-1);a-r+1<5&&(r=Math.max(1,a-5+1));for(let e=r;e<=a;e++){const s=document.createElement("button");s.className="join-item btn btn-sm "+(e===this.state.currentPage?"btn-active":""),s.textContent=e,s.addEventListener("click",()=>{this.state.currentPage=e,this.renderJobsTable()}),t.appendChild(s)}const n=document.createElement("button");n.className="join-item btn btn-sm "+(this.state.currentPage===e?"btn-disabled":""),n.innerHTML='<i class="bi bi-chevron-right"></i>',n.disabled=this.state.currentPage===e,this.state.currentPage<e&&n.addEventListener("click",()=>{this.state.currentPage++,this.renderJobsTable()}),t.appendChild(n),this.refs.jobsPagination.appendChild(t)}handleJobTableClick(e){const t=e.target.closest("button");if(!t)return;const s=t.dataset.jobId;if(!s)return;t.classList.contains("view-job")?this.viewJobDetails(s):t.classList.contains("process-job")?this.processJob(s):t.classList.contains("stop-job")?this.stopJob(s):t.classList.contains("delete-job")&&this.deleteJob(s);e.target.closest(".job-checkbox")&&this.updateJobSelectionState()}async viewJobDetails(e){const t=this.state.jobs.find(t=>t.id===e);t&&(this.state.currentJob=t,this.populateJobModal(t),this.refs.jobDetailsModal.showModal())}populateJobModal(e){this.refs.modalJobId.textContent=e.id.substring(0,8),this.refs.modalJobArrs.textContent=e.arrs.join(", "),this.refs.modalJobMediaIds.textContent=e.media_ids&&e.media_ids.length>0?e.media_ids.join(", "):"All media",this.refs.modalJobAutoProcess.textContent=e.auto_process?"Yes":"No",this.refs.modalJobStarted.textContent=new Date(e.created_at).toLocaleString(),this.refs.modalJobCompleted.textContent=e.finished_at?new Date(e.finished_at).toLocaleString():"N/A";const t=this.getJobStatus(e.status);this.refs.modalJobStatus.innerHTML=`<span class="badge ${t.class}">${t.text}</span>`,e.error?(this.refs.modalJobError.textContent=e.error,this.refs.errorContainer.classList.remove("hidden")):this.refs.errorContainer.classList.add("hidden"),this.refs.processJobBtn.classList.toggle("hidden","pending"!==e.status),this.refs.stopJobBtn.classList.toggle("hidden",!["started","processing"].includes(e.status)),e.broken_items?(this.state.allBrokenItems=this.processItemsData(e.broken_items),this.state.filteredItems=[...this.state.allBrokenItems],this.populateArrFilter(),this.state.currentItemsPage=1,this.renderBrokenItemsTable()):(this.state.allBrokenItems=[],this.state.filteredItems=[],this.renderBrokenItemsTable()),this.updateItemsStats()}processItemsData(e){const t=[];return Object.entries(e).forEach(([e,s])=>{s&&s.length>0&&s.forEach((s,r)=>{t.push({id:`${e}-${r}`,arr:e,path:s.path||s.file_path||"Unknown path",size:s.size||0,type:this.getFileType(s.path||""),fileId:s.fileId||s.id||`${e}-${r}`})})}),t}getFileType(e){const t=e.toLowerCase();return["/TV/","/Television/","/Series/","/Shows/","/tv/","/series/"].some(e=>t.includes(e.toLowerCase()))?"tv":[".mp4",".mkv",".avi",".mov",".wmv",".flv",".webm"].some(e=>t.endsWith(e))?t.includes("/movies/")||t.includes("/films/")?"movie":"tv":"other"}populateArrFilter(){this.refs.arrFilterSelect.innerHTML='<option value="">All Arrs</option>';[...new Set(this.state.allBrokenItems.map(e=>e.arr))].forEach(e=>{const t=document.createElement("option");t.value=e,t.textContent=e,this.refs.arrFilterSelect.appendChild(t)})}applyFilters(){const e=this.refs.itemSearchInput.value.toLowerCase(),t=this.refs.arrFilterSelect.value,s=this.refs.pathFilterSelect.value;this.state.filteredItems=this.state.allBrokenItems.filter(r=>{const a=!e||r.path.toLowerCase().includes(e),n=!t||r.arr===t,o=!s||r.type===s;return a&&n&&o}),this.state.currentItemsPage=1,this.renderBrokenItemsTable(),this.updateItemsStats()}clearFilters(){this.refs.itemSearchInput.value="",this.refs.arrFilterSelect.value="",this.refs.pathFilterSelect.value="",this.applyFilters()}renderBrokenItemsTable(){if(this.refs.brokenItemsTableBody.innerHTML="",this.refs.itemsPagination.innerHTML="",0===this.state.allBrokenItems.length)return this.refs.noBrokenItemsMessage.classList.remove("hidden"),void this.refs.noFilteredItemsMessage.classList.add("hidden");if(0===this.state.filteredItems.length)return this.refs.noBrokenItemsMessage.classList.add("hidden"),void this.refs.noFilteredItemsMessage.classList.remove("hidden");this.refs.noBrokenItemsMessage.classList.add("hidden"),this.refs.noFilteredItemsMessage.classList.add("hidden");const e=Math.ceil(this.state.filteredItems.length/this.state.itemsPerModalPage),t=(this.state.currentItemsPage-1)*this.state.itemsPerModalPage,s=Math.min(t+this.state.itemsPerModalPage,this.state.filteredItems.length);this.state.filteredItems.slice(t,s).forEach(e=>{const t=this.createBrokenItemRow(e);this.refs.brokenItemsTableBody.appendChild(t)}),this.renderItemsPagination(e)}createBrokenItemRow(e){const t=document.createElement("tr");t.className="hover:bg-base-200 transition-colors cursor-pointer",t.dataset.itemId=e.id;return t.innerHTML=`\n <td>\n <div class="badge badge-info badge-xs">${window.decypharrUtils.escapeHtml(e.arr)}</div>\n </td>\n <td>\n <div class="text-sm max-w-xs truncate" title="${window.decypharrUtils.escapeHtml(e.path)}">\n ${window.decypharrUtils.escapeHtml(e.path)}\n </div>\n </td>\n <td>\n <div class="badge ${{movie:"badge-primary",tv:"badge-secondary",other:"badge-ghost"}[e.type]} badge-xs">${e.type}</div>\n </td>\n <td>\n <span class="text-sm font-mono">${window.decypharrUtils.formatBytes(e.size)}</span>\n </td>\n `,t}renderItemsPagination(e){if(e<=1)return;const t=document.createElement("div");t.className="join";const s=document.createElement("button");s.className="join-item btn btn-sm "+(1===this.state.currentItemsPage?"btn-disabled":""),s.innerHTML='<i class="bi bi-chevron-left"></i>',s.disabled=1===this.state.currentItemsPage,this.state.currentItemsPage>1&&s.addEventListener("click",()=>{this.state.currentItemsPage--,this.renderBrokenItemsTable()}),t.appendChild(s);let r=Math.max(1,this.state.currentItemsPage-Math.floor(2.5)),a=Math.min(e,r+5-1);for(let e=r;e<=a;e++){const s=document.createElement("button");s.className="join-item btn btn-sm "+(e===this.state.currentItemsPage?"btn-active":""),s.textContent=e,s.addEventListener("click",()=>{this.state.currentItemsPage=e,this.renderBrokenItemsTable()}),t.appendChild(s)}const n=document.createElement("button");n.className="join-item btn btn-sm "+(this.state.currentItemsPage===e?"btn-disabled":""),n.innerHTML='<i class="bi bi-chevron-right"></i>',n.disabled=this.state.currentItemsPage===e,this.state.currentItemsPage<e&&n.addEventListener("click",()=>{this.state.currentItemsPage++,this.renderBrokenItemsTable()}),t.appendChild(n),this.refs.itemsPagination.appendChild(t)}updateItemsStats(){this.refs.totalItemsCount.textContent=this.state.allBrokenItems.length,this.refs.modalFooterStats.textContent=`Total: ${this.state.allBrokenItems.length} | Filtered: ${this.state.filteredItems.length}`}async processJob(e){try{const t=await window.decypharrUtils.fetcher(`/api/repair/jobs/${e}/process`,{method:"POST"});if(!t.ok){const e=await t.text();throw new Error(e||"Failed to process job")}window.decypharrUtils.createToast("Job processing started","success"),await this.loadJobs()}catch(e){console.error("Error processing job:",e),window.decypharrUtils.createToast(`Error processing job: ${e.message}`,"error")}}async stopJob(e){if(confirm("Are you sure you want to stop this job?"))try{const t=await window.decypharrUtils.fetcher(`/api/repair/jobs/${e}/stop`,{method:"POST"});if(!t.ok){const e=await t.text();throw new Error(e||"Failed to stop job")}window.decypharrUtils.createToast("Job stop requested","success"),await this.loadJobs()}catch(e){console.error("Error stopping job:",e),window.decypharrUtils.createToast(`Error stopping job: ${e.message}`,"error")}}async deleteJob(e){if(confirm("Are you sure you want to delete this job?"))try{const t=await window.decypharrUtils.fetcher("/api/repair/jobs",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({ids:[e]})});if(!t.ok){const e=await t.text();throw new Error(e||"Failed to delete job")}window.decypharrUtils.createToast("Job deleted successfully","success"),await this.loadJobs()}catch(e){console.error("Error deleting job:",e),window.decypharrUtils.createToast(`Error deleting job: ${e.message}`,"error")}}async deleteSelectedJobs(){const e=Array.from(document.querySelectorAll(".job-checkbox:checked")).map(e=>e.value);if(0!==e.length&&confirm(`Are you sure you want to delete ${e.length} job(s)?`))try{const t=await window.decypharrUtils.fetcher("/api/repair/jobs",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({ids:e})});if(!t.ok){const e=await t.text();throw new Error(e||"Failed to delete jobs")}window.decypharrUtils.createToast(`${e.length} job(s) deleted successfully`,"success"),await this.loadJobs()}catch(e){console.error("Error deleting jobs:",e),window.decypharrUtils.createToast(`Error deleting jobs: ${e.message}`,"error")}}toggleSelectAllJobs(e){document.querySelectorAll(".job-checkbox:not(:disabled)").forEach(t=>{t.checked=e}),this.updateJobSelectionState()}updateJobSelectionState(){document.querySelectorAll(".job-checkbox");const e=document.querySelectorAll(".job-checkbox:checked"),t=document.querySelectorAll(".job-checkbox:not(:disabled)");this.refs.deleteSelectedJobs.disabled=0===e.length,0===t.length?(this.refs.selectAllJobs.checked=!1,this.refs.selectAllJobs.indeterminate=!1):e.length===t.length?(this.refs.selectAllJobs.checked=!0,this.refs.selectAllJobs.indeterminate=!1):e.length>0?(this.refs.selectAllJobs.checked=!1,this.refs.selectAllJobs.indeterminate=!0):(this.refs.selectAllJobs.checked=!1,this.refs.selectAllJobs.indeterminate=!1)}async processCurrentJob(){this.state.currentJob&&(await this.processJob(this.state.currentJob.id),this.refs.jobDetailsModal.close())}async stopCurrentJob(){this.state.currentJob&&(await this.stopJob(this.state.currentJob.id),this.refs.jobDetailsModal.close())}handleItemTableClick(e){const t=e.target.closest("tr");if(!t)return;const s=t.dataset.itemId;s&&(this.state.selectedItems.has(s)?(this.state.selectedItems.delete(s),t.classList.remove("bg-primary/10")):(this.state.selectedItems.add(s),t.classList.add("bg-primary/10")))}startAutoRefresh(){this.refreshInterval=setInterval(()=>{!this.state.jobs.some(e=>["started","processing","pending"].includes(e.status))&&this.refs.jobDetailsModal.open||this.loadJobs()},1e4),document.addEventListener("visibilitychange",()=>{document.hidden?this.refreshInterval&&(clearInterval(this.refreshInterval),this.refreshInterval=null):this.refreshInterval||this.startAutoRefresh()}),window.addEventListener("beforeunload",()=>{this.refreshInterval&&clearInterval(this.refreshInterval)})}formatJobDuration(e,t){if(!e)return"N/A";const s=new Date(e),r=t?new Date(t):new Date,a=Math.floor((r-s)/1e3);return window.decypharrUtils.formatDuration(a)}getJobProgress(e){if(!e.broken_items)return 0;return 0===Object.values(e.broken_items).reduce((e,t)=>e+t.length,0)||"completed"===e.status?100:0}async exportJobData(e){const t=this.state.jobs.find(t=>t.id===e);if(!t)return;const s={job_id:t.id,status:t.status,created_at:t.created_at,finished_at:t.finished_at,arrs:t.arrs,media_ids:t.media_ids,auto_process:t.auto_process,broken_items:t.broken_items,error:t.error};try{const e=new Blob([JSON.stringify(s,null,2)],{type:"application/json"}),r=URL.createObjectURL(e),a=document.createElement("a");a.href=r,a.download=`repair-job-${t.id.substring(0,8)}-${(new Date).toISOString().split("T")[0]}.json`,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(r),window.decypharrUtils.createToast("Job data exported successfully","success")}catch(e){console.error("Error exporting job data:",e),window.decypharrUtils.createToast("Failed to export job data","error")}}getJobStatistics(){const e={total:this.state.jobs.length,pending:0,running:0,completed:0,failed:0,cancelled:0};return this.state.jobs.forEach(t=>{switch(t.status){case"pending":e.pending++;break;case"started":case"processing":e.running++;break;case"completed":e.completed++;break;case"failed":e.failed++;break;case"cancelled":e.cancelled++}}),e}searchJobs(e){if(!e)return this.state.jobs;const t=e.toLowerCase();return this.state.jobs.filter(e=>e.id.toLowerCase().includes(t)||e.arrs.some(e=>e.toLowerCase().includes(t))||e.media_ids&&e.media_ids.some(e=>e.toString().includes(t)))}filterJobsByStatus(e){return e?this.state.jobs.filter(t=>t.status===e):this.state.jobs}filterJobsByDate(e,t){return e||t?this.state.jobs.filter(s=>{const r=new Date(s.created_at);return!(e&&r<new Date(e))&&!(t&&r>new Date(t))}):this.state.jobs}destroy(){this.refreshInterval&&clearInterval(this.refreshInterval),Object.values(this.refs).forEach(e=>{e&&e.removeEventListener})}}const RepairUtils={formatRepairStatus:(e,t=null)=>({pending:{icon:"bi-clock",class:"text-warning",message:"Waiting to start"},started:{icon:"bi-play-circle",class:"text-primary",message:"Repair in progress"},processing:{icon:"bi-gear",class:"text-info",message:"Processing results"},completed:{icon:"bi-check-circle",class:"text-success",message:"Repair completed successfully"},failed:{icon:"bi-x-circle",class:"text-error",message:t||"Repair failed"},cancelled:{icon:"bi-stop-circle",class:"text-warning",message:"Repair was cancelled"}}[e]||{icon:"bi-question-circle",class:"text-gray-500",message:`Unknown status: ${e}`}),validateMediaIds(e){if(!e||!e.trim())return{valid:!0,ids:[]};const t=e.split(",").map(e=>e.trim()).filter(Boolean),s=t.filter(e=>!/^\d+$/.test(e));return s.length>0?{valid:!1,error:`Invalid media IDs: ${s.join(", ")}. Only numeric IDs are allowed.`,ids:[]}:{valid:!0,ids:t}},generateRepairSummary(e){if(!e.broken_items)return"No broken items found";const t=Object.entries(e.broken_items).map(([e,t])=>`${e}: ${t.length} items`);return`Found ${Object.values(e.broken_items).reduce((e,t)=>e+t.length,0)} broken items across ${Object.keys(e.broken_items).length} Arr instance(s): ${t.join(", ")}`},calculateProgress(e){switch(e.status){case"pending":case"failed":case"cancelled":default:return 0;case"started":return 25;case"processing":return 75;case"completed":return 100}}}; |