feat: auto update
This commit is contained in:
530
static/js/main.js
Normal file
530
static/js/main.js
Normal file
@@ -0,0 +1,530 @@
|
||||
let statusCheckInterval;
|
||||
|
||||
function startStatusCheck() {
|
||||
// Очищаем предыдущий интервал, если он существует
|
||||
if (statusCheckInterval) {
|
||||
clearInterval(statusCheckInterval);
|
||||
}
|
||||
// Проверяем статус каждые 500мс
|
||||
statusCheckInterval = setInterval(checkStatus, 500);
|
||||
// Сразу делаем первую проверку
|
||||
checkStatus();
|
||||
}
|
||||
|
||||
function checkStatus() {
|
||||
fetch('/status')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const status = document.getElementById('status');
|
||||
const button = document.getElementById('parseButton');
|
||||
|
||||
if (data.is_running) {
|
||||
let statusHtml = `
|
||||
<p>
|
||||
Парсинг запущено для категорії: ${data.current_category}<br>
|
||||
`;
|
||||
|
||||
if (data.total_products > 0) {
|
||||
statusHtml += `
|
||||
<strong>Товар ${data.current_product} із ${data.total_products}</strong><br>
|
||||
Прогрес: ${data.progress}%
|
||||
`;
|
||||
} else {
|
||||
statusHtml += `Отримання інформації про кількість товарів...`;
|
||||
}
|
||||
|
||||
statusHtml += `</p>`;
|
||||
status.innerHTML = statusHtml;
|
||||
setTimeout(checkStatus, 500);
|
||||
} else {
|
||||
if (data.error) {
|
||||
status.innerHTML = `<p class="error">Помилка: ${data.error}</p>`;
|
||||
} else if (data.total_products > 0) {
|
||||
status.innerHTML = `
|
||||
<p>
|
||||
Парсинг завершено<br>
|
||||
<strong>Всього оброблено товарів: ${data.total_products}</strong>
|
||||
</p>
|
||||
`;
|
||||
updateFilesList('parsed', 'parser', 'file-select');
|
||||
}
|
||||
button.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Status check error:', error);
|
||||
status.innerHTML = `<p class="error">Помилка: ${error.message}</p>`;
|
||||
button.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function stopStatusCheck() {
|
||||
if (statusCheckInterval) {
|
||||
clearInterval(statusCheckInterval);
|
||||
statusCheckInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteFile(filename, type) {
|
||||
if (confirm('Ви впевнені, що хочете видалити цей файл?')) {
|
||||
fetch(`/delete/${filename}`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
alert(data.error);
|
||||
} else {
|
||||
// Удаляем файл из списка
|
||||
const fileItem = document.querySelector(`a[href*="${filename}"]`).closest('li');
|
||||
fileItem.remove();
|
||||
|
||||
// Обновляем соответствующий select в зависимости от типа файла
|
||||
if (type === 'parsed') {
|
||||
const fileSelect = document.getElementById('file-select');
|
||||
const optionToRemove = Array.from(fileSelect.options).find(opt => opt.value === filename);
|
||||
if (optionToRemove) {
|
||||
optionToRemove.remove();
|
||||
}
|
||||
} else if (type === 'translated') {
|
||||
const ymlFileSelect = document.getElementById('yml-file-select');
|
||||
const optionToRemove = Array.from(ymlFileSelect.options).find(opt => opt.value === filename);
|
||||
if (optionToRemove) {
|
||||
optionToRemove.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Помилка при видаленні файлу');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function openTab(tabName) {
|
||||
// Приховуємо всі вкладки
|
||||
const tabContents = document.getElementsByClassName('tab-content');
|
||||
for (let content of tabContents) {
|
||||
content.classList.remove('active');
|
||||
}
|
||||
|
||||
// Деактивуємо всі кнопки
|
||||
const tabButtons = document.getElementsByClassName('tab-button');
|
||||
for (let button of tabButtons) {
|
||||
button.classList.remove('active');
|
||||
}
|
||||
|
||||
// Показуємо вибрану вкладку
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
|
||||
// Активуємо потрібну кнопку
|
||||
event.currentTarget.classList.add('active');
|
||||
}
|
||||
|
||||
function startTranslation() {
|
||||
const fileSelect = document.getElementById('file-select');
|
||||
const button = document.getElementById('translateButton');
|
||||
const status = document.getElementById('translation-status');
|
||||
|
||||
if (!fileSelect.value) {
|
||||
status.innerHTML = '<p class="error">Будь ласка, виберіть файл для обробки</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
status.innerHTML = '<p>Починаємо переклад...</p>';
|
||||
|
||||
fetch('/translate', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `filename=${encodeURIComponent(fileSelect.value)}`
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
status.innerHTML = `<p class="error">${data.error}</p>`;
|
||||
button.disabled = false;
|
||||
} else {
|
||||
checkTranslationStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function checkTranslationStatus() {
|
||||
fetch('/translation-status')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const status = document.getElementById('translation-status');
|
||||
const button = document.getElementById('translateButton');
|
||||
|
||||
if (data.is_running) {
|
||||
if (data.total_items > 0) {
|
||||
const percent = Math.round((data.processed_items / data.total_items) * 100);
|
||||
status.innerHTML = `
|
||||
<p>
|
||||
Переклад в процесі...<br>
|
||||
Оброблено: ${data.processed_items} з ${data.total_items}<br>
|
||||
Прогрес: ${percent}%
|
||||
</p>
|
||||
`;
|
||||
} else {
|
||||
status.innerHTML = '<p>Підготовка до перекладу...</p>';
|
||||
}
|
||||
setTimeout(checkTranslationStatus, 1000);
|
||||
} else {
|
||||
if (data.error) {
|
||||
status.innerHTML = `<p class="error">Помилка: ${data.error}</p>`;
|
||||
} else if (data.total_items > 0) {
|
||||
status.innerHTML = `
|
||||
<p>
|
||||
Переклад завершено<br>
|
||||
Всього оброблено товарів: ${data.total_items}
|
||||
</p>
|
||||
`;
|
||||
updateFilesList('translated', 'processor', 'yml-file-select');
|
||||
}
|
||||
button.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Translation status check error:', error);
|
||||
status.innerHTML = `<p class="error">Помилка: ${error.message}</p>`;
|
||||
document.getElementById('translateButton').disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function generateFullYML() {
|
||||
if (!confirm("Згенерувати загальний YML-файл для всіх категорій?")) return;
|
||||
|
||||
fetch('/generate-full-yml', { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert("Повний YML успішно згенеровано");
|
||||
updateFilesList('yml', 'generator');
|
||||
} else {
|
||||
alert("Помилка: " + (data.error || "Невідома помилка"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateItemsLimit(value) {
|
||||
fetch('/update-settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
items_limit: parseInt(value)
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
alert(data.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addCategory() {
|
||||
const categoryName = document.getElementById('category-name').value;
|
||||
const portalId = document.getElementById('portal-id').value;
|
||||
const categoryUrl = document.getElementById('category-url').value;
|
||||
|
||||
if (!categoryName || !categoryUrl) {
|
||||
alert('Будь ласка, заповніть назву і URL категорії');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/add-category', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: categoryName,
|
||||
portal_id: portalId,
|
||||
url: categoryUrl
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
alert(data.error);
|
||||
} else {
|
||||
const newId = data.id;
|
||||
|
||||
document.getElementById('category-name').value = '';
|
||||
document.getElementById('portal-id').value = '';
|
||||
document.getElementById('category-url').value = '';
|
||||
|
||||
const categoriesList = document.getElementById('categories-list');
|
||||
const li = document.createElement('li');
|
||||
li.title = categoryUrl;
|
||||
li.innerHTML = `
|
||||
<span>${newId} - ${categoryName} ${portalId ? `(portal_id: ${portalId})` : ''}</span>
|
||||
<button class="delete-btn" onclick="deleteCategory('${newId}')" title="Видалити категорію">🗑️</button>
|
||||
`;
|
||||
categoriesList.appendChild(li);
|
||||
|
||||
const ymlSelect = document.getElementById('yml-category-select');
|
||||
const option = document.createElement('option');
|
||||
option.value = newId;
|
||||
option.text = categoryName;
|
||||
ymlSelect.appendChild(option);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCategory(categoryId) {
|
||||
if (confirm('Ви впевнені, що хочете видалити цю категорію?')) {
|
||||
fetch('/delete-category', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: categoryId
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
alert(data.error);
|
||||
} else {
|
||||
// Удаляем категорию из списка
|
||||
const categoryItems = document.querySelectorAll('#categories-list li');
|
||||
categoryItems.forEach(item => {
|
||||
if (item.querySelector('span').textContent.startsWith(categoryId + ' -')) {
|
||||
item.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Удаляем опцию из select в генераторе YML
|
||||
const ymlSelect = document.getElementById('yml-category-select');
|
||||
const option = Array.from(ymlSelect.options).find(opt => opt.value === categoryId);
|
||||
if (option) option.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function generateYML() {
|
||||
console.log('generateYML called'); // Отладка
|
||||
const categoryId = document.getElementById('yml-category-select').value;
|
||||
const fileSelect = document.getElementById('yml-file-select');
|
||||
console.log('Selected category:', categoryId); // Отладка
|
||||
console.log('Selected file:', fileSelect.value); // Отладка
|
||||
const button = document.getElementById('generateButton');
|
||||
const status = document.getElementById('yml-status');
|
||||
|
||||
if (!categoryId || !fileSelect.value) {
|
||||
status.innerHTML = '<p class="error">Будь ласка, виберіть категорію та файл</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
status.innerHTML = '<p>Генерація YML...</p>';
|
||||
|
||||
fetch('/generate-yml', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
filename: fileSelect.value,
|
||||
category_id: categoryId
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
status.innerHTML = `<p class="error">${data.error}</p>`;
|
||||
} else {
|
||||
status.innerHTML = '<p>YML файл успішно згенеровано</p>';
|
||||
updateFilesList('yml', 'generator', null);
|
||||
}
|
||||
button.disabled = false;
|
||||
})
|
||||
.catch(error => {
|
||||
status.innerHTML = `<p class="error">Помилка: ${error.message}</p>`;
|
||||
button.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Добавляем вспомогательную функцию для поиска по тексту
|
||||
jQuery.expr[':'].contains = function (a, i, m) {
|
||||
return jQuery(a).text().toUpperCase()
|
||||
.indexOf(m[3].toUpperCase()) >= 0;
|
||||
};
|
||||
|
||||
function updateFilesList(fileType, containerId, selectId = null) {
|
||||
fetch(`/get-files/${fileType}`)
|
||||
.then(response => response.json())
|
||||
.then(files => {
|
||||
// Обновляем список файлов
|
||||
const filesList = document.querySelector(`#${containerId} .files ul`);
|
||||
filesList.innerHTML = files.map(file => `
|
||||
<li>
|
||||
<div class="file-info">
|
||||
<a href="/download/${file.name}" class="download-link" download>
|
||||
${file.name}
|
||||
</a>
|
||||
<span class="file-date">${file.modified}</span>
|
||||
<span class="file-size">${file.size}</span>
|
||||
</div>
|
||||
<button class="delete-btn" onclick="deleteFile('${file.name}', '${fileType}')" title="Видалити файл">
|
||||
🗑️
|
||||
</button>
|
||||
</li>
|
||||
`).join('');
|
||||
|
||||
// Обновляем select, если указан
|
||||
if (selectId) {
|
||||
const select = document.getElementById(selectId);
|
||||
select.innerHTML = '<option value="">Виберіть файл...</option>' +
|
||||
files.map(file => `
|
||||
<option value="${file.name}">${file.name}</option>
|
||||
`).join('');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showLoader(element) {
|
||||
element.classList.add('loading');
|
||||
}
|
||||
|
||||
function hideLoader(element) {
|
||||
element.classList.remove('loading');
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'error-message';
|
||||
errorDiv.textContent = message;
|
||||
document.body.appendChild(errorDiv);
|
||||
setTimeout(() => errorDiv.remove(), 3000);
|
||||
}
|
||||
|
||||
// Использование:
|
||||
async function someAction() {
|
||||
const element = document.getElementById('someElement');
|
||||
showLoader(element);
|
||||
try {
|
||||
const response = await fetch('/some-endpoint');
|
||||
const data = await response.json();
|
||||
if (data.error) throw new Error(data.error);
|
||||
// обработка успешного ответа
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
} finally {
|
||||
hideLoader(element);
|
||||
}
|
||||
}
|
||||
|
||||
function translateAllCategories() {
|
||||
if (!confirm("Запустити переклад для всіх категорій?")) return;
|
||||
|
||||
fetch('/manual-translate-all', {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.message) {
|
||||
alert(data.message);
|
||||
} else {
|
||||
alert("Переклад запущено.");
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert("Помилка при запуску перекладу: " + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function startParsing() {
|
||||
const url = document.getElementById('url').value;
|
||||
const button = document.getElementById('parseButton');
|
||||
const status = document.getElementById('status');
|
||||
|
||||
if (!url) {
|
||||
status.innerHTML = '<p class="error">Будь ласка, введіть URL</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
status.innerHTML = '<p>Починаємо парсинг...</p>';
|
||||
|
||||
fetch('/parse', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `url=${encodeURIComponent(url)}`
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
status.innerHTML = `<p class="error">${data.error}</p>`;
|
||||
button.disabled = false;
|
||||
} else {
|
||||
checkParsingStatus();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
status.innerHTML = `<p class="error">Помилка: ${error.message}</p>`;
|
||||
button.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function checkParsingStatus() {
|
||||
fetch('/status')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const status = document.getElementById('status');
|
||||
const button = document.getElementById('parseButton');
|
||||
|
||||
if (data.is_running) {
|
||||
if (data.total_items > 0) {
|
||||
const percent = Math.round((data.processed_items / data.total_items) * 100);
|
||||
status.innerHTML = `
|
||||
<p>
|
||||
Парсинг в процесі...<br>
|
||||
Оброблено: ${data.processed_items} з ${data.total_items}<br>
|
||||
Прогрес: ${percent}%
|
||||
</p>
|
||||
`;
|
||||
} else {
|
||||
status.innerHTML = '<p>Отримання інформації про товари...</p>';
|
||||
}
|
||||
setTimeout(checkParsingStatus, 1000);
|
||||
} else {
|
||||
if (data.error) {
|
||||
status.innerHTML = `<p class="error">Помилка: ${data.error}</p>`;
|
||||
} else {
|
||||
status.innerHTML = `
|
||||
<p>
|
||||
Парсинг завершено<br>
|
||||
Всього оброблено товарів: ${data.total_items}
|
||||
</p>
|
||||
`;
|
||||
updateFilesList('parsed', 'parser', 'file-select');
|
||||
}
|
||||
button.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Status check error:', error);
|
||||
status.innerHTML = `<p class="error">Помилка: ${error.message}</p>`;
|
||||
document.getElementById('parseButton').disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function refreshOldestCategory() {
|
||||
fetch('/manual-refresh-all', {
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user