atualiza versão para 0.0.7; adiciona exclusão em cascata de locais de armazenamento e nova rota para verificar impacto

This commit is contained in:
Alexander Sabino 2026-05-09 16:51:34 +01:00
parent 2479154f63
commit 164fe7ecf1
5 changed files with 62 additions and 6 deletions

View File

@ -9,7 +9,7 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/VERSION-0.0.6-blue?style=flat-square" />
<img src="https://img.shields.io/badge/VERSION-0.0.7-blue?style=flat-square" />
<img src="https://img.shields.io/badge/NODE.JS-%3E%3D20-339933?style=flat-square&logo=node.js&logoColor=white" />
<img src="https://img.shields.io/badge/DOCKER-ready-2496ED?style=flat-square&logo=docker&logoColor=white" />
<img src="https://img.shields.io/badge/READY-yes-brightgreen?style=flat-square" />
@ -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**
---
## <20> 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

View File

@ -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": {

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
});