feat(ui): добавляем анимированный прогресс-бар при обновлении сборки
- обновили updateInstance() с поддержкой SSE для реального прогресса - добавили showAnimatedProgress() и updateAnimatedProgress() методы - добавили CSS анимацию shimmer для прогресс-бара - теперь показывает: файл X из Y и процент текущего файла
This commit is contained in:
@@ -686,6 +686,30 @@ body {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-label {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-file {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill.animated {
|
||||||
|
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary), var(--accent-primary));
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: progressShimmer 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes progressShimmer {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
/* ==================== LOADING ==================== */
|
/* ==================== LOADING ==================== */
|
||||||
.loading-overlay {
|
.loading-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -325,7 +325,43 @@ class App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.addLog('Обновление сборки...', 'info');
|
this.addLog('Обновление сборки...', 'info');
|
||||||
this.showProgress('Обновление сборки...');
|
this.enablePlayButton(false);
|
||||||
|
|
||||||
|
const progressContainer = this.showAnimatedProgress('Обновление сборки...');
|
||||||
|
|
||||||
|
let eventSource = null;
|
||||||
|
let progressData = { totalFiles: 0, downloadedFiles: 0 };
|
||||||
|
|
||||||
|
try {
|
||||||
|
eventSource = new EventSource(`/api/instances/${this.currentInstance.name}/install/stream`);
|
||||||
|
eventSource.onmessage = (e) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(e.data);
|
||||||
|
if (data.phase === 'starting') {
|
||||||
|
progressData.totalFiles = data.totalFiles || 0;
|
||||||
|
this.updateAnimatedProgress(progressContainer, `Загрузка: 0/${progressData.totalFiles} файлов`, 5);
|
||||||
|
} else if (data.phase === 'downloading') {
|
||||||
|
progressData.downloadedFiles = data.downloadedFiles || 0;
|
||||||
|
const total = data.totalFiles || progressData.totalFiles || 1;
|
||||||
|
const percent = Math.round((progressData.downloadedFiles / total) * 100);
|
||||||
|
const fileName = data.currentFile ? data.currentFile.split('/').pop() : '';
|
||||||
|
const filePercent = data.filePercent || 0;
|
||||||
|
this.updateAnimatedProgress(progressContainer,
|
||||||
|
`Файл ${progressData.downloadedFiles}/${total} (${percent}%)`,
|
||||||
|
percent,
|
||||||
|
fileName,
|
||||||
|
filePercent
|
||||||
|
);
|
||||||
|
} else if (data.phase === 'complete') {
|
||||||
|
this.updateAnimatedProgress(progressContainer, 'Готово!', 100);
|
||||||
|
} else if (data.phase === 'error') {
|
||||||
|
this.addLog('Ошибка: ' + (data.message || 'неизвестная ошибка'), 'error');
|
||||||
|
}
|
||||||
|
} catch (err) {}
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.log('SSE not available, using fallback progress');
|
||||||
|
}
|
||||||
|
|
||||||
const result = await this.request('/instances/zernmc/install', {
|
const result = await this.request('/instances/zernmc/install', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -335,6 +371,10 @@ class App {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (eventSource) {
|
||||||
|
eventSource.close();
|
||||||
|
}
|
||||||
|
|
||||||
this.hideProgress();
|
this.hideProgress();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -362,6 +402,31 @@ class App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showAnimatedProgress(text) {
|
||||||
|
const progress = document.getElementById('download-progress');
|
||||||
|
const progressText = document.getElementById('progress-text');
|
||||||
|
const progressFill = document.getElementById('progress-fill');
|
||||||
|
|
||||||
|
progress.classList.remove('hidden');
|
||||||
|
progressText.innerHTML = `<div class="progress-label">${text}</div>
|
||||||
|
<div class="progress-file"></div>`;
|
||||||
|
progressFill.style.width = '5%';
|
||||||
|
progressFill.classList.add('animated');
|
||||||
|
|
||||||
|
return { container: progress, text: progressText, fill: progressFill };
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAnimatedProgress(progressContainer, text, percent, fileName = '', filePercent = 0) {
|
||||||
|
const { text: progressText, fill: progressFill } = progressContainer;
|
||||||
|
if (fileName) {
|
||||||
|
progressText.innerHTML = `<div class="progress-label">${text}</div>
|
||||||
|
<div class="progress-file">${fileName} (${filePercent}%)</div>`;
|
||||||
|
} else {
|
||||||
|
progressText.innerHTML = `<div class="progress-label">${text}</div>`;
|
||||||
|
}
|
||||||
|
progressFill.style.width = percent + '%';
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== DOWNLOAD MODAL ====================
|
// ==================== DOWNLOAD MODAL ====================
|
||||||
async showDownloadModal() {
|
async showDownloadModal() {
|
||||||
document.getElementById('download-modal').classList.remove('hidden');
|
document.getElementById('download-modal').classList.remove('hidden');
|
||||||
|
|||||||
Reference in New Issue
Block a user