adiciona modal para criação e edição de profiles; atualiza estilos e lógica de exibição
This commit is contained in:
parent
ef9ffdbe71
commit
11c58f0cfe
|
|
@ -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.');
|
||||
|
|
|
|||
|
|
@ -167,66 +167,14 @@
|
|||
<div id="view-profiles" class="view hidden">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Backup Profiles</h1>
|
||||
<button id="reloadProfiles" class="btn btn--secondary">Reload</button>
|
||||
<div class="page-header-actions">
|
||||
<button id="openCreateProfileModal" class="btn btn--primary">+ Criar Profile</button>
|
||||
<button id="reloadProfiles" class="btn btn--secondary">Reload</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profiles-layout">
|
||||
<div class="card">
|
||||
<div class="card-toolbar">
|
||||
<h2 class="card-title">New Profile</h2>
|
||||
<span id="formModeBadge" class="badge badge--accent">criar</span>
|
||||
</div>
|
||||
|
||||
<form id="profileForm" class="profile-form">
|
||||
<input type="hidden" id="profileId" />
|
||||
|
||||
<div class="form-field">
|
||||
<label for="profileName">Nome do profile</label>
|
||||
<input id="profileName" name="name" type="text" placeholder="Backup banco principal" required />
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="backupDir">Diretório de backup no host Docker</label>
|
||||
<input id="backupDir" name="backupDir" type="text" placeholder="/srv/docker-backups" required />
|
||||
</div>
|
||||
|
||||
<fieldset class="form-fieldset">
|
||||
<legend>Escopo do backup</legend>
|
||||
<div class="radio-grid">
|
||||
<label class="radio-card">
|
||||
<input type="radio" name="backupScope" value="volumes" checked />
|
||||
<span>Somente volumes</span>
|
||||
<small>mantém o comportamento atual de backup de volumes e binds</small>
|
||||
</label>
|
||||
<label class="radio-card">
|
||||
<input type="radio" name="backupScope" value="container" />
|
||||
<span>Container inteiro</span>
|
||||
<small>gera um único tar por container a partir de /</small>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="container-picker">
|
||||
<div class="picker-header">
|
||||
<span class="picker-label">Containers</span>
|
||||
<button id="refreshContainers" type="button" class="btn btn--ghost btn--sm">Atualizar lista</button>
|
||||
</div>
|
||||
<div id="containerOptions" class="container-options"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button class="btn btn--primary" type="submit">Salvar profile</button>
|
||||
<button class="btn btn--secondary" type="button" id="clearForm">Limpar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-toolbar">
|
||||
<h2 class="card-title">Profiles Salvos</h2>
|
||||
</div>
|
||||
<div id="profilesList" class="profiles-list"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div id="profilesList" class="profiles-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -318,6 +266,59 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="profileFormModal" class="modal hidden" aria-hidden="true">
|
||||
<div class="modal-backdrop" data-action="close-profile-modal"></div>
|
||||
<div class="modal-card modal-card--wide" role="dialog" aria-modal="true" aria-labelledby="profileModalTitle">
|
||||
<div class="modal-header">
|
||||
<h3 id="profileModalTitle">Novo Profile</h3>
|
||||
<button id="profileModalClose" class="btn btn--ghost btn--sm" type="button">Fechar</button>
|
||||
</div>
|
||||
|
||||
<form id="profileForm" class="profile-form">
|
||||
<input type="hidden" id="profileId" />
|
||||
|
||||
<div class="form-field">
|
||||
<label for="profileName">Nome do profile</label>
|
||||
<input id="profileName" name="name" type="text" placeholder="Backup banco principal" required />
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="backupDir">Diretório de backup no host Docker</label>
|
||||
<input id="backupDir" name="backupDir" type="text" placeholder="/srv/docker-backups" required />
|
||||
</div>
|
||||
|
||||
<fieldset class="form-fieldset">
|
||||
<legend>Escopo do backup</legend>
|
||||
<div class="radio-grid">
|
||||
<label class="radio-card">
|
||||
<input type="radio" name="backupScope" value="volumes" checked />
|
||||
<span>Somente volumes</span>
|
||||
<small>mantém o comportamento atual de backup de volumes e binds</small>
|
||||
</label>
|
||||
<label class="radio-card">
|
||||
<input type="radio" name="backupScope" value="container" />
|
||||
<span>Container inteiro</span>
|
||||
<small>gera um único tar por container a partir de /</small>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="container-picker">
|
||||
<div class="picker-header">
|
||||
<span class="picker-label">Containers</span>
|
||||
<button id="refreshContainers" type="button" class="btn btn--ghost btn--sm">Atualizar lista</button>
|
||||
</div>
|
||||
<div id="containerOptions" class="container-options"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button class="btn btn--primary" type="submit">Salvar profile</button>
|
||||
<button class="btn btn--secondary" type="button" id="clearForm">Cancelar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in New Issue