Add inline TPDB import progress bar and remove import confirmations
This commit is contained in:
parent
4067f9b793
commit
919cf65f30
|
|
@ -578,6 +578,50 @@ main.container {
|
|||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.job-progress {
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem 1rem;
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1900;
|
||||
min-width: 320px;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.job-progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.job-progress-bar {
|
||||
margin-top: 0.6rem;
|
||||
height: 10px;
|
||||
background: var(--color-bg-elevated);
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.job-progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, var(--color-brand) 0%, var(--color-keypoint) 100%);
|
||||
width: 0%;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
.job-progress-message {
|
||||
margin-top: 0.4rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Detail views */
|
||||
.breadcrumb {
|
||||
margin-bottom: 1.5rem;
|
||||
|
|
|
|||
|
|
@ -97,164 +97,62 @@ function displayGlobalSearchResults(data) {
|
|||
|
||||
// Bulk Import Functions
|
||||
async function bulkImportAll() {
|
||||
showLoader('Importing all data from TPDB...');
|
||||
if (!confirm('This will import ALL data from TPDB. This may take several hours. Continue?')) {
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
setImportStatus('import-all', 'Importing all data from TPDB... This may take a while.', false);
|
||||
|
||||
showLoader('Importing everything from TPDB...');
|
||||
startJobProgress('TPDB bulk import');
|
||||
try {
|
||||
const response = await fetch('/api/import/all', {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
let message = result.message + '\n\n';
|
||||
if (result.data) {
|
||||
result.data.forEach(r => {
|
||||
message += `${r.EntityType}: ${r.Imported}/${r.Total} imported, ${r.Failed} failed\n`;
|
||||
});
|
||||
}
|
||||
setImportStatus('import-all', message, true);
|
||||
setTimeout(() => {
|
||||
closeModal('import-all-modal');
|
||||
location.reload();
|
||||
}, 3000);
|
||||
} else {
|
||||
setImportStatus('import-all', result.message, false);
|
||||
}
|
||||
} catch (error) {
|
||||
setImportStatus('import-all', 'Error: ' + error.message, false);
|
||||
await importWithProgress('/api/import/all-performers/progress', 'Performers');
|
||||
await importWithProgress('/api/import/all-studios/progress', 'Studios');
|
||||
await importWithProgress('/api/import/all-scenes/progress', 'Scenes');
|
||||
setImportStatus('import-all', 'Bulk import complete', true);
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
} catch (err) {
|
||||
setImportStatus('import-all', `Bulk import error: ${err.message}`, false);
|
||||
} finally {
|
||||
stopJobProgress();
|
||||
hideLoader();
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkImportPerformers() {
|
||||
showLoader('Importing performers from TPDB...');
|
||||
if (!confirm('This will import ALL performers from TPDB. Continue?')) {
|
||||
startJobProgress('Importing performers from TPDB');
|
||||
try {
|
||||
await importWithProgress('/api/import/all-performers/progress', 'Performers');
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
} catch (err) {
|
||||
setImportStatus('performer', `Error: ${err.message}`, false);
|
||||
} finally {
|
||||
stopJobProgress();
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show progress modal
|
||||
showProgressModal('performers');
|
||||
|
||||
// Connect to SSE endpoint
|
||||
const eventSource = new EventSource('/api/import/all-performers/progress');
|
||||
|
||||
eventSource.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.error) {
|
||||
updateProgress('performers', 0, 0, data.error, true);
|
||||
eventSource.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.complete) {
|
||||
updateProgress('performers', 100, 100, `Complete! Imported ${data.result.Imported}/${data.result.Total} performers`, false);
|
||||
eventSource.close();
|
||||
setTimeout(() => {
|
||||
closeProgressModal();
|
||||
location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
updateProgress('performers', data.current, data.total, data.message, false);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = function() {
|
||||
updateProgress('performers', 0, 0, 'Connection error', true);
|
||||
eventSource.close();
|
||||
hideLoader();
|
||||
};
|
||||
}
|
||||
|
||||
async function bulkImportStudios() {
|
||||
showLoader('Importing studios from TPDB...');
|
||||
if (!confirm('This will import ALL studios from TPDB. Continue?')) {
|
||||
startJobProgress('Importing studios from TPDB');
|
||||
try {
|
||||
await importWithProgress('/api/import/all-studios/progress', 'Studios');
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
} catch (err) {
|
||||
setImportStatus('studio', `Error: ${err.message}`, false);
|
||||
} finally {
|
||||
stopJobProgress();
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show progress modal
|
||||
showProgressModal('studios');
|
||||
|
||||
// Connect to SSE endpoint
|
||||
const eventSource = new EventSource('/api/import/all-studios/progress');
|
||||
|
||||
eventSource.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.error) {
|
||||
updateProgress('studios', 0, 0, data.error, true);
|
||||
eventSource.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.complete) {
|
||||
updateProgress('studios', 100, 100, `Complete! Imported ${data.result.Imported}/${data.result.Total} studios`, false);
|
||||
eventSource.close();
|
||||
setTimeout(() => {
|
||||
closeProgressModal();
|
||||
location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
updateProgress('studios', data.current, data.total, data.message, false);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = function() {
|
||||
updateProgress('studios', 0, 0, 'Connection error', true);
|
||||
eventSource.close();
|
||||
hideLoader();
|
||||
};
|
||||
}
|
||||
|
||||
async function bulkImportScenes() {
|
||||
showLoader('Importing scenes from TPDB...');
|
||||
if (!confirm('This will import ALL scenes from TPDB. Continue?')) {
|
||||
startJobProgress('Importing scenes from TPDB');
|
||||
try {
|
||||
await importWithProgress('/api/import/all-scenes/progress', 'Scenes');
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
} catch (err) {
|
||||
setImportStatus('scene', `Error: ${err.message}`, false);
|
||||
} finally {
|
||||
stopJobProgress();
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show progress modal
|
||||
showProgressModal('scenes');
|
||||
|
||||
// Connect to SSE endpoint
|
||||
const eventSource = new EventSource('/api/import/all-scenes/progress');
|
||||
|
||||
eventSource.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.error) {
|
||||
updateProgress('scenes', 0, 0, data.error, true);
|
||||
eventSource.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.complete) {
|
||||
updateProgress('scenes', 100, 100, `Complete! Imported ${data.result.Imported}/${data.result.Total} scenes`, false);
|
||||
eventSource.close();
|
||||
setTimeout(() => {
|
||||
closeProgressModal();
|
||||
location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
updateProgress('scenes', data.current, data.total, data.message, false);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = function() {
|
||||
updateProgress('scenes', 0, 0, 'Connection error', true);
|
||||
eventSource.close();
|
||||
hideLoader();
|
||||
};
|
||||
}
|
||||
|
||||
function bulkImportMovies() {
|
||||
|
|
@ -581,6 +479,75 @@ function hideLoader() {
|
|||
const overlay = document.getElementById('global-loader');
|
||||
if (overlay) overlay.style.display = 'none';
|
||||
}
|
||||
|
||||
// Unified SSE import helper with progress bar
|
||||
function importWithProgress(url, label) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const eventSource = new EventSource(url);
|
||||
startJobProgress(label);
|
||||
eventSource.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.error) {
|
||||
updateJobProgress(0, 0, data.error, true);
|
||||
eventSource.close();
|
||||
reject(new Error(data.error));
|
||||
return;
|
||||
}
|
||||
if (data.complete) {
|
||||
updateJobProgress(data.result.Imported, data.result.Total, `${label} complete (${data.result.Imported}/${data.result.Total})`, false);
|
||||
eventSource.close();
|
||||
resolve(data.result);
|
||||
return;
|
||||
}
|
||||
updateJobProgress(data.current, data.total, data.message, false);
|
||||
};
|
||||
eventSource.onerror = function() {
|
||||
updateJobProgress(0, 0, `${label} connection error`, true);
|
||||
eventSource.close();
|
||||
reject(new Error('Connection error'));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function startJobProgress(label) {
|
||||
const container = document.getElementById('job-progress');
|
||||
const lbl = document.getElementById('job-progress-label');
|
||||
const msg = document.getElementById('job-progress-message');
|
||||
const fill = document.getElementById('job-progress-fill');
|
||||
const count = document.getElementById('job-progress-count');
|
||||
if (container && lbl && msg && fill && count) {
|
||||
container.style.display = 'block';
|
||||
lbl.textContent = label || 'Working...';
|
||||
msg.textContent = '';
|
||||
count.textContent = '';
|
||||
fill.style.width = '0%';
|
||||
}
|
||||
}
|
||||
|
||||
function updateJobProgress(current, total, message, isError) {
|
||||
const container = document.getElementById('job-progress');
|
||||
const msg = document.getElementById('job-progress-message');
|
||||
const fill = document.getElementById('job-progress-fill');
|
||||
const count = document.getElementById('job-progress-count');
|
||||
if (container && fill && msg && count) {
|
||||
const percent = total > 0 ? Math.min(100, (current / total) * 100) : 0;
|
||||
fill.style.width = `${percent}%`;
|
||||
count.textContent = total > 0 ? `${current}/${total}` : '';
|
||||
msg.textContent = message || '';
|
||||
if (isError) {
|
||||
fill.style.background = '#ff8a8a';
|
||||
msg.style.color = '#ff8a8a';
|
||||
} else {
|
||||
fill.style.background = 'linear-gradient(135deg, var(--color-brand) 0%, var(--color-keypoint) 100%)';
|
||||
msg.style.color = 'var(--color-text-secondary)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopJobProgress() {
|
||||
const container = document.getElementById('job-progress');
|
||||
if (container) container.style.display = 'none';
|
||||
}
|
||||
window.onclick = function(event) {
|
||||
if (event.target.classList.contains('modal')) {
|
||||
event.target.classList.remove('active');
|
||||
|
|
|
|||
|
|
@ -63,4 +63,14 @@
|
|||
<div id="global-loader-text">Working...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="job-progress" class="job-progress" style="display:none;">
|
||||
<div class="job-progress-header">
|
||||
<span id="job-progress-label">Importing...</span>
|
||||
<span id="job-progress-count"></span>
|
||||
</div>
|
||||
<div class="job-progress-bar">
|
||||
<div class="job-progress-fill" id="job-progress-fill" style="width:0%"></div>
|
||||
</div>
|
||||
<div class="job-progress-message" id="job-progress-message"></div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user