feat(deploy): redeploy dialog with keep-data or fresh-deploy option
Add a confirmation modal when clicking Redeploy that lets the user choose:
- Keep Data: containers are recreated without wiping the instance directory.
NetBird database, peer configs, and encryption keys are preserved.
- Fresh Deploy: full undeploy (removes all data) then redeploy from scratch.
Backend changes:
- POST /customers/{id}/deploy accepts keep_data query param (default false)
- When keep_data=true, undeploy_customer is skipped entirely
- deploy_customer now reuses existing npm_proxy_id/stream_id when the
deployment record is still present (avoids duplicate NPM proxy entries)
- DNS record creation is skipped on keep_data redeploy (already exists)
Frontend changes:
- customerAction('deploy') opens the redeploy modal instead of calling API
- showRedeployModal(id) shows the two-option confirmation card dialog
- confirmRedeploy(keepData) calls the API with the correct parameter
- i18n keys added in en.json and de.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1267,6 +1267,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal: Redeploy Confirmation -->
|
||||
<div class="modal fade" id="redeploy-modal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" data-i18n="redeployModal.title">Redeploy Customer</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><span data-i18n="redeployModal.intro">How should</span> <strong id="redeploy-customer-name"></strong> <span data-i18n="redeployModal.intro2">be redeployed?</span></p>
|
||||
<input type="hidden" id="redeploy-customer-id">
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-success" style="cursor:pointer" onclick="confirmRedeploy(true)" id="redeploy-card-keep">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title text-success"><i class="bi bi-shield-check me-2"></i><span data-i18n="redeployModal.keepTitle">Keep Data</span></h6>
|
||||
<p class="card-text small" data-i18n="redeployModal.keepDesc">Containers are stopped and restarted. The NetBird database, peer configurations, and encryption keys are preserved. Use this after a config change or image update.</p>
|
||||
</div>
|
||||
<div class="card-footer bg-success bg-opacity-10 text-success small" data-i18n="redeployModal.keepNote">
|
||||
Peers stay connected after restart.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-danger" style="cursor:pointer" onclick="confirmRedeploy(false)" id="redeploy-card-fresh">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title text-danger"><i class="bi bi-trash me-2"></i><span data-i18n="redeployModal.freshTitle">Fresh Deploy</span></h6>
|
||||
<p class="card-text small" data-i18n="redeployModal.freshDesc">All existing data is deleted — containers, volumes, config files, and the NetBird database. A completely new instance is created. All peers must re-enroll.</p>
|
||||
</div>
|
||||
<div class="card-footer bg-danger bg-opacity-10 text-danger small" data-i18n="redeployModal.freshNote">
|
||||
All peer data is lost. Cannot be undone.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="common.cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal: Delete Confirmation -->
|
||||
<div class="modal fade" id="delete-modal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
|
||||
@@ -667,9 +667,13 @@ async function confirmDeleteCustomer() {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Customer Actions (start/stop/restart)
|
||||
// Customer Actions (start/stop/restart/deploy)
|
||||
// ---------------------------------------------------------------------------
|
||||
async function customerAction(id, action) {
|
||||
if (action === 'deploy') {
|
||||
showRedeployModal(id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api('POST', `/customers/${id}/${action}`);
|
||||
if (currentPage === 'dashboard') loadCustomers();
|
||||
@@ -679,6 +683,26 @@ async function customerAction(id, action) {
|
||||
}
|
||||
}
|
||||
|
||||
function showRedeployModal(id) {
|
||||
const row = document.querySelector(`tr[data-customer-id="${id}"]`);
|
||||
const name = row ? row.querySelector('td')?.textContent?.trim() : `#${id}`;
|
||||
document.getElementById('redeploy-customer-id').value = id;
|
||||
document.getElementById('redeploy-customer-name').textContent = name;
|
||||
new bootstrap.Modal(document.getElementById('redeploy-modal')).show();
|
||||
}
|
||||
|
||||
async function confirmRedeploy(keepData) {
|
||||
const id = document.getElementById('redeploy-customer-id').value;
|
||||
bootstrap.Modal.getInstance(document.getElementById('redeploy-modal'))?.hide();
|
||||
try {
|
||||
await api('POST', `/customers/${id}/deploy?keep_data=${keepData}`);
|
||||
if (currentPage === 'dashboard') loadCustomers();
|
||||
if (currentCustomerId == id) viewCustomer(id);
|
||||
} catch (err) {
|
||||
alert(t('errors.actionFailed', { action: 'deploy', error: err.message }));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Customer Detail
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -354,6 +354,17 @@
|
||||
"saveAndDeploy": "Speichern & Bereitstellen",
|
||||
"saveChanges": "Änderungen speichern"
|
||||
},
|
||||
"redeployModal": {
|
||||
"title": "Kunde neu bereitstellen",
|
||||
"intro": "Wie soll",
|
||||
"intro2": "neu bereitgestellt werden?",
|
||||
"keepTitle": "Daten behalten",
|
||||
"keepDesc": "Container werden gestoppt und neu gestartet. Die NetBird-Datenbank, Peer-Konfigurationen und Verschlüsselungsschlüssel bleiben erhalten. Verwenden Sie dies nach einer Konfigurationsänderung oder einem Image-Update.",
|
||||
"keepNote": "Peers bleiben nach dem Neustart verbunden.",
|
||||
"freshTitle": "Neu aufsetzen",
|
||||
"freshDesc": "Alle bestehenden Daten werden gelöscht — Container, Volumes, Konfigurationsdateien und die NetBird-Datenbank. Eine komplett neue Instanz wird erstellt. Alle Peers müssen sich neu registrieren.",
|
||||
"freshNote": "Alle Peer-Daten gehen verloren. Kann nicht rückgängig gemacht werden."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Löschen bestätigen",
|
||||
"confirmText": "Möchten Sie den Kunden wirklich löschen:",
|
||||
|
||||
@@ -107,6 +107,17 @@
|
||||
"saveAndDeploy": "Save & Deploy",
|
||||
"saveChanges": "Save Changes"
|
||||
},
|
||||
"redeployModal": {
|
||||
"title": "Redeploy Customer",
|
||||
"intro": "How should",
|
||||
"intro2": "be redeployed?",
|
||||
"keepTitle": "Keep Data",
|
||||
"keepDesc": "Containers are stopped and restarted. The NetBird database, peer configurations, and encryption keys are preserved. Use this after a config change or image update.",
|
||||
"keepNote": "Peers stay connected after restart.",
|
||||
"freshTitle": "Fresh Deploy",
|
||||
"freshDesc": "All existing data is deleted — containers, volumes, config files, and the NetBird database. A completely new instance is created. All peers must re-enroll.",
|
||||
"freshNote": "All peer data is lost. Cannot be undone."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Confirm Deletion",
|
||||
"confirmText": "Are you sure you want to delete customer",
|
||||
|
||||
Reference in New Issue
Block a user