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 // GIT PUSHER - MAIN JAVASCRIPT
// Version 2.0 avec système de licence par fichier // Version 2.1 avec déploiement vers SH Cluster
// ============================================ // ============================================
// Configuration // Configuration
const GIT_PUSHER_CONFIG = { const GIT_PUSHER_CONFIG = {
// Détecter automatiquement l'URL du serveur
serverUrl: window.location.protocol + '//' + window.location.hostname + ':9999', serverUrl: window.location.protocol + '//' + window.location.hostname + ':9999',
credentialsKey: 'git_pusher_credentials', 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 // État global
let selectedApps = []; let selectedApps = [];
let isProcessing = false; let isProcessing = false;
let deployerAvailable = false;
// ============================================ // ============================================
// INITIALISATION // INITIALISATION
@ -25,7 +36,7 @@ require([
'splunkjs/mvc/simplexml/ready!' 'splunkjs/mvc/simplexml/ready!'
], function($, mvc, SearchManager) { ], function($, mvc, SearchManager) {
console.log("Git Pusher v2.0 initializing..."); console.log("Git Pusher v2.1 initializing...");
// Initialiser le système de licence // Initialiser le système de licence
if (typeof initializeLicense === 'function') { if (typeof initializeLicense === 'function') {
@ -37,6 +48,12 @@ require([
// Charger les credentials sauvegardés // Charger les credentials sauvegardés
loadSavedCredentials(); 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 // Récupérer les résultats de recherche pour les apps
const searchManager = mvc.Components.get('dsearch'); const searchManager = mvc.Components.get('dsearch');
@ -59,23 +76,6 @@ require([
window.toggleSelectAll = toggleSelectAll; 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 // RENDU DE LA LISTE DES APPLICATIONS
// ============================================ // ============================================
@ -84,16 +84,11 @@ function renderAppsList(rows, fields) {
const container = document.getElementById('dashboard-list'); const container = document.getElementById('dashboard-list');
if (!container) return; if (!container) return;
console.log("Fields:", fields);
console.log("First row:", rows[0]);
// Trouver les index des colonnes // Trouver les index des colonnes
const nameIdx = fields.indexOf('name'); const nameIdx = fields.indexOf('name');
const labelIdx = fields.indexOf('label'); const labelIdx = fields.indexOf('label');
const descIdx = fields.indexOf('description'); const descIdx = fields.indexOf('description');
console.log("Index - name:", nameIdx, "label:", labelIdx);
// Générer le HTML // Générer le HTML
let html = ` let html = `
<div class="app-header"> <div class="app-header">
@ -105,34 +100,16 @@ function renderAppsList(rows, fields) {
</div> </div>
`; `;
let validApps = 0;
rows.forEach((row, index) => { rows.forEach((row, index) => {
// Récupérer le nom - essayer plusieurs méthodes const name = row[nameIdx] || '';
let name = ''; const label = row[labelIdx] || name;
if (nameIdx >= 0 && row[nameIdx]) { const desc = row[descIdx] || '';
name = row[nameIdx];
} else if (labelIdx >= 0 && row[labelIdx]) { // Ignorer certaines apps système
// Si name est null, utiliser label comme fallback temporaire if (name.startsWith('splunk_') || name === 'learned' || name === 'launcher') {
// 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; return;
} }
validApps++;
html += ` html += `
<div class="app-item"> <div class="app-item">
<input type="checkbox" <input type="checkbox"
@ -142,7 +119,7 @@ function renderAppsList(rows, fields) {
onchange="updateSelectedApps()" /> onchange="updateSelectedApps()" />
<label for="app-${index}"> <label for="app-${index}">
<span class="app-badge">${name}</span> <span class="app-badge">${name}</span>
${label !== name ? ' - ' + label : ''} ${label !== name ? label : ''}
</label> </label>
</div> </div>
`; `;
@ -150,7 +127,7 @@ function renderAppsList(rows, fields) {
container.innerHTML = html; 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; 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 // Vérifier la licence AVANT tout
if (typeof checkLicenseBeforePush === 'function') { if (typeof checkLicenseBeforePush === 'function') {
const licenseOk = await checkLicenseBeforePush(); const licenseOk = await checkLicenseBeforePush();
@ -269,9 +225,14 @@ async function pushDashboards() {
saveCredentialsToStorage(gitUrl, gitBranch, gitToken); 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 // Démarrer le push
isProcessing = true; isProcessing = true;
showLoading(true); showLoading(true, deployToSHCluster);
hideMessages(); hideMessages();
try { try {
@ -285,10 +246,17 @@ async function pushDashboards() {
git_token: gitToken, git_token: gitToken,
commit_message: commitMessage, commit_message: commitMessage,
apps: JSON.stringify(selectedApps), 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 // Appeler le serveur
const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/push?${params.toString()}`, { const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/push?${params.toString()}`, {
@ -302,7 +270,18 @@ async function pushDashboards() {
console.log("Push result:", result); console.log("Push result:", result);
if (result.status === 'success') { 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 // Reset la sélection après succès
setTimeout(() => { setTimeout(() => {
@ -334,17 +313,30 @@ async function pushDashboards() {
// UTILITAIRES UI // UTILITAIRES UI
// ============================================ // ============================================
function showLoading(show) { function showLoading(show, deployToSHCluster = false) {
const loading = document.getElementById('loading'); const loading = document.getElementById('loading');
const pushBtn = document.getElementById('push-btn'); const pushBtn = document.getElementById('push-btn');
const loadingText = document.querySelector('.loading-text');
if (loading) { if (loading) {
loading.classList.toggle('active', show); 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) { if (pushBtn) {
pushBtn.disabled = show; 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 // Vérifier la santé du serveur au démarrage
setTimeout(async () => { setTimeout(async () => {
const healthy = await checkServerHealth(); const healthy = await checkServerHealth();
@ -503,12 +679,31 @@ setTimeout(async () => {
} }
}, 1000); }, 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 // EXPORT POUR DEBUG
// ============================================ // ============================================
window.GitPusher = { window.GitPusher = {
config: GIT_PUSHER_CONFIG, config: GIT_PUSHER_CONFIG,
deployerConfig: SH_DEPLOYER_CONFIG,
getSelectedApps: () => selectedApps, getSelectedApps: () => selectedApps,
checkServer: checkServerHealth, checkServer: checkServerHealth,
version: GIT_PUSHER_CONFIG.version version: GIT_PUSHER_CONFIG.version
@ -530,29 +725,44 @@ window.GitPusher = {
console.log("✓ Push button event attached"); console.log("✓ Push button event attached");
} }
// Bouton Reset - chercher par le texte ou la classe // Bouton Reset
var resetBtn = document.querySelector('button.btn-secondary'); var buttons = document.querySelectorAll('button.btn');
if (!resetBtn) { buttons.forEach(function(btn) {
// Chercher par le contenu
var allButtons = document.querySelectorAll('button.btn');
allButtons.forEach(function(btn) {
if (btn.textContent.includes('Reset') || btn.textContent.includes('🔄')) { if (btn.textContent.includes('Reset') || btn.textContent.includes('🔄')) {
resetBtn = btn; btn.addEventListener('click', function(e) {
}
});
}
if (resetBtn) {
resetBtn.addEventListener('click', function(e) {
e.preventDefault(); e.preventDefault();
console.log("Reset button clicked"); console.log("Reset button clicked");
resetForm(true); resetForm(true);
}); });
console.log("✓ Reset button event attached"); 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");
}
// Checkbox deploy to shcluster
var deployCheckbox = document.getElementById('deploy-to-shcluster');
if (deployCheckbox) {
deployCheckbox.addEventListener('change', function() {
var authSection = document.getElementById('deployer-auth');
if (authSection) {
authSection.classList.toggle('visible', this.checked);
}
});
console.log("✓ Deploy checkbox event attached");
}
// Si les boutons ne sont pas encore là, réessayer // Si les boutons ne sont pas encore là, réessayer
if (!pushBtn || !resetBtn) { if (!pushBtn) {
setTimeout(tryAttach, 500); setTimeout(tryAttach, 500);
} }
} }

@ -3,6 +3,7 @@
""" """
Git Pusher - Main Server Git Pusher - Main Server
Serveur HTTP pour pousser les applications Splunk vers Git 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 Avec système de licence par fichier .lic
""" """
@ -14,6 +15,9 @@ import logging
import tempfile import tempfile
import shutil import shutil
import subprocess import subprocess
import ssl
import urllib.request
import urllib.error
from datetime import datetime from datetime import datetime
from http.server import HTTPServer, BaseHTTPRequestHandler from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qs, urlparse from urllib.parse import parse_qs, urlparse
@ -41,6 +45,20 @@ except ImportError:
def get_usage_stats(): return {} def get_usage_stats(): return {}
def parse_license_content(c): 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 # Configuration du logging
log_dir = '/opt/splunk/var/log/splunk' log_dir = '/opt/splunk/var/log/splunk'
os.makedirs(log_dir, exist_ok=True) os.makedirs(log_dir, exist_ok=True)
@ -119,7 +137,54 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
response = { response = {
"status": "ok", "status": "ok",
"service": "git_pusher", "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()) self.wfile.write(json.dumps(response).encode())
@ -224,7 +289,7 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
self.wfile.write(json.dumps(response).encode()) self.wfile.write(json.dumps(response).encode())
def handle_git_push(self, query_params): 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: try:
# Extraire les paramètres # Extraire les paramètres
git_url = query_params.get('git_url', [''])[0] 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] apps_json = query_params.get('apps', query_params.get('dashboards', ['[]']))[0]
user = query_params.get('user', ['unknown'])[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 # Parser les apps
try: try:
@ -322,7 +394,7 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
if result.returncode != 0: if result.returncode != 0:
logger.warning(f"Commit warning: {result.stderr}") logger.warning(f"Commit warning: {result.stderr}")
logger.info("Pushing...") logger.info("Pushing to Git...")
result = subprocess.run(['git', 'push', 'origin', git_branch], result = subprocess.run(['git', 'push', 'origin', git_branch],
cwd=temp_dir, capture_output=True, text=True, timeout=60) cwd=temp_dir, capture_output=True, text=True, timeout=60)
@ -332,13 +404,59 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
# Incrémenter les stats d'utilisation # Incrémenter les stats d'utilisation
increment_usage() 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 = { response = {
"status": "success", "status": "success",
"message": f"Successfully pushed {len(app_directories)} application(s) to Git", "message": f"Successfully pushed {len(app_directories)} application(s) to Git",
"apps_pushed": len(app_directories), "apps_pushed": len(app_directories),
"license_type": license_info.get("type_name", "N/A") "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()) self.wfile.write(json.dumps(response).encode())
finally: finally:
@ -417,6 +535,146 @@ class GitPusherRequestHandler(BaseHTTPRequestHandler):
return app_directories 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): def start_server(port=9999, use_ssl=True):
"""Démarrer le serveur HTTP/HTTPS""" """Démarrer le serveur HTTP/HTTPS"""
import ssl import ssl

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