diff --git a/README.md b/README.md index 427bc6b..f0d54e1 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@
-
+
@@ -18,12 +18,20 @@
> ⚠️ **AVISO CRÍTICO:** Aplicação em estágio inicial de desenvolvimento. Não use em produção — há risco de perda de dados.
-Versão atual: **0.0.6**
+Versão atual: **0.0.7**
---
## � Changelog
+### [0.0.7] — 2026-05-09
+
+#### Adicionado
+- **Exclusão em cascata de Storage Location:** ao excluir um local de armazenamento, o sistema busca automaticamente todos os profiles vinculados e seus respectivos backups e os exclui junto. Antes de confirmar, o usuário recebe um aviso detalhado listando os nomes dos profiles afetados e a quantidade de backups que serão removidos.
+- **Rota `GET /api/storage-locations/:id/impact`:** nova rota que retorna, sem fazer alterações, quantos profiles e backups serão impactados pela exclusão de um local de armazenamento.
+
+---
+
### [0.0.6] — 2026-05-09
#### Adicionado
diff --git a/package.json b/package.json
index e396fa3..5bf7640 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "dockerbackup-app",
- "version": "0.0.6",
+ "version": "0.0.7",
"description": "Aplicacao web para backup e restauracao de volumes Docker",
"main": "src/server.js",
"scripts": {
diff --git a/public/app.js b/public/app.js
index 189d3e2..5d137bc 100644
--- a/public/app.js
+++ b/public/app.js
@@ -253,11 +253,32 @@ elements.storageLocationsList?.addEventListener('click', async (e) => {
const btn = e.target.closest('[data-storage-action="delete"]');
if (!btn) return;
const id = btn.dataset.storageId;
- if (!window.confirm('Excluir este local de armazenamento?')) return;
+
+ // Fetch impact before confirming
+ let impact = { profileCount: 0, profileNames: [], backupCount: 0 };
+ try {
+ impact = await api(`/api/storage-locations/${id}/impact`);
+ } catch {
+ // Non-fatal; proceed with generic message
+ }
+
+ let message = 'Excluir este local de armazenamento?';
+ if (impact.profileCount > 0) {
+ const names = impact.profileNames.map((n) => `• ${n}`).join('\n');
+ message =
+ `⚠️ ATENÇÃO: Esta ação também irá excluir permanentemente:\n\n` +
+ ` ${impact.profileCount} profile(s) de backup:\n${names}\n\n` +
+ ` ${impact.backupCount} backup(s) registrado(s) desses profiles\n\n` +
+ `Deseja continuar?`;
+ }
+
+ if (!window.confirm(message)) return;
try {
await api(`/api/storage-locations/${id}`, { method: 'DELETE' });
- await loadStorageLocations();
- showToast('Local removido.');
+ await Promise.all([loadStorageLocations(), loadProfiles()]);
+ showToast(impact.profileCount > 0
+ ? `Local removido junto com ${impact.profileCount} profile(s) e ${impact.backupCount} backup(s).`
+ : 'Local de armazenamento removido.');
} catch (error) {
showToast(error.message, true);
}
diff --git a/src/server.js b/src/server.js
index fd9e182..5185986 100644
--- a/src/server.js
+++ b/src/server.js
@@ -470,6 +470,19 @@ async function main() {
}
});
+ app.get('/api/storage-locations/:id/impact', authMiddleware, async (request, response) => {
+ try {
+ const impact = await store.storageLocationImpact(request.params.id);
+ response.json({
+ profileCount: impact.profiles.length,
+ profileNames: impact.profiles.map((p) => p.name),
+ backupCount: impact.backupCount,
+ });
+ } catch (error) {
+ response.status(500).json({ error: error.message });
+ }
+ });
+
app.delete('/api/storage-locations/:id', authMiddleware, async (request, response) => {
try {
await store.deleteStorageLocation(request.params.id);
diff --git a/src/store.js b/src/store.js
index 2bd8fc0..480ec57 100644
--- a/src/store.js
+++ b/src/store.js
@@ -58,6 +58,7 @@ class JsonStore {
backupScope: profileInput.backupScope || 'volumes',
volumeSelections: profileInput.volumeSelections || {},
backupDir: profileInput.backupDir,
+ storageLocationId: profileInput.storageLocationId || null,
updatedAt: now,
createdAt: profileInput.createdAt || now,
};
@@ -166,8 +167,21 @@ class JsonStore {
return location;
}
+ async storageLocationImpact(locationId) {
+ const data = await this.read();
+ const profiles = data.profiles.filter((p) => p.storageLocationId === locationId);
+ const profileIds = new Set(profiles.map((p) => p.id));
+ const backupCount = data.backups.filter((b) => profileIds.has(b.profileId)).length;
+ return { profiles, backupCount };
+ }
+
async deleteStorageLocation(locationId) {
await this.write((data) => {
+ const profileIds = new Set(
+ data.profiles.filter((p) => p.storageLocationId === locationId).map((p) => p.id)
+ );
+ data.backups = data.backups.filter((b) => !profileIds.has(b.profileId));
+ data.profiles = data.profiles.filter((p) => !profileIds.has(p.id));
data.storageLocations = data.storageLocations.filter((item) => item.id !== locationId);
return data;
});