nouvel_version_2.1

Pushed by: admin
License: 61BF9B31-726 (Enterprise)
Timestamp: 2026-02-06T23:37:28.325193
masterdev
Splunk Git Pusher 3 months ago
parent 5f9e86af1d
commit 0fdf7b6f32

@ -1,18 +1,29 @@
// ============================================
// GIT PUSHER - MAIN JAVASCRIPT
// Version 2.0 avec système de licence par fichier
// Version 2.1 avec déploiement vers SH Cluster
// ============================================
// Configuration
const GIT_PUSHER_CONFIG = {
// Détecter automatiquement l'URL du serveur
serverUrl: window.location.protocol + '//' + window.location.hostname + ':9999',
credentialsKey: 'git_pusher_credentials',
version: '2.0.0'
deployerConfigKey: 'git_pusher_deployer_config',
version: '2.1.0'
};
// Configuration SH Deployer (peut être modifiée via l'interface)
let SH_DEPLOYER_CONFIG = {
enabled: false,
host: '10.10.40.14',
port: 9998,
token: ''
};
// État global
let selectedApps = [];
let isProcessing = false;
let deployerAvailable = false;
// ============================================
// INITIALISATION
@ -25,7 +36,7 @@ require([
'splunkjs/mvc/simplexml/ready!'
], function($, mvc, SearchManager) {
console.log("Git Pusher v2.0 initializing...");
console.log("Git Pusher v2.1 initializing...");
// Initialiser le système de licence
if (typeof initializeLicense === 'function') {
@ -37,6 +48,12 @@ require([
// Charger les credentials sauvegardés
loadSavedCredentials();
// Charger la config du deployer
loadDeployerConfig();
// Vérifier la disponibilité du SH Deployer
checkDeployerHealth();
// Récupérer les résultats de recherche pour les apps
const searchManager = mvc.Components.get('dsearch');
@ -59,23 +76,6 @@ require([
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é
});
// ============================================
// RENDU DE LA LISTE DES APPLICATIONS
// ============================================
@ -84,16 +84,11 @@ 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 = `
<div class="app-header">
@ -105,34 +100,16 @@ function renderAppsList(rows, fields) {
</div>
`;
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}"`);
const name = row[nameIdx] || '';
const label = row[labelIdx] || name;
const desc = row[descIdx] || '';
// Ignorer si pas de nom ou apps système
if (!name || name.startsWith('splunk_') || name === 'learned' || name === 'launcher') {
// Ignorer certaines apps système
if (name.startsWith('splunk_') || name === 'learned' || name === 'launcher') {
return;
}
validApps++;
html += `
<div class="app-item">
<input type="checkbox"
@ -142,7 +119,7 @@ function renderAppsList(rows, fields) {
onchange="updateSelectedApps()" />
<label for="app-${index}">
<span class="app-badge">${name}</span>
${label !== name ? ' - ' + label : ''}
${label !== name ? label : ''}
</label>
</div>
`;
@ -150,7 +127,7 @@ function renderAppsList(rows, fields) {
container.innerHTML = html;
console.log(`Rendered ${validApps} valid applications out of ${rows.length}`);
console.log(`Rendered ${rows.length} applications`);
}
// ============================================
@ -203,27 +180,6 @@ async function pushDashboards() {
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;
}
// Vérifier la licence AVANT tout
if (typeof checkLicenseBeforePush === 'function') {
const licenseOk = await checkLicenseBeforePush();
@ -269,9 +225,14 @@ async function pushDashboards() {
saveCredentialsToStorage(gitUrl, gitBranch, gitToken);
}
// Vérifier si le déploiement vers SH Cluster est activé
const deployToSHCluster = document.getElementById('deploy-to-shcluster')?.checked || false;
const shAuthUser = document.getElementById('sh-auth-user')?.value?.trim() || '';
const shAuthPass = document.getElementById('sh-auth-pass')?.value?.trim() || '';
// Démarrer le push
isProcessing = true;
showLoading(true);
showLoading(true, deployToSHCluster);
hideMessages();
try {
@ -285,10 +246,17 @@ async function pushDashboards() {
git_token: gitToken,
commit_message: commitMessage,
apps: JSON.stringify(selectedApps),
user: currentUser
user: currentUser,
deploy_to_shcluster: deployToSHCluster.toString(),
deployer_host: SH_DEPLOYER_CONFIG.host,
deployer_token: SH_DEPLOYER_CONFIG.token
});
console.log(`Pushing ${selectedApps.length} apps to ${gitUrl}`);
// Ajouter les credentials SH si fournis
if (shAuthUser) params.append('sh_auth_user', shAuthUser);
if (shAuthPass) params.append('sh_auth_pass', shAuthPass);
console.log(`Pushing ${selectedApps.length} apps to ${gitUrl}${deployToSHCluster ? ' + SH Cluster deployment' : ''}`);
// Appeler le serveur
const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/push?${params.toString()}`, {
@ -302,7 +270,18 @@ async function pushDashboards() {
console.log("Push result:", result);
if (result.status === 'success') {
showMessage('success', `✅ Successfully deployed ${result.apps_pushed || selectedApps.length} application(s) to Git!`);
let message = `✅ Successfully deployed ${result.apps_pushed || selectedApps.length} application(s) to Git!`;
// Ajouter le statut du déploiement SH Cluster
if (deployToSHCluster && result.shcluster_deployment) {
if (result.shcluster_deployment.success) {
message += '\n🚀 SH Cluster deployment triggered successfully!';
} else {
message += `\n⚠️ SH Cluster deployment failed: ${result.shcluster_deployment.message}`;
}
}
showMessage('success', message);
// Reset la sélection après succès
setTimeout(() => {
@ -334,17 +313,30 @@ async function pushDashboards() {
// UTILITAIRES UI
// ============================================
function showLoading(show) {
function showLoading(show, deployToSHCluster = false) {
const loading = document.getElementById('loading');
const pushBtn = document.getElementById('push-btn');
const loadingText = document.querySelector('.loading-text');
if (loading) {
loading.classList.toggle('active', show);
}
if (loadingText && show) {
if (deployToSHCluster) {
loadingText.textContent = 'Deploying to Git and SH Cluster... Please wait';
} else {
loadingText.textContent = 'Deploying applications to Git... Please wait';
}
}
if (pushBtn) {
pushBtn.disabled = show;
pushBtn.textContent = show ? '⏳ Deploying...' : '✈️ Deploy to Git';
if (show) {
pushBtn.textContent = deployToSHCluster ? '⏳ Deploying to Git + SH...' : '⏳ Deploying...';
} else {
pushBtn.textContent = '✈️ Deploy to Git';
}
}
}
@ -493,6 +485,190 @@ async function checkServerHealth() {
}
}
// ============================================
// SH DEPLOYER FUNCTIONS
// ============================================
async function checkDeployerHealth() {
try {
const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/deployer/health`, {
method: 'GET',
timeout: 5000
});
const data = await response.json();
deployerAvailable = data.status === 'ok';
// Mettre à jour l'UI
updateDeployerUI();
console.log("SH Deployer status:", deployerAvailable ? "Available" : "Unavailable");
return deployerAvailable;
} catch (error) {
console.error("Deployer health check failed:", error);
deployerAvailable = false;
updateDeployerUI();
return false;
}
}
function updateDeployerUI() {
const deployerCheckbox = document.getElementById('deploy-to-shcluster');
const deployerStatus = document.getElementById('deployer-status');
const deployerSection = document.getElementById('deployer-section');
if (deployerCheckbox) {
deployerCheckbox.disabled = !deployerAvailable;
}
if (deployerStatus) {
if (deployerAvailable) {
deployerStatus.innerHTML = '<span style="color: #4CAF50;">● Connected</span>';
} else {
deployerStatus.innerHTML = '<span style="color: #f44336;">● Disconnected</span>';
}
}
if (deployerSection && !deployerAvailable) {
deployerSection.style.opacity = '0.6';
}
}
function loadDeployerConfig() {
try {
const saved = localStorage.getItem(GIT_PUSHER_CONFIG.deployerConfigKey);
if (saved) {
const config = JSON.parse(saved);
SH_DEPLOYER_CONFIG = { ...SH_DEPLOYER_CONFIG, ...config };
console.log("Deployer config loaded");
}
} catch (error) {
console.error("Error loading deployer config:", error);
}
}
function saveDeployerConfig() {
try {
localStorage.setItem(GIT_PUSHER_CONFIG.deployerConfigKey, JSON.stringify(SH_DEPLOYER_CONFIG));
console.log("Deployer config saved");
} catch (error) {
console.error("Error saving deployer config:", error);
}
}
function showDeployerConfigModal() {
const modal = document.createElement('div');
modal.id = 'deployer-config-modal';
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
`;
modal.innerHTML = `
<div style="background: white; border-radius: 16px; padding: 30px; max-width: 500px; width: 90%; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);">
<h2 style="margin: 0 0 20px 0; color: #333;"> SH Deployer Configuration</h2>
<div style="margin-bottom: 15px;">
<label style="display: block; font-weight: 600; margin-bottom: 5px;">Deployer Host</label>
<input type="text" id="deployer-config-host" value="${SH_DEPLOYER_CONFIG.host}"
style="width: 100%; padding: 10px; border: 2px solid #e0e0e0; border-radius: 6px; box-sizing: border-box;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; font-weight: 600; margin-bottom: 5px;">Port</label>
<input type="number" id="deployer-config-port" value="${SH_DEPLOYER_CONFIG.port}"
style="width: 100%; padding: 10px; border: 2px solid #e0e0e0; border-radius: 6px; box-sizing: border-box;">
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; font-weight: 600; margin-bottom: 5px;">Auth Token</label>
<input type="password" id="deployer-config-token" value="${SH_DEPLOYER_CONFIG.token}"
placeholder="Deployer agent token"
style="width: 100%; padding: 10px; border: 2px solid #e0e0e0; border-radius: 6px; box-sizing: border-box;">
</div>
<div style="display: flex; gap: 10px;">
<button onclick="saveDeployerConfigFromModal()" style="
flex: 1; padding: 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; border: none; border-radius: 8px; font-weight: 600; cursor: pointer;
">Save & Test</button>
<button onclick="closeDeployerConfigModal()" style="
flex: 1; padding: 12px; background: #f5f5f5; color: #333;
border: none; border-radius: 8px; font-weight: 600; cursor: pointer;
">Cancel</button>
</div>
<div id="deployer-config-message" style="margin-top: 15px; display: none; padding: 10px; border-radius: 6px;"></div>
</div>
`;
document.body.appendChild(modal);
}
function closeDeployerConfigModal() {
const modal = document.getElementById('deployer-config-modal');
if (modal) modal.remove();
}
async function saveDeployerConfigFromModal() {
const host = document.getElementById('deployer-config-host')?.value?.trim();
const port = parseInt(document.getElementById('deployer-config-port')?.value) || 9998;
const token = document.getElementById('deployer-config-token')?.value?.trim();
const msgEl = document.getElementById('deployer-config-message');
if (!host) {
if (msgEl) {
msgEl.style.display = 'block';
msgEl.style.background = '#ffebee';
msgEl.style.color = '#c62828';
msgEl.textContent = 'Please enter a host';
}
return;
}
// Mettre à jour la config
SH_DEPLOYER_CONFIG.host = host;
SH_DEPLOYER_CONFIG.port = port;
SH_DEPLOYER_CONFIG.token = token;
// Sauvegarder
saveDeployerConfig();
// Tester la connexion
if (msgEl) {
msgEl.style.display = 'block';
msgEl.style.background = '#e3f2fd';
msgEl.style.color = '#1565c0';
msgEl.textContent = 'Testing connection...';
}
const isHealthy = await checkDeployerHealth();
if (isHealthy) {
if (msgEl) {
msgEl.style.background = '#e8f5e9';
msgEl.style.color = '#2e7d32';
msgEl.textContent = '✓ Connected successfully!';
}
setTimeout(closeDeployerConfigModal, 1500);
} else {
if (msgEl) {
msgEl.style.background = '#ffebee';
msgEl.style.color = '#c62828';
msgEl.textContent = '✗ Connection failed. Check host and port.';
}
}
}
// Vérifier la santé du serveur au démarrage
setTimeout(async () => {
const healthy = await checkServerHealth();
@ -503,12 +679,31 @@ setTimeout(async () => {
}
}, 1000);
// ============================================
// EXPORT FONCTIONS GLOBALES
// ============================================
// Exposer les fonctions du deployer globalement pour les onclick du HTML
window.showDeployerConfigModal = showDeployerConfigModal;
window.closeDeployerConfigModal = closeDeployerConfigModal;
window.saveDeployerConfigFromModal = saveDeployerConfigFromModal;
// Fonction toggle pour le HTML
window.toggleDeployerAuth = function() {
var checkbox = document.getElementById('deploy-to-shcluster');
var authSection = document.getElementById('deployer-auth');
if (checkbox && authSection) {
authSection.classList.toggle('visible', checkbox.checked);
}
};
// ============================================
// EXPORT POUR DEBUG
// ============================================
window.GitPusher = {
config: GIT_PUSHER_CONFIG,
deployerConfig: SH_DEPLOYER_CONFIG,
getSelectedApps: () => selectedApps,
checkServer: checkServerHealth,
version: GIT_PUSHER_CONFIG.version
@ -530,29 +725,44 @@ window.GitPusher = {
console.log("✓ Push button event attached");
}
// Bouton Reset - chercher par le texte ou la classe
var resetBtn = document.querySelector('button.btn-secondary');
if (!resetBtn) {
// Chercher par le contenu
var allButtons = document.querySelectorAll('button.btn');
allButtons.forEach(function(btn) {
if (btn.textContent.includes('Reset') || btn.textContent.includes('🔄')) {
resetBtn = btn;
}
// Bouton Reset
var buttons = document.querySelectorAll('button.btn');
buttons.forEach(function(btn) {
if (btn.textContent.includes('Reset') || btn.textContent.includes('🔄')) {
btn.addEventListener('click', function(e) {
e.preventDefault();
console.log("Reset button clicked");
resetForm(true);
});
console.log("✓ Reset button event attached");
}
});
// Bouton Configure Deployer
var configBtn = document.querySelector('.deployer-config-btn');
if (configBtn) {
configBtn.addEventListener('click', function(e) {
e.preventDefault();
console.log("Configure button clicked");
showDeployerConfigModal();
});
console.log("✓ Configure button event attached");
}
if (resetBtn) {
resetBtn.addEventListener('click', function(e) {
e.preventDefault();
console.log("Reset button clicked");
resetForm(true);
// Checkbox deploy to shcluster
var deployCheckbox = document.getElementById('deploy-to-shcluster');
if (deployCheckbox) {
deployCheckbox.addEventListener('change', function() {
var authSection = document.getElementById('deployer-auth');
if (authSection) {
authSection.classList.toggle('visible', this.checked);
}
});
console.log("✓ Reset button event attached");
console.log("✓ Deploy checkbox event attached");
}
// Si les boutons ne sont pas encore là, réessayer
if (!pushBtn || !resetBtn) {
if (!pushBtn) {
setTimeout(tryAttach, 500);
}
}

@ -3,6 +3,7 @@
"""
Git Pusher - Main Server
Serveur HTTP pour pousser les applications Splunk vers Git
et déployer vers le Search Head Cluster via le SH Deployer
Avec système de licence par fichier .lic
"""
@ -14,6 +15,9 @@ import logging
import tempfile
import shutil
import subprocess
import ssl
import urllib.request
import urllib.error
from datetime import datetime
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qs, urlparse
@ -41,6 +45,20 @@ except ImportError:
def get_usage_stats(): return {}
def parse_license_content(c): return {}
# ============================================
# CONFIGURATION SH DEPLOYER
# ============================================
# Configuration du SH Deployer (peut être surchargée par les paramètres)
SH_DEPLOYER_CONFIG = {
"enabled": True,
"host": "10.10.40.14",
"port": 9998,
"use_ssl": True,
"token": "deployer_agent_secret_token_change_me_in_production",
"timeout": 30
}
# Configuration du logging
log_dir = '/opt/splunk/var/log/splunk'
os.makedirs(log_dir, exist_ok=True)
@ -119,7 +137,54 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
response = {
"status": "ok",
"service": "git_pusher",
"timestamp": datetime.now().isoformat()
"timestamp": datetime.now().isoformat(),
"sh_deployer": {
"enabled": SH_DEPLOYER_CONFIG.get("enabled", True),
"host": SH_DEPLOYER_CONFIG.get("host"),
"port": SH_DEPLOYER_CONFIG.get("port")
}
}
self.wfile.write(json.dumps(response).encode())
# ============================================
# ENDPOINTS SH DEPLOYER
# ============================================
elif path == '/deployer/health':
# Vérifier la santé du SH Deployer
result = call_deployer_agent("/health")
if result.get("success"):
response = {
"status": "ok",
"deployer": result.get("data"),
"config": {
"host": SH_DEPLOYER_CONFIG.get("host"),
"port": SH_DEPLOYER_CONFIG.get("port")
}
}
else:
response = {
"status": "error",
"error": result.get("error"),
"config": {
"host": SH_DEPLOYER_CONFIG.get("host"),
"port": SH_DEPLOYER_CONFIG.get("port")
}
}
self.wfile.write(json.dumps(response).encode())
elif path == '/deployer/status':
# Statut du SH Deployer
result = get_deployer_status()
self.wfile.write(json.dumps(result).encode())
elif path == '/deployer/config':
# Configuration actuelle du SH Deployer
response = {
"enabled": SH_DEPLOYER_CONFIG.get("enabled", True),
"host": SH_DEPLOYER_CONFIG.get("host"),
"port": SH_DEPLOYER_CONFIG.get("port"),
"use_ssl": SH_DEPLOYER_CONFIG.get("use_ssl", True)
}
self.wfile.write(json.dumps(response).encode())
@ -224,7 +289,7 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
self.wfile.write(json.dumps(response).encode())
def handle_git_push(self, query_params):
"""Gérer le push Git"""
"""Gérer le push Git et optionnellement le déploiement vers SH Cluster"""
try:
# Extraire les paramètres
git_url = query_params.get('git_url', [''])[0]
@ -234,7 +299,14 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
apps_json = query_params.get('apps', query_params.get('dashboards', ['[]']))[0]
user = query_params.get('user', ['unknown'])[0]
logger.info(f"Parameters: git_url={git_url}, branch={git_branch}, user={user}")
# Paramètres pour le déploiement SH Cluster
deploy_to_shcluster = query_params.get('deploy_to_shcluster', ['false'])[0].lower() == 'true'
deployer_host = query_params.get('deployer_host', [SH_DEPLOYER_CONFIG.get('host', '')])[0]
deployer_token = query_params.get('deployer_token', [SH_DEPLOYER_CONFIG.get('token', '')])[0]
sh_auth_user = query_params.get('sh_auth_user', [''])[0]
sh_auth_pass = query_params.get('sh_auth_pass', [''])[0]
logger.info(f"Parameters: git_url={git_url}, branch={git_branch}, user={user}, deploy_to_shcluster={deploy_to_shcluster}")
# Parser les apps
try:
@ -322,7 +394,7 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
if result.returncode != 0:
logger.warning(f"Commit warning: {result.stderr}")
logger.info("Pushing...")
logger.info("Pushing to Git...")
result = subprocess.run(['git', 'push', 'origin', git_branch],
cwd=temp_dir, capture_output=True, text=True, timeout=60)
@ -332,13 +404,59 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
# Incrémenter les stats d'utilisation
increment_usage()
logger.info("Push successful!")
logger.info("Git push successful!")
# ============================================
# DÉPLOIEMENT VERS SH CLUSTER (optionnel)
# ============================================
deployer_result = None
if deploy_to_shcluster:
logger.info("Triggering deployment to SH Cluster...")
# Configurer le deployer
deployer_config = SH_DEPLOYER_CONFIG.copy()
if deployer_host:
deployer_config["host"] = deployer_host
if deployer_token:
deployer_config["token"] = deployer_token
# Appeler le SH Deployer pour pull + deploy
deployer_result = trigger_deployer_pull_and_deploy(
git_url=git_url,
git_token=git_token,
auth_user=sh_auth_user if sh_auth_user else None,
auth_pass=sh_auth_pass if sh_auth_pass else None,
config=deployer_config
)
if deployer_result.get("success"):
logger.info("SH Cluster deployment triggered successfully")
else:
logger.error(f"SH Cluster deployment failed: {deployer_result.get('error')}")
# Préparer la réponse
response = {
"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")
}
# Ajouter les infos de déploiement si activé
if deploy_to_shcluster:
response["shcluster_deployment"] = {
"triggered": True,
"success": deployer_result.get("success", False) if deployer_result else False,
"message": deployer_result.get("data", {}).get("message") if deployer_result and deployer_result.get("success") else deployer_result.get("error") if deployer_result else "Not triggered"
}
if deployer_result and deployer_result.get("success"):
response["message"] += " and triggered SH Cluster deployment"
else:
response["message"] += " (SH Cluster deployment failed)"
self.wfile.write(json.dumps(response).encode())
finally:
@ -417,6 +535,146 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
return app_directories
# ============================================
# FONCTIONS SH DEPLOYER
# ============================================
def call_deployer_agent(endpoint, method="GET", data=None, config=None):
"""
Appeler l'agent SH Deployer
Args:
endpoint: Endpoint à appeler (ex: /health, /pull, /deploy)
method: GET ou POST
data: Données à envoyer (dict)
config: Configuration (override SH_DEPLOYER_CONFIG)
Returns:
dict avec success, data ou error
"""
if config is None:
config = SH_DEPLOYER_CONFIG
if not config.get("enabled", True):
return {"success": False, "error": "SH Deployer is disabled"}
host = config.get("host", "10.10.40.14")
port = config.get("port", 9998)
use_ssl = config.get("use_ssl", True)
token = config.get("token", "")
timeout = config.get("timeout", 30)
protocol = "https" if use_ssl else "http"
url = f"{protocol}://{host}:{port}{endpoint}"
logger.info(f"Calling SH Deployer: {method} {url}")
try:
# Créer le contexte SSL (ignorer les certificats auto-signés)
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
# Préparer les données
if data:
json_data = json.dumps(data).encode('utf-8')
else:
json_data = None
# Créer la requête
req = urllib.request.Request(url, data=json_data, method=method)
req.add_header('Content-Type', 'application/json')
req.add_header('X-Auth-Token', token)
# Exécuter la requête
with urllib.request.urlopen(req, timeout=timeout, context=ssl_context) as response:
response_data = json.loads(response.read().decode('utf-8'))
logger.info(f"SH Deployer response: {response_data}")
return {"success": True, "data": response_data}
except urllib.error.HTTPError as e:
error_body = e.read().decode('utf-8') if e.fp else str(e)
logger.error(f"SH Deployer HTTP error {e.code}: {error_body}")
return {"success": False, "error": f"HTTP {e.code}: {error_body}"}
except urllib.error.URLError as e:
logger.error(f"SH Deployer connection error: {e.reason}")
return {"success": False, "error": f"Connection error: {e.reason}"}
except Exception as e:
logger.error(f"SH Deployer error: {str(e)}")
return {"success": False, "error": str(e)}
def check_deployer_health(config=None):
"""Vérifier si l'agent SH Deployer est accessible"""
result = call_deployer_agent("/health", config=config)
return result.get("success", False)
def trigger_deployer_pull(git_url, git_token, config=None):
"""
Déclencher un pull sur le SH Deployer
Args:
git_url: URL du repository Git
git_token: Token Git pour l'authentification
config: Configuration du deployer
"""
data = {
"repo_url": git_url,
"git_token": git_token,
"apps_subdir": "apps"
}
return call_deployer_agent("/pull", method="POST", data=data, config=config)
def trigger_deployer_deploy(target_uri=None, auth_user=None, auth_pass=None, config=None):
"""
Déclencher le déploiement du bundle sur le SH Cluster
Args:
target_uri: URI du captain du SH Cluster (optionnel)
auth_user: Utilisateur Splunk
auth_pass: Mot de passe Splunk
config: Configuration du deployer
"""
data = {}
if target_uri:
data["target_uri"] = target_uri
if auth_user:
data["auth_user"] = auth_user
if auth_pass:
data["auth_pass"] = auth_pass
return call_deployer_agent("/deploy", method="POST", data=data, config=config)
def trigger_deployer_pull_and_deploy(git_url, git_token, target_uri=None, auth_user=None, auth_pass=None, config=None):
"""
Déclencher pull + deploy en une seule opération
"""
data = {
"repo_url": git_url,
"git_token": git_token,
"apps_subdir": "apps"
}
if target_uri:
data["target_uri"] = target_uri
if auth_user:
data["auth_user"] = auth_user
if auth_pass:
data["auth_pass"] = auth_pass
return call_deployer_agent("/pull-and-deploy", method="POST", data=data, config=config)
def get_deployer_status(config=None):
"""Récupérer le statut du SH Deployer"""
return call_deployer_agent("/status", config=config)
def start_server(port=9999, use_ssl=True):
"""Démarrer le serveur HTTP/HTTPS"""
import ssl

@ -1,451 +1,535 @@
<?xml version="1.0" encoding="UTF-8"?>
<dashboard version="1.1" script="license_validation.js, git_pusher.js" hideEdit="true" hideExport="true" hideTitle="true">
<label>🚀 Git Pusher - Deploy Applications</label>
<description>Modern interface to push Splunk applications to Git repository</description>
<dashboard version="1.1" script="license_validation.js, git_pusher.js" hideEdit="true" hideExport="true">
<label>Git Pusher - Deploy Applications</label>
<description>Push Splunk applications to Git repository and deploy to SH Cluster</description>
<!-- BADGE DE LICENCE -->
<row>
<panel>
<html>
<div id="license-badge-container" style="position: absolute; top: 20px; right: 20px; z-index: 1000;"></div>
</html>
</panel>
</row>
<search id="dsearch">
<query>| rest /services/apps/local | search disabled=0 | table title, label, description | rename title as name | sort label</query>
<earliest>-4h@h</earliest>
<earliest>-1m</earliest>
<latest>now</latest>
</search>
<row>
<panel>
<html>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
/* ============================================ */
/* GIT PUSHER STYLES - VERSION 2.1 */
/* ============================================ */
.git-pusher-container {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px 20px;
text-align: center;
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
/* Header avec badge de licence */
.header-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
border-radius: 0 0 20px 20px;
padding-bottom: 20px;
border-bottom: 2px solid #e0e0e0;
}
.header h1 {
font-size: 32px;
font-weight: 700;
margin-bottom: 8px;
letter-spacing: -0.5px;
.header-title {
display: flex;
align-items: center;
gap: 15px;
}
.header p {
font-size: 14px;
opacity: 0.9;
.header-title h1 {
margin: 0;
font-size: 28px;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
.version-badge {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
/* Container pour le badge de licence */
#license-badge-container {
min-width: 200px;
text-align: right;
}
.grid {
/* Grille principale */
.main-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
@media (max-width: 1024px) {
.grid {
@media (max-width: 1200px) {
.main-grid {
grid-template-columns: 1fr;
}
}
.card {
background: white;
/* Sections */
.section {
background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 16px;
padding: 30px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
padding: 25px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border: 1px solid #e8e8e8;
}
.card:hover {
box-shadow: 0 15px 50px rgba(102, 126, 234, 0.2);
transform: translateY(-5px);
}
.card-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
.section-title {
display: flex;
align-items: center;
gap: 10px;
margin: 0 0 20px 0;
padding-bottom: 15px;
border-bottom: 2px solid #e0e0e0;
color: #333;
font-size: 18px;
}
.card-title::before {
.section-title::before {
content: '';
display: inline-block;
display: block;
width: 4px;
height: 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 2px;
}
/* Formulaire */
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: 600;
color: #333;
margin-bottom: 8px;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #444;
}
.form-group input[type="text"],
.form-group input[type="password"],
.form-group textarea {
width: 100%;
padding: 12px 16px;
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
font-family: 'Segoe UI', sans-serif;
box-sizing: border-box;
}
.form-group input[type="text"]:focus,
.form-group input[type="password"]:focus,
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
outline: none;
}
.form-group textarea {
resize: vertical;
min-height: 100px;
min-height: 80px;
}
.form-hint {
font-size: 12px;
color: #888;
margin-top: 5px;
}
/* Checkbox personnalisé */
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
margin-top: 15px;
padding: 12px;
background: #f5f7ff;
background: #f8f9fa;
border-radius: 8px;
}
.checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
accent-color: #667eea;
}
.checkbox-group label {
margin: 0;
cursor: pointer;
font-weight: 500;
font-weight: normal;
color: #555;
font-size: 13px;
text-transform: none;
}
.apps-list {
border: 2px solid #e0e0e0;
border-radius: 12px;
/* Liste des applications */
#dashboard-list {
max-height: 400px;
overflow-y: auto;
background: #fafbfc;
padding-right: 10px;
}
.apps-list::-webkit-scrollbar {
#dashboard-list::-webkit-scrollbar {
width: 8px;
}
.apps-list::-webkit-scrollbar-track {
#dashboard-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
border-radius: 4px;
}
.apps-list::-webkit-scrollbar-thumb {
background: #667eea;
border-radius: 10px;
#dashboard-list::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
#dashboard-list::-webkit-scrollbar-thumb:hover {
background: #a1a1a1;
}
.app-header {
padding: 15px 16px;
border-bottom: 2px solid #e0e0e0;
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
font-weight: 600;
color: #667eea;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 10;
padding: 10px 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
margin-bottom: 15px;
color: white;
}
.app-count {
background: #667eea;
.app-header label {
color: white;
font-weight: 600;
}
.app-count {
background: rgba(255, 255, 255, 0.2);
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 700;
font-size: 13px;
}
.app-item {
padding: 12px 16px;
border-bottom: 1px solid #e0e0e0;
display: flex;
align-items: center;
gap: 12px;
padding: 12px 15px;
margin-bottom: 8px;
background: white;
border: 1px solid #e8e8e8;
border-radius: 8px;
transition: all 0.2s ease;
}
.app-item:hover {
background: #f0f4ff;
border-color: #667eea;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15);
}
.app-item input[type="checkbox"] {
margin-right: 12px;
width: 18px;
height: 18px;
cursor: pointer;
accent-color: #667eea;
}
.app-item label {
margin: 0;
flex: 1;
cursor: pointer;
font-weight: 500;
color: #333;
display: inline-flex;
font-size: 14px;
}
.app-badge {
background: #e8f0fe;
color: #1a73e8;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-family: monospace;
}
/* Section SH Deployer */
.deployer-section {
margin-top: 20px;
padding: 20px;
background: linear-gradient(145deg, #fff8e1 0%, #ffecb3 100%);
border-radius: 12px;
border: 2px solid #ffd54f;
}
.deployer-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.deployer-title {
display: flex;
align-items: center;
gap: 10px;
width: calc(100% - 40px);
font-weight: 600;
color: #f57c00;
}
.app-badge {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
.deployer-status {
font-size: 13px;
}
.deployer-config-btn {
background: none;
border: 1px solid #f57c00;
color: #f57c00;
padding: 5px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
}
.deployer-config-btn:hover {
background: #f57c00;
color: white;
padding: 3px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 700;
white-space: nowrap;
}
.deployer-checkbox {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
.deployer-checkbox input {
width: 20px;
height: 20px;
accent-color: #f57c00;
}
.deployer-auth {
display: none;
margin-top: 15px;
padding-top: 15px;
border-top: 1px dashed #ffd54f;
}
.deployer-auth.visible {
display: block;
}
.deployer-auth-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.deployer-auth input {
width: 100%;
padding: 10px;
border: 1px solid #ffd54f;
border-radius: 6px;
background: white;
}
/* Boutons */
.button-group {
display: flex;
gap: 12px;
margin-top: 30px;
gap: 15px;
margin-top: 25px;
}
.btn {
padding: 12px 28px;
padding: 14px 28px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
flex: 1;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
flex: 2;
}
.btn-primary:hover:not(:disabled) {
box-shadow: 0 6px 25px rgba(102, 126, 234, 0.5);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: #f5f7ff;
color: #667eea;
border: 2px solid #667eea;
background: #f5f5f5;
color: #333;
flex: 1;
}
.btn-secondary:hover:not(:disabled) {
background: #667eea;
color: white;
.btn-secondary:hover {
background: #e0e0e0;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
/* Messages */
.message {
padding: 15px 20px;
border-radius: 8px;
margin-top: 20px;
display: none;
align-items: center;
gap: 10px;
white-space: pre-line;
}
.message.active {
display: flex;
}
.message.success {
background: linear-gradient(145deg, #e8f5e9 0%, #c8e6c9 100%);
color: #2e7d32;
border: 1px solid #a5d6a7;
}
.message.error {
background: linear-gradient(145deg, #ffebee 0%, #ffcdd2 100%);
color: #c62828;
border: 1px solid #ef9a9a;
}
/* Loading */
.loading {
display: none;
text-align: center;
padding: 30px;
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
border-radius: 12px;
margin: 20px 0;
align-items: center;
gap: 15px;
padding: 20px;
background: linear-gradient(145deg, #e3f2fd 0%, #bbdefb 100%);
border-radius: 8px;
margin-top: 20px;
border: 1px solid #90caf9;
}
.loading.active {
display: block;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
display: flex;
}
.spinner {
border: 3px solid #e0e0e0;
border-top: 3px solid #667eea;
width: 24px;
height: 24px;
border: 3px solid #90caf9;
border-top-color: #1976d2;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
to { transform: rotate(360deg); }
}
.loading-text {
color: #667eea;
font-weight: 600;
font-size: 14px;
}
.message {
padding: 16px 20px;
border-radius: 12px;
margin: 15px 0;
display: none;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message.active {
display: block;
}
.success-message {
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
color: white;
border-left: 4px solid white;
}
.error-message {
background: linear-gradient(135deg, #f44336 0%, #da190b 100%);
color: white;
border-left: 4px solid white;
}
.info-banner {
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
border: 2px solid #667eea;
border-radius: 12px;
padding: 16px 20px;
margin-bottom: 20px;
color: #667eea;
font-size: 13px;
color: #1565c0;
font-weight: 500;
display: flex;
align-items: center;
gap: 10px;
}
.divider {
height: 1px;
background: linear-gradient(90deg, transparent, #e0e0e0, transparent);
margin: 30px 0;
}
.footer {
text-align: center;
color: #999;
font-size: 12px;
margin-top: 40px;
padding-bottom: 20px;
}
</style>
<div class="header">
<h1>🚀 Git Pusher</h1>
<p>Deploy your Splunk applications to Git with confidence</p>
</div>
<div class="container">
<div class="info-banner">
✨ Configure your Git settings below and select the applications you want to deploy to your repository
<div class="git-pusher-container">
<!-- Header -->
<div class="header-section">
<div class="header-title">
<h1>🚀 Git Pusher</h1>
<span class="version-badge">v2.1</span>
</div>
<div id="license-badge-container">
<!-- Le badge de licence sera inséré ici par JavaScript -->
</div>
</div>
<div class="grid">
<!-- Configuration Panel -->
<div class="card">
<div class="card-title">⚙️ Configuration</div>
<!-- Grille principale -->
<div class="main-grid">
<!-- Colonne gauche: Applications -->
<div class="section">
<h2 class="section-title">📦 Applications</h2>
<div id="dashboard-list">
<!-- Liste générée par JavaScript -->
<p style="color: #888; text-align: center; padding: 20px;">Loading applications...</p>
</div>
</div>
<!-- Colonne droite: Configuration -->
<div class="section">
<h2 class="section-title">⚙️ Git Configuration</h2>
<div class="form-group">
<label for="git-url">Git Repository URL</label>
<input type="text" id="git-url" placeholder="https://github.com/username/repo.git" />
<label for="git-url">Repository URL</label>
<input type="text" id="git-url" placeholder="https://github.com/user/repo.git" />
<div class="form-hint">HTTPS URL of your Git repository</div>
</div>
<div class="form-group">
<label for="git-branch">Target Branch</label>
<input type="text" id="git-branch" placeholder="main" value="main" />
<label for="git-branch">Branch</label>
<input type="text" id="git-branch" value="main" placeholder="main" />
</div>
<div class="form-group">
<label for="git-token">Git Token / Password</label>
<input type="password" id="git-token" placeholder="Enter your Git token or password" />
</div>
<div class="checkbox-group">
<input type="checkbox" id="save-credentials" />
<label for="save-credentials">Save credentials locally</label>
<label for="git-token">Access Token / Password</label>
<input type="password" id="git-token" placeholder="ghp_xxxx or personal access token" />
<div class="form-hint">Personal access token with write permissions</div>
</div>
<div class="form-group">
<label for="commit-message">Commit Message</label>
<textarea id="commit-message" placeholder="Describe your changes..."></textarea>
</div>
<div class="checkbox-group">
<input type="checkbox" id="save-credentials" />
<label for="save-credentials">Remember credentials for next time</label>
</div>
<!-- Section SH Deployer -->
<div class="deployer-section" id="deployer-section">
<div class="deployer-header">
<div class="deployer-title">
🎯 Deploy to Search Head Cluster
<span class="deployer-status" id="deployer-status">
<span style="color: #888;">● Checking...</span>
</span>
</div>
<button class="deployer-config-btn" onclick="showDeployerConfigModal()">⚙️ Configure</button>
</div>
<div class="deployer-checkbox">
<input type="checkbox" id="deploy-to-shcluster" onchange="toggleDeployerAuth()" />
<label for="deploy-to-shcluster">
<strong>Enable automatic deployment</strong><br/>
<small style="color: #888;">After pushing to Git, pull and apply bundle to SH Cluster</small>
</label>
</div>
<div class="deployer-auth" id="deployer-auth">
<div class="form-hint" style="margin-bottom: 10px;">
Splunk credentials for applying shcluster-bundle (optional if using default)
</div>
<div class="deployer-auth-grid">
<input type="text" id="sh-auth-user" placeholder="Splunk username (optional)" />
<input type="password" id="sh-auth-pass" placeholder="Splunk password (optional)" />
</div>
</div>
</div>
<!-- Boutons -->
<div class="button-group">
<button class="btn btn-primary" id="push-btn" onclick="pushDashboards()">
✈️ Deploy to Git
@ -454,40 +538,35 @@
🔄 Reset
</button>
</div>
</div>
<!-- Applications Panel -->
<div class="card">
<div class="card-title">📦 Applications</div>
<div class="apps-list" id="dashboard-list">
<div style="padding: 30px; text-align: center; color: #999;">
<div class="spinner"></div>
<p>Loading applications...</p>
</div>
<!-- Messages -->
<div class="loading" id="loading">
<div class="spinner"></div>
<span class="loading-text">Deploying applications to Git... Please wait</span>
</div>
<div class="message success" id="success-message"></div>
<div class="message error" id="error-message"></div>
</div>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<div class="loading-text">Deploying applications to Git... Please wait</div>
</div>
<div class="message success-message" id="success-msg">
<span id="success-text">Applications successfully deployed to Git!</span>
</div>
<div class="message error-message" id="error-msg">
<span id="error-text">Error occurred while deploying applications</span>
</div>
<div class="footer">
Made with ❤️ for Splunk • Git Pusher v2.0
</div>
</div>
<script>
function toggleDeployerAuth() {
var checkbox = document.getElementById('deploy-to-shcluster');
var authSection = document.getElementById('deployer-auth');
if (checkbox) {
if (authSection) {
if (checkbox.checked) {
authSection.classList.add('visible');
} else {
authSection.classList.remove('visible');
}
}
}
}
</script>
</html>
</panel>
</row>
</dashboard>

@ -1 +1 @@
{"pushes_today": 10, "pushes_total": 19, "last_push_date": "2026-02-01", "apps_pushed": []}
{"pushes_today": 7, "pushes_total": 28, "last_push_date": "2026-02-06", "apps_pushed": []}
Loading…
Cancel
Save