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.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
+};