Pushed by: admin License: 1CFBBDCA-31F (Starter) Timestamp: 2026-02-01T00:05:57.898363masterdev
parent
29f638f8f6
commit
a7c906e576
@ -1,565 +1,544 @@
|
||||
// ============================================
|
||||
// CHARGER LES DASHBOARDS DYNAMIQUEMENT
|
||||
// GIT PUSHER - MAIN JAVASCRIPT
|
||||
// Version 2.0 avec système de licence par fichier
|
||||
// ============================================
|
||||
|
||||
// Charger les applications
|
||||
function loadAvailableApps() {
|
||||
console.log("loadAvailableApps called");
|
||||
|
||||
const apiUrl = '/en-US/splunkd/__raw/services/apps/local?output_mode=json&count=0';
|
||||
|
||||
fetch(apiUrl)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('HTTP ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log("Apps Response: Found " + (data.entry ? data.entry.length : 0) + " apps");
|
||||
|
||||
if (data.entry && data.entry.length > 0) {
|
||||
const apps = data.entry
|
||||
.filter(item => {
|
||||
// Filtrer les apps système
|
||||
const isHidden = item.content && item.content.is_visible === 0;
|
||||
const appName = item.name;
|
||||
// Exclure les apps système
|
||||
const systemApps = ['launcher', 'splunk_monitoring_console', 'introspection'];
|
||||
return !isHidden && !systemApps.includes(appName);
|
||||
})
|
||||
.map(item => ({
|
||||
id: item.name,
|
||||
name: item.content.label || item.name,
|
||||
description: item.content.description || ''
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
console.log("Filtered apps: " + apps.length);
|
||||
populateAppsList(apps);
|
||||
// Configuration
|
||||
const GIT_PUSHER_CONFIG = {
|
||||
serverUrl: window.location.protocol + '//' + window.location.hostname + ':9999',
|
||||
credentialsKey: 'git_pusher_credentials',
|
||||
version: '2.0.0'
|
||||
};
|
||||
|
||||
// État global
|
||||
let selectedApps = [];
|
||||
let isProcessing = false;
|
||||
|
||||
// ============================================
|
||||
// INITIALISATION
|
||||
// ============================================
|
||||
|
||||
require([
|
||||
'jquery',
|
||||
'splunkjs/mvc',
|
||||
'splunkjs/mvc/searchmanager',
|
||||
'splunkjs/mvc/simplexml/ready!'
|
||||
], function($, mvc, SearchManager) {
|
||||
|
||||
console.log("Git Pusher v2.0 initializing...");
|
||||
|
||||
// Initialiser le système de licence
|
||||
if (typeof initializeLicense === 'function') {
|
||||
initializeLicense();
|
||||
} else {
|
||||
console.warn("No apps found");
|
||||
showAppsEmpty();
|
||||
console.warn("License system not loaded");
|
||||
}
|
||||
|
||||
// Charger les credentials sauvegardés
|
||||
loadSavedCredentials();
|
||||
|
||||
// Récupérer les résultats de recherche pour les apps
|
||||
const searchManager = mvc.Components.get('dsearch');
|
||||
|
||||
if (searchManager) {
|
||||
searchManager.on('search:done', function() {
|
||||
const results = searchManager.data('results');
|
||||
if (results) {
|
||||
results.on('data', function() {
|
||||
const rows = results.data().rows;
|
||||
const fields = results.data().fields;
|
||||
renderAppsList(rows, fields);
|
||||
});
|
||||
}
|
||||
})
|
||||
.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');
|
||||
// Exposer les fonctions globalement
|
||||
window.pushDashboards = pushDashboards;
|
||||
window.resetForm = resetForm;
|
||||
window.toggleSelectAll = toggleSelectAll;
|
||||
});
|
||||
|
||||
if (!container) {
|
||||
console.error("dashboard-list container not found");
|
||||
return;
|
||||
// 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();
|
||||
}
|
||||
|
||||
if (!apps || apps.length === 0) {
|
||||
showAppsEmpty();
|
||||
return;
|
||||
};
|
||||
console.log("Push button event attached");
|
||||
}
|
||||
}, 2000); // Attendre 2 secondes que tout soit chargé
|
||||
});
|
||||
|
||||
let html = '<div class="app-header">';
|
||||
html += '<span>Select Applications</span>';
|
||||
html += '<input type="checkbox" id="select-all">';
|
||||
html += '</div>';
|
||||
// ============================================
|
||||
// RENDU DE LA LISTE DES APPLICATIONS
|
||||
// ============================================
|
||||
|
||||
apps.forEach((app, index) => {
|
||||
const checkboxId = 'app-' + index;
|
||||
html += '<div class="app-item">';
|
||||
html += '<input type="checkbox" id="' + checkboxId + '" value="' + app.id + '" data-app="true" 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: #999; margin-top: 3px;">' + app.description + '</div>';
|
||||
function renderAppsList(rows, fields) {
|
||||
const container = document.getElementById('dashboard-list');
|
||||
if (!container) return;
|
||||
|
||||
console.log("Fields:", fields);
|
||||
console.log("First row:", rows[0]);
|
||||
|
||||
// Trouver les index des colonnes
|
||||
const nameIdx = fields.indexOf('name');
|
||||
const labelIdx = fields.indexOf('label');
|
||||
const descIdx = fields.indexOf('description');
|
||||
|
||||
console.log("Index - name:", nameIdx, "label:", labelIdx);
|
||||
|
||||
// Générer le HTML
|
||||
let html = `
|
||||
<div class="app-header">
|
||||
<div>
|
||||
<input type="checkbox" id="select-all" onchange="toggleSelectAll(this.checked)" />
|
||||
<label for="select-all" style="cursor: pointer; margin-left: 8px;">Select All</label>
|
||||
</div>
|
||||
<span class="app-count">${rows.length} apps</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let validApps = 0;
|
||||
|
||||
rows.forEach((row, index) => {
|
||||
// Récupérer le nom - essayer plusieurs méthodes
|
||||
let name = '';
|
||||
if (nameIdx >= 0 && row[nameIdx]) {
|
||||
name = row[nameIdx];
|
||||
} else if (labelIdx >= 0 && row[labelIdx]) {
|
||||
// Si name est null, utiliser label comme fallback temporaire
|
||||
// On va chercher le vrai nom via l'API
|
||||
name = row[labelIdx];
|
||||
} else if (row[0]) {
|
||||
name = row[0];
|
||||
} else if (row[1]) {
|
||||
name = row[1]; // Deuxième élément si premier est null
|
||||
}
|
||||
|
||||
const label = (labelIdx >= 0 && row[labelIdx]) ? row[labelIdx] : name;
|
||||
|
||||
console.log(`Row ${index}: name="${name}", label="${label}"`);
|
||||
|
||||
// Ignorer si pas de nom ou apps système
|
||||
if (!name || name.startsWith('splunk_') || name === 'learned' || name === 'launcher') {
|
||||
return;
|
||||
}
|
||||
html += '</label>';
|
||||
html += '</div>';
|
||||
|
||||
validApps++;
|
||||
|
||||
html += `
|
||||
<div class="app-item">
|
||||
<input type="checkbox"
|
||||
id="app-${index}"
|
||||
data-app-id="${name}"
|
||||
data-app-label="${label}"
|
||||
onchange="updateSelectedApps()" />
|
||||
<label for="app-${index}">
|
||||
<span class="app-badge">${name}</span>
|
||||
${label !== name ? ' - ' + label : ''}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
console.log('Successfully populated ' + apps.length + ' apps');
|
||||
|
||||
// Ajouter les event listeners
|
||||
addCheckboxListeners();
|
||||
console.log(`Rendered ${validApps} valid applications out of ${rows.length}`);
|
||||
}
|
||||
|
||||
function addCheckboxListeners() {
|
||||
console.log("addCheckboxListeners called");
|
||||
// ============================================
|
||||
// GESTION DE LA SÉLECTION
|
||||
// ============================================
|
||||
|
||||
const selectAllCheckbox = document.getElementById('select-all');
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.addEventListener('change', function() {
|
||||
toggleSelectAll(this);
|
||||
});
|
||||
}
|
||||
function updateSelectedApps() {
|
||||
const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app-id]');
|
||||
selectedApps = [];
|
||||
|
||||
const appCheckboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app]');
|
||||
appCheckboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
console.log("App checkbox changed");
|
||||
});
|
||||
checkboxes.forEach(cb => {
|
||||
if (cb.checked) {
|
||||
selectedApps.push({
|
||||
id: cb.getAttribute('data-app-id'),
|
||||
label: cb.getAttribute('data-app-label')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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");
|
||||
// Mettre à jour le "Select All"
|
||||
const selectAll = document.getElementById('select-all');
|
||||
if (selectAll) {
|
||||
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
||||
const someChecked = Array.from(checkboxes).some(cb => cb.checked);
|
||||
selectAll.checked = allChecked;
|
||||
selectAll.indeterminate = someChecked && !allChecked;
|
||||
}
|
||||
|
||||
// Attendre que le DOM soit chargé
|
||||
function initScript() {
|
||||
console.log("initScript called");
|
||||
console.log(`Selected ${selectedApps.length} apps`);
|
||||
}
|
||||
|
||||
// INITIALISER LA LICENCE EN PREMIER
|
||||
initializeLicense();
|
||||
function toggleSelectAll(checked) {
|
||||
const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app-id]');
|
||||
checkboxes.forEach(cb => {
|
||||
cb.checked = checked;
|
||||
});
|
||||
updateSelectedApps();
|
||||
}
|
||||
|
||||
// Charger les applications
|
||||
loadAvailableApps();
|
||||
// ============================================
|
||||
// PUSH VERS GIT
|
||||
// ============================================
|
||||
|
||||
// Charger les credentials sauvegardés
|
||||
loadSavedCredentials();
|
||||
async function pushDashboards() {
|
||||
console.log("Starting push process...");
|
||||
|
||||
// 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");
|
||||
// Vérifier si déjà en cours
|
||||
if (isProcessing) {
|
||||
console.log("Push already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
// Attacher l'event listener pour sauvegarder les credentials
|
||||
const saveCheckbox = document.getElementById('save-credentials');
|
||||
if (saveCheckbox) {
|
||||
saveCheckbox.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
saveCredentials();
|
||||
} else {
|
||||
clearSavedCredentials();
|
||||
}
|
||||
// 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')
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadSavedCredentials() {
|
||||
console.log("Loading saved credentials...");
|
||||
console.log("Apps selected from DOM:", apps);
|
||||
|
||||
try {
|
||||
const savedUrl = getCookie('git_pusher_url');
|
||||
const savedToken = getCookie('git_pusher_token');
|
||||
const savedBranch = getCookie('git_pusher_branch');
|
||||
// Mettre à jour selectedApps
|
||||
selectedApps = apps;
|
||||
|
||||
if (savedUrl) {
|
||||
document.getElementById('git-url').value = decodeURIComponent(savedUrl);
|
||||
console.log("Loaded saved URL from cookie");
|
||||
// Vérifier si déjà en cours
|
||||
if (isProcessing) {
|
||||
console.log("Push already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedToken) {
|
||||
document.getElementById('git-token').value = decodeURIComponent(savedToken);
|
||||
console.log("Loaded saved token from cookie");
|
||||
// Vérifier la licence AVANT tout
|
||||
if (typeof checkLicenseBeforePush === 'function') {
|
||||
const licenseOk = await checkLicenseBeforePush();
|
||||
if (!licenseOk) {
|
||||
console.log("License check failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedBranch) {
|
||||
document.getElementById('git-branch').value = decodeURIComponent(savedBranch);
|
||||
}
|
||||
|
||||
if (savedUrl && savedToken) {
|
||||
document.getElementById('save-credentials').checked = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Could not load saved credentials:", e);
|
||||
}
|
||||
}
|
||||
// Récupérer les valeurs du formulaire
|
||||
const gitUrl = document.getElementById('git-url')?.value?.trim();
|
||||
const gitBranch = document.getElementById('git-branch')?.value?.trim() || 'main';
|
||||
const gitToken = document.getElementById('git-token')?.value?.trim();
|
||||
const commitMessage = document.getElementById('commit-message')?.value?.trim();
|
||||
const saveCredentials = document.getElementById('save-credentials')?.checked;
|
||||
|
||||
function saveCredentials() {
|
||||
console.log("Saving credentials...");
|
||||
// Mettre à jour la liste des apps sélectionnées
|
||||
updateSelectedApps();
|
||||
|
||||
try {
|
||||
const gitUrl = document.getElementById('git-url').value;
|
||||
const gitToken = document.getElementById('git-token').value;
|
||||
const gitBranch = document.getElementById('git-branch').value;
|
||||
|
||||
if (gitUrl && gitToken) {
|
||||
setCookie('git_pusher_url', gitUrl, 30);
|
||||
setCookie('git_pusher_token', gitToken, 30);
|
||||
setCookie('git_pusher_branch', gitBranch, 30);
|
||||
console.log("Credentials saved to cookies");
|
||||
showSuccess("Credentials saved locally");
|
||||
} else {
|
||||
showError("Please fill in URL and Token before saving");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error saving credentials:", e);
|
||||
showError("Could not save credentials");
|
||||
}
|
||||
// Validation
|
||||
if (!gitUrl) {
|
||||
showMessage('error', 'Please enter a Git repository URL');
|
||||
return;
|
||||
}
|
||||
|
||||
function clearSavedCredentials() {
|
||||
console.log("Clearing saved credentials...");
|
||||
|
||||
try {
|
||||
deleteCookie('git_pusher_url');
|
||||
deleteCookie('git_pusher_token');
|
||||
deleteCookie('git_pusher_branch');
|
||||
console.log("Credentials cleared from cookies");
|
||||
showSuccess("Credentials cleared");
|
||||
} catch (e) {
|
||||
console.error("Error clearing credentials:", e);
|
||||
}
|
||||
if (!gitToken) {
|
||||
showMessage('error', 'Please enter a Git token or password');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fonctions utilitaires pour les cookies
|
||||
function setCookie(name, value, days) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires = "expires=" + d.toUTCString();
|
||||
document.cookie = name + "=" + encodeURIComponent(value) + ";" + expires + ";path=/";
|
||||
console.log("Cookie set: " + name);
|
||||
if (!commitMessage) {
|
||||
showMessage('error', 'Please enter a commit message');
|
||||
return;
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
const nameEQ = name + "=";
|
||||
const ca = document.cookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i].trim();
|
||||
if (c.indexOf(nameEQ) === 0) {
|
||||
return c.substring(nameEQ.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
if (selectedApps.length === 0) {
|
||||
showMessage('error', 'Please select at least one application to deploy');
|
||||
return;
|
||||
}
|
||||
|
||||
function deleteCookie(name) {
|
||||
setCookie(name, "", -1);
|
||||
console.log("Cookie deleted: " + name);
|
||||
// Sauvegarder les credentials si demandé
|
||||
if (saveCredentials) {
|
||||
saveCredentialsToStorage(gitUrl, gitBranch, gitToken);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
// Démarrer le push
|
||||
isProcessing = true;
|
||||
showLoading(true);
|
||||
hideMessages();
|
||||
|
||||
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");
|
||||
// Récupérer l'utilisateur courant
|
||||
const currentUser = await getCurrentUser();
|
||||
|
||||
const container = document.getElementById('dashboard-list');
|
||||
// Construire les paramètres
|
||||
const params = new URLSearchParams({
|
||||
git_url: gitUrl,
|
||||
git_branch: gitBranch,
|
||||
git_token: gitToken,
|
||||
commit_message: commitMessage,
|
||||
apps: JSON.stringify(selectedApps),
|
||||
user: currentUser
|
||||
});
|
||||
|
||||
if (!container) {
|
||||
console.error("dashboard-list container not found");
|
||||
return;
|
||||
}
|
||||
console.log(`Pushing ${selectedApps.length} apps to ${gitUrl}`);
|
||||
|
||||
if (!apps || apps.length === 0) {
|
||||
showAppsEmpty();
|
||||
return;
|
||||
// Appeler le serveur
|
||||
const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/push?${params.toString()}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
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>';
|
||||
const result = await response.json();
|
||||
console.log("Push result:", result);
|
||||
|
||||
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>';
|
||||
});
|
||||
if (result.status === 'success') {
|
||||
showMessage('success', `✅ Successfully deployed ${result.apps_pushed || selectedApps.length} application(s) to Git!`);
|
||||
|
||||
container.innerHTML = html;
|
||||
console.log('Successfully populated ' + apps.length + ' apps');
|
||||
}
|
||||
// Reset la sélection après succès
|
||||
setTimeout(() => {
|
||||
toggleSelectAll(false);
|
||||
}, 2000);
|
||||
} else if (result.error_code === 'LICENSE_ERROR') {
|
||||
showMessage('error', `🔐 ${result.message}`);
|
||||
|
||||
function showAppsEmpty() {
|
||||
const container = document.getElementById('dashboard-list');
|
||||
if (container) {
|
||||
container.innerHTML = '<div class="dashboard-empty">No apps found</div>';
|
||||
// Afficher le modal de licence
|
||||
if (typeof showLicenseModal === 'function') {
|
||||
showLicenseModal(result.message, result.error_code);
|
||||
}
|
||||
console.log("Displayed empty state");
|
||||
} else if (result.error_code === 'APP_LIMIT') {
|
||||
showMessage('error', `📦 ${result.message}`);
|
||||
} else {
|
||||
showMessage('error', result.message || 'Unknown error occurred');
|
||||
}
|
||||
|
||||
function toggleSelectAll(checkbox) {
|
||||
const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app]');
|
||||
checkboxes.forEach(cb => cb.checked = checkbox.checked);
|
||||
} catch (error) {
|
||||
console.error("Push error:", error);
|
||||
showMessage('error', `Connection error: ${error.message}. Is the Git Pusher server running?`);
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// POUSSER LES DASHBOARDS VERS GIT
|
||||
// UTILITAIRES UI
|
||||
// ============================================
|
||||
|
||||
function pushDashboards() {
|
||||
function showLoading(show) {
|
||||
const loading = document.getElementById('loading');
|
||||
const pushBtn = document.getElementById('push-btn');
|
||||
|
||||
// Vérifier la licence
|
||||
if (!checkLicenseBeforePush()) {
|
||||
return;
|
||||
if (loading) {
|
||||
loading.classList.toggle('active', show);
|
||||
}
|
||||
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;
|
||||
if (pushBtn) {
|
||||
pushBtn.disabled = show;
|
||||
pushBtn.textContent = show ? '⏳ Deploying...' : '✈️ Deploy to Git';
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Git URL:", gitUrl);
|
||||
console.log("Git Branch:", gitBranch);
|
||||
console.log("Commit Message:", commitMessage);
|
||||
function showMessage(type, text) {
|
||||
hideMessages();
|
||||
|
||||
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')
|
||||
}));
|
||||
const successMsg = document.getElementById('success-msg');
|
||||
const errorMsg = document.getElementById('error-msg');
|
||||
const successText = document.getElementById('success-text');
|
||||
const errorText = document.getElementById('error-text');
|
||||
|
||||
console.log("Selected apps:", selectedApps);
|
||||
if (type === 'success' && successMsg && successText) {
|
||||
successText.textContent = text;
|
||||
successMsg.classList.add('active');
|
||||
|
||||
// Validation
|
||||
if (!gitUrl.trim()) {
|
||||
console.warn("Validation failed: No Git URL");
|
||||
showError('Please enter a Git repository URL');
|
||||
return;
|
||||
// Auto-hide après 5 secondes
|
||||
setTimeout(() => {
|
||||
successMsg.classList.remove('active');
|
||||
}, 5000);
|
||||
} else if (type === 'error' && errorMsg && errorText) {
|
||||
errorText.textContent = text;
|
||||
errorMsg.classList.add('active');
|
||||
}
|
||||
|
||||
if (!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;
|
||||
}
|
||||
function hideMessages() {
|
||||
const successMsg = document.getElementById('success-msg');
|
||||
const errorMsg = document.getElementById('error-msg');
|
||||
|
||||
if (selectedApps.length === 0) {
|
||||
console.warn("Validation failed: No apps selected");
|
||||
showError('Please select at least one application');
|
||||
return;
|
||||
if (successMsg) successMsg.classList.remove('active');
|
||||
if (errorMsg) errorMsg.classList.remove('active');
|
||||
}
|
||||
|
||||
console.log("Validation passed, showing loading state...");
|
||||
function resetForm(clearCredentials = false) {
|
||||
// Reset les champs
|
||||
const commitMessage = document.getElementById('commit-message');
|
||||
if (commitMessage) commitMessage.value = '';
|
||||
|
||||
// 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;
|
||||
if (clearCredentials) {
|
||||
const gitUrl = document.getElementById('git-url');
|
||||
const gitToken = document.getElementById('git-token');
|
||||
const saveCredentials = document.getElementById('save-credentials');
|
||||
|
||||
// Sauvegarder les credentials si la case est cochée
|
||||
if (document.getElementById('save-credentials').checked) {
|
||||
try {
|
||||
setCookie('git_pusher_url', gitUrl, 30);
|
||||
setCookie('git_pusher_token', gitToken, 30);
|
||||
setCookie('git_pusher_branch', gitBranch, 30);
|
||||
console.log("Credentials auto-saved to cookies");
|
||||
} catch (e) {
|
||||
console.warn("Could not auto-save credentials:", e);
|
||||
if (gitUrl) gitUrl.value = '';
|
||||
if (gitToken) gitToken.value = '';
|
||||
if (saveCredentials) saveCredentials.checked = false;
|
||||
|
||||
// Supprimer les credentials sauvegardés
|
||||
localStorage.removeItem(GIT_PUSHER_CONFIG.credentialsKey);
|
||||
}
|
||||
|
||||
// Reset la sélection
|
||||
toggleSelectAll(false);
|
||||
|
||||
// Cacher les messages
|
||||
hideMessages();
|
||||
|
||||
console.log("Form reset" + (clearCredentials ? " (with credentials)" : ""));
|
||||
}
|
||||
|
||||
// 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()
|
||||
};
|
||||
// ============================================
|
||||
// GESTION DES CREDENTIALS
|
||||
// ============================================
|
||||
|
||||
console.log("Payload prepared:", payload);
|
||||
function saveCredentialsToStorage(gitUrl, gitBranch, gitToken) {
|
||||
try {
|
||||
const credentials = {
|
||||
gitUrl: gitUrl,
|
||||
gitBranch: gitBranch,
|
||||
// Note: En production, envisager une solution plus sécurisée
|
||||
gitToken: btoa(gitToken), // Encodage basique (pas sécurisé, juste pour l'obfuscation)
|
||||
savedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Appeler le script Python via serveur
|
||||
callPushScript(payload);
|
||||
localStorage.setItem(GIT_PUSHER_CONFIG.credentialsKey, JSON.stringify(credentials));
|
||||
console.log("Credentials saved");
|
||||
} catch (error) {
|
||||
console.error("Error saving credentials:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function callPushScript(payload) {
|
||||
console.log("callPushScript called");
|
||||
console.log("Payload:", payload);
|
||||
function loadSavedCredentials() {
|
||||
try {
|
||||
const saved = localStorage.getItem(GIT_PUSHER_CONFIG.credentialsKey);
|
||||
if (!saved) return;
|
||||
|
||||
// Construire l'URL vers le serveur Python sur le port 9999 en HTTP
|
||||
const hostname = window.location.hostname;
|
||||
const baseUrl = `http://${hostname}:9999`;
|
||||
const credentials = JSON.parse(saved);
|
||||
|
||||
const url = new URL('/push', baseUrl);
|
||||
const gitUrl = document.getElementById('git-url');
|
||||
const gitBranch = document.getElementById('git-branch');
|
||||
const gitToken = document.getElementById('git-token');
|
||||
const saveCredentials = document.getElementById('save-credentials');
|
||||
|
||||
// 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);
|
||||
if (gitUrl && credentials.gitUrl) {
|
||||
gitUrl.value = credentials.gitUrl;
|
||||
}
|
||||
|
||||
// Encoder correctement les apps en JSON
|
||||
const appsJson = JSON.stringify(payload.apps);
|
||||
console.log("Apps JSON:", appsJson);
|
||||
url.searchParams.append('apps', appsJson);
|
||||
if (gitBranch && credentials.gitBranch) {
|
||||
gitBranch.value = credentials.gitBranch;
|
||||
}
|
||||
|
||||
url.searchParams.append('user', payload.user);
|
||||
if (gitToken && credentials.gitToken) {
|
||||
gitToken.value = atob(credentials.gitToken);
|
||||
}
|
||||
|
||||
console.log("Calling:", url.toString());
|
||||
if (saveCredentials) {
|
||||
saveCredentials.checked = true;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
console.log("Credentials loaded from storage");
|
||||
} catch (error) {
|
||||
console.error("Error loading credentials:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentUser() {
|
||||
// ============================================
|
||||
// RÉCUPÉRATION DE L'UTILISATEUR SPLUNK
|
||||
// ============================================
|
||||
|
||||
async function getCurrentUser() {
|
||||
try {
|
||||
// Essayer plusieurs méthodes
|
||||
if (Splunk && Splunk.util && typeof Splunk.util.getCurrentUser === 'function') {
|
||||
return Splunk.util.getCurrentUser();
|
||||
const response = await fetch('/en-US/splunkd/__raw/services/authentication/current-context?output_mode=json');
|
||||
const data = await response.json();
|
||||
return data.entry?.[0]?.content?.username || 'unknown';
|
||||
} catch (error) {
|
||||
console.error("Error getting current user:", error);
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: chercher dans le DOM ou retourner 'unknown'
|
||||
return 'unknown_user';
|
||||
} catch (e) {
|
||||
console.warn("Could not get current user:", e);
|
||||
return 'unknown_user';
|
||||
// ============================================
|
||||
// VÉRIFICATION DU SERVEUR
|
||||
// ============================================
|
||||
|
||||
async function checkServerHealth() {
|
||||
try {
|
||||
const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/health`, {
|
||||
method: 'GET',
|
||||
timeout: 5000
|
||||
});
|
||||
const data = await response.json();
|
||||
return data.status === 'ok';
|
||||
} catch (error) {
|
||||
console.error("Server health check failed:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// Vérifier la santé du serveur au démarrage
|
||||
setTimeout(async () => {
|
||||
const healthy = await checkServerHealth();
|
||||
if (!healthy) {
|
||||
console.warn("Git Pusher server may not be running");
|
||||
} else {
|
||||
console.log("Git Pusher server is healthy");
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
function showError(message) {
|
||||
const errorMsg = document.getElementById('error-msg');
|
||||
document.getElementById('error-text').textContent = 'X ' + message;
|
||||
errorMsg.style.display = 'block';
|
||||
}
|
||||
// ============================================
|
||||
// EXPORT POUR DEBUG
|
||||
// ============================================
|
||||
|
||||
function resetForm(showConfirm = false) {
|
||||
document.getElementById('git-url').value = '';
|
||||
document.getElementById('git-branch').value = 'main';
|
||||
document.getElementById('git-token').value = '';
|
||||
document.getElementById('commit-message').value = '';
|
||||
document.querySelectorAll('#dashboard-list input[type="checkbox"]').forEach(cb => cb.checked = false);
|
||||
window.GitPusher = {
|
||||
config: GIT_PUSHER_CONFIG,
|
||||
getSelectedApps: () => selectedApps,
|
||||
checkServer: checkServerHealth,
|
||||
version: GIT_PUSHER_CONFIG.version
|
||||
};
|
||||
|
||||
// Demander si l'utilisateur veut aussi effacer les credentials sauvegardés
|
||||
// SEULEMENT si showConfirm = true (c'est-à-dire si l'utilisateur a cliqué sur Reset)
|
||||
if (showConfirm && document.getElementById('save-credentials').checked) {
|
||||
const confirmClear = confirm('Do you want to clear saved credentials?');
|
||||
if (confirmClear) {
|
||||
clearSavedCredentials();
|
||||
document.getElementById('save-credentials').checked = false;
|
||||
// ============================================
|
||||
// FIX: Attacher les événements aux boutons
|
||||
// ============================================
|
||||
(function attachButtonEvents() {
|
||||
function tryAttach() {
|
||||
var pushBtn = document.getElementById('push-btn');
|
||||
if (pushBtn) {
|
||||
pushBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
console.log("Push button clicked");
|
||||
pushDashboards();
|
||||
});
|
||||
console.log("✓ Push button event attached");
|
||||
} else {
|
||||
// Réessayer dans 500ms
|
||||
setTimeout(tryAttach, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Démarrer après un délai
|
||||
if (document.readyState === 'complete') {
|
||||
setTimeout(tryAttach, 1000);
|
||||
} else {
|
||||
window.addEventListener('load', function() {
|
||||
setTimeout(tryAttach, 1000);
|
||||
});
|
||||
}
|
||||
})();
|
||||
Binary file not shown.
@ -1 +1 @@
|
||||
919562
|
||||
212462
|
||||
|
||||
@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Git Pusher - License Endpoints
|
||||
Endpoints REST pour gérer les licences
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import tempfile
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
# Ajouter le chemin du module
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
||||
|
||||
from license_validator import LicenseValidator, get_license_status, LICENSE_FILE_PATH
|
||||
|
||||
class LicenseHandler(BaseHTTPRequestHandler):
|
||||
"""Handler pour les requêtes de licence"""
|
||||
|
||||
def do_OPTIONS(self):
|
||||
"""Gérer les requêtes OPTIONS (CORS preflight)"""
|
||||
self.send_response(200)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
"""Gérer les requêtes GET"""
|
||||
if self.path == '/custom/git_pusher/license_status':
|
||||
self.handle_license_status()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def do_POST(self):
|
||||
"""Gérer les requêtes POST"""
|
||||
if self.path == '/custom/git_pusher/install_license':
|
||||
self.handle_install_license()
|
||||
elif self.path == '/custom/git_pusher/request_trial':
|
||||
self.handle_request_trial()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def send_headers(self):
|
||||
"""Envoyer les headers CORS"""
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||
|
||||
def handle_license_status(self):
|
||||
"""Vérifier le statut de la licence"""
|
||||
try:
|
||||
status = get_license_status()
|
||||
|
||||
self.send_response(200)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps(status).encode())
|
||||
|
||||
except Exception as e:
|
||||
self.send_response(500)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps({
|
||||
'licensed': False,
|
||||
'errors': [f'Erreur serveur: {str(e)}']
|
||||
}).encode())
|
||||
|
||||
def handle_install_license(self):
|
||||
"""Installer un fichier de licence uploadé"""
|
||||
try:
|
||||
# Lire le body
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
body = self.rfile.read(content_length)
|
||||
data = json.loads(body.decode())
|
||||
|
||||
license_content = data.get('license_file')
|
||||
filename = data.get('filename', 'uploaded.lic')
|
||||
|
||||
if not license_content:
|
||||
raise Exception("Aucun contenu de licence fourni")
|
||||
|
||||
# Créer un fichier temporaire
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.lic', delete=False) as temp_file:
|
||||
temp_file.write(license_content)
|
||||
temp_path = temp_file.name
|
||||
|
||||
try:
|
||||
# Valider et installer
|
||||
validator = LicenseValidator()
|
||||
result = validator.install_license(temp_path, LICENSE_FILE_PATH)
|
||||
|
||||
self.send_response(200)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps(result).encode())
|
||||
|
||||
finally:
|
||||
# Nettoyer le fichier temporaire
|
||||
if os.path.exists(temp_path):
|
||||
os.remove(temp_path)
|
||||
|
||||
except Exception as e:
|
||||
self.send_response(400)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps({
|
||||
'success': False,
|
||||
'message': f'Erreur: {str(e)}'
|
||||
}).encode())
|
||||
|
||||
def handle_request_trial(self):
|
||||
"""Créer une licence d'essai"""
|
||||
try:
|
||||
from datetime import datetime, timedelta
|
||||
import socket
|
||||
import hashlib
|
||||
|
||||
# Générer une licence d'essai basique (7 jours)
|
||||
hostname = socket.gethostname()
|
||||
trial_license = {
|
||||
"license": {
|
||||
"version": "1.0",
|
||||
"license_id": "TRIAL-" + hashlib.md5(
|
||||
f"{hostname}{datetime.now()}".encode()
|
||||
).hexdigest()[:8].upper(),
|
||||
"customer": {
|
||||
"name": "Trial User",
|
||||
"hostname": hostname
|
||||
},
|
||||
"validity": {
|
||||
"issued": datetime.now().isoformat(),
|
||||
"expires": (datetime.now() + timedelta(days=7)).isoformat(),
|
||||
"days": 7
|
||||
},
|
||||
"limits": {
|
||||
"max_pushes": 50,
|
||||
"max_apps": None
|
||||
},
|
||||
"features": {
|
||||
"git_push": True,
|
||||
"multi_branch": False,
|
||||
"auto_commit": False,
|
||||
"webhooks": False
|
||||
},
|
||||
"type": "trial"
|
||||
},
|
||||
"signature": "TRIAL-NO-SIGNATURE",
|
||||
"checksum": "TRIAL"
|
||||
}
|
||||
|
||||
# Créer le dossier si nécessaire
|
||||
os.makedirs(os.path.dirname(LICENSE_FILE_PATH), exist_ok=True)
|
||||
|
||||
# Sauvegarder
|
||||
with open(LICENSE_FILE_PATH, 'w') as f:
|
||||
json.dump(trial_license, f, indent=2)
|
||||
|
||||
self.send_response(200)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps({
|
||||
'success': True,
|
||||
'message': 'Licence d\'essai créée (7 jours, 50 pushes max)',
|
||||
'license_info': {
|
||||
'license_id': trial_license['license']['license_id'],
|
||||
'type': 'trial',
|
||||
'expires': trial_license['license']['validity']['expires'],
|
||||
'days_remaining': 7
|
||||
}
|
||||
}).encode())
|
||||
|
||||
except Exception as e:
|
||||
self.send_response(500)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps({
|
||||
'success': False,
|
||||
'message': f'Erreur: {str(e)}'
|
||||
}).encode())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Override pour éviter les logs HTTP par défaut"""
|
||||
pass
|
||||
@ -1,173 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Git Pusher - License Generator
|
||||
Génère des clés de licence basées sur le hostname Splunk
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
import socket
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
# Secret key pour générer les licences (À CHANGER !)
|
||||
SECRET_KEY = "git_pusher_license_secret_2024"
|
||||
|
||||
def get_hostname():
|
||||
"""Récupérer le hostname du serveur"""
|
||||
return socket.gethostname()
|
||||
|
||||
def generate_license(hostname, days_valid=365, max_pushes=None):
|
||||
"""
|
||||
Générer une clé de licence
|
||||
|
||||
Args:
|
||||
hostname: nom d'hôte Splunk
|
||||
days_valid: nombre de jours de validité
|
||||
max_pushes: nombre maximum de pushes (None = illimité)
|
||||
|
||||
Returns:
|
||||
license_key: clé de licence formatée
|
||||
"""
|
||||
|
||||
# Créer la date d'expiration
|
||||
expiration_date = datetime.now() + timedelta(days=days_valid)
|
||||
expiration_str = expiration_date.strftime("%Y-%m-%d")
|
||||
|
||||
# Créer le payload
|
||||
payload = {
|
||||
"hostname": hostname,
|
||||
"expiration": expiration_str,
|
||||
"max_pushes": max_pushes,
|
||||
"issued": datetime.now().strftime("%Y-%m-%d")
|
||||
}
|
||||
|
||||
# Convertir en JSON et encoder en base64
|
||||
payload_json = json.dumps(payload, separators=(',', ':'))
|
||||
payload_b64 = base64.b64encode(payload_json.encode()).decode()
|
||||
|
||||
# Créer la signature HMAC
|
||||
signature = hmac.new(
|
||||
SECRET_KEY.encode(),
|
||||
payload_b64.encode(),
|
||||
hashlib.sha256
|
||||
).hexdigest()[:16] # Prendre les 16 premiers caractères
|
||||
|
||||
# Formater la clé de licence
|
||||
license_key = f"{signature}-{payload_b64}"
|
||||
|
||||
return license_key, payload
|
||||
|
||||
def validate_license(license_key, hostname):
|
||||
"""
|
||||
Valider une clé de licence
|
||||
|
||||
Args:
|
||||
license_key: clé à valider
|
||||
hostname: hostname Splunk actuel
|
||||
|
||||
Returns:
|
||||
dict: {valid: bool, error: str, expiration: str, max_pushes: int}
|
||||
"""
|
||||
|
||||
try:
|
||||
# Séparer signature et payload
|
||||
parts = license_key.split('-', 1)
|
||||
if len(parts) != 2:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': 'Format de clé invalide'
|
||||
}
|
||||
|
||||
signature, payload_b64 = parts
|
||||
|
||||
# Vérifier la signature
|
||||
expected_signature = hmac.new(
|
||||
SECRET_KEY.encode(),
|
||||
payload_b64.encode(),
|
||||
hashlib.sha256
|
||||
).hexdigest()[:16]
|
||||
|
||||
if signature != expected_signature:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': 'Signature invalide - clé corrompue ou falsifiée'
|
||||
}
|
||||
|
||||
# Décoder le payload
|
||||
try:
|
||||
payload_json = base64.b64decode(payload_b64).decode()
|
||||
payload = json.loads(payload_json)
|
||||
except Exception as e:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': f'Erreur de décodage: {str(e)}'
|
||||
}
|
||||
|
||||
# Vérifier le hostname
|
||||
if payload.get('hostname') != hostname:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': f'Cette licence est pour {payload.get("hostname")}, pas {hostname}'
|
||||
}
|
||||
|
||||
# Vérifier l'expiration
|
||||
expiration = datetime.strptime(payload.get('expiration'), '%Y-%m-%d')
|
||||
if datetime.now() > expiration:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': f'Licence expirée le {payload.get("expiration")}'
|
||||
}
|
||||
|
||||
return {
|
||||
'valid': True,
|
||||
'expiration': payload.get('expiration'),
|
||||
'max_pushes': payload.get('max_pushes'),
|
||||
'days_remaining': (expiration - datetime.now()).days
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': f'Erreur de validation: {str(e)}'
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
hostname = get_hostname()
|
||||
print("=" * 60)
|
||||
print("Git Pusher - License Generator")
|
||||
print("=" * 60)
|
||||
print(f"\nHostname détecté: {hostname}")
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == 'validate':
|
||||
# Mode validation
|
||||
license_key = sys.argv[2] if len(sys.argv) > 2 else input("Entrez la clé de licence: ")
|
||||
result = validate_license(license_key, hostname)
|
||||
|
||||
print("\nRésultat de validation:")
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
else:
|
||||
# Mode génération
|
||||
days = int(sys.argv[1]) if len(sys.argv) > 1 else 365
|
||||
max_pushes = int(sys.argv[2]) if len(sys.argv) > 2 else None
|
||||
|
||||
license_key, payload = generate_license(hostname, days, max_pushes)
|
||||
|
||||
print(f"\n📋 Payload:")
|
||||
print(json.dumps(payload, indent=2, ensure_ascii=False))
|
||||
|
||||
print(f"\n🔑 Clé de licence générée:")
|
||||
print(license_key)
|
||||
|
||||
print(f"\n✓ Valide pour: {days} jours")
|
||||
if max_pushes:
|
||||
print(f"✓ Pushes limités à: {max_pushes}")
|
||||
|
||||
# Tester la validation
|
||||
print(f"\n✔️ Test de validation:")
|
||||
result = validate_license(license_key, hostname)
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
@ -0,0 +1,485 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Git Pusher - License Validator (Server-side)
|
||||
Ce fichier doit être déployé sur le serveur Splunk dans l'application
|
||||
|
||||
Emplacement: /opt/splunk/etc/apps/pusher_app_prem/bin/license_validator.py
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
from datetime import datetime
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import logging
|
||||
|
||||
# Configuration du logging
|
||||
log_dir = '/opt/splunk/var/log/splunk'
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(os.path.join(log_dir, 'license_validator.log')),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger('license_validator')
|
||||
|
||||
# ============================================
|
||||
# CONFIGURATION - DOIT CORRESPONDRE AU GÉNÉRATEUR !
|
||||
# ============================================
|
||||
|
||||
# IMPORTANT: Cette clé DOIT être identique à celle du générateur
|
||||
SECRET_KEY = "git_pusher_super_secret_key_2024_change_me_in_production"
|
||||
|
||||
# Chemin vers le fichier de licence
|
||||
LICENSE_FILE_PATH = "/opt/splunk/etc/apps/pusher_app_prem/local/license.lic"
|
||||
|
||||
# Fichier de cache pour les stats d'utilisation
|
||||
USAGE_STATS_PATH = "/opt/splunk/etc/apps/pusher_app_prem/local/usage_stats.json"
|
||||
|
||||
|
||||
def get_splunk_hostname():
|
||||
"""Récupérer le hostname du serveur Splunk"""
|
||||
# Essayer d'abord via l'API Splunk
|
||||
try:
|
||||
import urllib.request
|
||||
import ssl
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
splunk_username = os.environ.get('SPLUNK_USERNAME', 'admin')
|
||||
splunk_password = os.environ.get('SPLUNK_PASSWORD', '2312Jocpam!?')
|
||||
credentials = base64.b64encode(f"{splunk_username}:{splunk_password}".encode()).decode()
|
||||
|
||||
req = urllib.request.Request("https://127.0.0.1:8089/services/server/info?output_mode=json")
|
||||
req.add_header('Authorization', f'Basic {credentials}')
|
||||
|
||||
with urllib.request.urlopen(req, timeout=5, context=ssl_context) as response:
|
||||
data = json.loads(response.read().decode('utf-8'))
|
||||
hostname = data.get('entry', [{}])[0].get('content', {}).get('host', '')
|
||||
if hostname:
|
||||
return hostname.lower().strip()
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get hostname via Splunk API: {e}")
|
||||
|
||||
# Fallback sur le hostname système
|
||||
return socket.gethostname().lower().strip()
|
||||
|
||||
|
||||
def create_signature(data_str):
|
||||
"""Créer une signature HMAC-SHA256"""
|
||||
signature = hmac.new(
|
||||
SECRET_KEY.encode('utf-8'),
|
||||
data_str.encode('utf-8'),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
return signature
|
||||
|
||||
|
||||
def verify_signature(data_str, signature):
|
||||
"""Vérifier une signature"""
|
||||
expected = create_signature(data_str)
|
||||
return hmac.compare_digest(expected, signature)
|
||||
|
||||
|
||||
def read_license_file(filepath=None):
|
||||
"""Lire et parser un fichier de licence"""
|
||||
if filepath is None:
|
||||
filepath = LICENSE_FILE_PATH
|
||||
|
||||
try:
|
||||
if not os.path.exists(filepath):
|
||||
return None
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Extraire la partie base64 (ignorer les commentaires)
|
||||
lines = content.strip().split('\n')
|
||||
base64_lines = [l for l in lines if not l.startswith('#') and l.strip()]
|
||||
base64_content = ''.join(base64_lines)
|
||||
|
||||
# Décoder
|
||||
decoded = base64.b64decode(base64_content).decode('utf-8')
|
||||
license_file = json.loads(decoded)
|
||||
|
||||
return license_file
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading license file: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def parse_license_content(content):
|
||||
"""Parser le contenu d'un fichier de licence (pour l'upload)"""
|
||||
try:
|
||||
# Extraire la partie base64 (ignorer les commentaires)
|
||||
lines = content.strip().split('\n')
|
||||
base64_lines = [l for l in lines if not l.startswith('#') and l.strip()]
|
||||
base64_content = ''.join(base64_lines)
|
||||
|
||||
# Décoder
|
||||
decoded = base64.b64decode(base64_content).decode('utf-8')
|
||||
license_file = json.loads(decoded)
|
||||
|
||||
return license_file
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing license content: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def validate_license(license_file=None, current_hostname=None):
|
||||
"""
|
||||
Valider une licence
|
||||
|
||||
Args:
|
||||
license_file: Contenu du fichier de licence (dict) - si None, lit le fichier par défaut
|
||||
current_hostname: Hostname actuel - si None, détecte automatiquement
|
||||
|
||||
Returns:
|
||||
dict avec {valid: bool, error: str, license_info: dict}
|
||||
"""
|
||||
try:
|
||||
# Lire la licence si non fournie
|
||||
if license_file is None:
|
||||
license_file = read_license_file()
|
||||
if license_file is None:
|
||||
return {
|
||||
"valid": False,
|
||||
"error": "Aucun fichier de licence trouvé",
|
||||
"error_code": "NO_LICENSE"
|
||||
}
|
||||
|
||||
# Récupérer le hostname si non fourni
|
||||
if current_hostname is None:
|
||||
current_hostname = get_splunk_hostname()
|
||||
|
||||
license_data = license_file.get("license", {})
|
||||
signature = license_file.get("signature", "")
|
||||
|
||||
# Recréer le JSON pour vérifier la signature
|
||||
license_json = json.dumps(license_data, separators=(',', ':'), sort_keys=True)
|
||||
|
||||
# Vérifier la signature
|
||||
if not verify_signature(license_json, signature):
|
||||
return {
|
||||
"valid": False,
|
||||
"error": "Signature invalide - fichier corrompu ou modifié",
|
||||
"error_code": "INVALID_SIGNATURE"
|
||||
}
|
||||
|
||||
# Vérifier le hostname
|
||||
expected_hostname = license_data.get("binding", {}).get("hostname", "").lower()
|
||||
current_hostname_clean = current_hostname.lower().strip()
|
||||
|
||||
if current_hostname_clean != expected_hostname:
|
||||
return {
|
||||
"valid": False,
|
||||
"error": f"Cette licence est pour '{expected_hostname}', pas '{current_hostname_clean}'",
|
||||
"error_code": "HOSTNAME_MISMATCH",
|
||||
"expected_hostname": expected_hostname,
|
||||
"current_hostname": current_hostname_clean
|
||||
}
|
||||
|
||||
# Vérifier l'expiration
|
||||
expires_timestamp = license_data.get("dates", {}).get("expires_timestamp", 0)
|
||||
if datetime.now().timestamp() > expires_timestamp:
|
||||
expires_date = license_data.get("dates", {}).get("expires", "unknown")
|
||||
return {
|
||||
"valid": False,
|
||||
"error": f"Licence expirée le {expires_date}",
|
||||
"error_code": "EXPIRED"
|
||||
}
|
||||
|
||||
# Calculer les jours restants
|
||||
days_remaining = (expires_timestamp - datetime.now().timestamp()) / (24 * 3600)
|
||||
|
||||
return {
|
||||
"valid": True,
|
||||
"license_id": license_data.get("license_id"),
|
||||
"type": license_data.get("type"),
|
||||
"type_name": license_data.get("type_name"),
|
||||
"customer": license_data.get("customer", {}),
|
||||
"expires": license_data.get("dates", {}).get("expires"),
|
||||
"days_remaining": int(days_remaining),
|
||||
"limits": license_data.get("limits", {}),
|
||||
"features": license_data.get("features", []),
|
||||
"hostname": expected_hostname
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Validation error: {e}")
|
||||
return {
|
||||
"valid": False,
|
||||
"error": f"Erreur de validation: {str(e)}",
|
||||
"error_code": "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
|
||||
def save_license_file(content):
|
||||
"""
|
||||
Sauvegarder un nouveau fichier de licence
|
||||
|
||||
Args:
|
||||
content: Contenu du fichier .lic
|
||||
|
||||
Returns:
|
||||
dict avec {success: bool, error: str}
|
||||
"""
|
||||
try:
|
||||
# Créer le dossier local si nécessaire
|
||||
local_dir = os.path.dirname(LICENSE_FILE_PATH)
|
||||
os.makedirs(local_dir, exist_ok=True)
|
||||
|
||||
# Parser d'abord pour valider
|
||||
license_file = parse_license_content(content)
|
||||
if license_file is None:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Format de fichier de licence invalide"
|
||||
}
|
||||
|
||||
# Valider la licence
|
||||
validation = validate_license(license_file)
|
||||
if not validation.get("valid"):
|
||||
return {
|
||||
"success": False,
|
||||
"error": validation.get("error", "Licence invalide")
|
||||
}
|
||||
|
||||
# Sauvegarder
|
||||
with open(LICENSE_FILE_PATH, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
logger.info(f"License saved: {validation.get('license_id')}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"license_info": validation
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving license: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
def get_usage_stats():
|
||||
"""Récupérer les statistiques d'utilisation"""
|
||||
try:
|
||||
if os.path.exists(USAGE_STATS_PATH):
|
||||
with open(USAGE_STATS_PATH, 'r') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
return {
|
||||
"pushes_today": 0,
|
||||
"pushes_total": 0,
|
||||
"last_push_date": None,
|
||||
"apps_pushed": []
|
||||
}
|
||||
|
||||
|
||||
def increment_usage():
|
||||
"""Incrémenter le compteur d'utilisation"""
|
||||
stats = get_usage_stats()
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
# Reset si nouveau jour
|
||||
if stats.get("last_push_date") != today:
|
||||
stats["pushes_today"] = 0
|
||||
stats["last_push_date"] = today
|
||||
|
||||
stats["pushes_today"] += 1
|
||||
stats["pushes_total"] += 1
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.dirname(USAGE_STATS_PATH), exist_ok=True)
|
||||
with open(USAGE_STATS_PATH, 'w') as f:
|
||||
json.dump(stats, f)
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving usage stats: {e}")
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
def check_limits():
|
||||
"""
|
||||
Vérifier si les limites de la licence sont respectées
|
||||
|
||||
Returns:
|
||||
dict avec {allowed: bool, error: str}
|
||||
"""
|
||||
validation = validate_license()
|
||||
|
||||
if not validation.get("valid"):
|
||||
return {
|
||||
"allowed": False,
|
||||
"error": validation.get("error")
|
||||
}
|
||||
|
||||
limits = validation.get("limits", {})
|
||||
stats = get_usage_stats()
|
||||
|
||||
# Vérifier pushes par jour
|
||||
max_pushes = limits.get("max_pushes_per_day", -1)
|
||||
if max_pushes > 0 and stats.get("pushes_today", 0) >= max_pushes:
|
||||
return {
|
||||
"allowed": False,
|
||||
"error": f"Limite quotidienne atteinte ({max_pushes} pushes/jour)"
|
||||
}
|
||||
|
||||
return {
|
||||
"allowed": True,
|
||||
"license_info": validation,
|
||||
"usage": stats
|
||||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# API REST POUR L'INTERFACE WEB
|
||||
# ============================================
|
||||
|
||||
class LicenseAPIHandler(BaseHTTPRequestHandler):
|
||||
"""Handler pour les requêtes de l'API licence"""
|
||||
|
||||
def send_cors_headers(self):
|
||||
"""Envoyer les headers CORS complets"""
|
||||
origin = self.headers.get('Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Origin', origin)
|
||||
self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin')
|
||||
self.send_header('Access-Control-Allow-Credentials', 'true')
|
||||
self.send_header('Access-Control-Max-Age', '86400')
|
||||
|
||||
def do_OPTIONS(self):
|
||||
logger.info(f"OPTIONS request from {self.headers.get('Origin', 'unknown')}")
|
||||
self.send_response(200)
|
||||
self.send_cors_headers()
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
def do_GET(self):
|
||||
"""GET /license - Récupérer les infos de licence"""
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.send_cors_headers()
|
||||
self.end_headers()
|
||||
|
||||
try:
|
||||
if '/license/status' in self.path or self.path == '/license':
|
||||
validation = validate_license()
|
||||
usage = get_usage_stats()
|
||||
hostname = get_splunk_hostname()
|
||||
|
||||
response = {
|
||||
"status": "valid" if validation.get("valid") else "invalid",
|
||||
"hostname": hostname,
|
||||
"license": validation if validation.get("valid") else None,
|
||||
"error": validation.get("error") if not validation.get("valid") else None,
|
||||
"error_code": validation.get("error_code") if not validation.get("valid") else None,
|
||||
"usage": usage
|
||||
}
|
||||
|
||||
elif '/license/hostname' in self.path:
|
||||
response = {
|
||||
"hostname": get_splunk_hostname()
|
||||
}
|
||||
|
||||
else:
|
||||
response = {"error": "Unknown endpoint"}
|
||||
|
||||
self.wfile.write(json.dumps(response).encode())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"GET error: {e}")
|
||||
self.wfile.write(json.dumps({"error": str(e)}).encode())
|
||||
|
||||
def do_POST(self):
|
||||
"""POST /license/upload - Uploader une nouvelle licence"""
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.send_cors_headers()
|
||||
self.end_headers()
|
||||
|
||||
try:
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
body = self.rfile.read(content_length).decode('utf-8')
|
||||
|
||||
if '/license/upload' in self.path:
|
||||
# Le body contient le contenu du fichier .lic
|
||||
data = json.loads(body)
|
||||
license_content = data.get('license_content', '')
|
||||
|
||||
if not license_content:
|
||||
response = {"success": False, "error": "Contenu de licence vide"}
|
||||
else:
|
||||
response = save_license_file(license_content)
|
||||
|
||||
elif '/license/delete' in self.path:
|
||||
# Supprimer la licence
|
||||
if os.path.exists(LICENSE_FILE_PATH):
|
||||
os.remove(LICENSE_FILE_PATH)
|
||||
response = {"success": True, "message": "Licence supprimée"}
|
||||
else:
|
||||
response = {"success": False, "error": "Aucune licence à supprimer"}
|
||||
|
||||
else:
|
||||
response = {"error": "Unknown endpoint"}
|
||||
|
||||
self.wfile.write(json.dumps(response).encode())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"POST error: {e}")
|
||||
self.wfile.write(json.dumps({"error": str(e)}).encode())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
logger.debug(format % args)
|
||||
|
||||
|
||||
def start_license_server(port=9998):
|
||||
"""Démarrer le serveur API licence (optionnel, peut être intégré au serveur principal)"""
|
||||
server = HTTPServer(('127.0.0.1', port), LicenseAPIHandler)
|
||||
logger.info(f"License API server listening on 127.0.0.1:{port}")
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
# ============================================
|
||||
# CLI POUR TESTS
|
||||
# ============================================
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "status":
|
||||
result = validate_license()
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
|
||||
elif sys.argv[1] == "hostname":
|
||||
print(f"Hostname: {get_splunk_hostname()}")
|
||||
|
||||
elif sys.argv[1] == "server":
|
||||
start_license_server()
|
||||
|
||||
else:
|
||||
print("Usage:")
|
||||
print(" python license_validator.py status # Vérifier la licence")
|
||||
print(" python license_validator.py hostname # Afficher le hostname")
|
||||
print(" python license_validator.py server # Démarrer l'API")
|
||||
else:
|
||||
result = validate_license()
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
@ -1,5 +1,249 @@
|
||||
#!/bin/bash
|
||||
export SPLUNK_USERNAME=admin
|
||||
export SPLUNK_PASSWORD='2312Jocpam!?'
|
||||
python3 /opt/splunk/etc/apps/pusher_app/bin/git_pusher.py > /opt/splunk/var/log/splunk/git_pusher_startup.log 2>&1 &
|
||||
echo $! > /opt/splunk/etc/apps/pusher_app/bin/git_pusher.pid
|
||||
# ============================================
|
||||
# Git Pusher - Start Script
|
||||
# Version 2.0 avec système de licence
|
||||
# ============================================
|
||||
|
||||
# Configuration
|
||||
SPLUNK_HOME=${SPLUNK_HOME:-/opt/splunk}
|
||||
APP_HOME="${SPLUNK_HOME}/etc/apps/pusher_app_prem"
|
||||
BIN_DIR="${APP_HOME}/bin"
|
||||
LOG_DIR="${SPLUNK_HOME}/var/log/splunk"
|
||||
PID_FILE="${BIN_DIR}/git_pusher.pid"
|
||||
|
||||
# Couleurs pour les logs
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Fonction de logging
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Vérifier si le serveur est déjà en cours d'exécution
|
||||
check_running() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
return 0 # Running
|
||||
fi
|
||||
fi
|
||||
return 1 # Not running
|
||||
}
|
||||
|
||||
# Démarrer le serveur
|
||||
start_server() {
|
||||
log_info "Starting Git Pusher server..."
|
||||
|
||||
# Vérifier si déjà en cours
|
||||
if check_running; then
|
||||
log_warn "Git Pusher is already running (PID: $(cat $PID_FILE))"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Créer le répertoire de logs
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
# Variables d'environnement pour l'authentification Splunk
|
||||
# IMPORTANT: Modifiez ces valeurs ou utilisez des variables d'environnement
|
||||
export SPLUNK_USERNAME=${SPLUNK_USERNAME:-admin}
|
||||
export SPLUNK_PASSWORD=${SPLUNK_PASSWORD:-changeme}
|
||||
|
||||
# Démarrer le serveur Python
|
||||
cd "$BIN_DIR"
|
||||
python3 git_pusher.py > "${LOG_DIR}/git_pusher_startup.log" 2>&1 &
|
||||
|
||||
# Sauvegarder le PID
|
||||
echo $! > "$PID_FILE"
|
||||
|
||||
# Attendre un peu et vérifier
|
||||
sleep 2
|
||||
|
||||
if check_running; then
|
||||
log_info "Git Pusher started successfully (PID: $(cat $PID_FILE))"
|
||||
log_info "Server listening on port 9999"
|
||||
|
||||
# Vérifier le statut de la licence
|
||||
HOSTNAME=$(hostname)
|
||||
log_info "Hostname: $HOSTNAME"
|
||||
|
||||
if [ -f "${APP_HOME}/local/license.lic" ]; then
|
||||
log_info "License file found"
|
||||
else
|
||||
log_warn "No license file found at ${APP_HOME}/local/license.lic"
|
||||
log_warn "The application will require license activation"
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to start Git Pusher"
|
||||
log_error "Check logs at ${LOG_DIR}/git_pusher.log"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Arrêter le serveur
|
||||
stop_server() {
|
||||
log_info "Stopping Git Pusher server..."
|
||||
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
kill $PID
|
||||
sleep 2
|
||||
|
||||
# Force kill si nécessaire
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
log_warn "Force killing process..."
|
||||
kill -9 $PID
|
||||
fi
|
||||
|
||||
rm -f "$PID_FILE"
|
||||
log_info "Git Pusher stopped"
|
||||
return 0
|
||||
else
|
||||
log_warn "Process not running, cleaning up PID file"
|
||||
rm -f "$PID_FILE"
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
log_warn "PID file not found, Git Pusher may not be running"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Redémarrer le serveur
|
||||
restart_server() {
|
||||
log_info "Restarting Git Pusher server..."
|
||||
stop_server
|
||||
sleep 1
|
||||
start_server
|
||||
}
|
||||
|
||||
# Afficher le statut
|
||||
show_status() {
|
||||
echo "============================================"
|
||||
echo "Git Pusher Status"
|
||||
echo "============================================"
|
||||
|
||||
if check_running; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
echo -e "Status: ${GREEN}RUNNING${NC}"
|
||||
echo "PID: $PID"
|
||||
echo "Port: 9999"
|
||||
else
|
||||
echo -e "Status: ${RED}STOPPED${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Paths:"
|
||||
echo " App Home: $APP_HOME"
|
||||
echo " Bin Dir: $BIN_DIR"
|
||||
echo " Log Dir: $LOG_DIR"
|
||||
echo ""
|
||||
|
||||
# Statut de la licence
|
||||
echo "License:"
|
||||
if [ -f "${APP_HOME}/local/license.lic" ]; then
|
||||
echo -e " File: ${GREEN}Present${NC}"
|
||||
# Essayer de lire quelques infos
|
||||
if command -v python3 &> /dev/null; then
|
||||
python3 -c "
|
||||
import sys
|
||||
sys.path.insert(0, '$BIN_DIR')
|
||||
from license_validator import validate_license
|
||||
result = validate_license()
|
||||
if result.get('valid'):
|
||||
print(f\" Type: {result.get('type_name', 'N/A')}\")
|
||||
print(f\" Expires: {result.get('expires', 'N/A')}\")
|
||||
print(f\" Days remaining: {result.get('days_remaining', 'N/A')}\")
|
||||
else:
|
||||
print(f\" Status: Invalid - {result.get('error', 'Unknown error')}\")
|
||||
" 2>/dev/null || echo " Unable to read license details"
|
||||
fi
|
||||
else
|
||||
echo -e " File: ${YELLOW}Not found${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Hostname: $(hostname)"
|
||||
echo "============================================"
|
||||
}
|
||||
|
||||
# Afficher les logs
|
||||
show_logs() {
|
||||
LOG_FILE="${LOG_DIR}/git_pusher.log"
|
||||
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
if [ "$1" == "-f" ]; then
|
||||
tail -f "$LOG_FILE"
|
||||
else
|
||||
tail -n 50 "$LOG_FILE"
|
||||
fi
|
||||
else
|
||||
log_warn "Log file not found at $LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Menu d'aide
|
||||
show_help() {
|
||||
echo "Git Pusher - Server Management Script"
|
||||
echo ""
|
||||
echo "Usage: $0 {start|stop|restart|status|logs|help}"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " start Start the Git Pusher server"
|
||||
echo " stop Stop the Git Pusher server"
|
||||
echo " restart Restart the Git Pusher server"
|
||||
echo " status Show the current status"
|
||||
echo " logs Show recent logs (use -f for follow)"
|
||||
echo " help Show this help message"
|
||||
echo ""
|
||||
echo "Environment variables:"
|
||||
echo " SPLUNK_USERNAME Splunk admin username (default: admin)"
|
||||
echo " SPLUNK_PASSWORD Splunk admin password (default: changeme)"
|
||||
echo " SPLUNK_HOME Splunk installation directory (default: /opt/splunk)"
|
||||
}
|
||||
|
||||
# Main
|
||||
case "$1" in
|
||||
start)
|
||||
start_server
|
||||
;;
|
||||
stop)
|
||||
stop_server
|
||||
;;
|
||||
restart)
|
||||
restart_server
|
||||
;;
|
||||
status)
|
||||
show_status
|
||||
;;
|
||||
logs)
|
||||
show_logs "$2"
|
||||
;;
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
# Par défaut, démarrer le serveur (pour compatibilité)
|
||||
if [ -z "$1" ]; then
|
||||
start_server
|
||||
else
|
||||
echo "Unknown command: $1"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
@ -0,0 +1 @@
|
||||
{"pushes_today": 9, "pushes_total": 9, "last_push_date": "2026-01-31", "apps_pushed": []}
|
||||
Loading…
Reference in new issue