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:
@@ -2,7 +2,7 @@
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import SessionLocal, get_db
|
||||
@@ -19,6 +19,14 @@ router = APIRouter()
|
||||
async def manual_deploy(
|
||||
customer_id: int,
|
||||
background_tasks: BackgroundTasks,
|
||||
keep_data: bool = Query(
|
||||
False,
|
||||
description=(
|
||||
"If True, preserve existing NetBird data (database, keys, peers). "
|
||||
"Containers are recreated without wiping the instance directory. "
|
||||
"If False (default), the instance is fully removed and redeployed from scratch."
|
||||
),
|
||||
),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
@@ -29,6 +37,7 @@ async def manual_deploy(
|
||||
|
||||
Args:
|
||||
customer_id: Customer ID.
|
||||
keep_data: Whether to preserve existing NetBird data.
|
||||
|
||||
Returns:
|
||||
Acknowledgement dict.
|
||||
@@ -40,12 +49,12 @@ async def manual_deploy(
|
||||
customer.status = "deploying"
|
||||
db.commit()
|
||||
|
||||
async def _deploy_bg(cid: int) -> None:
|
||||
async def _deploy_bg(cid: int, keep: bool) -> None:
|
||||
bg_db = SessionLocal()
|
||||
try:
|
||||
# Remove existing deployment if present
|
||||
existing = bg_db.query(Deployment).filter(Deployment.customer_id == cid).first()
|
||||
if existing:
|
||||
if existing and not keep:
|
||||
# Full redeploy: remove everything first
|
||||
await netbird_service.undeploy_customer(bg_db, cid)
|
||||
await netbird_service.deploy_customer(bg_db, cid)
|
||||
except Exception:
|
||||
@@ -53,7 +62,7 @@ async def manual_deploy(
|
||||
finally:
|
||||
bg_db.close()
|
||||
|
||||
background_tasks.add_task(_deploy_bg, customer_id)
|
||||
background_tasks.add_task(_deploy_bg, customer_id, keep_data)
|
||||
return {"message": "Deployment started in background.", "status": "deploying"}
|
||||
|
||||
|
||||
|
||||
@@ -264,10 +264,12 @@ async def deploy_customer(db: Session, customer_id: int) -> dict[str, Any]:
|
||||
_log_action(db, customer_id, "deploy", "info",
|
||||
"Auto-setup failed — admin must complete setup manually.")
|
||||
|
||||
# Step 9: Create NPM proxy host (production only)
|
||||
npm_proxy_id = None
|
||||
npm_stream_id = None
|
||||
if not local_mode:
|
||||
# Step 9: Create NPM proxy host (production only).
|
||||
# If an existing deployment already has an NPM proxy, reuse it — this happens
|
||||
# when keep_data=True was passed and undeploy_customer was NOT called beforehand.
|
||||
npm_proxy_id = existing_deployment.npm_proxy_id if existing_deployment else None
|
||||
npm_stream_id = existing_deployment.npm_stream_id if existing_deployment else None
|
||||
if not local_mode and not npm_proxy_id:
|
||||
forward_host = npm_service._get_forward_host()
|
||||
npm_result = await npm_service.create_proxy_host(
|
||||
api_url=config.npm_api_url,
|
||||
@@ -304,9 +306,14 @@ async def deploy_customer(db: Session, customer_id: int) -> dict[str, Any]:
|
||||
"SSL certificate not created automatically. "
|
||||
"Please create it manually in NPM or ensure DNS resolves and port 80 is reachable, then re-deploy.",
|
||||
)
|
||||
elif npm_proxy_id and not local_mode:
|
||||
_log_action(db, customer_id, "deploy", "info",
|
||||
f"Reusing existing NPM proxy (ID {npm_proxy_id}) — data preserved.")
|
||||
|
||||
# Step 10: Create Windows DNS A-record (non-fatal — failure does not abort deployment)
|
||||
if config.dns_enabled and config.dns_server and config.dns_zone and config.dns_record_ip:
|
||||
# Step 10: Create Windows DNS A-record (non-fatal — failure does not abort deployment).
|
||||
# Skip if an existing deployment is being kept (DNS record already exists).
|
||||
if config.dns_enabled and config.dns_server and config.dns_zone and config.dns_record_ip \
|
||||
and not existing_deployment:
|
||||
try:
|
||||
dns_result = await dns_service.create_dns_record(customer.subdomain, config)
|
||||
if dns_result["ok"]:
|
||||
|
||||
Reference in New Issue
Block a user