parent
3c2ecb79ab
commit
e7be2c8f38
@ -0,0 +1,406 @@
|
|||||||
|
// ============================================
|
||||||
|
// CHARGER LES DASHBOARDS DYNAMIQUEMENT
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Charger les applications
|
||||||
|
function loadAvailableApps() {
|
||||||
|
console.log("loadAvailableApps called");
|
||||||
|
|
||||||
|
const apiUrl = '/en-US/splunkd/__raw/services/apps/local?output_mode=json&count=0';
|
||||||
|
|
||||||
|
fetch(apiUrl)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('HTTP ' + response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log("Apps Response: Found " + (data.entry ? data.entry.length : 0) + " apps");
|
||||||
|
|
||||||
|
if (data.entry && data.entry.length > 0) {
|
||||||
|
const apps = data.entry
|
||||||
|
.filter(item => {
|
||||||
|
// Filtrer les apps système
|
||||||
|
const isHidden = item.content && item.content.is_visible === 0;
|
||||||
|
const appName = item.name;
|
||||||
|
// Exclure les apps système
|
||||||
|
const systemApps = ['launcher', 'splunk_monitoring_console', 'introspection'];
|
||||||
|
return !isHidden && !systemApps.includes(appName);
|
||||||
|
})
|
||||||
|
.map(item => ({
|
||||||
|
id: item.name,
|
||||||
|
name: item.content.label || item.name,
|
||||||
|
description: item.content.description || ''
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
console.log("Filtered apps: " + apps.length);
|
||||||
|
populateAppsList(apps);
|
||||||
|
} else {
|
||||||
|
console.warn("No apps found");
|
||||||
|
showAppsEmpty();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("API Error:", error);
|
||||||
|
showAppsEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateAppsList(apps) {
|
||||||
|
console.log("populateAppsList called with", apps.length, "apps");
|
||||||
|
|
||||||
|
const container = document.getElementById('dashboard-list');
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
console.error("dashboard-list container not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!apps || apps.length === 0) {
|
||||||
|
showAppsEmpty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '<div class="select-all-group">';
|
||||||
|
html += '<input type="checkbox" id="select-all" onchange="toggleSelectAll(this)">';
|
||||||
|
html += '<label for="select-all">Select All (' + apps.length + ' apps)</label>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
apps.forEach((app, index) => {
|
||||||
|
const checkboxId = 'app-' + index;
|
||||||
|
html += '<div class="dashboard-item">';
|
||||||
|
html += '<input type="checkbox" id="' + checkboxId + '" value="' + app.id + '" data-app="' + app.id + '" data-name="' + app.name + '">';
|
||||||
|
html += '<label for="' + checkboxId + '"><strong>' + app.name + '</strong> <span class="app-badge">' + app.id + '</span>';
|
||||||
|
if (app.description) {
|
||||||
|
html += '<div style="font-size: 11px; color: #666; margin-top: 3px;">' + app.description + '</div>';
|
||||||
|
}
|
||||||
|
html += '</label>';
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
console.log('Successfully populated ' + apps.length + ' apps');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAppsEmpty() {
|
||||||
|
const container = document.getElementById('dashboard-list');
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '<div class="dashboard-empty">No apps found</div>';
|
||||||
|
}
|
||||||
|
console.log("Displayed empty state");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attendre que le DOM soit chargé
|
||||||
|
function initScript() {
|
||||||
|
console.log("initScript called");
|
||||||
|
|
||||||
|
// Charger les applications
|
||||||
|
loadAvailableApps();
|
||||||
|
|
||||||
|
// Attacher les event listeners au bouton
|
||||||
|
const pushBtn = document.getElementById('push-btn');
|
||||||
|
if (pushBtn) {
|
||||||
|
console.log("Push button found, attaching listener");
|
||||||
|
pushBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log("Push button clicked!");
|
||||||
|
pushDashboards();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn("Push button not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log("DOM Ready - Initializing script...");
|
||||||
|
setTimeout(function() {
|
||||||
|
initScript();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("DOM already ready - Initializing script...");
|
||||||
|
setTimeout(function() {
|
||||||
|
initScript();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormKeyValue() {
|
||||||
|
try {
|
||||||
|
// Chercher dans les meta tags du DOM
|
||||||
|
const metaTag = document.querySelector('meta[name="splunk_form_key"]');
|
||||||
|
if (metaTag) {
|
||||||
|
return metaTag.getAttribute('content');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chercher dans les cookies
|
||||||
|
const cookies = document.cookie.split(';');
|
||||||
|
for (let cookie of cookies) {
|
||||||
|
const [name, value] = cookie.trim().split('=');
|
||||||
|
if (name === 'splunk_form_key') {
|
||||||
|
return decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("Could not find form key, proceeding without it");
|
||||||
|
return '';
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error getting form key:", e);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAvailableApps() {
|
||||||
|
console.log("loadAvailableApps called");
|
||||||
|
|
||||||
|
const apiUrl = '/en-US/splunkd/__raw/services/apps/local?output_mode=json&count=0';
|
||||||
|
|
||||||
|
fetch(apiUrl)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('HTTP ' + response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log("Apps Response: Found " + (data.entry ? data.entry.length : 0) + " apps");
|
||||||
|
|
||||||
|
if (data.entry && data.entry.length > 0) {
|
||||||
|
const apps = data.entry
|
||||||
|
.filter(item => {
|
||||||
|
// Filtrer les apps système
|
||||||
|
const isHidden = item.content && item.content.is_visible === 0;
|
||||||
|
const appName = item.name;
|
||||||
|
// Exclure les apps système
|
||||||
|
const systemApps = ['launcher', 'splunk_monitoring_console', 'introspection'];
|
||||||
|
return !isHidden && !systemApps.includes(appName);
|
||||||
|
})
|
||||||
|
.map(item => ({
|
||||||
|
id: item.name,
|
||||||
|
name: item.content.label || item.name,
|
||||||
|
description: item.content.description || ''
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
console.log("Filtered apps: " + apps.length);
|
||||||
|
populateAppsList(apps);
|
||||||
|
} else {
|
||||||
|
console.warn("No apps found");
|
||||||
|
showAppsEmpty();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("API Error:", error);
|
||||||
|
showAppsEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateAppsList(apps) {
|
||||||
|
console.log("populateAppsList called with", apps.length, "apps");
|
||||||
|
|
||||||
|
const container = document.getElementById('dashboard-list');
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
console.error("dashboard-list container not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!apps || apps.length === 0) {
|
||||||
|
showAppsEmpty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '<div class="select-all-group">';
|
||||||
|
html += '<input type="checkbox" id="select-all" onchange="toggleSelectAll(this)">';
|
||||||
|
html += '<label for="select-all">Select All (' + apps.length + ' apps)</label>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
apps.forEach((app, index) => {
|
||||||
|
const checkboxId = 'app-' + index;
|
||||||
|
html += '<div class="dashboard-item">';
|
||||||
|
html += '<input type="checkbox" id="' + checkboxId + '" value="' + app.id + '" data-app="' + app.id + '" data-name="' + app.name + '">';
|
||||||
|
html += '<label for="' + checkboxId + '"><strong>' + app.name + '</strong> <span class="app-badge">' + app.id + '</span>';
|
||||||
|
if (app.description) {
|
||||||
|
html += '<div style="font-size: 11px; color: #666; margin-top: 3px;">' + app.description + '</div>';
|
||||||
|
}
|
||||||
|
html += '</label>';
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
console.log('Successfully populated ' + apps.length + ' apps');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAppsEmpty() {
|
||||||
|
const container = document.getElementById('dashboard-list');
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '<div class="dashboard-empty">No apps found</div>';
|
||||||
|
}
|
||||||
|
console.log("Displayed empty state");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelectAll(checkbox) {
|
||||||
|
const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"]:not(#select-all)');
|
||||||
|
checkboxes.forEach(cb => cb.checked = checkbox.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// POUSSER LES DASHBOARDS VERS GIT
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
function pushDashboards() {
|
||||||
|
console.log("pushDashboards called");
|
||||||
|
|
||||||
|
const gitUrl = document.getElementById('git-url').value;
|
||||||
|
const gitBranch = document.getElementById('git-branch').value;
|
||||||
|
const gitToken = document.getElementById('git-token').value;
|
||||||
|
const commitMessage = document.getElementById('commit-message').value;
|
||||||
|
|
||||||
|
console.log("Git URL:", gitUrl);
|
||||||
|
console.log("Git Branch:", gitBranch);
|
||||||
|
console.log("Commit Message:", commitMessage);
|
||||||
|
|
||||||
|
const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"]:not(#select-all):checked');
|
||||||
|
const selectedApps = Array.from(checkboxes).map(cb => ({
|
||||||
|
id: cb.value,
|
||||||
|
name: cb.getAttribute('data-name')
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log("Selected apps:", selectedApps);
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!gitUrl.trim()) {
|
||||||
|
console.warn("Validation failed: No Git URL");
|
||||||
|
showError('Please enter a Git repository URL');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gitToken.trim()) {
|
||||||
|
console.warn("Validation failed: No Git token");
|
||||||
|
showError('Please enter your Git token or password');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!commitMessage.trim()) {
|
||||||
|
console.warn("Validation failed: No commit message");
|
||||||
|
showError('Please enter a commit message');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedApps.length === 0) {
|
||||||
|
console.warn("Validation failed: No apps selected");
|
||||||
|
showError('Please select at least one application');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Validation passed, showing loading state...");
|
||||||
|
|
||||||
|
// Afficher le loading
|
||||||
|
document.getElementById('loading').style.display = 'block';
|
||||||
|
document.getElementById('success-msg').style.display = 'none';
|
||||||
|
document.getElementById('error-msg').style.display = 'none';
|
||||||
|
document.getElementById('push-btn').disabled = true;
|
||||||
|
|
||||||
|
// Préparer les données - passer les apps au lieu des dashboards
|
||||||
|
const payload = {
|
||||||
|
git_url: gitUrl,
|
||||||
|
git_branch: gitBranch,
|
||||||
|
git_token: gitToken,
|
||||||
|
apps: selectedApps,
|
||||||
|
commit_message: commitMessage,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
user: getCurrentUser()
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Payload prepared:", payload);
|
||||||
|
|
||||||
|
// Appeler le script Python via serveur
|
||||||
|
callPushScript(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function callPushScript(payload) {
|
||||||
|
console.log("callPushScript called");
|
||||||
|
console.log("Payload:", payload);
|
||||||
|
|
||||||
|
// Construire l'URL vers le serveur Python sur le port 9999 en HTTP
|
||||||
|
const hostname = window.location.hostname;
|
||||||
|
const baseUrl = `http://${hostname}:9999`;
|
||||||
|
|
||||||
|
const url = new URL('/push', baseUrl);
|
||||||
|
|
||||||
|
// Ajouter les paramètres en query string
|
||||||
|
url.searchParams.append('git_url', payload.git_url);
|
||||||
|
url.searchParams.append('git_branch', payload.git_branch);
|
||||||
|
url.searchParams.append('git_token', payload.git_token);
|
||||||
|
url.searchParams.append('commit_message', payload.commit_message);
|
||||||
|
|
||||||
|
// Encoder correctement les apps en JSON
|
||||||
|
const appsJson = JSON.stringify(payload.apps);
|
||||||
|
console.log("Apps JSON:", appsJson);
|
||||||
|
url.searchParams.append('apps', appsJson);
|
||||||
|
|
||||||
|
url.searchParams.append('user', payload.user);
|
||||||
|
|
||||||
|
console.log("Calling:", url.toString());
|
||||||
|
|
||||||
|
fetch(url.toString(), {
|
||||||
|
method: 'POST',
|
||||||
|
mode: 'no-cors'
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
// Avec no-cors, on ne peut pas lire response.json()
|
||||||
|
// Donc on suppose que si la requête arrive au serveur, c'est bon
|
||||||
|
console.log("Request sent successfully");
|
||||||
|
document.getElementById('loading').style.display = 'none';
|
||||||
|
document.getElementById('push-btn').disabled = false;
|
||||||
|
showSuccess('Push request sent! Check server logs for details.');
|
||||||
|
resetForm();
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Fetch error:', error);
|
||||||
|
document.getElementById('loading').style.display = 'none';
|
||||||
|
document.getElementById('push-btn').disabled = false;
|
||||||
|
showError('Network error: ' + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentUser() {
|
||||||
|
try {
|
||||||
|
// Essayer plusieurs méthodes
|
||||||
|
if (Splunk && Splunk.util && typeof Splunk.util.getCurrentUser === 'function') {
|
||||||
|
return Splunk.util.getCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: chercher dans le DOM ou retourner 'unknown'
|
||||||
|
return 'unknown_user';
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Could not get current user:", e);
|
||||||
|
return 'unknown_user';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(message) {
|
||||||
|
const successMsg = document.getElementById('success-msg');
|
||||||
|
document.getElementById('success-text').textContent = message;
|
||||||
|
successMsg.style.display = 'block';
|
||||||
|
setTimeout(() => {
|
||||||
|
successMsg.style.display = 'none';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
const errorMsg = document.getElementById('error-msg');
|
||||||
|
document.getElementById('error-text').textContent = 'X ' + message;
|
||||||
|
errorMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
document.getElementById('git-url').value = '';
|
||||||
|
document.getElementById('git-branch').value = 'main';
|
||||||
|
document.getElementById('git-token').value = '';
|
||||||
|
document.getElementById('commit-message').value = '';
|
||||||
|
document.querySelectorAll('#dashboard-list input[type="checkbox"]').forEach(cb => cb.checked = false);
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
This is where you put any scripts you want to add to this app.
|
||||||
@ -0,0 +1 @@
|
|||||||
|
919562
|
||||||
@ -0,0 +1,319 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
|
# Configuration du logging
|
||||||
|
log_dir = '/opt/splunk/var/log/splunk'
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler(os.path.join(log_dir, 'git_pusher.log')),
|
||||||
|
logging.StreamHandler()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
logger = logging.getLogger('git_pusher')
|
||||||
|
|
||||||
|
|
||||||
|
class GitPusherRequestHandler(BaseHTTPRequestHandler):
|
||||||
|
"""Handler pour les requêtes HTTP"""
|
||||||
|
|
||||||
|
def do_OPTIONS(self):
|
||||||
|
"""Traiter les requêtes OPTIONS (CORS preflight)"""
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
|
||||||
|
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
"""Traiter les requêtes POST"""
|
||||||
|
# Envoyer les headers CORS EN PREMIER
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
|
||||||
|
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"POST request to {self.path}")
|
||||||
|
|
||||||
|
# Parser l'URL et les paramètres
|
||||||
|
parsed_url = urlparse(self.path)
|
||||||
|
query_params = parse_qs(parsed_url.query)
|
||||||
|
|
||||||
|
logger.info(f"Query params keys: {list(query_params.keys())}")
|
||||||
|
|
||||||
|
# Extraire les paramètres
|
||||||
|
git_url = query_params.get('git_url', [''])[0]
|
||||||
|
git_branch = query_params.get('git_branch', ['main'])[0]
|
||||||
|
git_token = query_params.get('git_token', [''])[0]
|
||||||
|
commit_message = query_params.get('commit_message', [''])[0]
|
||||||
|
|
||||||
|
# Accepter soit 'apps' soit 'dashboards'
|
||||||
|
apps_json = query_params.get('apps', query_params.get('dashboards', ['[]']))[0]
|
||||||
|
user = query_params.get('user', ['unknown'])[0]
|
||||||
|
|
||||||
|
logger.info(f"Parameters received: git_url={git_url}, branch={git_branch}, user={user}")
|
||||||
|
logger.info(f"Raw apps_json: '{apps_json}'")
|
||||||
|
|
||||||
|
# Parser les apps
|
||||||
|
try:
|
||||||
|
# parse_qs décode déjà, mais au cas où
|
||||||
|
if isinstance(apps_json, str):
|
||||||
|
apps = json.loads(apps_json)
|
||||||
|
else:
|
||||||
|
apps = apps_json
|
||||||
|
except (json.JSONDecodeError, TypeError) as e:
|
||||||
|
logger.error(f"JSON parse error: {e} - trying to parse: {apps_json}")
|
||||||
|
apps = []
|
||||||
|
|
||||||
|
logger.info(f"Parsed apps: {len(apps)} items - {apps}")
|
||||||
|
|
||||||
|
# Valider
|
||||||
|
if not git_url or not git_token or not commit_message or not apps:
|
||||||
|
logger.warning(f"Validation failed: git_url={bool(git_url)}, git_token={bool(git_token)}, commit_message={bool(commit_message)}, apps={len(apps)}")
|
||||||
|
response = {
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Missing required parameters'
|
||||||
|
}
|
||||||
|
self.wfile.write(json.dumps(response).encode())
|
||||||
|
return
|
||||||
|
|
||||||
|
# Créer un répertoire temporaire
|
||||||
|
temp_dir = tempfile.mkdtemp(prefix='splunk_git_')
|
||||||
|
logger.info(f"Created temp directory: {temp_dir}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Préparer l'URL Git
|
||||||
|
git_url_with_token = self.prepare_git_url(git_url, git_token)
|
||||||
|
|
||||||
|
# Cloner
|
||||||
|
logger.info("Cloning repository...")
|
||||||
|
self.clone_repository(temp_dir, git_url_with_token, git_branch)
|
||||||
|
|
||||||
|
# Récupérer TOUTES les applications (dossiers complets)
|
||||||
|
logger.info("Fetching applications from Splunk...")
|
||||||
|
dashboard_contents = self.fetch_apps_directories(apps)
|
||||||
|
|
||||||
|
# Créer le dossier apps
|
||||||
|
apps_dir = os.path.join(temp_dir, 'apps')
|
||||||
|
os.makedirs(apps_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Copier les applications
|
||||||
|
logger.info("Copying applications to repository...")
|
||||||
|
for app_data in dashboard_contents:
|
||||||
|
app_name = app_data['name']
|
||||||
|
app_path = app_data['path']
|
||||||
|
dest_path = os.path.join(apps_dir, app_name)
|
||||||
|
|
||||||
|
if os.path.exists(app_path):
|
||||||
|
logger.info(f"Copying app {app_name} from {app_path}")
|
||||||
|
shutil.copytree(app_path, dest_path)
|
||||||
|
logger.info(f"Copied app: {app_name}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"App path not found: {app_path}")
|
||||||
|
|
||||||
|
# Configurer git
|
||||||
|
logger.info("Configuring git...")
|
||||||
|
subprocess.run(['git', 'config', 'user.email', 'splunk@splunk.local'],
|
||||||
|
cwd=temp_dir, capture_output=True)
|
||||||
|
subprocess.run(['git', 'config', 'user.name', 'Splunk Git Pusher'],
|
||||||
|
cwd=temp_dir, capture_output=True)
|
||||||
|
|
||||||
|
# Commit et push
|
||||||
|
logger.info("Adding files...")
|
||||||
|
subprocess.run(['git', 'add', '-A'], cwd=temp_dir, capture_output=True)
|
||||||
|
|
||||||
|
full_message = f"{commit_message}\n\nPushed by: {user}\nTimestamp: {datetime.now().isoformat()}"
|
||||||
|
logger.info("Committing...")
|
||||||
|
result = subprocess.run(['git', 'commit', '-m', full_message],
|
||||||
|
cwd=temp_dir, capture_output=True, text=True)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
logger.warning(f"Commit may have failed or had no changes: {result.stderr}")
|
||||||
|
|
||||||
|
logger.info("Pushing...")
|
||||||
|
result = subprocess.run(['git', 'push', 'origin', git_branch],
|
||||||
|
cwd=temp_dir, capture_output=True, text=True, timeout=60)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception(f"Push failed: {result.stderr}")
|
||||||
|
|
||||||
|
logger.info("Push successful!")
|
||||||
|
response = {
|
||||||
|
'status': 'success',
|
||||||
|
'message': f'Successfully pushed {len(dashboard_contents)} dashboards from {len(apps)} application(s) to Git',
|
||||||
|
'dashboards_pushed': len(dashboard_contents)
|
||||||
|
}
|
||||||
|
self.wfile.write(json.dumps(response).encode())
|
||||||
|
|
||||||
|
finally:
|
||||||
|
logger.info(f"Cleaning up {temp_dir}")
|
||||||
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error: {str(e)}", exc_info=True)
|
||||||
|
response = {
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'Error: {str(e)}'
|
||||||
|
}
|
||||||
|
self.wfile.write(json.dumps(response).encode())
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
"""Éviter les logs HTTP par défaut"""
|
||||||
|
logger.debug(format % args)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def prepare_git_url(git_url, token):
|
||||||
|
"""Préparer l'URL Git avec le token"""
|
||||||
|
if git_url.startswith('https://'):
|
||||||
|
parts = git_url.replace('https://', '').split('/')
|
||||||
|
return f"https://{token}@{'/'.join(parts)}"
|
||||||
|
return git_url
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def clone_repository(dest_dir, git_url, branch):
|
||||||
|
"""Cloner le repository"""
|
||||||
|
try:
|
||||||
|
cmd = ['git', 'clone', '--depth', '1', '--branch', branch, git_url, dest_dir]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception(f"Clone failed: {result.stderr}")
|
||||||
|
|
||||||
|
logger.info("Repository cloned successfully")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
raise Exception("Git clone operation timed out")
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception("Git is not installed on this system")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fetch_apps_directories(apps):
|
||||||
|
"""Récupérer les dossiers complets des applications"""
|
||||||
|
logger.info(f"Fetching directories for {len(apps)} applications")
|
||||||
|
|
||||||
|
splunk_home = '/opt/splunk'
|
||||||
|
apps_base_path = os.path.join(splunk_home, 'etc', 'apps')
|
||||||
|
|
||||||
|
app_directories = []
|
||||||
|
|
||||||
|
for app in apps:
|
||||||
|
app_id = app.get('id') or app.get('app_id')
|
||||||
|
app_path = os.path.join(apps_base_path, app_id)
|
||||||
|
|
||||||
|
logger.info(f"Checking app directory: {app_path}")
|
||||||
|
|
||||||
|
if os.path.isdir(app_path):
|
||||||
|
app_directories.append({
|
||||||
|
'name': app_id,
|
||||||
|
'path': app_path,
|
||||||
|
'size': sum(os.path.getsize(os.path.join(dirpath, filename))
|
||||||
|
for dirpath, dirnames, filenames in os.walk(app_path)
|
||||||
|
for filename in filenames)
|
||||||
|
})
|
||||||
|
logger.info(f"Found app: {app_id} at {app_path}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"App directory not found: {app_path}")
|
||||||
|
|
||||||
|
logger.info(f"Successfully found {len(app_directories)} application directories")
|
||||||
|
return app_directories
|
||||||
|
"""Récupérer TOUS les dashboards de chaque application"""
|
||||||
|
logger.info(f"Fetching dashboards from {len(apps)} applications")
|
||||||
|
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
import ssl
|
||||||
|
import base64
|
||||||
|
|
||||||
|
# Ignorer les certificats SSL auto-signés
|
||||||
|
ssl_context = ssl.create_default_context()
|
||||||
|
ssl_context.check_hostname = False
|
||||||
|
ssl_context.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
dashboard_contents = []
|
||||||
|
|
||||||
|
# Lire le fichier de configuration Splunk pour obtenir les credentials
|
||||||
|
# Ou utiliser des credentials par défaut
|
||||||
|
splunk_username = os.environ.get('SPLUNK_USERNAME', 'admin')
|
||||||
|
splunk_password = os.environ.get('SPLUNK_PASSWORD', 'changeme')
|
||||||
|
|
||||||
|
# Créer l'authentification Basic
|
||||||
|
credentials = base64.b64encode(f"{splunk_username}:{splunk_password}".encode()).decode()
|
||||||
|
|
||||||
|
for app in apps:
|
||||||
|
app_id = app.get('id') or app.get('app_id')
|
||||||
|
logger.info(f"Fetching all dashboards from app: {app_id}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Récupérer la liste de TOUS les dashboards de cette app
|
||||||
|
api_url = f"https://127.0.0.1:8089/servicesNS/-/{app_id}/data/ui/views?output_mode=json&count=0"
|
||||||
|
|
||||||
|
logger.debug(f"API URL: {api_url}")
|
||||||
|
|
||||||
|
req = urllib.request.Request(api_url)
|
||||||
|
req.add_header('Authorization', f'Basic {credentials}')
|
||||||
|
|
||||||
|
with urllib.request.urlopen(req, timeout=15, context=ssl_context) as response:
|
||||||
|
api_data = json.loads(response.read().decode('utf-8'))
|
||||||
|
|
||||||
|
if 'entry' in api_data and len(api_data['entry']) > 0:
|
||||||
|
for entry in api_data['entry']:
|
||||||
|
try:
|
||||||
|
dashboard_id = entry.get('name')
|
||||||
|
content = entry.get('content', {})
|
||||||
|
|
||||||
|
# eai:data contient le XML complet du dashboard
|
||||||
|
dashboard_xml = content.get('eai:data', '')
|
||||||
|
|
||||||
|
if dashboard_xml:
|
||||||
|
dashboard_contents.append({
|
||||||
|
'id': f"{app_id}_{dashboard_id}",
|
||||||
|
'app': app_id,
|
||||||
|
'content': dashboard_xml,
|
||||||
|
'name': dashboard_id
|
||||||
|
})
|
||||||
|
logger.debug(f"Fetched: {dashboard_id} from {app_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing dashboard entry: {str(e)}")
|
||||||
|
|
||||||
|
logger.info(f"Found {len([d for d in dashboard_contents if d['app'] == app_id])} dashboards in {app_id}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"No dashboards found in app {app_id}")
|
||||||
|
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
logger.error(f"HTTP {e.code} when fetching app {app_id}: {e.reason}")
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
logger.error(f"Cannot reach Splunk API for app {app_id}: {e.reason}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error fetching dashboards from {app_id}: {str(e)}")
|
||||||
|
|
||||||
|
logger.info(f"Successfully fetched {len(dashboard_contents)} dashboards total")
|
||||||
|
return dashboard_contents
|
||||||
|
|
||||||
|
|
||||||
|
def start_server(port=9999):
|
||||||
|
"""Démarrer le serveur HTTP"""
|
||||||
|
server = HTTPServer(('0.0.0.0', port), GitPusherRequestHandler)
|
||||||
|
logger.info(f"Git Pusher server listening on 0.0.0.0:{port} (HTTP)")
|
||||||
|
server.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Démarrer le serveur en background
|
||||||
|
port = 9999
|
||||||
|
logger.info(f"Starting Git Pusher on port {port}")
|
||||||
|
start_server(port)
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
export SPLUNK_USERNAME=admin
|
||||||
|
export SPLUNK_PASSWORD='2312Jocpam!?'
|
||||||
|
python3 /opt/splunk/etc/apps/pusher_app/bin/git_pusher.py > /opt/splunk/var/log/splunk/git_pusher_startup.log 2>&1 &
|
||||||
|
echo $! > /opt/splunk/etc/apps/pusher_app/bin/git_pusher.pid
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDCTCCAfGgAwIBAgIUCuKo8SLloS5cjBOR04+X6ayZ40cwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDEyMzIyMTIxOFoXDTI3MDEy
|
||||||
|
MzIyMTIxOFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
|
||||||
|
AAOCAQ8AMIIBCgKCAQEAs0vF6sFTseKgZC1nZ6CVZdw45yk1Ni0W9Mc24KZ9NKCJ
|
||||||
|
rP0tHy0hs6mME/sq8DV1fh0YtqIvBCxcKEE84/cVXmUfZF9JRXO95734+JGPmo07
|
||||||
|
zpiu7p3r4WyIWmCXX5VB0UkMEXsPQmonqG1Kwtz+R1cfgis2lUk+xsC2zSjER8l4
|
||||||
|
2UODjHvtD25usgxKjpwPrCuZt43miArnVnwfB8OLbAqpwQeYIf18bPt/TrnQsdgd
|
||||||
|
ZZiQdE6UTaJ5xhqztwpYJO9pvZA24Bi3bGNfBciITds5RCGY2wQo8yxbeJsidTuW
|
||||||
|
7Z64DK9t33oVnB2PqlP6hVGD5Agthsv9ehRPxdd3MwIDAQABo1MwUTAdBgNVHQ4E
|
||||||
|
FgQUy0dni+ogqC7YuvfD/Pn0AuebsXQwHwYDVR0jBBgwFoAUy0dni+ogqC7YuvfD
|
||||||
|
/Pn0AuebsXQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAlyxg
|
||||||
|
vR15lsYp4TxJPi1WPzLZl1e6ewTl8GhyE1saxS8LRtyTyr8sa9EFRLQ0OIsqYrUw
|
||||||
|
zZi7FIDoDPZDKpd0/+U94UKlhUuPUyufQwl5vNu0A+SEpwKeznUMaj4Y98tHvVGd
|
||||||
|
1SCndZBWn/v2U4nXqHoTd6Y0xEOga0jUEsUMBckNC236BTo88Zk65/oa9Gncyb27
|
||||||
|
9vGVCbmPyzE70H4KFoVtxkoZrKywn+0ajHhgH5gqZNRPWpe6i8xTbMAeIXkCjmWL
|
||||||
|
LmOA7MkjeQBBEWewu4vMOXsvf+gCtxUj5owsAcOQlZ3g72Sng4MeMjuVx4ZRVxX9
|
||||||
|
fj+vCP9EFI8rX48tjQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCzS8XqwVOx4qBk
|
||||||
|
LWdnoJVl3DjnKTU2LRb0xzbgpn00oIms/S0fLSGzqYwT+yrwNXV+HRi2oi8ELFwo
|
||||||
|
QTzj9xVeZR9kX0lFc73nvfj4kY+ajTvOmK7unevhbIhaYJdflUHRSQwRew9Caieo
|
||||||
|
bUrC3P5HVx+CKzaVST7GwLbNKMRHyXjZQ4OMe+0Pbm6yDEqOnA+sK5m3jeaICudW
|
||||||
|
fB8Hw4tsCqnBB5gh/Xxs+39OudCx2B1lmJB0TpRNonnGGrO3Clgk72m9kDbgGLds
|
||||||
|
Y18FyIhN2zlEIZjbBCjzLFt4myJ1O5btnrgMr23fehWcHY+qU/qFUYPkCC2Gy/16
|
||||||
|
FE/F13czAgMBAAECggEAMrEMrvej0xpQ4KHZp3nGY3sk9242JjAPWntsb42CvrtY
|
||||||
|
0XjvJe5bpfEcspWDqVBj/Jj7YL9v7Y0hLRxsu8Mi3oJWoskx7RnxKjES0CxPXpHp
|
||||||
|
w9p1Mu+hPiWyU2MVySdo6WPuro6NXOiod70WswtKNR9TwDi5gPGpdwYLaOvKusSp
|
||||||
|
Rncm0m0H3IBhgVA691X0AUIomAW3Wmh+5If1XHfjrNHTB8cjcNf6koPMkCqHCEZ9
|
||||||
|
wtINxOJior+gGkjMXaDszqzNlicVBXFEFjaXWcp38xAif1uimpqKsRzZEF6RAUzi
|
||||||
|
H7cI3aF2dXG3C9l6Byi7OSgd8X4JUnE0dlCpC7qweQKBgQDgvoavo8G0kYruCUIQ
|
||||||
|
6vcSs1YBByOkl6yZBCZWk10NgRpU1wyu9zmlvEwNVlUfALs5eoLxnhe8Wklq0ckQ
|
||||||
|
r/Rl+r/lj/MZUFn49TgUCsUOIi/G7nWQG0bPo4bCB2QXsAiKdY+KZeC56620uyom
|
||||||
|
1VY+nS3y8O4EP0YHX0qHFfmIZwKBgQDMOywO0DSrZMDyvmbwL0ISzHRcNpRn0jk7
|
||||||
|
pEtzM/VOx+v0O93E+5OygzmXlBKjF0MwMXBidf8IZu4xO8qWqAM4EP2DD0cpoS1Z
|
||||||
|
WiHHkc5NZhjgeG6C4XaCXR++7CuY25VKKe01yz/+j51linDD8OAibKUspkjVufEN
|
||||||
|
R/AT0GFLVQKBgBxMYTEkcXOHD/NA/yyaKVoVcrLWb0p+PqFVwG4OSB03MFWWbmZp
|
||||||
|
gry3pOvY/wbUVL68CljaCysQQ0ZL/AE55pAgrqD9KyL41xtd5R3A7WcGLvXheLQY
|
||||||
|
eyYR9RnhTF0fMTQd8WD/yvgeENU86+XP3vgrWmnIpG+sd+jdusifn7fpAn9QkwfO
|
||||||
|
0FX3SMjW/EegewSWZhOCTgY+77Gk1izuRpGBg16T/QqBrL+Yri0KoGC593OKj/bG
|
||||||
|
4ca8id9vjSdgSOj8NbfO/TgWNICvv9+T3PKHlsA5z0nKWSloRVVA/ew1YmyD1gbA
|
||||||
|
MnAM/pwac4QJyf6jljmUZAZYTAPOOZN+PbglAoGBAJ9cOGDgT+BCOoNc0T1GJDAk
|
||||||
|
xOR8d+tD+j4JH5IVxB51DXjJOZxw9U3XhNH1OcE0x3fRzKJOtlQLxP6fHYVtMVFq
|
||||||
|
VpeekmTtJ9OfMg68ELOlf7ykA3GhMJ3FarM6e8+X+KliGf6ND4HBMb112FlMgIi6
|
||||||
|
yYi7sfSL53Dzp1Q2DxXV
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
#
|
||||||
|
# Splunk app configuration file
|
||||||
|
#
|
||||||
|
|
||||||
|
[install]
|
||||||
|
is_configured = 0
|
||||||
|
|
||||||
|
[ui]
|
||||||
|
is_visible = true
|
||||||
|
label = Pusher
|
||||||
|
|
||||||
|
[launcher]
|
||||||
|
author =
|
||||||
|
description =
|
||||||
|
version = 1.0.0
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<nav search_view="search">
|
||||||
|
<view name="search" default='true' />
|
||||||
|
<view name="analytics_workspace" />
|
||||||
|
<view name="datasets" />
|
||||||
|
<view name="reports" />
|
||||||
|
<view name="alerts" />
|
||||||
|
<view name="dashboards" />
|
||||||
|
</nav>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
Add all the views that your app needs in this directory
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
[ui]
|
||||||
|
|
||||||
|
[launcher]
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
# Application-level permissions
|
||||||
|
|
||||||
|
[]
|
||||||
|
access = read : [ * ], write : [ admin, power ]
|
||||||
|
|
||||||
|
### EVENT TYPES
|
||||||
|
|
||||||
|
[eventtypes]
|
||||||
|
export = system
|
||||||
|
|
||||||
|
|
||||||
|
### PROPS
|
||||||
|
|
||||||
|
[props]
|
||||||
|
export = system
|
||||||
|
|
||||||
|
|
||||||
|
### TRANSFORMS
|
||||||
|
|
||||||
|
[transforms]
|
||||||
|
export = system
|
||||||
|
|
||||||
|
|
||||||
|
### LOOKUPS
|
||||||
|
|
||||||
|
[lookups]
|
||||||
|
export = system
|
||||||
|
|
||||||
|
|
||||||
|
### VIEWSTATES: even normal users should be able to create shared viewstates
|
||||||
|
|
||||||
|
[viewstates]
|
||||||
|
access = read : [ * ], write : [ * ]
|
||||||
|
export = system
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
[app/ui]
|
||||||
|
version = 10.0.2
|
||||||
|
modtime = 1769115948.043388000
|
||||||
|
|
||||||
|
[app/launcher]
|
||||||
|
version = 10.0.2
|
||||||
|
modtime = 1769115948.046389000
|
||||||
|
|
||||||
|
[views/git_pusher_-_push_dashboards_to_git]
|
||||||
|
access = read : [ admin ], write : [ admin ]
|
||||||
|
export = system
|
||||||
|
owner = admin
|
||||||
|
version = 10.0.2
|
||||||
|
modtime = 1769276443.812957000
|
||||||
|
|
||||||
|
[views/git_pusher_-_push_applications_to_git]
|
||||||
|
owner = admin
|
||||||
|
version = 10.0.2
|
||||||
|
modtime = 1769283309.402236000
|
||||||
Loading…
Reference in new issue