diff --git a/backend/routers/agent.py b/backend/routers/agent.py
index c5fc3bb..a2206c5 100644
--- a/backend/routers/agent.py
+++ b/backend/routers/agent.py
@@ -650,6 +650,92 @@ async def generate_install_script(req_data: AgentScriptRequest, request: Request
logger.error(f"Failed to generate install script: {e}")
raise HTTPException(status_code=500, detail=f"Failed to generate install script: {str(e)}")
+@router.get("/generate-uninstall-script/{platform}", summary="Generate Agent Uninstall Script", response_description="Uninstall script")
+async def generate_uninstall_script(platform: str, authorization: str = Header(None), x_api_key: Optional[str] = Header(None)):
+ """
+ # Generate Agent Uninstall Script
+
+ Generate platform-specific uninstall script for completely removing agent from HAProxy servers.
+ This script removes ONLY agent-related files and services - HAProxy itself remains untouched.
+
+ ## Path Parameters
+ - **platform**: Platform type: "macos" or "linux" (required)
+
+ ## What Gets Removed
+ - Agent service (systemd/launchd)
+ - Agent binary (/usr/local/bin/haproxy-agent)
+ - Agent configuration (/etc/haproxy-agent)
+ - Agent logs (/var/log/haproxy-agent)
+ - Agent temp files and upgrade markers
+ - Agent backup files
+
+ ## What Is NOT Touched
+ - HAProxy service
+ - HAProxy configuration (/etc/haproxy/haproxy.cfg)
+ - HAProxy logs
+ - Any other HAProxy-related files
+
+ ## Example Request
+ ```bash
+ curl -X GET "{BASE_URL}/api/agents/generate-uninstall-script/linux" \\
+ -H "Authorization: Bearer eyJhbGciOiJIUz..."
+ ```
+
+ ## Usage on Remote Server
+ ```bash
+ # Save the script
+ curl -o uninstall-agent.sh "{BASE_URL}/api/agents/generate-uninstall-script/linux"
+ chmod +x uninstall-agent.sh
+ sudo ./uninstall-agent.sh
+ ```
+ """
+ try:
+ # Validate platform
+ platform_lower = platform.lower()
+ if platform_lower not in ['linux', 'macos']:
+ raise HTTPException(
+ status_code=400,
+ detail=f"Invalid platform: {platform}. Must be 'linux' or 'macos'"
+ )
+
+ # Read uninstall script from utils directory
+ import os
+ script_filename = f"uninstall-agent-{platform_lower}.sh"
+
+ # Try multiple possible paths
+ possible_paths = [
+ os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "utils", script_filename),
+ os.path.join(os.path.dirname(os.path.dirname(__file__)), "utils", script_filename),
+ f"/app/utils/{script_filename}",
+ ]
+
+ script_content = None
+ for path in possible_paths:
+ if os.path.exists(path):
+ with open(path, 'r') as f:
+ script_content = f.read()
+ break
+
+ if not script_content:
+ logger.error(f"Uninstall script not found for platform: {platform_lower}")
+ raise HTTPException(
+ status_code=404,
+ detail=f"Uninstall script not found for platform: {platform_lower}"
+ )
+
+ return {
+ "platform": platform_lower,
+ "script": script_content,
+ "filename": script_filename,
+ "usage": f"chmod +x {script_filename} && sudo ./{script_filename}"
+ }
+
+ except HTTPException:
+ raise
+ except Exception as e:
+ logger.error(f"Failed to generate uninstall script: {e}")
+ raise HTTPException(status_code=500, detail=f"Failed to generate uninstall script: {str(e)}")
+
@router.delete("/{agent_id}", summary="Delete Agent", response_description="Agent deleted successfully")
async def delete_agent(agent_id: int, authorization: str = Header(None)):
"""
diff --git a/frontend/src/components/AgentManagement.js b/frontend/src/components/AgentManagement.js
index d5063dc..5ef72b3 100644
--- a/frontend/src/components/AgentManagement.js
+++ b/frontend/src/components/AgentManagement.js
@@ -157,8 +157,16 @@ const AgentManagement = () => {
const [poolClusters, setPoolClusters] = useState([]);
const [selectedClusterId, setSelectedClusterId] = useState(null);
const [installScript, setInstallScript] = useState('');
+ const [uninstallScript, setUninstallScript] = useState('');
const [scriptGenerating, setScriptGenerating] = useState(false);
+ // Delete Agent Modal state
+ const [deleteModalVisible, setDeleteModalVisible] = useState(false);
+ const [agentToDelete, setAgentToDelete] = useState(null);
+ const [deleteUninstallScript, setDeleteUninstallScript] = useState('');
+ const [deleteConfirmText, setDeleteConfirmText] = useState('');
+ const [deleting, setDeleting] = useState(false);
+
// Pools state for the setup wizard
const [supportedPlatforms, setSupportedPlatforms] = useState({});
@@ -457,6 +465,20 @@ const AgentManagement = () => {
});
setInstallScript(response.data.script);
+
+ // Also fetch uninstall script for this platform
+ try {
+ const uninstallResponse = await axios.get(`/api/agents/generate-uninstall-script/${selectedPlatform}`, {
+ headers: {
+ 'Authorization': `Bearer ${localStorage.getItem('authToken') || ''}`,
+ },
+ });
+ setUninstallScript(uninstallResponse.data.script);
+ } catch (uninstallError) {
+ console.warn('Failed to fetch uninstall script:', uninstallError);
+ setUninstallScript(''); // Clear on error
+ }
+
setCurrentStep(3); // Move to script step
notification.success({
@@ -488,6 +510,7 @@ const AgentManagement = () => {
setPoolClusters([]);
setSelectedClusterId(null);
setInstallScript('');
+ setUninstallScript('');
};
// Generate random hostname
@@ -529,9 +552,42 @@ const AgentManagement = () => {
}
};
+ // Open delete agent modal with uninstall script
+ const openDeleteModal = async (agent) => {
+ setAgentToDelete(agent);
+ setDeleteConfirmText('');
+ setDeleteModalVisible(true);
+
+ // Fetch uninstall script for agent's platform
+ const platform = agent.platform || 'linux';
+ try {
+ const response = await axios.get(`/api/agents/generate-uninstall-script/${platform}`, {
+ headers: {
+ 'Authorization': `Bearer ${localStorage.getItem('authToken') || ''}`,
+ },
+ });
+ setDeleteUninstallScript(response.data.script);
+ } catch (error) {
+ console.warn('Failed to fetch uninstall script:', error);
+ setDeleteUninstallScript('');
+ }
+ };
+
+ // Close delete modal
+ const closeDeleteModal = () => {
+ setDeleteModalVisible(false);
+ setAgentToDelete(null);
+ setDeleteUninstallScript('');
+ setDeleteConfirmText('');
+ };
+
// Enhanced delete agent
- const deleteAgent = async (agentId, agentName) => {
- const hide = message.loading(`Deleting agent ${agentName}...`);
+ const deleteAgent = async () => {
+ if (!agentToDelete) return;
+
+ const { id: agentId, name: agentName } = agentToDelete;
+ setDeleting(true);
+
try {
// Optimistic update: Remove agent from local state immediately
const updatedAgents = agents.filter(agent => agent.id !== agentId);
@@ -547,10 +603,12 @@ const AgentManagement = () => {
notification.success({
message: 'Agent Deleted',
- description: `Agent "${agentName}" has been successfully removed.`,
+ description: `Agent "${agentName}" has been successfully removed from the system.`,
duration: 4
});
+ closeDeleteModal();
+
// Refresh from server to ensure consistency (force=true to bypass throttling)
await fetchAgents(true);
} catch (error) {
@@ -564,7 +622,7 @@ const AgentManagement = () => {
// Revert optimistic update on error (force=true to bypass throttling)
await fetchAgents(true);
} finally {
- hide();
+ setDeleting(false);
}
};
@@ -1346,24 +1404,15 @@ const AgentManagement = () => {
onClick={() => showAgentDetails(record)}
/>
-