diff --git a/git_pusher-3.js b/git_pusher-3.js new file mode 100644 index 00000000..0c752936 --- /dev/null +++ b/git_pusher-3.js @@ -0,0 +1,1171 @@ +// ============================================ +// GIT PUSHER - MAIN JAVASCRIPT +// Version 2.1 avec déploiement vers SH Cluster +// ============================================ + +// Configuration par défaut +const DEFAULT_CONFIG = { + api: { + url: '', + port: 9999, + useProxy: true + }, + deployer: { + enabled: false, + host: '', + port: 9998, + token: '', + useSSL: true + } +}; + +// Charger la configuration +function loadAppConfig() { + try { + const stored = localStorage.getItem('git_pusher_config'); + if (stored) { + return JSON.parse(stored); + } + } catch (e) { + console.warn('Erreur chargement config localStorage:', e); + } + return DEFAULT_CONFIG; +} + +// Déterminer l'URL du serveur API +function getServerUrl() { + const config = loadAppConfig(); + const hostname = window.location.hostname; + const protocol = window.location.protocol; + + // Si une URL est configurée, l'utiliser + if (config.api && config.api.url) { + let url = config.api.url; + // Ajouter le port si pas de proxy + if (!config.api.useProxy && config.api.port) { + url = url.replace(/\/$/, '') + ':' + config.api.port; + } + return url; + } + + // Fallback : auto-détection basée sur le hostname + // Si c'est une IP ou localhost, ajouter le port 9999 + if (/^(\d{1,3}\.){3}\d{1,3}$/.test(hostname) || hostname === 'localhost') { + return protocol + '//' + hostname + ':9999'; + } + + // Si c'est un domaine, essayer d'ajouter -api au sous-domaine + // Exemple: splunk.example.com → splunk-api.example.com + const parts = hostname.split('.'); + if (parts.length >= 2) { + parts[0] = parts[0] + '-api'; + return protocol + '//' + parts.join('.'); + } + + // Dernier fallback : même hostname avec port 9999 + return protocol + '//' + hostname + ':9999'; +} + +// Configuration +const GIT_PUSHER_CONFIG = { + serverUrl: getServerUrl(), + credentialsKey: 'git_pusher_credentials', + deployerConfigKey: 'git_pusher_deployer_config', + version: '2.1.0' +}; + +// Configuration SH Deployer (peut être modifiée via l'interface) +let SH_DEPLOYER_CONFIG = { + enabled: false, + host: '10.10.40.14', + port: 9998, + token: '' +}; + +// État global +let selectedApps = []; +let selectedShClusterApps = []; // Apps sélectionnées pour le SH Cluster +let isProcessing = false; +let deployerAvailable = false; + +// ============================================ +// INITIALISATION +// ============================================ + +require([ + 'jquery', + 'splunkjs/mvc', + 'splunkjs/mvc/searchmanager', + 'splunkjs/mvc/simplexml/ready!' +], function($, mvc, SearchManager) { + + console.log("Git Pusher v2.1 initializing..."); + + // Initialiser le système de licence + if (typeof initializeLicense === 'function') { + initializeLicense(); + } else { + console.warn("License system not loaded"); + } + + // Charger les credentials sauvegardés + loadSavedCredentials(); + + // Charger la config du deployer + loadDeployerConfig(); + + // Vérifier la disponibilité du SH Deployer + checkDeployerHealth(); + + // ============================================ + // GESTION ROBUSTE DE LA LISTE DES APPLICATIONS + // ============================================ + + function loadApplications() { + console.log("Loading applications..."); + + // Méthode 1: Essayer de récupérer la recherche existante du dashboard + var searchManager = mvc.Components.get('dsearch'); + + if (searchManager) { + console.log("Found dashboard search 'dsearch'"); + + // Vérifier si la recherche a déjà des résultats + var existingResults = searchManager.data('results'); + if (existingResults && existingResults.hasData && existingResults.hasData()) { + console.log("Search already has data, rendering..."); + var rows = existingResults.data().rows; + var fields = existingResults.data().fields; + if (rows && rows.length > 0) { + renderAppsList(rows, fields); + return; + } + } + + // Écouter les événements de la recherche + searchManager.on('search:done', function(properties) { + console.log("Dashboard search completed"); + var results = searchManager.data('results'); + if (results) { + results.on('data', function() { + if (results.hasData()) { + var rows = results.data().rows; + var fields = results.data().fields; + renderAppsList(rows, fields); + } + }); + // Forcer la lecture si les données sont déjà là + if (results.hasData()) { + var rows = results.data().rows; + var fields = results.data().fields; + renderAppsList(rows, fields); + } + } + }); + + // Si la recherche est déjà terminée, forcer la récupération + if (searchManager.attributes && searchManager.attributes.data) { + var job = searchManager.job; + if (job && job.state() === 'done') { + console.log("Search already done, fetching results..."); + var results = searchManager.data('results'); + if (results) { + results.on('data', function() { + if (results.hasData()) { + renderAppsList(results.data().rows, results.data().fields); + } + }); + } + } + } + } else { + console.log("Dashboard search not found, creating our own..."); + } + + // Méthode 2: Fallback - Créer notre propre recherche après un délai + setTimeout(function() { + var container = document.getElementById('dashboard-list'); + if (container && container.innerHTML.indexOf('Loading') !== -1) { + console.log("Still loading, trying REST API fallback..."); + loadAppsViaREST(); + } + }, 3000); + } + + // Fallback: Charger via l'API REST directement + function loadAppsViaREST() { + console.log("Loading apps via REST API..."); + + fetch('/en-US/splunkd/__raw/services/apps/local?output_mode=json&count=0', { + credentials: 'include' + }) + .then(function(response) { + if (!response.ok) throw new Error('REST API error: ' + response.status); + return response.json(); + }) + .then(function(data) { + if (data && data.entry) { + var apps = data.entry + .filter(function(app) { + return !app.content.disabled; + }) + .map(function(app) { + return { + name: app.name, + label: app.content.label || app.name, + description: app.content.description || '' + }; + }) + .sort(function(a, b) { + return (a.label || '').localeCompare(b.label || ''); + }); + + console.log("Loaded " + apps.length + " apps via REST"); + renderAppsListFromObjects(apps); + } + }) + .catch(function(error) { + console.error("REST API fallback failed:", error); + var container = document.getElementById('dashboard-list'); + if (container) { + container.innerHTML = '

Error loading applications. Please refresh the page.

'; + } + }); + } + + // Render depuis des objets (pour le fallback REST) + function renderAppsListFromObjects(apps) { + var container = document.getElementById('dashboard-list'); + if (!container) return; + + var rows = apps.map(function(app) { + return [app.name, app.label, app.description]; + }); + var fields = ['name', 'label', 'description']; + + renderAppsList(rows, fields); + } + + // Lancer le chargement + loadApplications(); + + // Exposer les fonctions globalement + window.pushDashboards = pushDashboards; + window.resetForm = resetForm; + window.toggleSelectAll = toggleSelectAll; + window.toggleShClusterAllApps = toggleShClusterAllApps; + window.updateSelectedShClusterApps = updateSelectedShClusterApps; + window.loadApplications = loadApplications; + window.loadAppsViaREST = loadAppsViaREST; + + // 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 +// ============================================ + +function renderAppsList(rows, fields) { + const container = document.getElementById('dashboard-list'); + if (!container) return; + + // Trouver les index des colonnes + const nameIdx = fields.indexOf('name'); + const labelIdx = fields.indexOf('label'); + const descIdx = fields.indexOf('description'); + + // Générer le HTML + let html = ` +
+
+ + +
+ ${rows.length} apps +
+ `; + + rows.forEach((row, index) => { + const name = row[nameIdx] || ''; + const label = row[labelIdx] || name; + const desc = row[descIdx] || ''; + + // Ignorer certaines apps système + if (name.startsWith('splunk_') || name === 'learned' || name === 'launcher') { + return; + } + + html += ` +
+ + +
+ `; + }); + + container.innerHTML = html; + + console.log(`Rendered ${rows.length} applications`); +} + +// ============================================ +// GESTION DE LA SÉLECTION +// ============================================ + +function updateSelectedApps() { + const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app-id]'); + selectedApps = []; + + checkboxes.forEach(cb => { + if (cb.checked) { + selectedApps.push({ + id: cb.getAttribute('data-app-id'), + label: cb.getAttribute('data-app-label') + }); + } + }); + + // Mettre à jour le "Select All" + const selectAll = document.getElementById('select-all'); + if (selectAll) { + const allChecked = Array.from(checkboxes).every(cb => cb.checked); + const someChecked = Array.from(checkboxes).some(cb => cb.checked); + selectAll.checked = allChecked; + 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 => { + cb.checked = checked; + }); + updateSelectedApps(); +} + +// ============================================ +// PUSH VERS GIT +// ============================================ + +async function pushDashboards() { + console.log("Starting push process..."); + + // Vérifier si déjà en cours + if (isProcessing) { + console.log("Push already in progress"); + return; + } + + // Vérifier la licence AVANT tout + if (typeof checkLicenseBeforePush === 'function') { + const licenseOk = await checkLicenseBeforePush(); + if (!licenseOk) { + console.log("License check failed"); + return; + } + } + + // Récupérer les valeurs du formulaire + const gitUrl = document.getElementById('git-url')?.value?.trim(); + const gitBranch = document.getElementById('git-branch')?.value?.trim() || 'main'; + const gitToken = document.getElementById('git-token')?.value?.trim(); + const commitMessage = document.getElementById('commit-message')?.value?.trim(); + const saveCredentials = document.getElementById('save-credentials')?.checked; + + // 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) { + showMessage('error', 'Please enter a Git repository URL'); + return; + } + + if (!gitToken) { + showMessage('error', 'Please enter a Git token or password'); + return; + } + + if (!commitMessage) { + showMessage('error', 'Please enter a commit message'); + return; + } + + if (selectedApps.length === 0) { + showMessage('error', 'Please select at least one application to deploy'); + return; + } + + // Sauvegarder les credentials si demandé + if (saveCredentials) { + saveCredentialsToStorage(gitUrl, gitBranch, gitToken); + } + + // Vérifier si le déploiement vers SH Cluster est activé + const deployToSHCluster = document.getElementById('deploy-to-shcluster')?.checked || false; + 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); + hideMessages(); + + try { + // Récupérer l'utilisateur courant + const currentUser = await getCurrentUser(); + + // Récupérer les infos de licence depuis le localStorage + const licenseInfo = getLicenseInfo ? getLicenseInfo() : null; + const licenseType = licenseInfo?.type_name || ''; + const licenseId = licenseInfo?.license_id || ''; + + // Construire les paramètres + const params = new URLSearchParams({ + git_url: gitUrl, + git_branch: gitBranch, + 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, + deployer_token: SH_DEPLOYER_CONFIG.token, + license_type: licenseType, + license_id: licenseId + }); + + // Ajouter les credentials SH si fournis + if (shAuthUser) params.append('sh_auth_user', shAuthUser); + if (shAuthPass) params.append('sh_auth_pass', shAuthPass); + + 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()}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + + const result = await response.json(); + console.log("Push result:", result); + + if (result.status === 'success') { + // Incrémenter le compteur d'utilisation côté client + console.log("=== INCREMENT USAGE ==="); + console.log("typeof incrementUsage:", typeof incrementUsage); + console.log("typeof window.incrementUsage:", typeof window.incrementUsage); + + try { + if (typeof window.incrementUsage === 'function') { + const stats = window.incrementUsage(); + console.log("✓ Usage incremented successfully:", stats); + } else if (typeof incrementUsage === 'function') { + const stats = incrementUsage(); + console.log("✓ Usage incremented (local):", stats); + } else { + console.warn("✗ incrementUsage function not available"); + } + } catch (e) { + console.error("✗ Error incrementing usage:", e); + } + + let message = `✅ Successfully deployed ${result.apps_pushed || selectedApps.length} application(s) to Git!`; + + // Ajouter le statut du déploiement SH Cluster + if (deployToSHCluster && result.shcluster_deployment) { + if (result.shcluster_deployment.success) { + message += '\n🚀 SH Cluster deployment triggered successfully!'; + } else { + message += `\n⚠️ SH Cluster deployment failed: ${result.shcluster_deployment.message}`; + } + } + + showMessage('success', message); + + // Reset la sélection après succès + setTimeout(() => { + toggleSelectAll(false); + }, 2000); + } else if (result.error_code === 'LICENSE_ERROR') { + showMessage('error', `🔐 ${result.message}`); + + // Afficher le modal de licence + if (typeof showLicenseModal === 'function') { + showLicenseModal(result.message, result.error_code); + } + } else if (result.error_code === 'APP_LIMIT') { + showMessage('error', `📦 ${result.message}`); + } else { + showMessage('error', result.message || 'Unknown error occurred'); + } + + } catch (error) { + console.error("Push error:", error); + showMessage('error', `Connection error: ${error.message}. Is the Git Pusher server running?`); + } finally { + isProcessing = false; + showLoading(false); + } +} + +// ============================================ +// UTILITAIRES UI +// ============================================ + +function showLoading(show, deployToSHCluster = false) { + const loading = document.getElementById('loading'); + const pushBtn = document.getElementById('push-btn'); + const loadingText = document.querySelector('.loading-text'); + + if (loading) { + loading.classList.toggle('active', show); + } + + if (loadingText && show) { + if (deployToSHCluster) { + loadingText.textContent = 'Deploying to Git and SH Cluster... Please wait'; + } else { + loadingText.textContent = 'Deploying applications to Git... Please wait'; + } + } + + if (pushBtn) { + pushBtn.disabled = show; + if (show) { + pushBtn.textContent = deployToSHCluster ? '⏳ Deploying to Git + SH...' : '⏳ Deploying...'; + } else { + pushBtn.textContent = '✈️ Deploy to Git'; + } + } +} + +function showMessage(type, text) { + hideMessages(); + + const successMsg = document.getElementById('success-msg'); + const errorMsg = document.getElementById('error-msg'); + const successText = document.getElementById('success-text'); + const errorText = document.getElementById('error-text'); + + if (type === 'success' && successMsg && successText) { + successText.textContent = text; + successMsg.classList.add('active'); + + // Auto-hide après 5 secondes + setTimeout(() => { + successMsg.classList.remove('active'); + }, 5000); + } else if (type === 'error' && errorMsg && errorText) { + errorText.textContent = text; + errorMsg.classList.add('active'); + } +} + +function hideMessages() { + const successMsg = document.getElementById('success-msg'); + const errorMsg = document.getElementById('error-msg'); + + if (successMsg) successMsg.classList.remove('active'); + if (errorMsg) errorMsg.classList.remove('active'); +} + +function resetForm(clearCredentials = false) { + // Reset les champs + const commitMessage = document.getElementById('commit-message'); + if (commitMessage) commitMessage.value = ''; + + if (clearCredentials) { + const gitUrl = document.getElementById('git-url'); + const gitToken = document.getElementById('git-token'); + const saveCredentials = document.getElementById('save-credentials'); + + if (gitUrl) gitUrl.value = ''; + if (gitToken) gitToken.value = ''; + if (saveCredentials) saveCredentials.checked = false; + + // Supprimer les credentials sauvegardés + localStorage.removeItem(GIT_PUSHER_CONFIG.credentialsKey); + } + + // Reset la sélection + toggleSelectAll(false); + + // Cacher les messages + hideMessages(); + + console.log("Form reset" + (clearCredentials ? " (with credentials)" : "")); +} + +// ============================================ +// GESTION DES CREDENTIALS +// ============================================ + +function saveCredentialsToStorage(gitUrl, gitBranch, gitToken) { + try { + const credentials = { + gitUrl: gitUrl, + gitBranch: gitBranch, + // Note: En production, envisager une solution plus sécurisée + gitToken: btoa(gitToken), // Encodage basique (pas sécurisé, juste pour l'obfuscation) + savedAt: new Date().toISOString() + }; + + localStorage.setItem(GIT_PUSHER_CONFIG.credentialsKey, JSON.stringify(credentials)); + console.log("Credentials saved"); + } catch (error) { + console.error("Error saving credentials:", error); + } +} + +function loadSavedCredentials() { + try { + const saved = localStorage.getItem(GIT_PUSHER_CONFIG.credentialsKey); + if (!saved) return; + + const credentials = JSON.parse(saved); + + const gitUrl = document.getElementById('git-url'); + const gitBranch = document.getElementById('git-branch'); + const gitToken = document.getElementById('git-token'); + const saveCredentials = document.getElementById('save-credentials'); + + if (gitUrl && credentials.gitUrl) { + gitUrl.value = credentials.gitUrl; + } + + if (gitBranch && credentials.gitBranch) { + gitBranch.value = credentials.gitBranch; + } + + if (gitToken && credentials.gitToken) { + gitToken.value = atob(credentials.gitToken); + } + + if (saveCredentials) { + saveCredentials.checked = true; + } + + console.log("Credentials loaded from storage"); + } catch (error) { + console.error("Error loading credentials:", error); + } +} + +// ============================================ +// RÉCUPÉRATION DE L'UTILISATEUR SPLUNK +// ============================================ + +async function getCurrentUser() { + try { + const response = await fetch('/en-US/splunkd/__raw/services/authentication/current-context?output_mode=json'); + const data = await response.json(); + return data.entry?.[0]?.content?.username || 'unknown'; + } catch (error) { + console.error("Error getting current user:", error); + return 'unknown'; + } +} + +// ============================================ +// VÉRIFICATION DU SERVEUR +// ============================================ + +async function checkServerHealth() { + try { + const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/health`, { + method: 'GET', + timeout: 5000 + }); + const data = await response.json(); + return data.status === 'ok'; + } catch (error) { + console.error("Server health check failed:", error); + return false; + } +} + +// ============================================ +// SH DEPLOYER FUNCTIONS +// ============================================ + +async function checkDeployerHealth() { + try { + const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/deployer/health`, { + method: 'GET', + timeout: 5000 + }); + const data = await response.json(); + + deployerAvailable = data.status === 'ok'; + + // Mettre à jour l'UI + updateDeployerUI(); + + console.log("SH Deployer status:", deployerAvailable ? "Available" : "Unavailable"); + return deployerAvailable; + } catch (error) { + console.error("Deployer health check failed:", error); + deployerAvailable = false; + updateDeployerUI(); + return false; + } +} + +function updateDeployerUI() { + const deployerCheckbox = document.getElementById('deploy-to-shcluster'); + const deployerStatus = document.getElementById('deployer-status'); + const deployerSection = document.getElementById('deployer-section'); + + if (deployerCheckbox) { + deployerCheckbox.disabled = !deployerAvailable; + } + + if (deployerStatus) { + if (deployerAvailable) { + deployerStatus.innerHTML = '● Connected'; + } else { + deployerStatus.innerHTML = '● Disconnected'; + } + } + + if (deployerSection && !deployerAvailable) { + deployerSection.style.opacity = '0.6'; + } +} + +function loadDeployerConfig() { + try { + const saved = localStorage.getItem(GIT_PUSHER_CONFIG.deployerConfigKey); + if (saved) { + const config = JSON.parse(saved); + SH_DEPLOYER_CONFIG = { ...SH_DEPLOYER_CONFIG, ...config }; + console.log("Deployer config loaded"); + } + } catch (error) { + console.error("Error loading deployer config:", error); + } +} + +function saveDeployerConfig() { + try { + localStorage.setItem(GIT_PUSHER_CONFIG.deployerConfigKey, JSON.stringify(SH_DEPLOYER_CONFIG)); + console.log("Deployer config saved"); + } catch (error) { + console.error("Error saving deployer config:", error); + } +} + +function showDeployerConfigModal() { + const modal = document.createElement('div'); + modal.id = 'deployer-config-modal'; + modal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + `; + + modal.innerHTML = ` +
+

⚙️ SH Deployer Configuration

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ `; + + document.body.appendChild(modal); +} + +function closeDeployerConfigModal() { + const modal = document.getElementById('deployer-config-modal'); + if (modal) modal.remove(); +} + +async function saveDeployerConfigFromModal() { + const host = document.getElementById('deployer-config-host')?.value?.trim(); + const port = parseInt(document.getElementById('deployer-config-port')?.value) || 9998; + const token = document.getElementById('deployer-config-token')?.value?.trim(); + const msgEl = document.getElementById('deployer-config-message'); + + if (!host) { + if (msgEl) { + msgEl.style.display = 'block'; + msgEl.style.background = '#ffebee'; + msgEl.style.color = '#c62828'; + msgEl.textContent = 'Please enter a host'; + } + return; + } + + // Mettre à jour la config + SH_DEPLOYER_CONFIG.host = host; + SH_DEPLOYER_CONFIG.port = port; + SH_DEPLOYER_CONFIG.token = token; + + // Sauvegarder + saveDeployerConfig(); + + // Tester la connexion + if (msgEl) { + msgEl.style.display = 'block'; + msgEl.style.background = '#e3f2fd'; + msgEl.style.color = '#1565c0'; + msgEl.textContent = 'Testing connection...'; + } + + const isHealthy = await checkDeployerHealth(); + + if (isHealthy) { + if (msgEl) { + msgEl.style.background = '#e8f5e9'; + msgEl.style.color = '#2e7d32'; + msgEl.textContent = '✓ Connected successfully!'; + } + setTimeout(closeDeployerConfigModal, 1500); + } else { + if (msgEl) { + msgEl.style.background = '#ffebee'; + msgEl.style.color = '#c62828'; + msgEl.textContent = '✗ Connection failed. Check host and port.'; + } + } +} + +// Vérifier la santé du serveur au démarrage +setTimeout(async () => { + const healthy = await checkServerHealth(); + if (!healthy) { + console.warn("Git Pusher server may not be running"); + } else { + console.log("Git Pusher server is healthy"); + } +}, 1000); + +// ============================================ +// EXPORT FONCTIONS GLOBALES +// ============================================ + +// Exposer les fonctions du deployer globalement pour les onclick du HTML +window.showDeployerConfigModal = showDeployerConfigModal; +window.closeDeployerConfigModal = closeDeployerConfigModal; +window.saveDeployerConfigFromModal = saveDeployerConfigFromModal; + +// Exposer les fonctions principales +window.pushDashboards = pushDashboards; +window.resetForm = resetForm; +window.toggleSelectAll = toggleSelectAll; +window.updateSelectedApps = updateSelectedApps; + +// Fonction toggle pour le HTML +window.toggleDeployerAuth = function() { + var checkbox = document.getElementById('deploy-to-shcluster'); + var authSection = document.getElementById('deployer-auth'); + if (checkbox && authSection) { + authSection.classList.toggle('visible', checkbox.checked); + } +}; + +// ============================================ +// ATTACHEMENT DES ÉVÉNEMENTS AUX BOUTONS +// ============================================ + +(function attachButtonEvents() { + function tryAttach() { + console.log("Trying to attach button events..."); + + // Bouton Deploy to Git + var pushBtn = document.getElementById('push-btn'); + if (pushBtn) { + // Supprimer les anciens listeners + pushBtn.replaceWith(pushBtn.cloneNode(true)); + pushBtn = document.getElementById('push-btn'); + + pushBtn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + console.log("Deploy button clicked!"); + pushDashboards(); + }); + console.log("✓ Deploy button event attached"); + } else { + console.log("✗ Deploy button not found yet"); + } + + // Bouton Reset - chercher par classe ou contenu + var buttons = document.querySelectorAll('button.btn, button.btn-secondary'); + buttons.forEach(function(btn) { + if (btn.textContent.includes('Reset') || btn.textContent.includes('🔄')) { + // Supprimer les anciens listeners + var newBtn = btn.cloneNode(true); + btn.parentNode.replaceChild(newBtn, btn); + + newBtn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + console.log("Reset button clicked!"); + resetForm(true); + }); + console.log("✓ Reset button event attached"); + } + }); + + // Bouton Configure Deployer + var configBtn = document.querySelector('.deployer-config-btn'); + if (configBtn) { + // Supprimer les anciens listeners + var newConfigBtn = configBtn.cloneNode(true); + configBtn.parentNode.replaceChild(newConfigBtn, configBtn); + + newConfigBtn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + console.log("Configure button clicked!"); + showDeployerConfigModal(); + }); + console.log("✓ Configure button event attached"); + } + + // Checkbox deploy to shcluster + var deployCheckbox = document.getElementById('deploy-to-shcluster'); + if (deployCheckbox) { + deployCheckbox.addEventListener('change', function() { + var authSection = document.getElementById('deployer-auth'); + if (authSection) { + authSection.classList.toggle('visible', this.checked); + } + }); + console.log("✓ Deploy checkbox event attached"); + } + + // Si le bouton principal n'est pas encore là, réessayer + if (!pushBtn) { + console.log("Retrying in 500ms..."); + setTimeout(tryAttach, 500); + } else { + console.log("=== All button events attached successfully ==="); + } + } + + // Démarrer après un délai pour laisser le DOM se charger + if (document.readyState === 'complete') { + console.log("Document ready, attaching events in 1s..."); + setTimeout(tryAttach, 1000); + } else { + window.addEventListener('load', function() { + console.log("Window loaded, attaching events in 1s..."); + setTimeout(tryAttach, 1000); + }); + } +})(); + +// ============================================ +// EXPORT POUR DEBUG +// ============================================ + +window.GitPusher = { + config: GIT_PUSHER_CONFIG, + deployerConfig: SH_DEPLOYER_CONFIG, + getSelectedApps: () => selectedApps, + checkServer: checkServerHealth, + checkDeployer: checkDeployerHealth, + version: GIT_PUSHER_CONFIG.version +};