Adiciona seleção de volumes para backup e implementa modal de escolha

This commit is contained in:
Alexander Sabino
2026-05-04 18:46:43 +01:00
parent f354c66f5a
commit f34ce0ead3
6 changed files with 250 additions and 9 deletions
+27 -3
View File
@@ -306,11 +306,35 @@ class BackupService {
const archiveRelativePath = path.posix.join(safeProfileName, safeContainerName, `${stamp}-${runMode}.tar.gz`);
const snapshotRelativePath = path.posix.join(safeProfileName, safeContainerName, 'latest.snar');
// Filter mounts to only those the user selected (if volumeSelections is defined for this container)
const selectedPaths = profile.volumeSelections?.[containerId] || profile.volumeSelections?.[inspect.Id];
const activeMounts = (backupScope === 'volumes' && selectedPaths?.length)
? mounts.filter((m) => selectedPaths.includes(m.destination))
: mounts;
if (backupScope === 'volumes' && !activeMounts.length) {
onProgress({
containerName,
status: 'skipped',
step: 'concluido',
message: 'Nenhum volume selecionado para backup.',
percent: 100,
file: { current: 0, total: 0, currentFile: null, percent: 100 },
});
return {
containerId: inspect.Id,
containerName,
status: 'skipped',
mode: runMode,
message: 'Nenhum volume selecionado para backup.',
};
}
const containerBackup = {
containerId: inspect.Id,
containerName,
backupScope,
backupPaths: backupScope === 'container' ? ['/'] : mounts.map((mount) => mount.destination),
backupPaths: backupScope === 'container' ? ['/'] : activeMounts.map((mount) => mount.destination),
mountSignature: mounts,
archiveRelativePath,
snapshotRelativePath,
@@ -378,7 +402,7 @@ class BackupService {
const sourcePaths = backupScope === 'container'
? ['/']
: mounts.map((mount) => mount.destination);
: activeMounts.map((mount) => mount.destination);
const relSourcePaths = sourcePaths.map((item) => toContainerRelPath(item));
if (backupScope === 'volumes') {
@@ -480,7 +504,7 @@ class BackupService {
}
const binds = [`${backupRoot}:/backuproot`];
for (const [index, mount] of mounts.entries()) {
for (const [index, mount] of activeMounts.entries()) {
binds.push(`${getMountBindingSource(mount)}:/payload/m${index}:ro`);
}
+19
View File
@@ -40,6 +40,24 @@ async function main() {
}
});
app.get('/api/containers/:containerId/mounts', async (request, response) => {
try {
const inspect = await dockerService.inspectContainer(request.params.containerId);
const mounts = (inspect.Mounts || [])
.filter((m) => m.Type === 'bind' || m.Type === 'volume')
.map((m) => ({
type: m.Type,
name: m.Name || null,
source: m.Source,
destination: m.Destination,
rw: m.RW,
}));
response.json(mounts);
} catch (error) {
response.status(500).json({ error: error.message });
}
});
app.get('/api/profiles', async (_request, response) => {
try {
const profiles = await store.listProfiles();
@@ -71,6 +89,7 @@ async function main() {
containerIds: payload.containerIds,
mode: existing?.mode || 'full',
backupScope: payload.backupScope || existing?.backupScope || 'volumes',
volumeSelections: payload.volumeSelections || existing?.volumeSelections || {},
});
response.status(payload.id ? 200 : 201).json(profile);
+1
View File
@@ -54,6 +54,7 @@ class JsonStore {
containerIds: profileInput.containerIds,
mode: profileInput.mode,
backupScope: profileInput.backupScope || 'volumes',
volumeSelections: profileInput.volumeSelections || {},
backupDir: profileInput.backupDir,
updatedAt: now,
createdAt: profileInput.createdAt || now,