From 50267b73400d18661b19ff1288369aeb6e3d9d4c Mon Sep 17 00:00:00 2001 From: Splunk Git Pusher Date: Sun, 25 Jan 2026 21:58:55 +0100 Subject: [PATCH] Premimun app pusher Pushed by: unknown_user Timestamp: 2026-01-25T21:58:55.092327 --- .../appserver/static/git_pusher.js | 565 ++++++++++++++++++ .../appserver/static/license_validation.js | 368 ++++++++++++ apps/pusher_app_prem/bin/README | 1 + apps/pusher_app_prem/bin/git_pusher.pid | 1 + apps/pusher_app_prem/bin/git_pusher.py | 340 +++++++++++ apps/pusher_app_prem/bin/license_generator.py | 173 ++++++ apps/pusher_app_prem/bin/start_git_pusher.sh | 5 + apps/pusher_app_prem/certs/server.crt | 19 + apps/pusher_app_prem/certs/server.key | 28 + apps/pusher_app_prem/default/app.conf | 16 + .../default/data/ui/nav/default.xml | 8 + .../default/data/ui/views/README | 1 + apps/pusher_app_prem/local/app.conf | 3 + .../git_pusher_-_deploy_applications.xml | 493 +++++++++++++++ .../git_pusher_-_push_applications_to_git.xml | 256 ++++++++ .../git_pusher_-_push_dashboards_to_git.xml | 254 ++++++++ apps/pusher_app_prem/metadata/default.meta | 35 ++ apps/pusher_app_prem/metadata/local.meta | 26 + 18 files changed, 2592 insertions(+) create mode 100644 apps/pusher_app_prem/appserver/static/git_pusher.js create mode 100644 apps/pusher_app_prem/appserver/static/license_validation.js create mode 100644 apps/pusher_app_prem/bin/README create mode 100644 apps/pusher_app_prem/bin/git_pusher.pid create mode 100644 apps/pusher_app_prem/bin/git_pusher.py create mode 100644 apps/pusher_app_prem/bin/license_generator.py create mode 100644 apps/pusher_app_prem/bin/start_git_pusher.sh create mode 100644 apps/pusher_app_prem/certs/server.crt create mode 100644 apps/pusher_app_prem/certs/server.key create mode 100644 apps/pusher_app_prem/default/app.conf create mode 100644 apps/pusher_app_prem/default/data/ui/nav/default.xml create mode 100644 apps/pusher_app_prem/default/data/ui/views/README create mode 100644 apps/pusher_app_prem/local/app.conf create mode 100644 apps/pusher_app_prem/local/data/ui/views/git_pusher_-_deploy_applications.xml create mode 100644 apps/pusher_app_prem/local/data/ui/views/git_pusher_-_push_applications_to_git.xml create mode 100644 apps/pusher_app_prem/local/data/ui/views/git_pusher_-_push_dashboards_to_git.xml create mode 100644 apps/pusher_app_prem/metadata/default.meta create mode 100644 apps/pusher_app_prem/metadata/local.meta diff --git a/apps/pusher_app_prem/appserver/static/git_pusher.js b/apps/pusher_app_prem/appserver/static/git_pusher.js new file mode 100644 index 00000000..b65c4ae9 --- /dev/null +++ b/apps/pusher_app_prem/appserver/static/git_pusher.js @@ -0,0 +1,565 @@ +// ============================================ +// CHARGER LES DASHBOARDS DYNAMIQUEMENT +// ============================================ + +// Charger les applications +function loadAvailableApps() { + console.log("loadAvailableApps called"); + + const apiUrl = '/en-US/splunkd/__raw/services/apps/local?output_mode=json&count=0'; + + fetch(apiUrl) + .then(response => { + if (!response.ok) { + throw new Error('HTTP ' + response.status); + } + return response.json(); + }) + .then(data => { + console.log("Apps Response: Found " + (data.entry ? data.entry.length : 0) + " apps"); + + if (data.entry && data.entry.length > 0) { + const apps = data.entry + .filter(item => { + // Filtrer les apps système + const isHidden = item.content && item.content.is_visible === 0; + const appName = item.name; + // Exclure les apps système + const systemApps = ['launcher', 'splunk_monitoring_console', 'introspection']; + return !isHidden && !systemApps.includes(appName); + }) + .map(item => ({ + id: item.name, + name: item.content.label || item.name, + description: item.content.description || '' + })) + .sort((a, b) => a.name.localeCompare(b.name)); + + console.log("Filtered apps: " + apps.length); + populateAppsList(apps); + } else { + console.warn("No apps found"); + showAppsEmpty(); + } + }) + .catch(error => { + console.error("API Error:", error); + showAppsEmpty(); + }); +} + +function populateAppsList(apps) { + console.log("populateAppsList called with", apps.length, "apps"); + + const container = document.getElementById('dashboard-list'); + + if (!container) { + console.error("dashboard-list container not found"); + return; + } + + if (!apps || apps.length === 0) { + showAppsEmpty(); + return; + } + + let html = '
'; + html += 'Select Applications'; + html += ''; + html += '
'; + + apps.forEach((app, index) => { + const checkboxId = 'app-' + index; + html += '
'; + html += ''; + html += ''; + html += '
'; + }); + + container.innerHTML = html; + console.log('Successfully populated ' + apps.length + ' apps'); + + // Ajouter les event listeners + addCheckboxListeners(); +} + +function addCheckboxListeners() { + console.log("addCheckboxListeners called"); + + const selectAllCheckbox = document.getElementById('select-all'); + if (selectAllCheckbox) { + selectAllCheckbox.addEventListener('change', function() { + toggleSelectAll(this); + }); + } + + const appCheckboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app]'); + appCheckboxes.forEach(checkbox => { + checkbox.addEventListener('change', function() { + console.log("App checkbox changed"); + }); + }); +} + +function showAppsEmpty() { + const container = document.getElementById('dashboard-list'); + if (container) { + container.innerHTML = '
No apps found
'; + } + console.log("Displayed empty state"); +} + +// Attendre que le DOM soit chargé +function initScript() { + console.log("initScript called"); + + // INITIALISER LA LICENCE EN PREMIER + initializeLicense(); + + // Charger les applications + loadAvailableApps(); + + // Charger les credentials sauvegardés + loadSavedCredentials(); + + // Attacher les event listeners au bouton + const pushBtn = document.getElementById('push-btn'); + if (pushBtn) { + console.log("Push button found, attaching listener"); + pushBtn.addEventListener('click', function(e) { + e.preventDefault(); + console.log("Push button clicked!"); + pushDashboards(); + }); + } else { + console.warn("Push button not found"); + } + + // Attacher l'event listener pour sauvegarder les credentials + const saveCheckbox = document.getElementById('save-credentials'); + if (saveCheckbox) { + saveCheckbox.addEventListener('change', function() { + if (this.checked) { + saveCredentials(); + } else { + clearSavedCredentials(); + } + }); + } +} + +function loadSavedCredentials() { + console.log("Loading saved credentials..."); + + try { + const savedUrl = getCookie('git_pusher_url'); + const savedToken = getCookie('git_pusher_token'); + const savedBranch = getCookie('git_pusher_branch'); + + if (savedUrl) { + document.getElementById('git-url').value = decodeURIComponent(savedUrl); + console.log("Loaded saved URL from cookie"); + } + + if (savedToken) { + document.getElementById('git-token').value = decodeURIComponent(savedToken); + console.log("Loaded saved token from cookie"); + } + + if (savedBranch) { + document.getElementById('git-branch').value = decodeURIComponent(savedBranch); + } + + if (savedUrl && savedToken) { + document.getElementById('save-credentials').checked = true; + } + } catch (e) { + console.warn("Could not load saved credentials:", e); + } +} + +function saveCredentials() { + console.log("Saving credentials..."); + + try { + const gitUrl = document.getElementById('git-url').value; + const gitToken = document.getElementById('git-token').value; + const gitBranch = document.getElementById('git-branch').value; + + if (gitUrl && gitToken) { + setCookie('git_pusher_url', gitUrl, 30); + setCookie('git_pusher_token', gitToken, 30); + setCookie('git_pusher_branch', gitBranch, 30); + console.log("Credentials saved to cookies"); + showSuccess("Credentials saved locally"); + } else { + showError("Please fill in URL and Token before saving"); + } + } catch (e) { + console.error("Error saving credentials:", e); + showError("Could not save credentials"); + } +} + +function clearSavedCredentials() { + console.log("Clearing saved credentials..."); + + try { + deleteCookie('git_pusher_url'); + deleteCookie('git_pusher_token'); + deleteCookie('git_pusher_branch'); + console.log("Credentials cleared from cookies"); + showSuccess("Credentials cleared"); + } catch (e) { + console.error("Error clearing credentials:", e); + } +} + +// Fonctions utilitaires pour les cookies +function setCookie(name, value, days) { + const d = new Date(); + d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); + const expires = "expires=" + d.toUTCString(); + document.cookie = name + "=" + encodeURIComponent(value) + ";" + expires + ";path=/"; + console.log("Cookie set: " + name); +} + +function getCookie(name) { + const nameEQ = name + "="; + const ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i].trim(); + if (c.indexOf(nameEQ) === 0) { + return c.substring(nameEQ.length); + } + } + return ""; +} + +function deleteCookie(name) { + setCookie(name, "", -1); + console.log("Cookie deleted: " + name); +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function() { + console.log("DOM Ready - Initializing script..."); + setTimeout(function() { + initScript(); + }, 1000); + }); +} else { + console.log("DOM already ready - Initializing script..."); + setTimeout(function() { + initScript(); + }, 1000); +} + +function getFormKeyValue() { + try { + // Chercher dans les meta tags du DOM + const metaTag = document.querySelector('meta[name="splunk_form_key"]'); + if (metaTag) { + return metaTag.getAttribute('content'); + } + + // Chercher dans les cookies + const cookies = document.cookie.split(';'); + for (let cookie of cookies) { + const [name, value] = cookie.trim().split('='); + if (name === 'splunk_form_key') { + return decodeURIComponent(value); + } + } + + console.warn("Could not find form key, proceeding without it"); + return ''; + } catch (e) { + console.error("Error getting form key:", e); + return ''; + } +} + +function loadAvailableApps() { + console.log("loadAvailableApps called"); + + const apiUrl = '/en-US/splunkd/__raw/services/apps/local?output_mode=json&count=0'; + + fetch(apiUrl) + .then(response => { + if (!response.ok) { + throw new Error('HTTP ' + response.status); + } + return response.json(); + }) + .then(data => { + console.log("Apps Response: Found " + (data.entry ? data.entry.length : 0) + " apps"); + + if (data.entry && data.entry.length > 0) { + const apps = data.entry + .filter(item => { + // Filtrer les apps système + const isHidden = item.content && item.content.is_visible === 0; + const appName = item.name; + // Exclure les apps système + const systemApps = ['launcher', 'splunk_monitoring_console', 'introspection']; + return !isHidden && !systemApps.includes(appName); + }) + .map(item => ({ + id: item.name, + name: item.content.label || item.name, + description: item.content.description || '' + })) + .sort((a, b) => a.name.localeCompare(b.name)); + + console.log("Filtered apps: " + apps.length); + populateAppsList(apps); + } else { + console.warn("No apps found"); + showAppsEmpty(); + } + }) + .catch(error => { + console.error("API Error:", error); + showAppsEmpty(); + }); +} + +function populateAppsList(apps) { + console.log("populateAppsList called with", apps.length, "apps"); + + const container = document.getElementById('dashboard-list'); + + if (!container) { + console.error("dashboard-list container not found"); + return; + } + + if (!apps || apps.length === 0) { + showAppsEmpty(); + return; + } + + let html = '
'; + html += ''; + html += ''; + html += '
'; + + apps.forEach((app, index) => { + const checkboxId = 'app-' + index; + html += '
'; + html += ''; + html += ''; + html += '
'; + }); + + container.innerHTML = html; + console.log('Successfully populated ' + apps.length + ' apps'); +} + +function showAppsEmpty() { + const container = document.getElementById('dashboard-list'); + if (container) { + container.innerHTML = '
No apps found
'; + } + console.log("Displayed empty state"); +} + +function toggleSelectAll(checkbox) { + const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app]'); + checkboxes.forEach(cb => cb.checked = checkbox.checked); +} + +// ============================================ +// POUSSER LES DASHBOARDS VERS GIT +// ============================================ + +function pushDashboards() { + + // Vérifier la licence + if (!checkLicenseBeforePush()) { + return; + } + console.log("pushDashboards called"); + + const gitUrl = document.getElementById('git-url').value; + const gitBranch = document.getElementById('git-branch').value; + const gitToken = document.getElementById('git-token').value; + const commitMessage = document.getElementById('commit-message').value; + + console.log("Git URL:", gitUrl); + console.log("Git Branch:", gitBranch); + console.log("Commit Message:", commitMessage); + + const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"]:not(#select-all):checked'); + const selectedApps = Array.from(checkboxes).map(cb => ({ + id: cb.value, + name: cb.getAttribute('data-name') + })); + + console.log("Selected apps:", selectedApps); + + // Validation + if (!gitUrl.trim()) { + console.warn("Validation failed: No Git URL"); + showError('Please enter a Git repository URL'); + return; + } + + if (!gitToken.trim()) { + console.warn("Validation failed: No Git token"); + showError('Please enter your Git token or password'); + return; + } + + if (!commitMessage.trim()) { + console.warn("Validation failed: No commit message"); + showError('Please enter a commit message'); + return; + } + + if (selectedApps.length === 0) { + console.warn("Validation failed: No apps selected"); + showError('Please select at least one application'); + return; + } + + console.log("Validation passed, showing loading state..."); + + // Afficher le loading + document.getElementById('loading').style.display = 'block'; + document.getElementById('success-msg').style.display = 'none'; + document.getElementById('error-msg').style.display = 'none'; + document.getElementById('push-btn').disabled = true; + + // Sauvegarder les credentials si la case est cochée + if (document.getElementById('save-credentials').checked) { + try { + setCookie('git_pusher_url', gitUrl, 30); + setCookie('git_pusher_token', gitToken, 30); + setCookie('git_pusher_branch', gitBranch, 30); + console.log("Credentials auto-saved to cookies"); + } catch (e) { + console.warn("Could not auto-save credentials:", e); + } + } + + // Préparer les données - passer les apps au lieu des dashboards + const payload = { + git_url: gitUrl, + git_branch: gitBranch, + git_token: gitToken, + apps: selectedApps, + commit_message: commitMessage, + timestamp: new Date().toISOString(), + user: getCurrentUser() + }; + + console.log("Payload prepared:", payload); + + // Appeler le script Python via serveur + callPushScript(payload); +} + +function callPushScript(payload) { + console.log("callPushScript called"); + console.log("Payload:", payload); + + // Construire l'URL vers le serveur Python sur le port 9999 en HTTP + const hostname = window.location.hostname; + const baseUrl = `http://${hostname}:9999`; + + const url = new URL('/push', baseUrl); + + // Ajouter les paramètres en query string + url.searchParams.append('git_url', payload.git_url); + url.searchParams.append('git_branch', payload.git_branch); + url.searchParams.append('git_token', payload.git_token); + url.searchParams.append('commit_message', payload.commit_message); + + // Encoder correctement les apps en JSON + const appsJson = JSON.stringify(payload.apps); + console.log("Apps JSON:", appsJson); + url.searchParams.append('apps', appsJson); + + url.searchParams.append('user', payload.user); + + console.log("Calling:", url.toString()); + + fetch(url.toString(), { + method: 'POST', + mode: 'no-cors' + }) + .then(response => { + // Avec no-cors, on ne peut pas lire response.json() + // Donc on suppose que si la requête arrive au serveur, c'est bon + console.log("Request sent successfully"); + document.getElementById('loading').style.display = 'none'; + document.getElementById('push-btn').disabled = false; + showSuccess('Push request sent! Check server logs for details.'); + resetForm(); + return; + }) + .catch(error => { + console.error('Fetch error:', error); + document.getElementById('loading').style.display = 'none'; + document.getElementById('push-btn').disabled = false; + showError('Network error: ' + error.message); + }); +} + +function getCurrentUser() { + try { + // Essayer plusieurs méthodes + if (Splunk && Splunk.util && typeof Splunk.util.getCurrentUser === 'function') { + return Splunk.util.getCurrentUser(); + } + + // Fallback: chercher dans le DOM ou retourner 'unknown' + return 'unknown_user'; + } catch (e) { + console.warn("Could not get current user:", e); + return 'unknown_user'; + } +} + +function showSuccess(message) { + const successMsg = document.getElementById('success-msg'); + document.getElementById('success-text').textContent = message; + successMsg.style.display = 'block'; + setTimeout(() => { + successMsg.style.display = 'none'; + }, 5000); +} + +function showError(message) { + const errorMsg = document.getElementById('error-msg'); + document.getElementById('error-text').textContent = 'X ' + message; + errorMsg.style.display = 'block'; +} + +function resetForm(showConfirm = false) { + document.getElementById('git-url').value = ''; + document.getElementById('git-branch').value = 'main'; + document.getElementById('git-token').value = ''; + document.getElementById('commit-message').value = ''; + document.querySelectorAll('#dashboard-list input[type="checkbox"]').forEach(cb => cb.checked = false); + + // Demander si l'utilisateur veut aussi effacer les credentials sauvegardés + // SEULEMENT si showConfirm = true (c'est-à-dire si l'utilisateur a cliqué sur Reset) + if (showConfirm && document.getElementById('save-credentials').checked) { + const confirmClear = confirm('Do you want to clear saved credentials?'); + if (confirmClear) { + clearSavedCredentials(); + document.getElementById('save-credentials').checked = false; + } + } +} \ No newline at end of file diff --git a/apps/pusher_app_prem/appserver/static/license_validation.js b/apps/pusher_app_prem/appserver/static/license_validation.js new file mode 100644 index 00000000..b668a4a8 --- /dev/null +++ b/apps/pusher_app_prem/appserver/static/license_validation.js @@ -0,0 +1,368 @@ +// ============================================ +// SYSTÈME DE VALIDATION DE LICENCE +// ============================================ + +const LICENSE_STORAGE_KEY = 'git_pusher_license'; + +function initializeLicense() { + console.log("Initializing license system..."); + + // Vérifier si une licence est déjà stockée + const storedLicense = getCookie(LICENSE_STORAGE_KEY); + + if (storedLicense) { + // Valider la licence stockée + validateStoredLicense(storedLicense); + // Afficher les infos de licence + displayLicenseInfo(storedLicense); + } else { + // Afficher la page de licence + showLicenseModal(); + } +} + +function displayLicenseInfo(license) { + console.log("Displaying license info..."); + + // Chercher le container du badge + const container = document.getElementById('license-badge-container'); + if (!container) { + console.error("license-badge-container not found"); + return; + } + + // Créer le badge + const badge = document.createElement('div'); + badge.id = 'license-badge'; + badge.style.cssText = ` + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 12px 20px; + border-radius: 8px; + font-size: 12px; + font-weight: 600; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); + cursor: pointer; + transition: all 0.3s ease; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + text-align: center; + min-width: 200px; + `; + + let badgeText = '✓ Licence Activée'; + + // Si c'est une licence d'essai + if (license.startsWith('TRIAL-')) { + const daysRemaining = getTrialDaysRemaining(license); + + if (daysRemaining <= 0) { + badgeText = '⏱️ Essai expiré'; + badge.style.background = 'linear-gradient(135deg, #f44336 0%, #da190b 100%)'; + } else if (daysRemaining <= 2) { + badgeText = `⚠️ ${daysRemaining} jour${daysRemaining > 1 ? 's' : ''} restant${daysRemaining > 1 ? 's' : ''}`; + badge.style.background = 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)'; + } else { + badgeText = `⏱️ Essai: ${daysRemaining} jours`; + } + } + + badge.textContent = badgeText; + badge.onclick = function() { + alert('Licence: ' + license.substring(0, 50) + '...\n\nClique sur le logo pour gérer ta licence.'); + }; + + container.appendChild(badge); + + // Ajouter un hover effect + badge.addEventListener('mouseenter', function() { + this.style.transform = 'translateY(-3px)'; + this.style.boxShadow = '0 6px 25px rgba(102, 126, 234, 0.5)'; + }); + + badge.addEventListener('mouseleave', function() { + this.style.transform = 'translateY(0)'; + this.style.boxShadow = '0 4px 15px rgba(102, 126, 234, 0.3)'; + }); +} + +function getTrialDaysRemaining(trialLicense) { + // Extraire le timestamp du license (format: TRIAL-timestamp) + const parts = trialLicense.split('-'); + if (parts.length !== 2) return 0; + + const timestamp = parseInt(parts[1]); + if (isNaN(timestamp)) return 0; + + // Créer la date de création + const createdDate = new Date(timestamp); + + // Ajouter 7 jours + const expirationDate = new Date(createdDate.getTime() + (7 * 24 * 60 * 60 * 1000)); + + // Calculer les jours restants + const now = new Date(); + const daysRemaining = Math.ceil((expirationDate - now) / (1000 * 60 * 60 * 24)); + + console.log("Trial created:", createdDate); + console.log("Trial expires:", expirationDate); + console.log("Days remaining:", daysRemaining); + + return Math.max(0, daysRemaining); +} + +function showLicenseModal() { + console.log("Showing license modal"); + + // Créer le modal HTML + const modal = document.createElement('div'); + modal.id = 'license-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; + `; + + const content = document.createElement('div'); + content.style.cssText = ` + background: white; + border-radius: 16px; + padding: 40px; + max-width: 500px; + width: 90%; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + `; + + content.innerHTML = ` +
+

🔐 Git Pusher

+

Activation de licence requise

+
+ +
+

+ 📋 Hostname détecté: Chargement... +

+
+ +
+ + + + Vous n'avez pas de licence? Cliquez ici + +
+ + + +
+ + +
+ `; + + modal.appendChild(content); + document.body.appendChild(modal); + + // Afficher le hostname + getHostname().then(hostname => { + document.getElementById('detected-hostname').textContent = hostname; + }); +} + +function showGeneratorInfo() { + alert(`Pour générer une clé de licence, exécutez sur le serveur Splunk: + +python /opt/splunk/etc/apps/pusher_app/bin/license_generator.py + +Cela générera une clé basée sur votre hostname.`); +} + +function getHostname() { + return new Promise((resolve) => { + fetch('/en-US/splunkd/__raw/services/server/info?output_mode=json') + .then(r => r.json()) + .then(d => { + const hostname = d.entry?.[0]?.content?.host || 'unknown'; + resolve(hostname); + }) + .catch(() => resolve('unknown')); + }); +} + +function validateLicenseInput() { + const licenseInput = document.getElementById('license-input').value.trim(); + + if (!licenseInput) { + showLicenseMessage('Veuillez entrer une clé de licence', 'error'); + return; + } + + // Afficher le message de chargement + showLicenseMessage('Validation en cours...', 'info'); + + // Simuler la validation (en production, faire un appel à un serveur) + // Pour l'instant, on accepte juste n'importe quelle licence + if (licenseInput.length > 20) { + // Stocker la licence + setCookie(LICENSE_STORAGE_KEY, licenseInput, 365); + + showLicenseMessage('✓ Licence activée avec succès!', 'success'); + + setTimeout(() => { + closeLicenseModal(); + // Afficher les infos de licence + displayLicenseInfo(licenseInput); + }, 1500); + } else { + showLicenseMessage('Format de licence invalide', 'error'); + } +} + +function skipLicense() { + // Créer une licence d'essai avec timestamp (format: TRIAL-timestamp) + const trialLicense = 'TRIAL-' + Date.now(); + setCookie(LICENSE_STORAGE_KEY, trialLicense, 7); + + const messageEl = document.getElementById('license-message'); + messageEl.style.display = 'block'; + messageEl.style.background = '#fff3cd'; + messageEl.style.color = '#856404'; + messageEl.style.border = '1px solid #ffeaa7'; + messageEl.textContent = '⏱️ Mode essai activé pour 7 jours'; + + setTimeout(() => { + closeLicenseModal(); + // Afficher les infos de licence + displayLicenseInfo(trialLicense); + }, 1500); +} + +function showLicenseMessage(message, type) { + const messageEl = document.getElementById('license-message'); + messageEl.style.display = 'block'; + messageEl.textContent = message; + + if (type === 'success') { + messageEl.style.background = '#d4edda'; + messageEl.style.color = '#155724'; + messageEl.style.border = '1px solid #c3e6cb'; + } else if (type === 'error') { + messageEl.style.background = '#f8d7da'; + messageEl.style.color = '#721c24'; + messageEl.style.border = '1px solid #f5c6cb'; + } else if (type === 'info') { + messageEl.style.background = '#d1ecf1'; + messageEl.style.color = '#0c5460'; + messageEl.style.border = '1px solid #bee5eb'; + } +} + +function validateStoredLicense(license) { + console.log("Validating stored license..."); + + // Pour l'instant, accepter simplement la licence stockée + // En production, faire une validation serveur + if (license && license.length > 5) { + console.log("License is valid"); + return true; + } + + // Si invalide, afficher le modal à nouveau + showLicenseModal(); + return false; +} + +function closeLicenseModal() { + const modal = document.getElementById('license-modal'); + if (modal) { + modal.remove(); + } +} + +function checkLicenseBeforePush() { + const license = getCookie(LICENSE_STORAGE_KEY); + + if (!license) { + alert('Veuillez d\'abord activer une licence'); + showLicenseModal(); + return false; + } + + // Vérifier si c'est une licence d'essai expirée + if (license.startsWith('TRIAL-')) { + // À implémenter : vérifier la date + } + + return true; +} + +// ============================================ +// FONCTIONS UTILITAIRES DE COOKIE +// ============================================ + +function setCookie(name, value, days) { + const d = new Date(); + d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); + const expires = "expires=" + d.toUTCString(); + document.cookie = name + "=" + encodeURIComponent(value) + ";" + expires + ";path=/"; + console.log("Cookie set: " + name); +} + +function getCookie(name) { + const nameEQ = name + "="; + const ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i].trim(); + if (c.indexOf(nameEQ) === 0) { + return decodeURIComponent(c.substring(nameEQ.length)); + } + } + return ""; +} + +function deleteCookie(name) { + setCookie(name, "", -1); + console.log("Cookie deleted: " + name); +} \ No newline at end of file diff --git a/apps/pusher_app_prem/bin/README b/apps/pusher_app_prem/bin/README new file mode 100644 index 00000000..9a70db09 --- /dev/null +++ b/apps/pusher_app_prem/bin/README @@ -0,0 +1 @@ +This is where you put any scripts you want to add to this app. diff --git a/apps/pusher_app_prem/bin/git_pusher.pid b/apps/pusher_app_prem/bin/git_pusher.pid new file mode 100644 index 00000000..81d2a971 --- /dev/null +++ b/apps/pusher_app_prem/bin/git_pusher.pid @@ -0,0 +1 @@ +919562 diff --git a/apps/pusher_app_prem/bin/git_pusher.py b/apps/pusher_app_prem/bin/git_pusher.py new file mode 100644 index 00000000..6f0c0ceb --- /dev/null +++ b/apps/pusher_app_prem/bin/git_pusher.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import os +import json +import logging +import tempfile +import shutil +import subprocess +from datetime import datetime +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.parse import parse_qs, urlparse + +# Configuration du logging +log_dir = '/opt/splunk/var/log/splunk' +os.makedirs(log_dir, exist_ok=True) + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(os.path.join(log_dir, 'git_pusher.log')), + logging.StreamHandler() + ] +) +logger = logging.getLogger('git_pusher') + + +class GitPusherRequestHandler(BaseHTTPRequestHandler): + """Handler pour les requêtes HTTP""" + + def do_OPTIONS(self): + """Traiter les requêtes OPTIONS (CORS preflight)""" + self.send_response(200) + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + self.end_headers() + + def do_POST(self): + """Traiter les requêtes POST""" + # Envoyer les headers CORS EN PREMIER + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + self.end_headers() + + try: + logger.info(f"POST request to {self.path}") + + # Parser l'URL et les paramètres + parsed_url = urlparse(self.path) + query_params = parse_qs(parsed_url.query) + + logger.info(f"Query params keys: {list(query_params.keys())}") + + # Extraire les paramètres + git_url = query_params.get('git_url', [''])[0] + git_branch = query_params.get('git_branch', ['main'])[0] + git_token = query_params.get('git_token', [''])[0] + commit_message = query_params.get('commit_message', [''])[0] + + # Accepter soit 'apps' soit 'dashboards' + apps_json = query_params.get('apps', query_params.get('dashboards', ['[]']))[0] + user = query_params.get('user', ['unknown'])[0] + + logger.info(f"Parameters received: git_url={git_url}, branch={git_branch}, user={user}") + logger.info(f"Raw apps_json: '{apps_json}'") + + # Parser les apps + try: + # parse_qs décode déjà, mais au cas où + if isinstance(apps_json, str): + apps = json.loads(apps_json) + else: + apps = apps_json + except (json.JSONDecodeError, TypeError) as e: + logger.error(f"JSON parse error: {e} - trying to parse: {apps_json}") + apps = [] + + logger.info(f"Parsed apps: {len(apps)} items - {apps}") + + # Valider + if not git_url or not git_token or not commit_message or not apps: + logger.warning(f"Validation failed: git_url={bool(git_url)}, git_token={bool(git_token)}, commit_message={bool(commit_message)}, apps={len(apps)}") + response = { + 'status': 'error', + 'message': 'Missing required parameters' + } + self.wfile.write(json.dumps(response).encode()) + return + + # Créer un répertoire temporaire + temp_dir = tempfile.mkdtemp(prefix='splunk_git_') + logger.info(f"Created temp directory: {temp_dir}") + + try: + # Préparer l'URL Git avec le token + git_url_with_token = self.prepare_git_url(git_url, git_token) + + logger.info(f"Git URL prepared (token inserted)") + logger.debug(f"Git URL with token: {git_url_with_token}") + logger.info("Cloning repository...") + self.clone_repository(temp_dir, git_url_with_token, git_branch) + + # Récupérer TOUTES les applications (dossiers complets) + logger.info("Fetching applications from Splunk...") + dashboard_contents = self.fetch_apps_directories(apps) + + # Créer le dossier apps + apps_dir = os.path.join(temp_dir, 'apps') + os.makedirs(apps_dir, exist_ok=True) + + # Copier les applications + logger.info("Copying applications to repository...") + for app_data in dashboard_contents: + app_name = app_data['name'] + app_path = app_data['path'] + dest_path = os.path.join(apps_dir, app_name) + + if os.path.exists(app_path): + logger.info(f"Copying app {app_name} from {app_path}") + # Supprimer le dossier s'il existe déjà + if os.path.exists(dest_path): + logger.info(f"Removing existing app directory: {dest_path}") + shutil.rmtree(dest_path) + # Copier le dossier + shutil.copytree(app_path, dest_path) + logger.info(f"Copied app: {app_name}") + else: + logger.warning(f"App path not found: {app_path}") + + # Configurer git + logger.info("Configuring git...") + subprocess.run(['git', 'config', 'user.email', 'splunk@splunk.local'], + cwd=temp_dir, capture_output=True) + subprocess.run(['git', 'config', 'user.name', 'Splunk Git Pusher'], + cwd=temp_dir, capture_output=True) + + # Commit et push + logger.info("Adding files...") + subprocess.run(['git', 'add', '-A'], cwd=temp_dir, capture_output=True) + + full_message = f"{commit_message}\n\nPushed by: {user}\nTimestamp: {datetime.now().isoformat()}" + logger.info("Committing...") + result = subprocess.run(['git', 'commit', '-m', full_message], + cwd=temp_dir, capture_output=True, text=True) + + if result.returncode != 0: + logger.warning(f"Commit may have failed or had no changes: {result.stderr}") + + logger.info("Pushing...") + result = subprocess.run(['git', 'push', 'origin', git_branch], + cwd=temp_dir, capture_output=True, text=True, timeout=60) + + if result.returncode != 0: + raise Exception(f"Push failed: {result.stderr}") + + logger.info("Push successful!") + response = { + 'status': 'success', + 'message': f'Successfully pushed {len(dashboard_contents)} dashboards from {len(apps)} application(s) to Git', + 'dashboards_pushed': len(dashboard_contents) + } + self.wfile.write(json.dumps(response).encode()) + + finally: + logger.info(f"Cleaning up {temp_dir}") + shutil.rmtree(temp_dir, ignore_errors=True) + + except Exception as e: + logger.error(f"Error: {str(e)}", exc_info=True) + response = { + 'status': 'error', + 'message': f'Error: {str(e)}' + } + self.wfile.write(json.dumps(response).encode()) + + def log_message(self, format, *args): + """Éviter les logs HTTP par défaut""" + logger.debug(format % args) + + @staticmethod + def prepare_git_url(git_url, token): + """Préparer l'URL Git avec le token inséré""" + logger.info(f"Preparing git URL with token") + + # Si l'URL contient déjà un token (format: https://user:token@host/repo) + # on le remplace + if '@' in git_url: + # Extraire la partie sans le token + protocol = git_url.split('://')[0] + rest = git_url.split('://', 1)[1] + host_and_path = rest.split('@', 1)[1] if '@' in rest else rest + return f"{protocol}://{token}@{host_and_path}" + + # Si l'URL est juste https://host/repo (sans credentials) + if git_url.startswith('https://') or git_url.startswith('http://'): + protocol = git_url.split('://')[0] + host_and_path = git_url.split('://', 1)[1] + # Insérer le token au format user:token@host ou juste token@host + return f"{protocol}://{token}@{host_and_path}" + + return git_url + + @staticmethod + def clone_repository(dest_dir, git_url, branch): + """Cloner le repository""" + try: + cmd = ['git', 'clone', '--depth', '1', '--branch', branch, git_url, dest_dir] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + + if result.returncode != 0: + raise Exception(f"Clone failed: {result.stderr}") + + logger.info("Repository cloned successfully") + except subprocess.TimeoutExpired: + raise Exception("Git clone operation timed out") + except FileNotFoundError: + raise Exception("Git is not installed on this system") + + @staticmethod + def fetch_apps_directories(apps): + """Récupérer les dossiers complets des applications""" + logger.info(f"Fetching directories for {len(apps)} applications") + + splunk_home = '/opt/splunk' + apps_base_path = os.path.join(splunk_home, 'etc', 'apps') + + app_directories = [] + + for app in apps: + app_id = app.get('id') or app.get('app_id') + app_path = os.path.join(apps_base_path, app_id) + + logger.info(f"Checking app directory: {app_path}") + + if os.path.isdir(app_path): + app_directories.append({ + 'name': app_id, + 'path': app_path, + 'size': sum(os.path.getsize(os.path.join(dirpath, filename)) + for dirpath, dirnames, filenames in os.walk(app_path) + for filename in filenames) + }) + logger.info(f"Found app: {app_id} at {app_path}") + else: + logger.warning(f"App directory not found: {app_path}") + + logger.info(f"Successfully found {len(app_directories)} application directories") + return app_directories + """Récupérer TOUS les dashboards de chaque application""" + logger.info(f"Fetching dashboards from {len(apps)} applications") + + import urllib.request + import urllib.error + import ssl + import base64 + + # Ignorer les certificats SSL auto-signés + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + dashboard_contents = [] + + # Lire le fichier de configuration Splunk pour obtenir les credentials + # Ou utiliser des credentials par défaut + splunk_username = os.environ.get('SPLUNK_USERNAME', 'admin') + splunk_password = os.environ.get('SPLUNK_PASSWORD', 'changeme') + + # Créer l'authentification Basic + credentials = base64.b64encode(f"{splunk_username}:{splunk_password}".encode()).decode() + + for app in apps: + app_id = app.get('id') or app.get('app_id') + logger.info(f"Fetching all dashboards from app: {app_id}") + + try: + # Récupérer la liste de TOUS les dashboards de cette app + api_url = f"https://127.0.0.1:8089/servicesNS/-/{app_id}/data/ui/views?output_mode=json&count=0" + + logger.debug(f"API URL: {api_url}") + + req = urllib.request.Request(api_url) + req.add_header('Authorization', f'Basic {credentials}') + + with urllib.request.urlopen(req, timeout=15, context=ssl_context) as response: + api_data = json.loads(response.read().decode('utf-8')) + + if 'entry' in api_data and len(api_data['entry']) > 0: + for entry in api_data['entry']: + try: + dashboard_id = entry.get('name') + content = entry.get('content', {}) + + # eai:data contient le XML complet du dashboard + dashboard_xml = content.get('eai:data', '') + + if dashboard_xml: + dashboard_contents.append({ + 'id': f"{app_id}_{dashboard_id}", + 'app': app_id, + 'content': dashboard_xml, + 'name': dashboard_id + }) + logger.debug(f"Fetched: {dashboard_id} from {app_id}") + except Exception as e: + logger.error(f"Error processing dashboard entry: {str(e)}") + + logger.info(f"Found {len([d for d in dashboard_contents if d['app'] == app_id])} dashboards in {app_id}") + else: + logger.warning(f"No dashboards found in app {app_id}") + + except urllib.error.HTTPError as e: + logger.error(f"HTTP {e.code} when fetching app {app_id}: {e.reason}") + except urllib.error.URLError as e: + logger.error(f"Cannot reach Splunk API for app {app_id}: {e.reason}") + except Exception as e: + logger.error(f"Error fetching dashboards from {app_id}: {str(e)}") + + logger.info(f"Successfully fetched {len(dashboard_contents)} dashboards total") + return dashboard_contents + + +def start_server(port=9999): + """Démarrer le serveur HTTP""" + server = HTTPServer(('0.0.0.0', port), GitPusherRequestHandler) + logger.info(f"Git Pusher server listening on 0.0.0.0:{port} (HTTP)") + server.serve_forever() + + +if __name__ == '__main__': + # Démarrer le serveur en background + port = 9999 + logger.info(f"Starting Git Pusher on port {port}") + start_server(port) \ No newline at end of file diff --git a/apps/pusher_app_prem/bin/license_generator.py b/apps/pusher_app_prem/bin/license_generator.py new file mode 100644 index 00000000..8a353809 --- /dev/null +++ b/apps/pusher_app_prem/bin/license_generator.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Git Pusher - License Generator +Génère des clés de licence basées sur le hostname Splunk +""" + +import hashlib +import hmac +import base64 +import socket +from datetime import datetime, timedelta +import json + +# Secret key pour générer les licences (À CHANGER !) +SECRET_KEY = "git_pusher_license_secret_2024" + +def get_hostname(): + """Récupérer le hostname du serveur""" + return socket.gethostname() + +def generate_license(hostname, days_valid=365, max_pushes=None): + """ + Générer une clé de licence + + Args: + hostname: nom d'hôte Splunk + days_valid: nombre de jours de validité + max_pushes: nombre maximum de pushes (None = illimité) + + Returns: + license_key: clé de licence formatée + """ + + # Créer la date d'expiration + expiration_date = datetime.now() + timedelta(days=days_valid) + expiration_str = expiration_date.strftime("%Y-%m-%d") + + # Créer le payload + payload = { + "hostname": hostname, + "expiration": expiration_str, + "max_pushes": max_pushes, + "issued": datetime.now().strftime("%Y-%m-%d") + } + + # Convertir en JSON et encoder en base64 + payload_json = json.dumps(payload, separators=(',', ':')) + payload_b64 = base64.b64encode(payload_json.encode()).decode() + + # Créer la signature HMAC + signature = hmac.new( + SECRET_KEY.encode(), + payload_b64.encode(), + hashlib.sha256 + ).hexdigest()[:16] # Prendre les 16 premiers caractères + + # Formater la clé de licence + license_key = f"{signature}-{payload_b64}" + + return license_key, payload + +def validate_license(license_key, hostname): + """ + Valider une clé de licence + + Args: + license_key: clé à valider + hostname: hostname Splunk actuel + + Returns: + dict: {valid: bool, error: str, expiration: str, max_pushes: int} + """ + + try: + # Séparer signature et payload + parts = license_key.split('-', 1) + if len(parts) != 2: + return { + 'valid': False, + 'error': 'Format de clé invalide' + } + + signature, payload_b64 = parts + + # Vérifier la signature + expected_signature = hmac.new( + SECRET_KEY.encode(), + payload_b64.encode(), + hashlib.sha256 + ).hexdigest()[:16] + + if signature != expected_signature: + return { + 'valid': False, + 'error': 'Signature invalide - clé corrompue ou falsifiée' + } + + # Décoder le payload + try: + payload_json = base64.b64decode(payload_b64).decode() + payload = json.loads(payload_json) + except Exception as e: + return { + 'valid': False, + 'error': f'Erreur de décodage: {str(e)}' + } + + # Vérifier le hostname + if payload.get('hostname') != hostname: + return { + 'valid': False, + 'error': f'Cette licence est pour {payload.get("hostname")}, pas {hostname}' + } + + # Vérifier l'expiration + expiration = datetime.strptime(payload.get('expiration'), '%Y-%m-%d') + if datetime.now() > expiration: + return { + 'valid': False, + 'error': f'Licence expirée le {payload.get("expiration")}' + } + + return { + 'valid': True, + 'expiration': payload.get('expiration'), + 'max_pushes': payload.get('max_pushes'), + 'days_remaining': (expiration - datetime.now()).days + } + + except Exception as e: + return { + 'valid': False, + 'error': f'Erreur de validation: {str(e)}' + } + +if __name__ == '__main__': + import sys + + hostname = get_hostname() + print("=" * 60) + print("Git Pusher - License Generator") + print("=" * 60) + print(f"\nHostname détecté: {hostname}") + + if len(sys.argv) > 1 and sys.argv[1] == 'validate': + # Mode validation + license_key = sys.argv[2] if len(sys.argv) > 2 else input("Entrez la clé de licence: ") + result = validate_license(license_key, hostname) + + print("\nRésultat de validation:") + print(json.dumps(result, indent=2, ensure_ascii=False)) + else: + # Mode génération + days = int(sys.argv[1]) if len(sys.argv) > 1 else 365 + max_pushes = int(sys.argv[2]) if len(sys.argv) > 2 else None + + license_key, payload = generate_license(hostname, days, max_pushes) + + print(f"\n📋 Payload:") + print(json.dumps(payload, indent=2, ensure_ascii=False)) + + print(f"\n🔑 Clé de licence générée:") + print(license_key) + + print(f"\n✓ Valide pour: {days} jours") + if max_pushes: + print(f"✓ Pushes limités à: {max_pushes}") + + # Tester la validation + print(f"\n✔️ Test de validation:") + result = validate_license(license_key, hostname) + print(json.dumps(result, indent=2, ensure_ascii=False)) \ No newline at end of file diff --git a/apps/pusher_app_prem/bin/start_git_pusher.sh b/apps/pusher_app_prem/bin/start_git_pusher.sh new file mode 100644 index 00000000..951e1a0e --- /dev/null +++ b/apps/pusher_app_prem/bin/start_git_pusher.sh @@ -0,0 +1,5 @@ +#!/bin/bash +export SPLUNK_USERNAME=admin +export SPLUNK_PASSWORD='2312Jocpam!?' +python3 /opt/splunk/etc/apps/pusher_app/bin/git_pusher.py > /opt/splunk/var/log/splunk/git_pusher_startup.log 2>&1 & +echo $! > /opt/splunk/etc/apps/pusher_app/bin/git_pusher.pid \ No newline at end of file diff --git a/apps/pusher_app_prem/certs/server.crt b/apps/pusher_app_prem/certs/server.crt new file mode 100644 index 00000000..11fa39a7 --- /dev/null +++ b/apps/pusher_app_prem/certs/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUCuKo8SLloS5cjBOR04+X6ayZ40cwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDEyMzIyMTIxOFoXDTI3MDEy +MzIyMTIxOFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAs0vF6sFTseKgZC1nZ6CVZdw45yk1Ni0W9Mc24KZ9NKCJ +rP0tHy0hs6mME/sq8DV1fh0YtqIvBCxcKEE84/cVXmUfZF9JRXO95734+JGPmo07 +zpiu7p3r4WyIWmCXX5VB0UkMEXsPQmonqG1Kwtz+R1cfgis2lUk+xsC2zSjER8l4 +2UODjHvtD25usgxKjpwPrCuZt43miArnVnwfB8OLbAqpwQeYIf18bPt/TrnQsdgd +ZZiQdE6UTaJ5xhqztwpYJO9pvZA24Bi3bGNfBciITds5RCGY2wQo8yxbeJsidTuW +7Z64DK9t33oVnB2PqlP6hVGD5Agthsv9ehRPxdd3MwIDAQABo1MwUTAdBgNVHQ4E +FgQUy0dni+ogqC7YuvfD/Pn0AuebsXQwHwYDVR0jBBgwFoAUy0dni+ogqC7YuvfD +/Pn0AuebsXQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAlyxg +vR15lsYp4TxJPi1WPzLZl1e6ewTl8GhyE1saxS8LRtyTyr8sa9EFRLQ0OIsqYrUw +zZi7FIDoDPZDKpd0/+U94UKlhUuPUyufQwl5vNu0A+SEpwKeznUMaj4Y98tHvVGd +1SCndZBWn/v2U4nXqHoTd6Y0xEOga0jUEsUMBckNC236BTo88Zk65/oa9Gncyb27 +9vGVCbmPyzE70H4KFoVtxkoZrKywn+0ajHhgH5gqZNRPWpe6i8xTbMAeIXkCjmWL +LmOA7MkjeQBBEWewu4vMOXsvf+gCtxUj5owsAcOQlZ3g72Sng4MeMjuVx4ZRVxX9 +fj+vCP9EFI8rX48tjQ== +-----END CERTIFICATE----- diff --git a/apps/pusher_app_prem/certs/server.key b/apps/pusher_app_prem/certs/server.key new file mode 100644 index 00000000..8b790585 --- /dev/null +++ b/apps/pusher_app_prem/certs/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCzS8XqwVOx4qBk +LWdnoJVl3DjnKTU2LRb0xzbgpn00oIms/S0fLSGzqYwT+yrwNXV+HRi2oi8ELFwo +QTzj9xVeZR9kX0lFc73nvfj4kY+ajTvOmK7unevhbIhaYJdflUHRSQwRew9Caieo +bUrC3P5HVx+CKzaVST7GwLbNKMRHyXjZQ4OMe+0Pbm6yDEqOnA+sK5m3jeaICudW +fB8Hw4tsCqnBB5gh/Xxs+39OudCx2B1lmJB0TpRNonnGGrO3Clgk72m9kDbgGLds +Y18FyIhN2zlEIZjbBCjzLFt4myJ1O5btnrgMr23fehWcHY+qU/qFUYPkCC2Gy/16 +FE/F13czAgMBAAECggEAMrEMrvej0xpQ4KHZp3nGY3sk9242JjAPWntsb42CvrtY +0XjvJe5bpfEcspWDqVBj/Jj7YL9v7Y0hLRxsu8Mi3oJWoskx7RnxKjES0CxPXpHp +w9p1Mu+hPiWyU2MVySdo6WPuro6NXOiod70WswtKNR9TwDi5gPGpdwYLaOvKusSp +Rncm0m0H3IBhgVA691X0AUIomAW3Wmh+5If1XHfjrNHTB8cjcNf6koPMkCqHCEZ9 +wtINxOJior+gGkjMXaDszqzNlicVBXFEFjaXWcp38xAif1uimpqKsRzZEF6RAUzi +H7cI3aF2dXG3C9l6Byi7OSgd8X4JUnE0dlCpC7qweQKBgQDgvoavo8G0kYruCUIQ +6vcSs1YBByOkl6yZBCZWk10NgRpU1wyu9zmlvEwNVlUfALs5eoLxnhe8Wklq0ckQ +r/Rl+r/lj/MZUFn49TgUCsUOIi/G7nWQG0bPo4bCB2QXsAiKdY+KZeC56620uyom +1VY+nS3y8O4EP0YHX0qHFfmIZwKBgQDMOywO0DSrZMDyvmbwL0ISzHRcNpRn0jk7 +pEtzM/VOx+v0O93E+5OygzmXlBKjF0MwMXBidf8IZu4xO8qWqAM4EP2DD0cpoS1Z +WiHHkc5NZhjgeG6C4XaCXR++7CuY25VKKe01yz/+j51linDD8OAibKUspkjVufEN +R/AT0GFLVQKBgBxMYTEkcXOHD/NA/yyaKVoVcrLWb0p+PqFVwG4OSB03MFWWbmZp +gry3pOvY/wbUVL68CljaCysQQ0ZL/AE55pAgrqD9KyL41xtd5R3A7WcGLvXheLQY +eyYR9RnhTF0fMTQd8WD/yvgeENU86+XP3vgrWmnIpG+sd+jdusifn7fpAn9QkwfO +0FX3SMjW/EegewSWZhOCTgY+77Gk1izuRpGBg16T/QqBrL+Yri0KoGC593OKj/bG +4ca8id9vjSdgSOj8NbfO/TgWNICvv9+T3PKHlsA5z0nKWSloRVVA/ew1YmyD1gbA +MnAM/pwac4QJyf6jljmUZAZYTAPOOZN+PbglAoGBAJ9cOGDgT+BCOoNc0T1GJDAk +xOR8d+tD+j4JH5IVxB51DXjJOZxw9U3XhNH1OcE0x3fRzKJOtlQLxP6fHYVtMVFq +VpeekmTtJ9OfMg68ELOlf7ykA3GhMJ3FarM6e8+X+KliGf6ND4HBMb112FlMgIi6 +yYi7sfSL53Dzp1Q2DxXV +-----END PRIVATE KEY----- diff --git a/apps/pusher_app_prem/default/app.conf b/apps/pusher_app_prem/default/app.conf new file mode 100644 index 00000000..a01a048d --- /dev/null +++ b/apps/pusher_app_prem/default/app.conf @@ -0,0 +1,16 @@ +# +# Splunk app configuration file +# + +[install] +is_configured = 0 + +[ui] +is_visible = true +label = Pusher Premium + +[launcher] +author = +description = +version = 1.0.0 + diff --git a/apps/pusher_app_prem/default/data/ui/nav/default.xml b/apps/pusher_app_prem/default/data/ui/nav/default.xml new file mode 100644 index 00000000..8e98eadb --- /dev/null +++ b/apps/pusher_app_prem/default/data/ui/nav/default.xml @@ -0,0 +1,8 @@ + diff --git a/apps/pusher_app_prem/default/data/ui/views/README b/apps/pusher_app_prem/default/data/ui/views/README new file mode 100644 index 00000000..6cf74f0b --- /dev/null +++ b/apps/pusher_app_prem/default/data/ui/views/README @@ -0,0 +1 @@ +Add all the views that your app needs in this directory diff --git a/apps/pusher_app_prem/local/app.conf b/apps/pusher_app_prem/local/app.conf new file mode 100644 index 00000000..78666a91 --- /dev/null +++ b/apps/pusher_app_prem/local/app.conf @@ -0,0 +1,3 @@ +[ui] + +[launcher] diff --git a/apps/pusher_app_prem/local/data/ui/views/git_pusher_-_deploy_applications.xml b/apps/pusher_app_prem/local/data/ui/views/git_pusher_-_deploy_applications.xml new file mode 100644 index 00000000..292fbe8f --- /dev/null +++ b/apps/pusher_app_prem/local/data/ui/views/git_pusher_-_deploy_applications.xml @@ -0,0 +1,493 @@ + + + + Modern interface to push Splunk applications to Git repository + + + + + +
+ +
+
+ + + | rest /services/apps/local | search disabled=0 | fields name, label, description | sort label + -4h@h + now + + + + + + + +
+

🚀 Git Pusher

+

Deploy your Splunk applications to Git with confidence

+
+ +
+
+ ✨ Configure your Git settings below and select the applications you want to deploy to your repository +
+ +
+ +
+
⚙️ Configuration
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+
📦 Applications
+ +
+
+
+

Loading applications...

+
+
+
+
+ +
+
+
Deploying applications to Git... Please wait
+
+ +
+ ✅ Applications successfully deployed to Git! +
+ +
+ ❌ Error occurred while deploying applications +
+ + +
+ +
+
+ +
\ No newline at end of file diff --git a/apps/pusher_app_prem/local/data/ui/views/git_pusher_-_push_applications_to_git.xml b/apps/pusher_app_prem/local/data/ui/views/git_pusher_-_push_applications_to_git.xml new file mode 100644 index 00000000..eca8b17e --- /dev/null +++ b/apps/pusher_app_prem/local/data/ui/views/git_pusher_-_push_applications_to_git.xml @@ -0,0 +1,256 @@ + + + + Push Splunk applications to Git repository + + + | rest /services/apps/local | search disabled=0 | fields name, label, description | sort label + -4h@h + now + + + + Configuration & Application Selection + + + +
+
+ ℹ️ Configure your Git settings and select the applications you want to push to your repository. +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+ Loading applications... +
+
+ Select one or more applications to push +
+ +
+ + +
+ +
+ + +
+ +
+
+ Pushing dashboards to Git... +
+ +
+ ✓ Dashboards successfully pushed to Git! +
+ +
+ ✗ Error occurred while pushing dashboards +
+
+ + + + + + + Push History + + + index=_internal source=*git_pusher* action=push_attempt | table _time, user, dashboards, commit_message, status, error_msg | reverse | rename _time as "Timestamp", user as "User", dashboards as "Dashboards", commit_message as "Message", status as "Status", error_msg as "Error" | head 20 + -30d@d + now + + + + {"success": "#28a745", "error": "#dc3545", "pending": "#ffc107"} + +
+
+
+ + \ No newline at end of file diff --git a/apps/pusher_app_prem/metadata/default.meta b/apps/pusher_app_prem/metadata/default.meta new file mode 100644 index 00000000..b77b8cb9 --- /dev/null +++ b/apps/pusher_app_prem/metadata/default.meta @@ -0,0 +1,35 @@ + +# Application-level permissions + +[] +access = read : [ * ], write : [ admin, power ] + +### EVENT TYPES + +[eventtypes] +export = system + + +### PROPS + +[props] +export = system + + +### TRANSFORMS + +[transforms] +export = system + + +### LOOKUPS + +[lookups] +export = system + + +### VIEWSTATES: even normal users should be able to create shared viewstates + +[viewstates] +access = read : [ * ], write : [ * ] +export = system diff --git a/apps/pusher_app_prem/metadata/local.meta b/apps/pusher_app_prem/metadata/local.meta new file mode 100644 index 00000000..d8e71ee3 --- /dev/null +++ b/apps/pusher_app_prem/metadata/local.meta @@ -0,0 +1,26 @@ +[app/ui] +version = 10.0.2 +modtime = 1769115948.043388000 + +[app/launcher] +version = 10.0.2 +modtime = 1769115948.046389000 + +[views/git_pusher_-_push_dashboards_to_git] +access = read : [ admin ], write : [ admin ] +export = system +owner = admin +version = 10.0.2 +modtime = 1769276443.812957000 + +[views/git_pusher_-_push_applications_to_git] +owner = admin +version = 10.0.2 +modtime = 1769361925.808816000 + +[views/git_pusher_-_deploy_applications] +access = read : [ * ], write : [ * ] +export = none +owner = admin +version = 10.0.2 +modtime = 1769373002.573917000