Add smart self-healing for sync errors
Implements automatic repair mechanism for pending changes with sync errors: - Projects: validates and fixes empty name/code fields - Configurations: ensures project references exist or assigns system project - Clears errors and resets attempts to give changes another sync chance Backend: - LocalDB.RepairPendingChanges() with smart validation logic - POST /api/sync/repair endpoint - Detailed repair results with remaining errors Frontend: - Auto-repair section in sync modal shown when errors exist - "ИСПРАВИТЬ" button with clear explanation of actions - Real-time feedback with result messages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,26 @@
|
||||
<h4 class="font-medium text-gray-900 mb-2">Ошибки синхронизации</h4>
|
||||
<div id="modal-errors-list" class="text-sm max-h-40 overflow-y-auto space-y-1"></div>
|
||||
</div>
|
||||
|
||||
<!-- Section 5: Self-Healing (shown only if errors exist) -->
|
||||
<div id="modal-repair-section" class="hidden">
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<h4 class="font-medium text-blue-900 mb-2">Автоматическое исправление</h4>
|
||||
<p class="text-sm text-blue-700 mb-3">
|
||||
Система может исправить данные и очистить ошибки синхронизации:
|
||||
</p>
|
||||
<ul class="text-sm text-blue-700 mb-3 ml-4 list-disc space-y-1">
|
||||
<li>Проверит и исправит названия проектов</li>
|
||||
<li>Восстановит битые ссылки на проекты</li>
|
||||
<li>Очистит ошибки и даст pending changes еще шанс</li>
|
||||
</ul>
|
||||
<button id="repair-button" onclick="repairPendingChanges()"
|
||||
class="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
ИСПРАВИТЬ
|
||||
</button>
|
||||
<div id="repair-result" class="mt-2 text-sm hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
@@ -235,7 +255,8 @@
|
||||
// Section 4: Errors
|
||||
const errorsSection = document.getElementById('modal-errors-section');
|
||||
const errorsList = document.getElementById('modal-errors-list');
|
||||
if (data.errors && data.errors.length > 0) {
|
||||
const hasErrors = data.errors && data.errors.length > 0;
|
||||
if (hasErrors) {
|
||||
errorsSection.classList.remove('hidden');
|
||||
errorsList.innerHTML = data.errors.map(error => {
|
||||
const time = new Date(error.timestamp).toLocaleString('ru-RU');
|
||||
@@ -246,12 +267,65 @@
|
||||
} else {
|
||||
errorsSection.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Section 5: Repair (show only if errors exist)
|
||||
const repairSection = document.getElementById('modal-repair-section');
|
||||
const repairResult = document.getElementById('repair-result');
|
||||
if (hasErrors) {
|
||||
repairSection.classList.remove('hidden');
|
||||
repairResult.classList.add('hidden');
|
||||
repairResult.innerHTML = '';
|
||||
} else {
|
||||
repairSection.classList.add('hidden');
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Failed to load sync info:', e);
|
||||
document.getElementById('modal-db-status').textContent = 'Ошибка загрузки';
|
||||
}
|
||||
}
|
||||
|
||||
// Repair pending changes
|
||||
async function repairPendingChanges() {
|
||||
const button = document.getElementById('repair-button');
|
||||
const resultDiv = document.getElementById('repair-result');
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Исправление...';
|
||||
resultDiv.classList.add('hidden');
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/sync/repair', { method: 'POST' });
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.success) {
|
||||
resultDiv.classList.remove('hidden');
|
||||
if (data.repaired > 0) {
|
||||
resultDiv.className = 'mt-2 text-sm text-green-700 bg-green-50 rounded px-3 py-2';
|
||||
resultDiv.textContent = `✓ Исправлено: ${data.repaired}`;
|
||||
// Reload sync info after repair
|
||||
setTimeout(() => loadSyncInfo(), 1000);
|
||||
} else {
|
||||
resultDiv.className = 'mt-2 text-sm text-yellow-700 bg-yellow-50 rounded px-3 py-2';
|
||||
resultDiv.textContent = 'Нечего исправлять или проблемы остались';
|
||||
if (data.remaining_errors && data.remaining_errors.length > 0) {
|
||||
resultDiv.innerHTML += '<div class="mt-1 text-xs">' + data.remaining_errors.join('<br>') + '</div>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.className = 'mt-2 text-sm text-red-700 bg-red-50 rounded px-3 py-2';
|
||||
resultDiv.textContent = 'Ошибка: ' + (data.error || 'неизвестная ошибка');
|
||||
}
|
||||
} catch (e) {
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.className = 'mt-2 text-sm text-red-700 bg-red-50 rounded px-3 py-2';
|
||||
resultDiv.textContent = 'Ошибка запроса: ' + e.message;
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = 'ИСПРАВИТЬ';
|
||||
}
|
||||
}
|
||||
|
||||
// Event delegation for sync dropdown and actions
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadDBUser();
|
||||
|
||||
Reference in New Issue
Block a user