diff --git a/public/app.js b/public/app.js index 8a9fbf3..cf95238 100644 --- a/public/app.js +++ b/public/app.js @@ -14,8 +14,9 @@ const elements = { backupDir: document.querySelector('#backupDir'), containerOptions: document.querySelector('#containerOptions'), profilesList: document.querySelector('#profilesList'), - formModeBadge: document.querySelector('#formModeBadge'), toast: document.querySelector('#toast'), + profileFormModal: document.querySelector('#profileFormModal'), + profileModalClose: document.querySelector('#profileModalClose'), restoreModal: document.querySelector('#restoreModal'), restoreModalSubtitle: document.querySelector('#restoreModalSubtitle'), restoreContainerOptions: document.querySelector('#restoreContainerOptions'), @@ -67,6 +68,31 @@ document.querySelector('.sidebar').addEventListener('click', (e) => { document.querySelector('#createProfileBtn')?.addEventListener('click', () => navigateTo('profiles')); document.querySelector('#refreshServers')?.addEventListener('click', () => renderServers()); +// ─── Profile form modal ─────────────────────────────────── +function openProfileModal(title = 'Novo Profile') { + document.querySelector('#profileModalTitle').textContent = title; + elements.profileFormModal.classList.remove('hidden'); + elements.profileFormModal.setAttribute('aria-hidden', 'false'); +} + +function closeProfileModal() { + elements.profileFormModal.classList.add('hidden'); + elements.profileFormModal.setAttribute('aria-hidden', 'true'); +} + +document.querySelector('#openCreateProfileModal')?.addEventListener('click', () => { + resetForm(); + openProfileModal('Novo Profile'); +}); + +elements.profileModalClose?.addEventListener('click', closeProfileModal); + +elements.profileFormModal?.addEventListener('click', (e) => { + if (e.target.closest('[data-action="close-profile-modal"]')) { + closeProfileModal(); + } +}); + function renderServers() { const list = document.querySelector('#serversList'); if (!list) return; @@ -754,9 +780,9 @@ async function renderProfiles() { function resetForm() { elements.profileForm.reset(); elements.profileId.value = ''; - elements.formModeBadge.textContent = 'criar'; state.volumeSelections = {}; renderContainers(); + closeProfileModal(); } function fillForm(profile) { @@ -765,7 +791,6 @@ function fillForm(profile) { elements.backupDir.value = profile.backupDir; const backupScope = profile.backupScope === 'container' ? 'container' : 'volumes'; document.querySelector(`input[name="backupScope"][value="${backupScope}"]`).checked = true; - elements.formModeBadge.textContent = 'editar'; state.volumeSelections = Object.assign({}, profile.volumeSelections || {}); renderContainers(); for (const containerId of profile.containerIds) { @@ -774,7 +799,7 @@ function fillForm(profile) { input.checked = true; } } - window.scrollTo({ top: 0, behavior: 'smooth' }); + openProfileModal('Editar Profile'); } async function loadContainers() { @@ -815,6 +840,7 @@ async function saveProfile(event) { method: 'POST', body: JSON.stringify(payload), }); + closeProfileModal(); await loadProfiles(); resetForm(); showToast('Profile salvo.'); diff --git a/public/index.html b/public/index.html index 4f98f7d..1601b3e 100644 --- a/public/index.html +++ b/public/index.html @@ -167,66 +167,14 @@ + + \ No newline at end of file diff --git a/public/styles.css b/public/styles.css index 0b0e9dd..dcbe327 100644 --- a/public/styles.css +++ b/public/styles.css @@ -146,6 +146,12 @@ button, input, select { margin-bottom: 24px; } +.page-header-actions { + display: flex; + gap: 8px; + align-items: center; +} + .page-title { font-size: 22px; font-weight: 700; @@ -806,6 +812,11 @@ button, input, select { gap: 12px; } +.modal-card--wide { + width: min(720px, calc(100vw - 24px)); + max-height: min(90vh, 800px); +} + .modal-header { display: flex; justify-content: space-between; diff --git a/src/backupService.js b/src/backupService.js index ab6119f..98017b1 100644 --- a/src/backupService.js +++ b/src/backupService.js @@ -510,6 +510,9 @@ class BackupService { pushLog(`Arquivo gerado via helper: ${absoluteArchivePath}`, 'finalizando'); pushLog('Snapshot incremental salvo no diretorio de backup.', 'finalizando'); + containerBackup.fileCount = Math.max(fileCurrent, fileTotal); + try { containerBackup.archiveSize = (await fs.stat(absoluteArchivePath)).size; } catch {} + onProgress({ containerName, status: 'ok', @@ -585,6 +588,9 @@ class BackupService { } } + containerBackup.fileCount = Math.max(fileCurrent, fileTotal); + try { containerBackup.archiveSize = (await fs.stat(absoluteArchivePath)).size; } catch {} + onProgress({ containerName, status: 'ok', @@ -655,6 +661,12 @@ class BackupService { await this.dockerService.startContainer(containerId).catch(() => null); } + containerBackup.fileCount = Math.max(fileCurrent, fileTotal); + try { + const hostArchivePath = path.join(profile.backupDir, ...archiveRelativePath.split('/')); + containerBackup.archiveSize = (await fs.stat(hostArchivePath)).size; + } catch {} + onProgress({ containerName, status: 'ok',