Pushed by: unknown_user
Timestamp: 2026-01-24T21:55:04.885348
masterdev
Splunk Git Pusher 3 months ago
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,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,255 @@
<?xml version="1.0" encoding="UTF-8"?>
<dashboard version="1.1" script="git_pusher.js">
<label>Git Pusher - Push Applications to Git</label>
<description>Push Splunk applications to Git repository</description>
<!-- Recherche cachée (non utilisée, mais gardée pour compatibilité) -->
<search id="dsearch">
<query>| rest /services/apps/local | search disabled=0 | fields name, label, description | sort label</query>
<earliest>-4h@h</earliest>
<latest>now</latest>
</search>
<row>
<panel>
<title>Configuration &amp; Application Selection</title>
<html>
<style>
.git-container {
padding: 20px;
background-color: #f7f8fa;
border-radius: 4px;
margin: 10px 0;
}
.success-message {
padding: 10px;
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
border-radius: 4px;
margin: 10px 0;
display: none;
}
.error-message {
padding: 10px;
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
border-radius: 4px;
margin: 10px 0;
display: none;
}
.info-message {
padding: 10px;
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
border-radius: 4px;
margin: 10px 0;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.form-group textarea {
resize: vertical;
min-height: 100px;
}
.button-group {
margin-top: 20px;
}
.btn {
padding: 10px 20px;
margin-right: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #545b62;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.loading {
display: none;
margin: 20px 0;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
display: inline-block;
margin-right: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.dashboard-list {
border: 1px solid #ddd;
border-radius: 4px;
max-height: 400px;
overflow-y: auto;
padding: 10px;
}
.dashboard-item {
padding: 8px;
border-bottom: 1px solid #eee;
}
.dashboard-item:last-child {
border-bottom: none;
}
.dashboard-item input[type="checkbox"] {
margin-right: 10px;
}
.dashboard-item label {
margin: 0;
font-weight: normal;
cursor: pointer;
}
.dashboard-loading {
text-align: center;
padding: 20px;
color: #666;
}
.dashboard-empty {
text-align: center;
padding: 20px;
color: #999;
font-style: italic;
}
.select-all-group {
padding: 10px;
border-bottom: 2px solid #ddd;
background-color: #f9f9f9;
}
.select-all-group input[type="checkbox"] {
margin-right: 10px;
}
.select-all-group label {
margin: 0;
font-weight: bold;
cursor: pointer;
}
.app-badge {
display: inline-block;
background-color: #e7f3ff;
color: #0066cc;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
margin-left: 8px;
}
</style>
<div class="git-container">
<div class="info-message">
Configure your Git settings and select the applications you want to push to your repository.
</div>
<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" />
</div>
<div class="form-group">
<label for="git-branch">Target Branch:</label>
<input type="text" id="git-branch" placeholder="main" value="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="form-group">
<label>Available Applications:</label>
<div class="dashboard-list" id="dashboard-list">
<div class="dashboard-loading">
<div class="spinner"></div>
<span>Loading applications...</span>
</div>
</div>
<small style="color: #666; margin-top: 5px; display: block;">Select one or more applications to push</small>
</div>
<div class="form-group">
<label for="commit-message">Commit Message:</label>
<textarea id="commit-message" placeholder="Describe your changes... e.g., 'Update applications with new dashboards'"></textarea>
</div>
<div class="button-group">
<button class="btn btn-primary" id="push-btn" onclick="pushDashboards()">
Push to Git
</button>
<button class="btn btn-secondary" onclick="resetForm()">
Reset
</button>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<span id="loading-text">Pushing applications to Git...</span>
</div>
<div class="success-message" id="success-msg">
<span id="success-text">Applications successfully pushed to Git!</span>
</div>
<div class="error-message" id="error-msg">
<span id="error-text">Error occurred while pushing applications</span>
</div>
</div>
</html>
</panel>
</row>
<row>
<panel>
<title>Push History</title>
<table>
<search>
<query>index=_internal source=*git_pusher* action=push_attempt | table _time, user, apps, commit_message, status, error_msg | reverse | rename _time as "Timestamp", user as "User", apps as "Applications", commit_message as "Message", status as "Status", error_msg as "Error" | head 20</query>
<earliest>-30d@d</earliest>
<latest>now</latest>
</search>
<option name="drilldown">none</option>
<format type="color" field="Status">
<colorPalette type="map">{"success": "#28a745", "error": "#dc3545", "pending": "#ffc107"}</colorPalette>
</format>
</table>
</panel>
</row>
</dashboard>

@ -0,0 +1,254 @@
<dashboard version="1.1" script="git_pusher.js">
<label>Git Pusher - Push Dashboards to Git</label>
<description>Push Splunk dashboards to Git repository</description>
<!-- Recherche cachée pour charger les dashboards -->
<search id="dsearch">
<query>| rest /services/data/ui/views | search title!="" | fields label, id, eai:acl.app | rename label as "Dashboard Name", id as "dashboard_id", "eai:acl.app" as "app" | sort "Dashboard Name"</query>
<earliest>-4h@h</earliest>
<latest>now</latest>
</search>
<row>
<panel>
<title>Configuration &amp; Dashboard Selection</title>
<html>
<style>
.git-container {
padding: 20px;
background-color: #f7f8fa;
border-radius: 4px;
margin: 10px 0;
}
.success-message {
padding: 10px;
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
border-radius: 4px;
margin: 10px 0;
display: none;
}
.error-message {
padding: 10px;
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
border-radius: 4px;
margin: 10px 0;
display: none;
}
.info-message {
padding: 10px;
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
border-radius: 4px;
margin: 10px 0;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.form-group textarea {
resize: vertical;
min-height: 100px;
}
.button-group {
margin-top: 20px;
}
.btn {
padding: 10px 20px;
margin-right: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #545b62;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.loading {
display: none;
margin: 20px 0;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
display: inline-block;
margin-right: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.dashboard-list {
border: 1px solid #ddd;
border-radius: 4px;
max-height: 400px;
overflow-y: auto;
padding: 10px;
}
.dashboard-item {
padding: 8px;
border-bottom: 1px solid #eee;
}
.dashboard-item:last-child {
border-bottom: none;
}
.dashboard-item input[type="checkbox"] {
margin-right: 10px;
}
.dashboard-item label {
margin: 0;
font-weight: normal;
cursor: pointer;
}
.dashboard-loading {
text-align: center;
padding: 20px;
color: #666;
}
.dashboard-empty {
text-align: center;
padding: 20px;
color: #999;
font-style: italic;
}
.select-all-group {
padding: 10px;
border-bottom: 2px solid #ddd;
background-color: #f9f9f9;
}
.select-all-group input[type="checkbox"] {
margin-right: 10px;
}
.select-all-group label {
margin: 0;
font-weight: bold;
cursor: pointer;
}
.app-badge {
display: inline-block;
background-color: #e7f3ff;
color: #0066cc;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
margin-left: 8px;
}
</style>
<div class="git-container">
<div class="info-message">
Configure your Git settings and select the dashboards you want to push to your repository.
</div>
<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" />
</div>
<div class="form-group">
<label for="git-branch">Target Branch:</label>
<input type="text" id="git-branch" placeholder="main" value="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="form-group">
<label>Available Dashboards:</label>
<div class="dashboard-list" id="dashboard-list">
<div class="dashboard-loading">
<div class="spinner"></div>
<span>Loading dashboards...</span>
</div>
</div>
<small style="color: #666; margin-top: 5px; display: block;">Select one or more dashboards to push</small>
</div>
<div class="form-group">
<label for="commit-message">Commit Message:</label>
<textarea id="commit-message" placeholder="Describe your changes... e.g., 'Update sales dashboard with new metrics'"></textarea>
</div>
<div class="button-group">
<button class="btn btn-primary" id="push-btn" onclick="pushDashboards()">
Push to Git
</button>
<button class="btn btn-secondary" onclick="resetForm()">
Reset
</button>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<span id="loading-text">Pushing dashboards to Git...</span>
</div>
<div class="success-message" id="success-msg">
<span id="success-text">Dashboards successfully pushed to Git!</span>
</div>
<div class="error-message" id="error-msg">
<span id="error-text">Error occurred while pushing dashboards</span>
</div>
</div>
</html>
</panel>
</row>
<row>
<panel>
<title>Push History</title>
<table>
<search>
<query>index=_internal source=*git_pusher* action=push_attempt | table _time, user, dashboards, commit_message, status, error_msg | reverse | rename _time as "Timestamp", user as "User", dashboards as "Dashboards", commit_message as "Message", status as "Status", error_msg as "Error" | head 20</query>
<earliest>-30d@d</earliest>
<latest>now</latest>
</search>
<option name="drilldown">none</option>
<format type="color" field="Status">
<colorPalette type="map">{"success": "#28a745", "error": "#dc3545", "pending": "#ffc107"}</colorPalette>
</format>
</table>
</panel>
</row>
</dashboard>

@ -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…
Cancel
Save