From a7c906e576fafca4bd7cab253f9356a6f2ed03f5 Mon Sep 17 00:00:00 2001 From: Splunk Git Pusher Date: Sun, 1 Feb 2026 00:05:57 +0100 Subject: [PATCH] Git_pusher with file licence Pushed by: admin License: 1CFBBDCA-31F (Starter) Timestamp: 2026-02-01T00:05:57.898363 --- .../appserver/static/git_pusher.js | 987 +++++++++--------- .../static/license_file_management.js | 613 +++++++++++ .../appserver/static/license_validation.js | 878 ++++++++++------ apps/pusher_app_prem/bin/README | 0 .../license_validator.cpython-39.pyc | Bin 0 -> 12190 bytes apps/pusher_app_prem/bin/git_pusher.pid | 2 +- apps/pusher_app_prem/bin/git_pusher.py | 374 ++++--- apps/pusher_app_prem/bin/license_endpoints.py | 191 ++++ apps/pusher_app_prem/bin/license_generator.py | 173 --- apps/pusher_app_prem/bin/license_validator.py | 485 +++++++++ apps/pusher_app_prem/bin/start_git_pusher.sh | 252 ++++- apps/pusher_app_prem/certs/server.crt | 0 apps/pusher_app_prem/certs/server.key | 0 apps/pusher_app_prem/default/app.conf | 0 .../default/data/ui/nav/default.xml | 0 .../default/data/ui/views/README | 0 apps/pusher_app_prem/local/app.conf | 0 .../git_pusher_-_deploy_applications.xml | 2 +- .../git_pusher_-_push_applications_to_git.xml | 0 .../git_pusher_-_push_dashboards_to_git.xml | 0 apps/pusher_app_prem/local/usage_stats.json | 1 + apps/pusher_app_prem/metadata/default.meta | 0 apps/pusher_app_prem/metadata/local.meta | 2 +- 23 files changed, 2817 insertions(+), 1143 deletions(-) mode change 100644 => 100755 apps/pusher_app_prem/appserver/static/git_pusher.js create mode 100755 apps/pusher_app_prem/appserver/static/license_file_management.js mode change 100644 => 100755 apps/pusher_app_prem/appserver/static/license_validation.js mode change 100644 => 100755 apps/pusher_app_prem/bin/README create mode 100644 apps/pusher_app_prem/bin/__pycache__/license_validator.cpython-39.pyc mode change 100644 => 100755 apps/pusher_app_prem/bin/git_pusher.py create mode 100755 apps/pusher_app_prem/bin/license_endpoints.py delete mode 100644 apps/pusher_app_prem/bin/license_generator.py create mode 100755 apps/pusher_app_prem/bin/license_validator.py mode change 100644 => 100755 apps/pusher_app_prem/bin/start_git_pusher.sh mode change 100644 => 100755 apps/pusher_app_prem/certs/server.crt mode change 100644 => 100755 apps/pusher_app_prem/certs/server.key mode change 100644 => 100755 apps/pusher_app_prem/default/app.conf mode change 100644 => 100755 apps/pusher_app_prem/default/data/ui/nav/default.xml mode change 100644 => 100755 apps/pusher_app_prem/default/data/ui/views/README mode change 100644 => 100755 apps/pusher_app_prem/local/app.conf mode change 100644 => 100755 apps/pusher_app_prem/local/data/ui/views/git_pusher_-_push_applications_to_git.xml mode change 100644 => 100755 apps/pusher_app_prem/local/data/ui/views/git_pusher_-_push_dashboards_to_git.xml create mode 100644 apps/pusher_app_prem/local/usage_stats.json mode change 100644 => 100755 apps/pusher_app_prem/metadata/default.meta diff --git a/apps/pusher_app_prem/appserver/static/git_pusher.js b/apps/pusher_app_prem/appserver/static/git_pusher.js old mode 100644 new mode 100755 index b65c4ae9..977b7fef --- a/apps/pusher_app_prem/appserver/static/git_pusher.js +++ b/apps/pusher_app_prem/appserver/static/git_pusher.js @@ -1,565 +1,544 @@ // ============================================ -// CHARGER LES DASHBOARDS DYNAMIQUEMENT +// GIT PUSHER - MAIN JAVASCRIPT +// Version 2.0 avec système de licence par fichier // ============================================ -// 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(); - }); -} +// Configuration +const GIT_PUSHER_CONFIG = { + serverUrl: window.location.protocol + '//' + window.location.hostname + ':9999', + credentialsKey: 'git_pusher_credentials', + version: '2.0.0' +}; -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; - } +// État global +let selectedApps = []; +let isProcessing = false; - if (!apps || apps.length === 0) { - showAppsEmpty(); - return; - } - - let html = '
'; - html += 'Select Applications'; - html += ''; - html += '
'; +// ============================================ +// INITIALISATION +// ============================================ - apps.forEach((app, index) => { - const checkboxId = 'app-' + index; - html += '
'; - html += ''; - html += ''; - html += '
'; - }); + + // Charger les credentials sauvegardés + loadSavedCredentials(); + + // Récupérer les résultats de recherche pour les apps + const searchManager = mvc.Components.get('dsearch'); + + if (searchManager) { + searchManager.on('search:done', function() { + const results = searchManager.data('results'); + if (results) { + results.on('data', function() { + const rows = results.data().rows; + const fields = results.data().fields; + renderAppsList(rows, fields); + }); + } + }); + } + + // Exposer les fonctions globalement + window.pushDashboards = pushDashboards; + window.resetForm = resetForm; + window.toggleSelectAll = toggleSelectAll; +}); + +// Attacher l'événement au bouton après chargement du DOM +document.addEventListener('DOMContentLoaded', function() { + setTimeout(function() { + var pushBtn = document.getElementById('push-btn'); + if (pushBtn) { + pushBtn.onclick = function() { + if (typeof pushDashboards === 'function') { + pushDashboards(); + } else if (typeof window.pushDashboards === 'function') { + window.pushDashboards(); + } + }; + console.log("Push button event attached"); + } + }, 2000); // Attendre 2 secondes que tout soit chargé +}); - container.innerHTML = html; - console.log('Successfully populated ' + apps.length + ' apps'); - - // Ajouter les event listeners - addCheckboxListeners(); -} +// ============================================ +// RENDU DE LA LISTE DES APPLICATIONS +// ============================================ -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 renderAppsList(rows, fields) { + const container = document.getElementById('dashboard-list'); + if (!container) return; + + console.log("Fields:", fields); + console.log("First row:", rows[0]); + + // Trouver les index des colonnes + const nameIdx = fields.indexOf('name'); + const labelIdx = fields.indexOf('label'); + const descIdx = fields.indexOf('description'); + + console.log("Index - name:", nameIdx, "label:", labelIdx); + + // Générer le HTML + let html = ` +
+
+ + +
+ ${rows.length} apps +
+ `; + + let validApps = 0; + + rows.forEach((row, index) => { + // Récupérer le nom - essayer plusieurs méthodes + let name = ''; + if (nameIdx >= 0 && row[nameIdx]) { + name = row[nameIdx]; + } else if (labelIdx >= 0 && row[labelIdx]) { + // Si name est null, utiliser label comme fallback temporaire + // On va chercher le vrai nom via l'API + name = row[labelIdx]; + } else if (row[0]) { + name = row[0]; + } else if (row[1]) { + name = row[1]; // Deuxième élément si premier est null + } + + const label = (labelIdx >= 0 && row[labelIdx]) ? row[labelIdx] : name; + + console.log(`Row ${index}: name="${name}", label="${label}"`); + + // Ignorer si pas de nom ou apps système + if (!name || name.startsWith('splunk_') || name === 'learned' || name === 'launcher') { + return; + } + + validApps++; + + html += ` +
+ + +
+ `; }); - }); -} - -function showAppsEmpty() { - const container = document.getElementById('dashboard-list'); - if (container) { - container.innerHTML = '
No apps found
'; - } - console.log("Displayed empty state"); + + container.innerHTML = html; + + console.log(`Rendered ${validApps} valid applications out of ${rows.length}`); } -// Attendre que le DOM soit chargé -function initScript() { - console.log("initScript called"); - - // INITIALISER LA LICENCE EN PREMIER - initializeLicense(); +// ============================================ +// GESTION DE LA SÉLECTION +// ============================================ - // 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(); +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') + }); + } }); - } 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(); - } + + // 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; + } + + console.log(`Selected ${selectedApps.length} apps`); +} + +function toggleSelectAll(checked) { + const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app-id]'); + checkboxes.forEach(cb => { + cb.checked = checked; }); - } + updateSelectedApps(); } -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"); +// ============================================ +// 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; + } + + // Récupérer les apps sélectionnées directement depuis le DOM + var apps = []; + var checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app-id]:checked'); + checkboxes.forEach(function(cb) { + apps.push({ + id: cb.getAttribute('data-app-id'), + label: cb.getAttribute('data-app-label') || cb.getAttribute('data-app-id') + }); + }); + + console.log("Apps selected from DOM:", apps); + + // Mettre à jour selectedApps + selectedApps = apps; + + // Vérifier si déjà en cours + if (isProcessing) { + console.log("Push already in progress"); + return; } - if (savedToken) { - document.getElementById('git-token').value = decodeURIComponent(savedToken); - console.log("Loaded saved token from cookie"); + // Vérifier la licence AVANT tout + if (typeof checkLicenseBeforePush === 'function') { + const licenseOk = await checkLicenseBeforePush(); + if (!licenseOk) { + console.log("License check failed"); + return; + } } - if (savedBranch) { - document.getElementById('git-branch').value = decodeURIComponent(savedBranch); + // 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; + + // Mettre à jour la liste des apps sélectionnées + updateSelectedApps(); + + // Validation + if (!gitUrl) { + showMessage('error', 'Please enter a Git repository URL'); + return; } - if (savedUrl && savedToken) { - document.getElementById('save-credentials').checked = true; + if (!gitToken) { + showMessage('error', 'Please enter a Git token or password'); + return; } - } 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"); + + 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); + } + + // Démarrer le push + isProcessing = true; + showLoading(true); + hideMessages(); + + try { + // Récupérer l'utilisateur courant + const currentUser = await getCurrentUser(); + + // Construire les paramètres + const params = new URLSearchParams({ + git_url: gitUrl, + git_branch: gitBranch, + git_token: gitToken, + commit_message: commitMessage, + apps: JSON.stringify(selectedApps), + user: currentUser + }); + + console.log(`Pushing ${selectedApps.length} apps to ${gitUrl}`); + + // 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') { + showMessage('success', `✅ Successfully deployed ${result.apps_pushed || selectedApps.length} application(s) to Git!`); + + // 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); } - } 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); -} +// ============================================ +// UTILITAIRES UI +// ============================================ -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); +function showLoading(show) { + const loading = document.getElementById('loading'); + const pushBtn = document.getElementById('push-btn'); + + if (loading) { + loading.classList.toggle('active', show); + } + + if (pushBtn) { + pushBtn.disabled = show; + pushBtn.textContent = show ? '⏳ Deploying...' : '✈️ Deploy to Git'; } - } - return ""; } -function deleteCookie(name) { - setCookie(name, "", -1); - console.log("Cookie deleted: " + name); +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'); + } } -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 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 getFormKeyValue() { - try { - // Chercher dans les meta tags du DOM - const metaTag = document.querySelector('meta[name="splunk_form_key"]'); - if (metaTag) { - return metaTag.getAttribute('content'); - } +function resetForm(clearCredentials = false) { + // Reset les champs + const commitMessage = document.getElementById('commit-message'); + if (commitMessage) commitMessage.value = ''; - // 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); - } + 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); } - console.warn("Could not find form key, proceeding without it"); - return ''; - } catch (e) { - console.error("Error getting form key:", e); - return ''; - } + // Reset la sélection + toggleSelectAll(false); + + // Cacher les messages + hideMessages(); + + console.log("Form reset" + (clearCredentials ? " (with credentials)" : "")); } -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(); +// ============================================ +// 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); } - }) - .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"); -} +// ============================================ +// RÉCUPÉRATION DE L'UTILISATEUR SPLUNK +// ============================================ -function toggleSelectAll(checkbox) { - const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app]'); - checkboxes.forEach(cb => cb.checked = checkbox.checked); +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'; + } } // ============================================ -// POUSSER LES DASHBOARDS VERS GIT +// VÉRIFICATION DU SERVEUR // ============================================ -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) { +async function checkServerHealth() { 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); + 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; } - } - - // 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(); +// 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"); } - - // Fallback: chercher dans le DOM ou retourner 'unknown' - return 'unknown_user'; - } catch (e) { - console.warn("Could not get current user:", e); - return 'unknown_user'; - } -} +}, 1000); -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); -} +// ============================================ +// EXPORT POUR DEBUG +// ============================================ -function showError(message) { - const errorMsg = document.getElementById('error-msg'); - document.getElementById('error-text').textContent = 'X ' + message; - errorMsg.style.display = 'block'; -} +window.GitPusher = { + config: GIT_PUSHER_CONFIG, + getSelectedApps: () => selectedApps, + checkServer: checkServerHealth, + version: GIT_PUSHER_CONFIG.version +}; -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; +// ============================================ +// FIX: Attacher les événements aux boutons +// ============================================ +(function attachButtonEvents() { + function tryAttach() { + var pushBtn = document.getElementById('push-btn'); + if (pushBtn) { + pushBtn.addEventListener('click', function(e) { + e.preventDefault(); + console.log("Push button clicked"); + pushDashboards(); + }); + console.log("✓ Push button event attached"); + } else { + // Réessayer dans 500ms + setTimeout(tryAttach, 500); + } + } + + // Démarrer après un délai + if (document.readyState === 'complete') { + setTimeout(tryAttach, 1000); + } else { + window.addEventListener('load', function() { + setTimeout(tryAttach, 1000); + }); } - } -} \ No newline at end of file +})(); \ No newline at end of file diff --git a/apps/pusher_app_prem/appserver/static/license_file_management.js b/apps/pusher_app_prem/appserver/static/license_file_management.js new file mode 100755 index 00000000..f59a6676 --- /dev/null +++ b/apps/pusher_app_prem/appserver/static/license_file_management.js @@ -0,0 +1,613 @@ +// ============================================ +// SYSTÈME DE GESTION DE LICENCE FICHIER .LIC +// ============================================ + +const LICENSE_FILE_KEY = 'git_pusher_license_file'; +const LICENSE_STATUS_ENDPOINT = '/custom/git_pusher/license_status'; + +let currentLicenseStatus = null; + +function initializeLicenseSystem() { + console.log("Initializing file-based license system..."); + + // Vérifier le statut de la licence + checkLicenseStatus(); +} + +function checkLicenseStatus() { + console.log("Checking license status..."); + + // Appeler le backend pour vérifier la licence + fetch(LICENSE_STATUS_ENDPOINT) + .then(response => response.json()) + .then(data => { + console.log("License status:", data); + currentLicenseStatus = data; + + if (data.licensed) { + // Licence valide + displayLicenseBadge(data.license_info, data.warnings); + } else { + // Pas de licence ou invalide + showLicenseUploadModal(data.errors); + } + }) + .catch(error => { + console.error("Error checking license:", error); + // En cas d'erreur, afficher le modal + showLicenseUploadModal(["Impossible de vérifier la licence"]); + }); +} + +function displayLicenseBadge(licenseInfo, warnings) { + console.log("Displaying license 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; + `; + + // Déterminer le texte et la couleur selon le type + let badgeText = '✓ Licence Activée'; + let badgeColor = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'; + + if (licenseInfo.type === 'trial') { + badgeText = `⏱️ Essai: ${licenseInfo.days_remaining} jours`; + badgeColor = 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)'; + } else if (licenseInfo.type === 'standard') { + badgeText = `✓ Standard (${licenseInfo.days_remaining}j)`; + } else if (licenseInfo.type === 'enterprise') { + badgeText = `✓ Enterprise (${licenseInfo.days_remaining}j)`; + badgeColor = 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)'; + } + + // Avertissement si expire bientôt + if (licenseInfo.days_remaining < 30) { + badgeColor = 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)'; + badgeText = `⚠️ Expire dans ${licenseInfo.days_remaining}j`; + } + + badge.style.background = badgeColor; + badge.textContent = badgeText; + + // Clic pour afficher les détails + badge.onclick = function() { + showLicenseDetailsModal(licenseInfo, warnings); + }; + + container.appendChild(badge); + + // 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 showLicenseUploadModal(errors = []) { + console.log("Showing license upload modal"); + + // Créer le modal + 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.8); + 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: 550px; + width: 90%; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + animation: slideIn 0.3s ease; + `; + + let errorsHtml = ''; + if (errors.length > 0) { + errorsHtml = ` +
+

+ ⚠️ Problèmes détectés: +

+ +
+ `; + } + + content.innerHTML = ` +
+

🔐 Git Pusher

+

Activation de licence requise

+
+ + ${errorsHtml} + +
+

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

+
+ +
+ + +
+
📄
+

+ Glissez votre fichier .lic ici +

+

+ ou cliquez pour parcourir +

+ +
+ + + + + 💡 Vous n'avez pas de licence? Contactez-nous pour obtenir votre fichier .lic personnalisé + +
+ + + +
+ + + +
+ `; + + modal.appendChild(content); + document.body.appendChild(modal); + + // Ajouter l'animation CSS + const style = document.createElement('style'); + style.textContent = ` + @keyframes slideIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + `; + document.head.appendChild(style); + + // Récupérer et afficher le hostname + getHostname().then(hostname => { + document.getElementById('detected-hostname').textContent = hostname; + }); + + // Configurer le drag & drop et file input + setupFileUpload(); +} + +function setupFileUpload() { + const dropZone = document.getElementById('drop-zone'); + const fileInput = document.getElementById('license-file-input'); + const fileInfo = document.getElementById('file-info'); + const uploadBtn = document.getElementById('upload-btn'); + + let selectedFile = null; + + // Clic sur la zone pour ouvrir le sélecteur + dropZone.onclick = () => fileInput.click(); + + // Hover effect + dropZone.addEventListener('mouseenter', function() { + this.style.background = '#eef1ff'; + this.style.borderColor = '#5568d3'; + }); + + dropZone.addEventListener('mouseleave', function() { + this.style.background = '#f8f9ff'; + this.style.borderColor = '#667eea'; + }); + + // Drag & drop + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.style.background = '#e3e7ff'; + dropZone.style.borderColor = '#5568d3'; + }); + + dropZone.addEventListener('dragleave', () => { + dropZone.style.background = '#f8f9ff'; + dropZone.style.borderColor = '#667eea'; + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.style.background = '#f8f9ff'; + dropZone.style.borderColor = '#667eea'; + + const files = e.dataTransfer.files; + if (files.length > 0) { + handleFileSelection(files[0]); + } + }); + + // Sélection de fichier + fileInput.addEventListener('change', (e) => { + if (e.target.files.length > 0) { + handleFileSelection(e.target.files[0]); + } + }); + + function handleFileSelection(file) { + selectedFile = file; + + // Vérifier l'extension + if (!file.name.endsWith('.lic')) { + showLicenseMessage('⚠️ Veuillez sélectionner un fichier .lic', 'warning'); + return; + } + + // Afficher les infos du fichier + document.getElementById('file-name').textContent = file.name; + document.getElementById('file-size').textContent = formatFileSize(file.size); + fileInfo.style.display = 'block'; + + // Activer le bouton + uploadBtn.disabled = false; + uploadBtn.style.cursor = 'pointer'; + uploadBtn.style.opacity = '1'; + + // Stocker le fichier pour l'upload + window.selectedLicenseFile = file; + + showLicenseMessage('✓ Fichier prêt à être installé', 'success'); + } +} + +function formatFileSize(bytes) { + if (bytes < 1024) return bytes + ' B'; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; + return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; +} + +function uploadLicenseFile() { + const file = window.selectedLicenseFile; + + if (!file) { + showLicenseMessage('❌ Aucun fichier sélectionné', 'error'); + return; + } + + showLicenseMessage('⏳ Installation de la licence...', 'info'); + + // Lire le fichier + const reader = new FileReader(); + + reader.onload = function(e) { + const fileContent = e.target.result; + + // Envoyer au backend pour validation et installation + fetch('/custom/git_pusher/install_license', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + license_file: fileContent, + filename: file.name + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showLicenseMessage('✅ Licence installée avec succès !', 'success'); + + setTimeout(() => { + closeLicenseModal(); + // Recharger pour afficher le badge + checkLicenseStatus(); + }, 1500); + } else { + showLicenseMessage('❌ ' + (data.message || 'Erreur d\'installation'), 'error'); + + if (data.errors && data.errors.length > 0) { + const errorList = data.errors.map(e => `• ${e}`).join('\n'); + console.error('License errors:', errorList); + } + } + }) + .catch(error => { + console.error('Upload error:', error); + showLicenseMessage('❌ Erreur de connexion au serveur', 'error'); + }); + }; + + reader.onerror = function() { + showLicenseMessage('❌ Erreur de lecture du fichier', 'error'); + }; + + reader.readAsText(file); +} + +function requestTrial() { + showLicenseMessage('⏳ Demande d\'essai en cours...', 'info'); + + // Appeler le backend pour créer une licence d'essai + fetch('/custom/git_pusher/request_trial', { + method: 'POST' + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showLicenseMessage('✅ Licence d\'essai activée (7 jours) !', 'success'); + + setTimeout(() => { + closeLicenseModal(); + checkLicenseStatus(); + }, 1500); + } else { + showLicenseMessage('❌ ' + (data.message || 'Erreur'), 'error'); + } + }) + .catch(error => { + console.error('Trial request error:', error); + showLicenseMessage('❌ Erreur de connexion', 'error'); + }); +} + +function showLicenseDetailsModal(licenseInfo, warnings) { + // Créer un modal pour afficher les détails de la licence + const modal = document.createElement('div'); + modal.id = 'license-details-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: 10001; + 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.5); + `; + + let warningsHtml = ''; + if (warnings && warnings.length > 0) { + warningsHtml = ` +
+ ${warnings.map(w => `

${w}

`).join('')} +
+ `; + } + + const featuresHtml = Object.entries(licenseInfo.features || {}) + .map(([feature, enabled]) => { + const icon = enabled ? '✅' : '❌'; + return `
${icon} ${feature}
`; + }).join(''); + + content.innerHTML = ` +
+

🔐 Informations de licence

+
+ +
+
+ 🆔 ID de licence: +
${licenseInfo.license_id}
+
+ +
+ 👤 Client: +
${licenseInfo.customer}
+
+ +
+ 🏷️ Type: +
${licenseInfo.type.toUpperCase()}
+
+ +
+ 📅 Expire le: +
${new Date(licenseInfo.expires).toLocaleDateString('fr-FR')}
+
+ +
+ ⏰ Jours restants: +
${licenseInfo.days_remaining} jours
+
+
+ +
+ ✨ Fonctionnalités: +
+ ${featuresHtml} +
+
+ + ${warningsHtml} + +
+ + + +
+ `; + + modal.appendChild(content); + document.body.appendChild(modal); +} + +function closeDetailsModal() { + const modal = document.getElementById('license-details-modal'); + if (modal) { + modal.remove(); + } +} + +function showLicenseMessage(message, type) { + const messageEl = document.getElementById('license-message'); + if (!messageEl) return; + + 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 === 'warning') { + messageEl.style.background = '#fff3cd'; + messageEl.style.color = '#856404'; + messageEl.style.border = '1px solid #ffeaa7'; + } else if (type === 'info') { + messageEl.style.background = '#d1ecf1'; + messageEl.style.color = '#0c5460'; + messageEl.style.border = '1px solid #bee5eb'; + } +} + +function closeLicenseModal() { + const modal = document.getElementById('license-modal'); + if (modal) { + modal.remove(); + } +} + +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 checkLicenseBeforePush() { + if (!currentLicenseStatus || !currentLicenseStatus.licensed) { + alert('⚠️ Aucune licence valide détectée. Veuillez installer une licence.'); + showLicenseUploadModal([]); + return false; + } + + return true; +} + +// Initialiser au chargement de la page +document.addEventListener('DOMContentLoaded', function() { + initializeLicenseSystem(); +}); diff --git a/apps/pusher_app_prem/appserver/static/license_validation.js b/apps/pusher_app_prem/appserver/static/license_validation.js old mode 100644 new mode 100755 index b668a4a8..ad0093db --- a/apps/pusher_app_prem/appserver/static/license_validation.js +++ b/apps/pusher_app_prem/appserver/static/license_validation.js @@ -1,368 +1,598 @@ // ============================================ -// SYSTÈME DE VALIDATION DE LICENCE +// SYSTÈME DE VALIDATION DE LICENCE - VERSION 2.0 +// Avec support fichier .lic // ============================================ -const LICENSE_STORAGE_KEY = 'git_pusher_license'; +const LICENSE_API_URL = window.location.protocol + '//' + window.location.hostname + ':9999'; -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(); - } +// État global de la licence +let currentLicense = null; +let currentHostname = null; + +// ============================================ +// INITIALISATION +// ============================================ + +async function initializeLicense() { + console.log("Initializing license system v2.0..."); + + try { + // Récupérer le statut de la licence depuis le serveur + const response = await fetch(`${LICENSE_API_URL}/license/status`); + const data = await response.json(); + + console.log("License status:", data); + + currentHostname = data.hostname; + + if (data.status === 'valid' && data.license) { + // Licence valide + currentLicense = data.license; + displayLicenseInfo(data.license); + hideLicenseModal(); + } else { + // Pas de licence ou licence invalide + showLicenseModal(data.error, data.error_code, data.hostname); + } + } catch (error) { + console.error("Error checking license:", error); + // En cas d'erreur réseau, afficher le modal + showLicenseModal("Impossible de vérifier la licence. Le serveur est-il démarré?", "CONNECTION_ERROR"); + } } +// ============================================ +// AFFICHAGE DU BADGE DE LICENCE +// ============================================ + 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); + console.log("Displaying license info:", license); + + const container = document.getElementById('license-badge-container'); + if (!container) { + console.error("license-badge-container not found"); + return; + } + + // Supprimer l'ancien badge s'il existe + const oldBadge = document.getElementById('license-badge'); + if (oldBadge) oldBadge.remove(); + + // Créer le badge + const badge = document.createElement('div'); + badge.id = 'license-badge'; + + // Déterminer le style selon le type et les jours restants + let badgeStyle = ''; + let badgeText = ''; + let badgeIcon = '✓'; + + const daysRemaining = license.days_remaining || 0; + const licenseType = license.type_name || license.type || 'Unknown'; 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%)'; + // Expirée + badgeStyle = 'background: linear-gradient(135deg, #f44336 0%, #da190b 100%);'; + badgeText = '⚠ Licence expirée'; + badgeIcon = '⚠'; + } else if (daysRemaining <= 7) { + // Expire bientôt + badgeStyle = 'background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);'; + badgeText = `⚠ ${licenseType} - ${daysRemaining}j restants`; + badgeIcon = '⚠'; + } else if (license.type === 'trial') { + // Essai + badgeStyle = 'background: linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%);'; + badgeText = `⏱ Essai - ${daysRemaining}j restants`; + badgeIcon = '⏱'; } else { - badgeText = `⏱️ Essai: ${daysRemaining} jours`; + // Licence normale valide + badgeStyle = 'background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);'; + badgeText = `✓ ${licenseType}`; + badgeIcon = '✓'; } - } - - 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; - }); + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + text-align: center; + display: flex; + align-items: center; + gap: 8px; + `; + + badge.innerHTML = ` + ${badgeIcon} + ${badgeText} + `; + + // Click pour voir les détails + badge.onclick = function() { + showLicenseDetails(license); + }; + + // Hover effect + badge.addEventListener('mouseenter', function() { + this.style.transform = 'translateY(-2px)'; + this.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.3)'; + }); + + badge.addEventListener('mouseleave', function() { + this.style.transform = 'translateY(0)'; + this.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.2)'; + }); + + container.appendChild(badge); +} + +// ============================================ +// MODAL DE DÉTAILS DE LICENCE +// ============================================ + +function showLicenseDetails(license) { + const modal = document.createElement('div'); + modal.id = 'license-details-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 features = (license.features || []).map(f => `${f}`).join(''); + + const limits = license.limits || {}; + const maxApps = limits.max_apps === -1 ? 'Illimité' : limits.max_apps; + const maxPushes = limits.max_pushes_per_day === -1 ? 'Illimité' : limits.max_pushes_per_day + '/jour'; + + modal.innerHTML = ` +
+
+

📋 Détails de la licence

+ +
+ +
+
+
+
Type
+
${license.type_name || license.type}
+
+
+
ID
+
${license.license_id}
+
+
+
Expire
+
${license.expires}
+
+
+
Jours restants
+
${license.days_remaining}
+
+
+
+ +
+
Client
+
${license.customer?.name || 'N/A'} (${license.customer?.email || 'N/A'})
+
+ +
+
Hostname
+
${license.hostname || currentHostname}
+
+ +
+
Limites
+
Apps: ${maxApps} | Pushes: ${maxPushes}
+
+ +
+
Fonctionnalités
+
${features || 'Aucune'}
+
+ +
+ + +
+
+ `; + + document.body.appendChild(modal); } -function showGeneratorInfo() { - alert(`Pour générer une clé de licence, exécutez sur le serveur Splunk: +// ============================================ +// MODAL D'UPLOAD DE LICENCE +// ============================================ -python /opt/splunk/etc/apps/pusher_app/bin/license_generator.py +function showLicenseModal(error = null, errorCode = null, hostname = null) { + console.log("Showing license modal", { error, errorCode, hostname }); + + // Supprimer l'ancien modal s'il existe + hideLicenseModal(); + const detailsModal = document.getElementById('license-details-modal'); + if (detailsModal) detailsModal.remove(); + + 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; + `; + + // Message d'erreur si présent + let errorHtml = ''; + if (error) { + let errorStyle = 'background: #ffebee; color: #c62828; border-left: 4px solid #f44336;'; + if (errorCode === 'NO_LICENSE') { + errorStyle = 'background: #fff3e0; color: #e65100; border-left: 4px solid #ff9800;'; + } + errorHtml = ` +
+ ${error} +
+ `; + } + + modal.innerHTML = ` +
+
+

🔐 Git Pusher

+

Activation de licence requise

+
+ + ${errorHtml} + +
+

+ 📋 Hostname Splunk:
+ ${hostname || 'Chargement...'} +

+

+ Communiquez ce hostname pour obtenir votre licence. +

+
+ +
+ + +
+
📁
+
+ Glissez votre fichier .lic ici
+ ou cliquez pour sélectionner +
+ +
+ + +
+ + + + + +
+ + Besoin d'une licence ? Contactez-nous + +
+ + ${currentLicense ? ` +
+ +
+ ` : ''} +
+ `; + + document.body.appendChild(modal); + + // Récupérer le hostname si pas fourni + if (!hostname) { + fetch(`${LICENSE_API_URL}/license/hostname`) + .then(r => r.json()) + .then(d => { + const codeEl = modal.querySelector('code'); + if (codeEl) codeEl.textContent = d.hostname || 'Inconnu'; + currentHostname = d.hostname; + }) + .catch(() => {}); + } +} -Cela générera une clé basée sur votre hostname.`); +function hideLicenseModal() { + const modal = document.getElementById('license-modal'); + if (modal) modal.remove(); } -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')); - }); +// ============================================ +// GESTION DU FICHIER +// ============================================ + +let selectedLicenseContent = null; + +function handleDragOver(event) { + event.preventDefault(); + event.stopPropagation(); + event.currentTarget.style.borderColor = '#667eea'; + event.currentTarget.style.background = '#f0f4ff'; } -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 handleDragLeave(event) { + event.preventDefault(); + event.stopPropagation(); + event.currentTarget.style.borderColor = '#ccc'; + event.currentTarget.style.background = '#fafafa'; } -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 handleDrop(event) { + event.preventDefault(); + event.stopPropagation(); + event.currentTarget.style.borderColor = '#ccc'; + event.currentTarget.style.background = '#fafafa'; + + const files = event.dataTransfer.files; + if (files.length > 0) { + processFile(files[0]); + } } -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 handleFileSelect(event) { + const files = event.target.files; + if (files.length > 0) { + processFile(files[0]); + } } -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 processFile(file) { + console.log("Processing file:", file.name); + + if (!file.name.endsWith('.lic')) { + showLicenseMessage('Veuillez sélectionner un fichier .lic', 'error'); + return; + } + + const reader = new FileReader(); + reader.onload = function(e) { + selectedLicenseContent = e.target.result; + + // Afficher le nom du fichier + document.getElementById('selected-file-info').style.display = 'block'; + document.getElementById('selected-file-name').textContent = '📄 ' + file.name; + + // Activer le bouton + const btn = document.getElementById('activate-btn'); + btn.disabled = false; + btn.style.opacity = '1'; + btn.style.cursor = 'pointer'; + + showLicenseMessage('Fichier prêt à être activé', 'info'); + }; + reader.onerror = function() { + showLicenseMessage('Erreur de lecture du fichier', 'error'); + }; + reader.readAsText(file); } -function closeLicenseModal() { - const modal = document.getElementById('license-modal'); - if (modal) { - modal.remove(); - } +function clearSelectedFile() { + selectedLicenseContent = null; + document.getElementById('selected-file-info').style.display = 'none'; + document.getElementById('license-file-input').value = ''; + + const btn = document.getElementById('activate-btn'); + btn.disabled = true; + btn.style.opacity = '0.5'; + + const msgEl = document.getElementById('license-message'); + msgEl.style.display = 'none'; } -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; +// ============================================ +// UPLOAD ET ACTIVATION +// ============================================ + +async function uploadLicense() { + if (!selectedLicenseContent) { + showLicenseMessage('Veuillez sélectionner un fichier de licence', 'error'); + return; + } + + showLicenseMessage('⏳ Validation en cours...', 'info'); + + const btn = document.getElementById('activate-btn'); + btn.disabled = true; + btn.textContent = 'Validation...'; + + try { + const response = await fetch(`${LICENSE_API_URL}/license/upload`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + license_content: selectedLicenseContent + }) + }); + + const result = await response.json(); + console.log("Upload result:", result); + + if (result.success) { + showLicenseMessage('✓ Licence activée avec succès!', 'success'); + currentLicense = result.license_info; + + setTimeout(() => { + hideLicenseModal(); + displayLicenseInfo(result.license_info); + }, 1500); + } else { + showLicenseMessage(result.error || 'Erreur d\'activation', 'error'); + btn.disabled = false; + btn.textContent = 'Activer la licence'; + } + } catch (error) { + console.error("Upload error:", error); + showLicenseMessage('Erreur de connexion au serveur', 'error'); + btn.disabled = false; + btn.textContent = 'Activer la licence'; + } } // ============================================ -// FONCTIONS UTILITAIRES DE COOKIE +// UTILITAIRES // ============================================ -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 showLicenseMessage(message, type) { + const msgEl = document.getElementById('license-message'); + if (!msgEl) return; + + msgEl.style.display = 'block'; + msgEl.textContent = message; + + switch (type) { + case 'success': + msgEl.style.background = '#e8f5e9'; + msgEl.style.color = '#2e7d32'; + msgEl.style.border = '1px solid #a5d6a7'; + break; + case 'error': + msgEl.style.background = '#ffebee'; + msgEl.style.color = '#c62828'; + msgEl.style.border = '1px solid #ef9a9a'; + break; + case 'info': + msgEl.style.background = '#e3f2fd'; + msgEl.style.color = '#1565c0'; + msgEl.style.border = '1px solid #90caf9'; + break; + } +} + +function showContactInfo() { + alert(`Pour obtenir une licence Git Pusher: + +1. Copiez votre hostname Splunk affiché ci-dessus +2. Contactez-nous avec: + - Votre hostname + - Votre email + - Le type de licence souhaité + +Email: support@gitpusher.com +Site: https://gitpusher.com`); } -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)); +// ============================================ +// VÉRIFICATION AVANT PUSH +// ============================================ + +async function checkLicenseBeforePush() { + try { + const response = await fetch(`${LICENSE_API_URL}/license/status`); + const data = await response.json(); + + if (data.status !== 'valid') { + alert('⚠️ ' + (data.error || 'Licence invalide ou absente')); + showLicenseModal(data.error, data.error_code, data.hostname); + return false; + } + + // Vérifier les limites + const limits = data.license?.limits || {}; + const usage = data.usage || {}; + + if (limits.max_pushes_per_day > 0 && usage.pushes_today >= limits.max_pushes_per_day) { + alert(`⚠️ Limite quotidienne atteinte (${limits.max_pushes_per_day} pushes/jour)`); + return false; + } + + return true; + } catch (error) { + console.error("License check error:", error); + alert('⚠️ Impossible de vérifier la licence'); + return false; } - } - return ""; } -function deleteCookie(name) { - setCookie(name, "", -1); - console.log("Cookie deleted: " + name); -} \ No newline at end of file +// ============================================ +// EXPORT POUR UTILISATION EXTERNE +// ============================================ + +window.LicenseManager = { + init: initializeLicense, + check: checkLicenseBeforePush, + showModal: showLicenseModal, + getLicense: () => currentLicense, + getHostname: () => currentHostname +}; diff --git a/apps/pusher_app_prem/bin/README b/apps/pusher_app_prem/bin/README old mode 100644 new mode 100755 diff --git a/apps/pusher_app_prem/bin/__pycache__/license_validator.cpython-39.pyc b/apps/pusher_app_prem/bin/__pycache__/license_validator.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe7ac122ded7e88c7e18a91439df63847cd5bb1f GIT binary patch literal 12190 zcmbVSS!^81dG2d^dJdkVsAIL--qks>C~9R{E7NoQ8G#GF+K*ZsV3RfaAvl; zn-bf@jbkmGAn?ZPJUD?@n+$UpP!b?P5;%Sc0>na$JmeuyerWh1K#+&cYk)i`*>%4E z@17YBMJ=3=n5ydPs_Huakg(iBdl>IV+M{@z+tfBy)a=?+mNb!nPp6jcF;Uo5%ezIf zJT3;xd&FRQuNV@;?<(beVnmE0-!FEFG2~B)&xze){9UztK_0McX}mPeD%`EuW_r$j_wtXVd&F_uT*F z?+R^RDV+}=;;i$pd4YW+^p{;}PuN$zs$2J6`*o-02`6Ys`}n*o*IYT_d%`W{FSz!i zS6%kdSTxY_;hzT5wZ+2+&01sq;RD+brCoDvpZcIU->il8+qQ7(zFj-!G@CWA>I7b+ zp3l#$Hfv7RU3Kfhls(yK29rKFo^*riBwG5DO`cQ*gm`JHO6 z=Rd+Sl-ZIRTbQyNc&lzni}RNk7H%YSkH^30_%1!qxo?NAA6$0oqUOqI|JLuWIdZbr zSW2g{@BqWPcRfF-G;T*{j~sXWDv#`AmyR5-JNQUathsA$ZOikj>-)|U-bTA0pDjL6 z(&G&GiOt1^Ty=ujT2AKU$0LvRD4$>Qf=ap-e%N$n#doXH4JxTs z|D%;_kD=G@P2~+mQ-o$HhH?)=>J4Qczut3w_<6?-jdRd7dlS`VF)c+D87Jd_AA!rIgtvtO>fOwZ51 zaeeM$R6O(9=g)kpQEfV_&%W|Vor+#u4uYmXH97hGnHNr-#{ctE=TD#i{3NkFnA7Ki zD<{4BV&j!Y7&OD6vf2>tOGF;!f$1<&Rv+9Td2%lpPh*d5Qg63 zIua8D)MFh}h|OCNHRsO8xm)MXy7ej+9-Ez9xcr<=r%=wO;wa8LLR6Mr zN4T<_#XK8Lw;mg`VGtD!NBFUUc?x>Co*$^$FTvy>p+gBbQi1c14TZMk34$mR}v;q67BIW%)gROtuy**jO!mo zqUeTdsg`D`CelH5P(7d()lqFs9aEoE4fSPpSUsxC&!T0O=)Uu(p~;BhRLdzO zq&=H%HE_jalTSIQJNY-!+^1QK26h!C3$6gswyy~VRk$+!%+n84#sE^b`4*;B?&1K-<@q%SZT!t#`)ah8Q zZ9m8Si#5O_qJWocX!oD^DbW!URFw{lS@de|sTUby_%EZ-X)kwC+XFLF0%1}QEFoDR zuqSnb$R3GDu3ot?Gdn+1xpd{~Oy$P(!ezM+{bHl(1j`Vsgw7xuNm@!`W1cRgBTyz( z#7e`f%LCM&WN{*U;Mi4xL8NkY<7UP1C}RQM8-sKm_HCE4^qpGdXL zdeJip=~CCNq?>l0vS&*Lrbc<4zH@^TB1QQXN}fc7at?1xS*AL9lUlw`$s3fAjFfLt zvJ*+|-f;~Pn)@W>6-{IH*hflVMSY)gAh=E{gq6cTCvg34Dr=7i$~{Qi&Ql^?MdrRv zq|ySZK^~tARJ0)lstJ?e@`lFctgslw>jdpZjv)DaI$(UJ1p>UM`D+-hCfYB zLgjVva=3$DvX2X|8kArj`<^}9sJkc8J0NqU<1qk{lB)nvJLrC(8cGRJ*@fCu_T`T8 z;#7mst!*6-odbdtHb!W`a`s@&lLMhQ`#BeYtiELdG$-3xbF20@*=I0i-)b~!Cv6t@ zQ#N20O*3&SkjXM-Q;!WEarP2JzUi>Kv*-$BBV2p3hYbspWtu5@;_+JNR- zZH9Iuv|(rgvSCUiS7u+IzIx?iW&X-*v(pPV=Vr=TfNavUauOp(doQ>FHZnba8_?5c zL3}K-j-3Sl@a`h`^`Y zjy2AZYX%Muv<1fTD`e>bXskBbB83r^)W=!)i|UO#^^oM&RXUg|5D~ou?j~X?XJ_8L zab<4iqW7(@s7jpg9-fGeV7=+aIZ7*J!^BoK^g$>vjLe$1>H#cRi?H_q+5J*JS%ybz zS)t@MB?M&T5|VgOIO~1|ekaExi%PE2n>r=Df1}JqY{hnHDH?}TAg`BjZ9XM_J7t%L z`6#9ru3Vq3%*@SQpDPWsB4^7)K1E{^B*7kqt4*H)uslMw1ivKdKNu2?J6Ks~(_-C& zLEJ?z39;!QW~d!j5(1sdT_{2p1(RK`ii}HHrB>tH&#F?bxlUa!@(`W#qT`bdRPCsL zhST-B9X0wMTKdaKl!B4kXPS!tkF6}MHCSmyeOS)}=_GY~)uLwMX_%f>?Z2(um^!MC zp~m7qV>*@SKaVyh(!8qumo+e~4l`QPK9(WA)rX8sRCJLMoGwcA<3L$~of8mnS5XG^ zC2thDprMS|4uXQxp~}va=Si6NVKTs+Fk5iNZs;u?#z7X)`5OX?3uTL3)ZJEApp$A7 zMHn6XH;k6i&XG|1oAKdi!EQhq%tXEVAb@HBo z-#)QSa285=l!n#g+Bo#9i|j;)=4n8(Z+QxwT0B^D;OM6Y6%urDfdUYT*IfXRlhw(G zE3csx58s@her={Qzc9TppO~d|n28KG*osk;Q|Y%m;vsToZSk1z!6y3S=n8xeldq_r zf@m{o>*@c0R7LfTMCvtqCT2N>gTMEob2u=vMe4lXp z!L|cE3-L9u{&NT~9m4BCFxVb~OCIj~M0l~AJHMg7qr=V3If<(l#BQk=lK#6=uR`?` z%7e6GdvdiIu$J9=)RA@Mt%)P66Gw#nHahW{%Wt9d5fQ5VbtH*Ak#|v?jrJ$@Tgu3M zCQL|>Mo`YQ@vL{{+bG2r6zd}C-h^|R8oo(Qh~FhyS@K;IZkv!=A)vSVA?@agm4d#>KkXO8B~vI^1FEd7_|_^FpH8j>DX|I zYXHnb_8B6!IA!BL9Dn?jA&Ug+QG+c9halc3VHc=BVcj<5GOQUDRvhfImVP@Umtlz{ z;h4gQ@VL=92(N%T#K3)0I)fo?K5K-YM-di6%|%F%KpAop=T29m1DOPH3i;$c1nBdB zHErV5$T<%2y&c~t+Jw>GCDshU+S#;qqA3X4l}A^dy9vjG@eyT%kof4A`0J5U^z>Dp zul;t|2t47rbvS_?plDz**FGK%*olOhT!BZUv;apbp&+@AB+f92p^9KiLPkVnB}2YL z6*L3+TS&_J-ZZwkE#hJllt_^5D?}4EdMH^_BhhVrVrJkr(u?X|7UZVMQsiU#KHBur z1$81&`6_<0hUi&Rm^Y zn0Z3?%Nzyrqn8qB6-+F^nmK9rh0RXdZ%(A4BUenk0Y@zwV4}?cpW?Yk{OtSNCVrt8 z!GWR_w#n?nZ4;!|f zUPrhs%u_#&ZTU+?v_%}N0(TTCJo=9gJw`*P9_!ghB^X5^zb6YeB4CQ&05K~R2p|I9 z)H><~R02{al#CLB0GykY3sNze*=ToqI835s1~0;zw&m|&SV;!VNB@Kb7=SRE+@RzK zl#taH5AZxmR8bU6{*dYly$$k9s{B1_ybTBBMWd2_wMjcdDqT_c!=@n%Cpz%>e)Kx@ zZG?$_T|96OLt&Bd|D?-h*Kdba(xvN^M?Z!E1M_E1@n57|WNvHnM?r?XcY#TAKgc2l zqaomp*oOR?FzcGgwt6-7mWmpy)53q3OTY_#wLj$Ad|IoC!hLhYs2?Q#Kwp=CCkE(C z9wIDtuq?cODB&HP&5SW{aP0vK?sLd|fnqE0iJeXjp(&2845i_HKH^SKkqbu1B=WEq zy%FFWv&b+4(%_s8BMW3O9Fk2BR<%19A>d!xjx!0KC#ov(O2`k;=TWi4r{r~bG`!`C z%OsiD(NJf^Nz(nHA6cCW)<|(SwMk|ZRYVSzl8NvwH)dCZr078Jt^6Jyqmj-ilbxt2 zw|z8xGjX5Tpu#2sAVE~1Wu@M((x|*ogZ(ZgM6L2eN*KnlVq)M!@zR98Gk3^pkS(fs zj*=!N2Px_IJ#>r}rgqk7G|OcA6I9yCJ~2VnnA{y=0X9msJj6j_l8g}&hBhOIz%WqG zxT-y}atMR`(#%-eenLNLw0B2F!H|7SLXx7(BV zCEp$jhQVlm1$aC{CCH~iy4IHT!U!XxNM*3esMrNIp_?wF?OpilSc}4uyZhLLZkC(@ zn^5d#if@*T^5c$4*-eohunFhPCcD8Va{J5BL9HMpp7IN?6AyLx4wiRBaTE#qAERRNk)!`cnIA0sqY`@$ zOqc-;gvj9ti}N9+ z3%96yE5xKF3QD9Izx5eMZ5s?`$kH-G ziWFXivFyMP-FFXj3*NSm8CnX`tCL$it zdpz2ft)yLy$Dj@o52+>RhzpSP$$zA6B?LrZ5~1;*P)nR7IqY#vzkx^jhe$r6J-via zn23MCjSv@cgA^fv3T||IUgFKqcY;pv{{1bQN9Iks*fo$SsLB6zGm0Ksar4a(u+Ch+yHC8IG6>?2IHYNL;{G z2(Jo}yyz{(#+BJi*W0YB?~E+rrLj`XbK3A9j^5t|gRljwV2IFL9?Z z4*3E-(kUbnldn>Vp$dyj#w*{Yy7ws|Zeu!-e~A*V_^tb~4jIVuVl9R=^>NED&JbA# z37FY|X=CffL l+LeFPI%ADkd#oeFPQCzq&%ni0$B~cXXORaM{{S}X{{kB^oh1MO literal 0 HcmV?d00001 diff --git a/apps/pusher_app_prem/bin/git_pusher.pid b/apps/pusher_app_prem/bin/git_pusher.pid index 81d2a971..679e2eb3 100644 --- a/apps/pusher_app_prem/bin/git_pusher.pid +++ b/apps/pusher_app_prem/bin/git_pusher.pid @@ -1 +1 @@ -919562 +212462 diff --git a/apps/pusher_app_prem/bin/git_pusher.py b/apps/pusher_app_prem/bin/git_pusher.py old mode 100644 new mode 100755 index 6f0c0ceb..f312e389 --- a/apps/pusher_app_prem/bin/git_pusher.py +++ b/apps/pusher_app_prem/bin/git_pusher.py @@ -1,5 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" +Git Pusher - Main Server +Serveur HTTP pour pousser les applications Splunk vers Git + +Avec système de licence par fichier .lic +""" import sys import os @@ -12,6 +18,29 @@ from datetime import datetime from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.parse import parse_qs, urlparse +# Importer le validateur de licence +# En production, ce fichier sera dans le même dossier +try: + from license_validator import ( + validate_license, + save_license_file, + check_limits, + increment_usage, + get_splunk_hostname, + get_usage_stats, + parse_license_content + ) +except ImportError: + # Fallback pour le développement + print("Warning: license_validator not found, running without license checks") + def validate_license(): return {"valid": True, "type": "dev", "days_remaining": 999} + def save_license_file(c): return {"success": True} + def check_limits(): return {"allowed": True} + def increment_usage(): return {} + def get_splunk_hostname(): return "dev-host" + def get_usage_stats(): return {} + def parse_license_content(c): return {} + # Configuration du logging log_dir = '/opt/splunk/var/log/splunk' os.makedirs(log_dir, exist_ok=True) @@ -30,65 +59,209 @@ logger = logging.getLogger('git_pusher') class GitPusherRequestHandler(BaseHTTPRequestHandler): """Handler pour les requêtes HTTP""" + def send_cors_headers(self): + """Envoyer les headers CORS complets""" + # Permettre toutes les origines + origin = self.headers.get('Origin', '*') + self.send_header('Access-Control-Allow-Origin', origin) + self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE') + self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin') + self.send_header('Access-Control-Allow-Credentials', 'true') + self.send_header('Access-Control-Max-Age', '86400') # Cache preflight 24h + def do_OPTIONS(self): """Traiter les requêtes OPTIONS (CORS preflight)""" + logger.info(f"OPTIONS request from {self.headers.get('Origin', 'unknown')}") 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.send_cors_headers() self.end_headers() + # Important: ne rien écrire dans le body pour OPTIONS + return + + def do_GET(self): + """Traiter les requêtes GET""" + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.send_cors_headers() + self.end_headers() + + try: + parsed_url = urlparse(self.path) + path = parsed_url.path + + # ============================================ + # ENDPOINTS LICENCE + # ============================================ + + if path == '/license' or path == '/license/status': + # Récupérer le statut de la licence + validation = validate_license() + usage = get_usage_stats() + hostname = get_splunk_hostname() + + response = { + "status": "valid" if validation.get("valid") else "invalid", + "hostname": hostname, + "license": validation if validation.get("valid") else None, + "error": validation.get("error") if not validation.get("valid") else None, + "error_code": validation.get("error_code") if not validation.get("valid") else None, + "usage": usage + } + self.wfile.write(json.dumps(response).encode()) + + elif path == '/license/hostname': + # Juste le hostname + response = {"hostname": get_splunk_hostname()} + self.wfile.write(json.dumps(response).encode()) + + elif path == '/health': + # Health check + response = { + "status": "ok", + "service": "git_pusher", + "timestamp": datetime.now().isoformat() + } + self.wfile.write(json.dumps(response).encode()) + + else: + response = {"error": "Unknown endpoint", "path": path} + self.wfile.write(json.dumps(response).encode()) + + except Exception as e: + logger.error(f"GET error: {e}") + self.wfile.write(json.dumps({"error": str(e)}).encode()) 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.send_cors_headers() self.end_headers() try: - logger.info(f"POST request to {self.path}") - - # Parser l'URL et les paramètres parsed_url = urlparse(self.path) + path = parsed_url.path query_params = parse_qs(parsed_url.query) - logger.info(f"Query params keys: {list(query_params.keys())}") + logger.info(f"POST request to {path}") + # ============================================ + # ENDPOINTS LICENCE + # ============================================ + + if path == '/license/upload': + # Uploader une nouvelle licence + content_length = int(self.headers.get('Content-Length', 0)) + body = self.rfile.read(content_length).decode('utf-8') + + try: + data = json.loads(body) + license_content = data.get('license_content', '') + except: + license_content = body + + if not license_content: + response = {"success": False, "error": "Contenu de licence vide"} + else: + response = save_license_file(license_content) + + self.wfile.write(json.dumps(response).encode()) + return + + elif path == '/license/delete': + # Supprimer la licence + license_path = "/opt/splunk/etc/apps/pusher_app_prem/local/license.lic" + if os.path.exists(license_path): + os.remove(license_path) + response = {"success": True, "message": "Licence supprimée"} + else: + response = {"success": False, "error": "Aucune licence à supprimer"} + + self.wfile.write(json.dumps(response).encode()) + return + + # ============================================ + # ENDPOINT PUSH GIT + # ============================================ + + elif path == '/push' or path.startswith('/services/'): + # Vérifier la licence avant le push + license_check = check_limits() + if not license_check.get("allowed"): + response = { + "status": "error", + "error_code": "LICENSE_ERROR", + "message": license_check.get("error", "Licence invalide ou limite atteinte") + } + self.wfile.write(json.dumps(response).encode()) + return + + # Traiter le push Git + self.handle_git_push(query_params) + return + + else: + # Ancien comportement pour compatibilité + # Vérifier la licence + license_check = check_limits() + if not license_check.get("allowed"): + response = { + "status": "error", + "error_code": "LICENSE_ERROR", + "message": license_check.get("error", "Licence invalide ou limite atteinte") + } + self.wfile.write(json.dumps(response).encode()) + return + + self.handle_git_push(query_params) + + except Exception as e: + logger.error(f"POST error: {str(e)}", exc_info=True) + response = { + "status": "error", + "message": f"Error: {str(e)}" + } + self.wfile.write(json.dumps(response).encode()) + + def handle_git_push(self, query_params): + """Gérer le push Git""" + try: # 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}'") + logger.info(f"Parameters: git_url={git_url}, branch={git_branch}, user={user}") # 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 + apps = json.loads(apps_json) if isinstance(apps_json, str) else apps_json except (json.JSONDecodeError, TypeError) as e: - logger.error(f"JSON parse error: {e} - trying to parse: {apps_json}") + logger.error(f"JSON parse error: {e}") apps = [] - logger.info(f"Parsed apps: {len(apps)} items - {apps}") + logger.info(f"Parsed apps: {len(apps)} items") + + # Vérifier les limites d'apps + license_info = validate_license() + max_apps = license_info.get("limits", {}).get("max_apps", -1) + if max_apps > 0 and len(apps) > max_apps: + response = { + "status": "error", + "error_code": "APP_LIMIT", + "message": f"Votre licence permet {max_apps} apps max. Vous en avez sélectionné {len(apps)}." + } + self.wfile.write(json.dumps(response).encode()) + return - # Valider + # Valider les paramètres 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' + "status": "error", + "message": "Missing required parameters" } self.wfile.write(json.dumps(response).encode()) return @@ -101,14 +274,12 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): # 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) + # Récupérer les applications logger.info("Fetching applications from Splunk...") - dashboard_contents = self.fetch_apps_directories(apps) + app_directories = self.fetch_apps_directories(apps) # Créer le dossier apps apps_dir = os.path.join(temp_dir, 'apps') @@ -116,41 +287,40 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): # Copier les applications logger.info("Copying applications to repository...") - for app_data in dashboard_contents: + for app_data in app_directories: 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...") + # Ajouter les infos de licence au commit + license_info = validate_license() + full_message = f"{commit_message}\n\n" + full_message += f"Pushed by: {user}\n" + full_message += f"License: {license_info.get('license_id', 'N/A')} ({license_info.get('type_name', 'N/A')})\n" + full_message += f"Timestamp: {datetime.now().isoformat()}" + 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.warning(f"Commit warning: {result.stderr}") logger.info("Pushing...") result = subprocess.run(['git', 'push', 'origin', git_branch], @@ -159,11 +329,15 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): if result.returncode != 0: raise Exception(f"Push failed: {result.stderr}") + # Incrémenter les stats d'utilisation + increment_usage() + 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) + "status": "success", + "message": f"Successfully pushed {len(app_directories)} application(s) to Git", + "apps_pushed": len(app_directories), + "license_type": license_info.get("type_name", "N/A") } self.wfile.write(json.dumps(response).encode()) @@ -172,36 +346,28 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): shutil.rmtree(temp_dir, ignore_errors=True) except Exception as e: - logger.error(f"Error: {str(e)}", exc_info=True) + logger.error(f"Git push error: {str(e)}", exc_info=True) response = { - 'status': 'error', - 'message': f'Error: {str(e)}' + "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 + """Préparer l'URL Git avec le token""" 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 @@ -224,7 +390,7 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): @staticmethod def fetch_apps_directories(apps): - """Récupérer les dossiers complets des applications""" + """Récupérer les dossiers des applications""" logger.info(f"Fetching directories for {len(apps)} applications") splunk_home = '/opt/splunk' @@ -233,10 +399,14 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): 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) + app_id = app.get('id') or app.get('app_id') or app.get('name') - logger.info(f"Checking app directory: {app_path}") + # Vérifier que app_id n'est pas None + if not app_id: + logger.warning(f"Skipping app with no ID: {app}") + continue + + app_path = os.path.join(apps_base_path, app_id) if os.path.isdir(app_path): app_directories.append({ @@ -246,95 +416,29 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler): for dirpath, dirnames, filenames in os.walk(app_path) for filename in filenames) }) - logger.info(f"Found app: {app_id} at {app_path}") + logger.info(f"Found app: {app_id}") 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)") + logger.info(f"Git Pusher server listening on 0.0.0.0:{port}") + + # Afficher le statut de la licence au démarrage + license_status = validate_license() + if license_status.get("valid"): + logger.info(f"License: {license_status.get('type_name')} - {license_status.get('days_remaining')} days remaining") + else: + logger.warning(f"License: {license_status.get('error', 'Invalid')}") + 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_endpoints.py b/apps/pusher_app_prem/bin/license_endpoints.py new file mode 100755 index 00000000..e71d4f8d --- /dev/null +++ b/apps/pusher_app_prem/bin/license_endpoints.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Git Pusher - License Endpoints +Endpoints REST pour gérer les licences +""" + +import sys +import os +import json +import tempfile +from http.server import BaseHTTPRequestHandler + +# Ajouter le chemin du module +sys.path.insert(0, os.path.join(os.path.dirname(__file__))) + +from license_validator import LicenseValidator, get_license_status, LICENSE_FILE_PATH + +class LicenseHandler(BaseHTTPRequestHandler): + """Handler pour les requêtes de licence""" + + def do_OPTIONS(self): + """Gérer les requêtes OPTIONS (CORS preflight)""" + self.send_response(200) + self.send_headers() + self.end_headers() + + def do_GET(self): + """Gérer les requêtes GET""" + if self.path == '/custom/git_pusher/license_status': + self.handle_license_status() + else: + self.send_error(404) + + def do_POST(self): + """Gérer les requêtes POST""" + if self.path == '/custom/git_pusher/install_license': + self.handle_install_license() + elif self.path == '/custom/git_pusher/request_trial': + self.handle_request_trial() + else: + self.send_error(404) + + def send_headers(self): + """Envoyer les headers CORS""" + 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') + + def handle_license_status(self): + """Vérifier le statut de la licence""" + try: + status = get_license_status() + + self.send_response(200) + self.send_headers() + self.end_headers() + + self.wfile.write(json.dumps(status).encode()) + + except Exception as e: + self.send_response(500) + self.send_headers() + self.end_headers() + + self.wfile.write(json.dumps({ + 'licensed': False, + 'errors': [f'Erreur serveur: {str(e)}'] + }).encode()) + + def handle_install_license(self): + """Installer un fichier de licence uploadé""" + try: + # Lire le body + content_length = int(self.headers['Content-Length']) + body = self.rfile.read(content_length) + data = json.loads(body.decode()) + + license_content = data.get('license_file') + filename = data.get('filename', 'uploaded.lic') + + if not license_content: + raise Exception("Aucun contenu de licence fourni") + + # Créer un fichier temporaire + with tempfile.NamedTemporaryFile(mode='w', suffix='.lic', delete=False) as temp_file: + temp_file.write(license_content) + temp_path = temp_file.name + + try: + # Valider et installer + validator = LicenseValidator() + result = validator.install_license(temp_path, LICENSE_FILE_PATH) + + self.send_response(200) + self.send_headers() + self.end_headers() + + self.wfile.write(json.dumps(result).encode()) + + finally: + # Nettoyer le fichier temporaire + if os.path.exists(temp_path): + os.remove(temp_path) + + except Exception as e: + self.send_response(400) + self.send_headers() + self.end_headers() + + self.wfile.write(json.dumps({ + 'success': False, + 'message': f'Erreur: {str(e)}' + }).encode()) + + def handle_request_trial(self): + """Créer une licence d'essai""" + try: + from datetime import datetime, timedelta + import socket + import hashlib + + # Générer une licence d'essai basique (7 jours) + hostname = socket.gethostname() + trial_license = { + "license": { + "version": "1.0", + "license_id": "TRIAL-" + hashlib.md5( + f"{hostname}{datetime.now()}".encode() + ).hexdigest()[:8].upper(), + "customer": { + "name": "Trial User", + "hostname": hostname + }, + "validity": { + "issued": datetime.now().isoformat(), + "expires": (datetime.now() + timedelta(days=7)).isoformat(), + "days": 7 + }, + "limits": { + "max_pushes": 50, + "max_apps": None + }, + "features": { + "git_push": True, + "multi_branch": False, + "auto_commit": False, + "webhooks": False + }, + "type": "trial" + }, + "signature": "TRIAL-NO-SIGNATURE", + "checksum": "TRIAL" + } + + # Créer le dossier si nécessaire + os.makedirs(os.path.dirname(LICENSE_FILE_PATH), exist_ok=True) + + # Sauvegarder + with open(LICENSE_FILE_PATH, 'w') as f: + json.dump(trial_license, f, indent=2) + + self.send_response(200) + self.send_headers() + self.end_headers() + + self.wfile.write(json.dumps({ + 'success': True, + 'message': 'Licence d\'essai créée (7 jours, 50 pushes max)', + 'license_info': { + 'license_id': trial_license['license']['license_id'], + 'type': 'trial', + 'expires': trial_license['license']['validity']['expires'], + 'days_remaining': 7 + } + }).encode()) + + except Exception as e: + self.send_response(500) + self.send_headers() + self.end_headers() + + self.wfile.write(json.dumps({ + 'success': False, + 'message': f'Erreur: {str(e)}' + }).encode()) + + def log_message(self, format, *args): + """Override pour éviter les logs HTTP par défaut""" + pass diff --git a/apps/pusher_app_prem/bin/license_generator.py b/apps/pusher_app_prem/bin/license_generator.py deleted file mode 100644 index 8a353809..00000000 --- a/apps/pusher_app_prem/bin/license_generator.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/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/license_validator.py b/apps/pusher_app_prem/bin/license_validator.py new file mode 100755 index 00000000..eca87391 --- /dev/null +++ b/apps/pusher_app_prem/bin/license_validator.py @@ -0,0 +1,485 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Git Pusher - License Validator (Server-side) +Ce fichier doit être déployé sur le serveur Splunk dans l'application + +Emplacement: /opt/splunk/etc/apps/pusher_app_prem/bin/license_validator.py +""" + +import hashlib +import hmac +import base64 +import json +import os +import socket +from datetime import datetime +from http.server import HTTPServer, BaseHTTPRequestHandler +import logging + +# 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, 'license_validator.log')), + logging.StreamHandler() + ] +) +logger = logging.getLogger('license_validator') + +# ============================================ +# CONFIGURATION - DOIT CORRESPONDRE AU GÉNÉRATEUR ! +# ============================================ + +# IMPORTANT: Cette clé DOIT être identique à celle du générateur +SECRET_KEY = "git_pusher_super_secret_key_2024_change_me_in_production" + +# Chemin vers le fichier de licence +LICENSE_FILE_PATH = "/opt/splunk/etc/apps/pusher_app_prem/local/license.lic" + +# Fichier de cache pour les stats d'utilisation +USAGE_STATS_PATH = "/opt/splunk/etc/apps/pusher_app_prem/local/usage_stats.json" + + +def get_splunk_hostname(): + """Récupérer le hostname du serveur Splunk""" + # Essayer d'abord via l'API Splunk + try: + import urllib.request + import ssl + + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + splunk_username = os.environ.get('SPLUNK_USERNAME', 'admin') + splunk_password = os.environ.get('SPLUNK_PASSWORD', '2312Jocpam!?') + credentials = base64.b64encode(f"{splunk_username}:{splunk_password}".encode()).decode() + + req = urllib.request.Request("https://127.0.0.1:8089/services/server/info?output_mode=json") + req.add_header('Authorization', f'Basic {credentials}') + + with urllib.request.urlopen(req, timeout=5, context=ssl_context) as response: + data = json.loads(response.read().decode('utf-8')) + hostname = data.get('entry', [{}])[0].get('content', {}).get('host', '') + if hostname: + return hostname.lower().strip() + except Exception as e: + logger.warning(f"Could not get hostname via Splunk API: {e}") + + # Fallback sur le hostname système + return socket.gethostname().lower().strip() + + +def create_signature(data_str): + """Créer une signature HMAC-SHA256""" + signature = hmac.new( + SECRET_KEY.encode('utf-8'), + data_str.encode('utf-8'), + hashlib.sha256 + ).hexdigest() + return signature + + +def verify_signature(data_str, signature): + """Vérifier une signature""" + expected = create_signature(data_str) + return hmac.compare_digest(expected, signature) + + +def read_license_file(filepath=None): + """Lire et parser un fichier de licence""" + if filepath is None: + filepath = LICENSE_FILE_PATH + + try: + if not os.path.exists(filepath): + return None + + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + # Extraire la partie base64 (ignorer les commentaires) + lines = content.strip().split('\n') + base64_lines = [l for l in lines if not l.startswith('#') and l.strip()] + base64_content = ''.join(base64_lines) + + # Décoder + decoded = base64.b64decode(base64_content).decode('utf-8') + license_file = json.loads(decoded) + + return license_file + + except Exception as e: + logger.error(f"Error reading license file: {e}") + return None + + +def parse_license_content(content): + """Parser le contenu d'un fichier de licence (pour l'upload)""" + try: + # Extraire la partie base64 (ignorer les commentaires) + lines = content.strip().split('\n') + base64_lines = [l for l in lines if not l.startswith('#') and l.strip()] + base64_content = ''.join(base64_lines) + + # Décoder + decoded = base64.b64decode(base64_content).decode('utf-8') + license_file = json.loads(decoded) + + return license_file + + except Exception as e: + logger.error(f"Error parsing license content: {e}") + return None + + +def validate_license(license_file=None, current_hostname=None): + """ + Valider une licence + + Args: + license_file: Contenu du fichier de licence (dict) - si None, lit le fichier par défaut + current_hostname: Hostname actuel - si None, détecte automatiquement + + Returns: + dict avec {valid: bool, error: str, license_info: dict} + """ + try: + # Lire la licence si non fournie + if license_file is None: + license_file = read_license_file() + if license_file is None: + return { + "valid": False, + "error": "Aucun fichier de licence trouvé", + "error_code": "NO_LICENSE" + } + + # Récupérer le hostname si non fourni + if current_hostname is None: + current_hostname = get_splunk_hostname() + + license_data = license_file.get("license", {}) + signature = license_file.get("signature", "") + + # Recréer le JSON pour vérifier la signature + license_json = json.dumps(license_data, separators=(',', ':'), sort_keys=True) + + # Vérifier la signature + if not verify_signature(license_json, signature): + return { + "valid": False, + "error": "Signature invalide - fichier corrompu ou modifié", + "error_code": "INVALID_SIGNATURE" + } + + # Vérifier le hostname + expected_hostname = license_data.get("binding", {}).get("hostname", "").lower() + current_hostname_clean = current_hostname.lower().strip() + + if current_hostname_clean != expected_hostname: + return { + "valid": False, + "error": f"Cette licence est pour '{expected_hostname}', pas '{current_hostname_clean}'", + "error_code": "HOSTNAME_MISMATCH", + "expected_hostname": expected_hostname, + "current_hostname": current_hostname_clean + } + + # Vérifier l'expiration + expires_timestamp = license_data.get("dates", {}).get("expires_timestamp", 0) + if datetime.now().timestamp() > expires_timestamp: + expires_date = license_data.get("dates", {}).get("expires", "unknown") + return { + "valid": False, + "error": f"Licence expirée le {expires_date}", + "error_code": "EXPIRED" + } + + # Calculer les jours restants + days_remaining = (expires_timestamp - datetime.now().timestamp()) / (24 * 3600) + + return { + "valid": True, + "license_id": license_data.get("license_id"), + "type": license_data.get("type"), + "type_name": license_data.get("type_name"), + "customer": license_data.get("customer", {}), + "expires": license_data.get("dates", {}).get("expires"), + "days_remaining": int(days_remaining), + "limits": license_data.get("limits", {}), + "features": license_data.get("features", []), + "hostname": expected_hostname + } + + except Exception as e: + logger.error(f"Validation error: {e}") + return { + "valid": False, + "error": f"Erreur de validation: {str(e)}", + "error_code": "VALIDATION_ERROR" + } + + +def save_license_file(content): + """ + Sauvegarder un nouveau fichier de licence + + Args: + content: Contenu du fichier .lic + + Returns: + dict avec {success: bool, error: str} + """ + try: + # Créer le dossier local si nécessaire + local_dir = os.path.dirname(LICENSE_FILE_PATH) + os.makedirs(local_dir, exist_ok=True) + + # Parser d'abord pour valider + license_file = parse_license_content(content) + if license_file is None: + return { + "success": False, + "error": "Format de fichier de licence invalide" + } + + # Valider la licence + validation = validate_license(license_file) + if not validation.get("valid"): + return { + "success": False, + "error": validation.get("error", "Licence invalide") + } + + # Sauvegarder + with open(LICENSE_FILE_PATH, 'w', encoding='utf-8') as f: + f.write(content) + + logger.info(f"License saved: {validation.get('license_id')}") + + return { + "success": True, + "license_info": validation + } + + except Exception as e: + logger.error(f"Error saving license: {e}") + return { + "success": False, + "error": str(e) + } + + +def get_usage_stats(): + """Récupérer les statistiques d'utilisation""" + try: + if os.path.exists(USAGE_STATS_PATH): + with open(USAGE_STATS_PATH, 'r') as f: + return json.load(f) + except: + pass + + return { + "pushes_today": 0, + "pushes_total": 0, + "last_push_date": None, + "apps_pushed": [] + } + + +def increment_usage(): + """Incrémenter le compteur d'utilisation""" + stats = get_usage_stats() + today = datetime.now().strftime("%Y-%m-%d") + + # Reset si nouveau jour + if stats.get("last_push_date") != today: + stats["pushes_today"] = 0 + stats["last_push_date"] = today + + stats["pushes_today"] += 1 + stats["pushes_total"] += 1 + + try: + os.makedirs(os.path.dirname(USAGE_STATS_PATH), exist_ok=True) + with open(USAGE_STATS_PATH, 'w') as f: + json.dump(stats, f) + except Exception as e: + logger.error(f"Error saving usage stats: {e}") + + return stats + + +def check_limits(): + """ + Vérifier si les limites de la licence sont respectées + + Returns: + dict avec {allowed: bool, error: str} + """ + validation = validate_license() + + if not validation.get("valid"): + return { + "allowed": False, + "error": validation.get("error") + } + + limits = validation.get("limits", {}) + stats = get_usage_stats() + + # Vérifier pushes par jour + max_pushes = limits.get("max_pushes_per_day", -1) + if max_pushes > 0 and stats.get("pushes_today", 0) >= max_pushes: + return { + "allowed": False, + "error": f"Limite quotidienne atteinte ({max_pushes} pushes/jour)" + } + + return { + "allowed": True, + "license_info": validation, + "usage": stats + } + + +# ============================================ +# API REST POUR L'INTERFACE WEB +# ============================================ + +class LicenseAPIHandler(BaseHTTPRequestHandler): + """Handler pour les requêtes de l'API licence""" + + def send_cors_headers(self): + """Envoyer les headers CORS complets""" + origin = self.headers.get('Origin', '*') + self.send_header('Access-Control-Allow-Origin', origin) + self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE') + self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin') + self.send_header('Access-Control-Allow-Credentials', 'true') + self.send_header('Access-Control-Max-Age', '86400') + + def do_OPTIONS(self): + logger.info(f"OPTIONS request from {self.headers.get('Origin', 'unknown')}") + self.send_response(200) + self.send_cors_headers() + self.end_headers() + return + + def do_GET(self): + """GET /license - Récupérer les infos de licence""" + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.send_cors_headers() + self.end_headers() + + try: + if '/license/status' in self.path or self.path == '/license': + validation = validate_license() + usage = get_usage_stats() + hostname = get_splunk_hostname() + + response = { + "status": "valid" if validation.get("valid") else "invalid", + "hostname": hostname, + "license": validation if validation.get("valid") else None, + "error": validation.get("error") if not validation.get("valid") else None, + "error_code": validation.get("error_code") if not validation.get("valid") else None, + "usage": usage + } + + elif '/license/hostname' in self.path: + response = { + "hostname": get_splunk_hostname() + } + + else: + response = {"error": "Unknown endpoint"} + + self.wfile.write(json.dumps(response).encode()) + + except Exception as e: + logger.error(f"GET error: {e}") + self.wfile.write(json.dumps({"error": str(e)}).encode()) + + def do_POST(self): + """POST /license/upload - Uploader une nouvelle licence""" + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.send_cors_headers() + self.end_headers() + + try: + content_length = int(self.headers.get('Content-Length', 0)) + body = self.rfile.read(content_length).decode('utf-8') + + if '/license/upload' in self.path: + # Le body contient le contenu du fichier .lic + data = json.loads(body) + license_content = data.get('license_content', '') + + if not license_content: + response = {"success": False, "error": "Contenu de licence vide"} + else: + response = save_license_file(license_content) + + elif '/license/delete' in self.path: + # Supprimer la licence + if os.path.exists(LICENSE_FILE_PATH): + os.remove(LICENSE_FILE_PATH) + response = {"success": True, "message": "Licence supprimée"} + else: + response = {"success": False, "error": "Aucune licence à supprimer"} + + else: + response = {"error": "Unknown endpoint"} + + self.wfile.write(json.dumps(response).encode()) + + except Exception as e: + logger.error(f"POST error: {e}") + self.wfile.write(json.dumps({"error": str(e)}).encode()) + + def log_message(self, format, *args): + logger.debug(format % args) + + +def start_license_server(port=9998): + """Démarrer le serveur API licence (optionnel, peut être intégré au serveur principal)""" + server = HTTPServer(('127.0.0.1', port), LicenseAPIHandler) + logger.info(f"License API server listening on 127.0.0.1:{port}") + server.serve_forever() + + +# ============================================ +# CLI POUR TESTS +# ============================================ + +if __name__ == '__main__': + import sys + + if len(sys.argv) > 1: + if sys.argv[1] == "status": + result = validate_license() + print(json.dumps(result, indent=2, ensure_ascii=False)) + + elif sys.argv[1] == "hostname": + print(f"Hostname: {get_splunk_hostname()}") + + elif sys.argv[1] == "server": + start_license_server() + + else: + print("Usage:") + print(" python license_validator.py status # Vérifier la licence") + print(" python license_validator.py hostname # Afficher le hostname") + print(" python license_validator.py server # Démarrer l'API") + else: + result = validate_license() + 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 old mode 100644 new mode 100755 index 951e1a0e..d205939a --- a/apps/pusher_app_prem/bin/start_git_pusher.sh +++ b/apps/pusher_app_prem/bin/start_git_pusher.sh @@ -1,5 +1,249 @@ #!/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 +# ============================================ +# Git Pusher - Start Script +# Version 2.0 avec système de licence +# ============================================ + +# Configuration +SPLUNK_HOME=${SPLUNK_HOME:-/opt/splunk} +APP_HOME="${SPLUNK_HOME}/etc/apps/pusher_app_prem" +BIN_DIR="${APP_HOME}/bin" +LOG_DIR="${SPLUNK_HOME}/var/log/splunk" +PID_FILE="${BIN_DIR}/git_pusher.pid" + +# Couleurs pour les logs +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Fonction de logging +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Vérifier si le serveur est déjà en cours d'exécution +check_running() { + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p $PID > /dev/null 2>&1; then + return 0 # Running + fi + fi + return 1 # Not running +} + +# Démarrer le serveur +start_server() { + log_info "Starting Git Pusher server..." + + # Vérifier si déjà en cours + if check_running; then + log_warn "Git Pusher is already running (PID: $(cat $PID_FILE))" + return 1 + fi + + # Créer le répertoire de logs + mkdir -p "$LOG_DIR" + + # Variables d'environnement pour l'authentification Splunk + # IMPORTANT: Modifiez ces valeurs ou utilisez des variables d'environnement + export SPLUNK_USERNAME=${SPLUNK_USERNAME:-admin} + export SPLUNK_PASSWORD=${SPLUNK_PASSWORD:-changeme} + + # Démarrer le serveur Python + cd "$BIN_DIR" + python3 git_pusher.py > "${LOG_DIR}/git_pusher_startup.log" 2>&1 & + + # Sauvegarder le PID + echo $! > "$PID_FILE" + + # Attendre un peu et vérifier + sleep 2 + + if check_running; then + log_info "Git Pusher started successfully (PID: $(cat $PID_FILE))" + log_info "Server listening on port 9999" + + # Vérifier le statut de la licence + HOSTNAME=$(hostname) + log_info "Hostname: $HOSTNAME" + + if [ -f "${APP_HOME}/local/license.lic" ]; then + log_info "License file found" + else + log_warn "No license file found at ${APP_HOME}/local/license.lic" + log_warn "The application will require license activation" + fi + + return 0 + else + log_error "Failed to start Git Pusher" + log_error "Check logs at ${LOG_DIR}/git_pusher.log" + return 1 + fi +} + +# Arrêter le serveur +stop_server() { + log_info "Stopping Git Pusher server..." + + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + + if ps -p $PID > /dev/null 2>&1; then + kill $PID + sleep 2 + + # Force kill si nécessaire + if ps -p $PID > /dev/null 2>&1; then + log_warn "Force killing process..." + kill -9 $PID + fi + + rm -f "$PID_FILE" + log_info "Git Pusher stopped" + return 0 + else + log_warn "Process not running, cleaning up PID file" + rm -f "$PID_FILE" + return 0 + fi + else + log_warn "PID file not found, Git Pusher may not be running" + return 1 + fi +} + +# Redémarrer le serveur +restart_server() { + log_info "Restarting Git Pusher server..." + stop_server + sleep 1 + start_server +} + +# Afficher le statut +show_status() { + echo "============================================" + echo "Git Pusher Status" + echo "============================================" + + if check_running; then + PID=$(cat "$PID_FILE") + echo -e "Status: ${GREEN}RUNNING${NC}" + echo "PID: $PID" + echo "Port: 9999" + else + echo -e "Status: ${RED}STOPPED${NC}" + fi + + echo "" + echo "Paths:" + echo " App Home: $APP_HOME" + echo " Bin Dir: $BIN_DIR" + echo " Log Dir: $LOG_DIR" + echo "" + + # Statut de la licence + echo "License:" + if [ -f "${APP_HOME}/local/license.lic" ]; then + echo -e " File: ${GREEN}Present${NC}" + # Essayer de lire quelques infos + if command -v python3 &> /dev/null; then + python3 -c " +import sys +sys.path.insert(0, '$BIN_DIR') +from license_validator import validate_license +result = validate_license() +if result.get('valid'): + print(f\" Type: {result.get('type_name', 'N/A')}\") + print(f\" Expires: {result.get('expires', 'N/A')}\") + print(f\" Days remaining: {result.get('days_remaining', 'N/A')}\") +else: + print(f\" Status: Invalid - {result.get('error', 'Unknown error')}\") +" 2>/dev/null || echo " Unable to read license details" + fi + else + echo -e " File: ${YELLOW}Not found${NC}" + fi + + echo "" + echo "Hostname: $(hostname)" + echo "============================================" +} + +# Afficher les logs +show_logs() { + LOG_FILE="${LOG_DIR}/git_pusher.log" + + if [ -f "$LOG_FILE" ]; then + if [ "$1" == "-f" ]; then + tail -f "$LOG_FILE" + else + tail -n 50 "$LOG_FILE" + fi + else + log_warn "Log file not found at $LOG_FILE" + fi +} + +# Menu d'aide +show_help() { + echo "Git Pusher - Server Management Script" + echo "" + echo "Usage: $0 {start|stop|restart|status|logs|help}" + echo "" + echo "Commands:" + echo " start Start the Git Pusher server" + echo " stop Stop the Git Pusher server" + echo " restart Restart the Git Pusher server" + echo " status Show the current status" + echo " logs Show recent logs (use -f for follow)" + echo " help Show this help message" + echo "" + echo "Environment variables:" + echo " SPLUNK_USERNAME Splunk admin username (default: admin)" + echo " SPLUNK_PASSWORD Splunk admin password (default: changeme)" + echo " SPLUNK_HOME Splunk installation directory (default: /opt/splunk)" +} + +# Main +case "$1" in + start) + start_server + ;; + stop) + stop_server + ;; + restart) + restart_server + ;; + status) + show_status + ;; + logs) + show_logs "$2" + ;; + help|--help|-h) + show_help + ;; + *) + # Par défaut, démarrer le serveur (pour compatibilité) + if [ -z "$1" ]; then + start_server + else + echo "Unknown command: $1" + show_help + exit 1 + fi + ;; +esac diff --git a/apps/pusher_app_prem/certs/server.crt b/apps/pusher_app_prem/certs/server.crt old mode 100644 new mode 100755 diff --git a/apps/pusher_app_prem/certs/server.key b/apps/pusher_app_prem/certs/server.key old mode 100644 new mode 100755 diff --git a/apps/pusher_app_prem/default/app.conf b/apps/pusher_app_prem/default/app.conf old mode 100644 new mode 100755 diff --git a/apps/pusher_app_prem/default/data/ui/nav/default.xml b/apps/pusher_app_prem/default/data/ui/nav/default.xml old mode 100644 new mode 100755 diff --git a/apps/pusher_app_prem/default/data/ui/views/README b/apps/pusher_app_prem/default/data/ui/views/README old mode 100644 new mode 100755 diff --git a/apps/pusher_app_prem/local/app.conf b/apps/pusher_app_prem/local/app.conf old mode 100644 new mode 100755 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 index 292fbe8f..237cacba 100644 --- 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 @@ -13,7 +13,7 @@ - | rest /services/apps/local | search disabled=0 | fields name, label, description | sort label + | rest /services/apps/local | search disabled=0 | table title, label, description | rename title as name | sort label -4h@h now 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 old mode 100644 new mode 100755 diff --git a/apps/pusher_app_prem/local/data/ui/views/git_pusher_-_push_dashboards_to_git.xml b/apps/pusher_app_prem/local/data/ui/views/git_pusher_-_push_dashboards_to_git.xml old mode 100644 new mode 100755 diff --git a/apps/pusher_app_prem/local/usage_stats.json b/apps/pusher_app_prem/local/usage_stats.json new file mode 100644 index 00000000..73139971 --- /dev/null +++ b/apps/pusher_app_prem/local/usage_stats.json @@ -0,0 +1 @@ +{"pushes_today": 9, "pushes_total": 9, "last_push_date": "2026-01-31", "apps_pushed": []} \ No newline at end of file diff --git a/apps/pusher_app_prem/metadata/default.meta b/apps/pusher_app_prem/metadata/default.meta old mode 100644 new mode 100755 diff --git a/apps/pusher_app_prem/metadata/local.meta b/apps/pusher_app_prem/metadata/local.meta index d8e71ee3..cf1356db 100644 --- a/apps/pusher_app_prem/metadata/local.meta +++ b/apps/pusher_app_prem/metadata/local.meta @@ -23,4 +23,4 @@ access = read : [ * ], write : [ * ] export = none owner = admin version = 10.0.2 -modtime = 1769373002.573917000 +modtime = 1769889828.514647000