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:
parent
2479154f63
commit
164fe7ecf1
12
README.md
12
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
14
src/store.js
14
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;
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue