diff --git a/apps/pusher_app_prem/appserver/static/git_pusher.js b/apps/pusher_app_prem/appserver/static/git_pusher.js index e07576ee..74e7881c 100644 --- a/apps/pusher_app_prem/appserver/static/git_pusher.js +++ b/apps/pusher_app_prem/appserver/static/git_pusher.js @@ -86,6 +86,7 @@ let SH_DEPLOYER_CONFIG = { // État global let selectedApps = []; +let selectedShClusterApps = []; // Apps sélectionnées pour le SH Cluster let isProcessing = false; let deployerAvailable = false; @@ -138,8 +139,52 @@ require([ window.pushDashboards = pushDashboards; window.resetForm = resetForm; window.toggleSelectAll = toggleSelectAll; + window.toggleShClusterAllApps = toggleShClusterAllApps; + window.updateSelectedShClusterApps = updateSelectedShClusterApps; + + // Attacher les événements pour la section SH Cluster + setTimeout(function() { + // Checkbox "Deploy to SH Cluster" + const deployCheckbox = document.getElementById('deploy-to-shcluster'); + if (deployCheckbox) { + deployCheckbox.addEventListener('change', function() { + toggleDeployerOptions(); + }); + } + + // Checkbox "All apps" + const allAppsCheckbox = document.getElementById('shcluster-all-apps'); + if (allAppsCheckbox) { + allAppsCheckbox.addEventListener('change', function() { + toggleShClusterAllApps(); + }); + } + + // Bouton configure + const configBtn = document.getElementById('deployer-config-btn'); + if (configBtn) { + configBtn.addEventListener('click', function() { + showDeployerConfigModal(); + }); + } + }, 500); }); +// Afficher/masquer les options du deployer +function toggleDeployerOptions() { + const checkbox = document.getElementById('deploy-to-shcluster'); + const appsSection = document.getElementById('deployer-apps-section'); + const authSection = document.getElementById('deployer-auth'); + + if (checkbox && checkbox.checked) { + if (appsSection) appsSection.classList.add('visible'); + if (authSection) authSection.classList.add('visible'); + } else { + if (appsSection) appsSection.classList.remove('visible'); + if (authSection) authSection.classList.remove('visible'); + } +} + // ============================================ // RENDU DE LA LISTE DES APPLICATIONS // ============================================ @@ -220,9 +265,109 @@ function updateSelectedApps() { selectAll.indeterminate = someChecked && !allChecked; } + // Mettre à jour la liste des apps pour le SH Cluster + updateShClusterAppsList(); + console.log(`Selected ${selectedApps.length} apps`); } +function updateShClusterAppsList() { + const container = document.getElementById('shcluster-apps-container'); + if (!container) return; + + if (selectedApps.length === 0) { + container.innerHTML = '

Select apps from the left panel first

'; + selectedShClusterApps = []; + return; + } + + // Sauvegarder l'état actuel des checkboxes + const currentState = {}; + const existingCheckboxes = container.querySelectorAll('input[type="checkbox"][data-app-id]'); + existingCheckboxes.forEach(cb => { + currentState[cb.getAttribute('data-app-id')] = cb.checked; + }); + + // Vérifier si la liste a changé (nouvelles apps ajoutées ou apps retirées) + const currentAppIds = Array.from(existingCheckboxes).map(cb => cb.getAttribute('data-app-id')); + const newAppIds = selectedApps.map(app => app.id); + const listChanged = currentAppIds.length !== newAppIds.length || + !currentAppIds.every(id => newAppIds.includes(id)); + + // Ne recréer le HTML que si la liste a changé + if (listChanged || existingCheckboxes.length === 0) { + let html = ''; + selectedApps.forEach((app, index) => { + // Préserver l'état si l'app existait, sinon cocher par défaut + const isChecked = currentState.hasOwnProperty(app.id) ? currentState[app.id] : true; + html += ` +
+ + +
+ `; + }); + + container.innerHTML = html; + } + + // Mettre à jour la liste des apps SH Cluster sélectionnées (sans recréer le HTML) + updateSelectedShClusterApps(); +} + +function updateSelectedShClusterApps() { + const allAppsCheckbox = document.getElementById('shcluster-all-apps'); + + if (allAppsCheckbox && allAppsCheckbox.checked) { + // Toutes les apps sélectionnées pour Git + selectedShClusterApps = [...selectedApps]; + } else { + // Seulement les apps cochées dans la liste SH Cluster + const checkboxes = document.querySelectorAll('#shcluster-apps-container input[type="checkbox"][data-app-id]'); + selectedShClusterApps = []; + + checkboxes.forEach(cb => { + if (cb.checked) { + selectedShClusterApps.push({ + id: cb.getAttribute('data-app-id'), + label: cb.getAttribute('data-app-label') + }); + } + }); + } + + console.log(`Selected ${selectedShClusterApps.length} apps for SH Cluster`); +} + +function toggleShClusterAllApps() { + const allAppsCheckbox = document.getElementById('shcluster-all-apps'); + const appsList = document.getElementById('shcluster-apps-list'); + + if (allAppsCheckbox && appsList) { + if (allAppsCheckbox.checked) { + // Masquer la liste et utiliser toutes les apps + appsList.style.display = 'none'; + selectedShClusterApps = [...selectedApps]; + console.log('SH Cluster: Using all selected apps'); + } else { + // Afficher la liste pour permettre la sélection manuelle + appsList.style.display = 'block'; + // Ne PAS appeler updateSelectedShClusterApps() ici + // L'utilisateur va faire sa sélection manuellement + // La liste garde son état actuel (tous cochés par défaut) + console.log('SH Cluster: Manual selection enabled'); + } + } +} + function toggleSelectAll(checked) { const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app-id]'); checkboxes.forEach(cb => { @@ -260,8 +405,8 @@ async function pushDashboards() { const commitMessage = document.getElementById('commit-message')?.value?.trim(); const saveCredentials = document.getElementById('save-credentials')?.checked; - // Mettre à jour la liste des apps sélectionnées - updateSelectedApps(); + // Ne PAS rappeler updateSelectedApps() ici car cela réinitialiserait la liste SH Cluster + // La liste selectedApps est déjà à jour grâce aux événements onchange // Validation if (!gitUrl) { @@ -294,6 +439,15 @@ async function pushDashboards() { const shAuthUser = document.getElementById('sh-auth-user')?.value?.trim() || ''; const shAuthPass = document.getElementById('sh-auth-pass')?.value?.trim() || ''; + // La liste selectedShClusterApps est déjà à jour via les événements onchange + // Ne pas rappeler updateSelectedShClusterApps() pour éviter de réinitialiser la sélection + + // Validation des apps SH Cluster si déploiement activé + if (deployToSHCluster && selectedShClusterApps.length === 0) { + showMessage('error', 'Please select at least one application to deploy to SH Cluster'); + return; + } + // Démarrer le push isProcessing = true; showLoading(true, deployToSHCluster); @@ -315,6 +469,7 @@ async function pushDashboards() { git_token: gitToken, commit_message: commitMessage, apps: JSON.stringify(selectedApps), + shcluster_apps: JSON.stringify(selectedShClusterApps), // Apps pour le SH Cluster user: currentUser, deploy_to_shcluster: deployToSHCluster.toString(), deployer_host: SH_DEPLOYER_CONFIG.host, @@ -327,7 +482,7 @@ async function pushDashboards() { if (shAuthUser) params.append('sh_auth_user', shAuthUser); if (shAuthPass) params.append('sh_auth_pass', shAuthPass); - console.log(`Pushing ${selectedApps.length} apps to ${gitUrl}${deployToSHCluster ? ' + SH Cluster deployment' : ''}`); + console.log(`Pushing ${selectedApps.length} apps to Git${deployToSHCluster ? `, ${selectedShClusterApps.length} apps to SH Cluster` : ''}`); // Appeler le serveur const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/push?${params.toString()}`, { diff --git a/apps/pusher_app_prem/bin/git_pusher.pid b/apps/pusher_app_prem/bin/git_pusher.pid index cc6fad70..36d6e0a2 100644 --- a/apps/pusher_app_prem/bin/git_pusher.pid +++ b/apps/pusher_app_prem/bin/git_pusher.pid @@ -1 +1 @@ -1988048 +2001609 diff --git a/apps/pusher_app_prem/bin/git_pusher.py b/apps/pusher_app_prem/bin/git_pusher.py index 593743a1..23aadd1e 100755 --- a/apps/pusher_app_prem/bin/git_pusher.py +++ b/apps/pusher_app_prem/bin/git_pusher.py @@ -459,6 +459,7 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): git_token = query_params.get('git_token', [''])[0] commit_message = query_params.get('commit_message', [''])[0] apps_json = query_params.get('apps', query_params.get('dashboards', ['[]']))[0] + shcluster_apps_json = query_params.get('shcluster_apps', ['[]'])[0] # Apps pour le SH Cluster user = query_params.get('user', ['unknown'])[0] # Paramètres pour le déploiement SH Cluster @@ -474,14 +475,21 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): logger.info(f"Parameters: git_url={git_url}, branch={git_branch}, user={user}, deploy_to_shcluster={deploy_to_shcluster}") - # Parser les apps + # Parser les apps pour Git try: apps = json.loads(apps_json) if isinstance(apps_json, str) else apps_json except (json.JSONDecodeError, TypeError) as e: - logger.error(f"JSON parse error: {e}") + logger.error(f"JSON parse error for apps: {e}") apps = [] - logger.info(f"Parsed apps: {len(apps)} items") + # Parser les apps pour SH Cluster + try: + shcluster_apps = json.loads(shcluster_apps_json) if isinstance(shcluster_apps_json, str) else shcluster_apps_json + except (json.JSONDecodeError, TypeError) as e: + logger.error(f"JSON parse error for shcluster_apps: {e}") + shcluster_apps = apps # Fallback: utiliser toutes les apps + + logger.info(f"Apps for Git: {len(apps)}, Apps for SH Cluster: {len(shcluster_apps)}") # NOTE: La vérification des limites est maintenant faite côté client # Le serveur fait confiance aux informations envoyées par le client @@ -599,10 +607,17 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): # ============================================ deployer_result = None + shcluster_apps_deployed = 0 if deploy_to_shcluster: logger.info("Triggering deployment to SH Cluster...") + # Extraire les IDs des apps à déployer sur le SH Cluster + shcluster_app_ids = [app.get('id') or app.get('name') for app in shcluster_apps] + logger.info(f"Apps to deploy to SH Cluster: {shcluster_app_ids}") + + shcluster_apps_deployed = len(shcluster_app_ids) + # Configurer le deployer deployer_config = SH_DEPLOYER_CONFIG.copy() if deployer_host: @@ -611,16 +626,18 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): deployer_config["token"] = deployer_token # Appeler le SH Deployer pour pull + deploy + # Passer la liste des apps à déployer deployer_result = trigger_deployer_pull_and_deploy( git_url=git_url, git_token=git_token, auth_user=sh_auth_user if sh_auth_user else None, auth_pass=sh_auth_pass if sh_auth_pass else None, - config=deployer_config + config=deployer_config, + apps_to_deploy=shcluster_app_ids # Liste des apps à déployer ) if deployer_result.get("success"): - logger.info("SH Cluster deployment triggered successfully") + logger.info(f"SH Cluster deployment triggered successfully for {shcluster_apps_deployed} apps") else: logger.error(f"SH Cluster deployment failed: {deployer_result.get('error')}") @@ -636,12 +653,13 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): if deploy_to_shcluster: response["shcluster_deployment"] = { "triggered": True, + "apps_count": shcluster_apps_deployed, "success": deployer_result.get("success", False) if deployer_result else False, "message": deployer_result.get("data", {}).get("message") if deployer_result and deployer_result.get("success") else deployer_result.get("error") if deployer_result else "Not triggered" } if deployer_result and deployer_result.get("success"): - response["message"] += " and triggered SH Cluster deployment" + response["message"] += f" and triggered SH Cluster deployment ({shcluster_apps_deployed} apps)" else: response["message"] += " (SH Cluster deployment failed)" @@ -848,9 +866,18 @@ def trigger_deployer_deploy(target_uri=None, auth_user=None, auth_pass=None, con return call_deployer_agent("/deploy", method="POST", data=data, config=config) -def trigger_deployer_pull_and_deploy(git_url, git_token, target_uri=None, auth_user=None, auth_pass=None, config=None): +def trigger_deployer_pull_and_deploy(git_url, git_token, target_uri=None, auth_user=None, auth_pass=None, config=None, apps_to_deploy=None): """ Déclencher pull + deploy en une seule opération + + Args: + git_url: URL du repo Git + git_token: Token d'accès Git + target_uri: URI cible pour le déploiement + auth_user: Utilisateur Splunk pour l'authentification + auth_pass: Mot de passe Splunk + config: Configuration du deployer + apps_to_deploy: Liste des IDs d'apps à déployer (si None, toutes les apps) """ data = { "repo_url": git_url, @@ -863,6 +890,8 @@ def trigger_deployer_pull_and_deploy(git_url, git_token, target_uri=None, auth_u data["auth_user"] = auth_user if auth_pass: data["auth_pass"] = auth_pass + if apps_to_deploy: + data["apps_to_deploy"] = apps_to_deploy # Liste des apps à déployer return call_deployer_agent("/pull-and-deploy", method="POST", data=data, config=config) diff --git a/apps/pusher_app_prem/default/data/ui/nav/default.xml b/apps/pusher_app_prem/default/data/ui/nav/default.xml index 9b4f4d60..86a19282 100644 --- a/apps/pusher_app_prem/default/data/ui/nav/default.xml +++ b/apps/pusher_app_prem/default/data/ui/nav/default.xml @@ -1,4 +1,5 @@ diff --git a/apps/pusher_app_prem/local/data/ui/views/git_pusher_dashboard.xml b/apps/pusher_app_prem/local/data/ui/views/git_pusher_dashboard.xml new file mode 100644 index 00000000..4cf246db --- /dev/null +++ b/apps/pusher_app_prem/local/data/ui/views/git_pusher_dashboard.xml @@ -0,0 +1,661 @@ + + + Push Splunk applications to Git repository and deploy to SH Cluster + + + | rest /services/apps/local | search disabled=0 | table title, label, description | rename title as name | sort label + -1m + now + + + + + + + +
+ +
+
+

🚀 Git Pusher

+ v2.1 +
+
+ +
+
+ + +
+ +
+

📦 Applications

+
+ +

Loading applications...

+
+
+ + +
+

⚙️ Git Configuration

+ +
+ + +
HTTPS URL of your Git repository
+
+ +
+ + +
+ +
+ + +
Personal access token with write permissions
+
+ +
+ + +
+ +
+ + +
+ + +
+
+
+ 🎯 Deploy to Search Head Cluster + + ● Checking... + +
+ +
+ +
+ + +
+ + +
+
+ +
+ + +
+ +
+
+ Splunk credentials for applying shcluster-bundle (optional if using default) +
+
+ + +
+
+
+ + +
+ + +
+ + +
+
+ Deploying applications to Git... Please wait +
+ +
+
+
+
+
+ + + +
+
+