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
|
// Configuration
|
||||||
function loadAvailableApps() {
|
const GIT_PUSHER_CONFIG = {
|
||||||
console.log("loadAvailableApps called");
|
serverUrl: window.location.protocol + '//' + window.location.hostname + ':9999',
|
||||||
|
credentialsKey: 'git_pusher_credentials',
|
||||||
const apiUrl = '/en-US/splunkd/__raw/services/apps/local?output_mode=json&count=0';
|
version: '2.0.0'
|
||||||
|
};
|
||||||
fetch(apiUrl)
|
|
||||||
.then(response => {
|
// État global
|
||||||
if (!response.ok) {
|
let selectedApps = [];
|
||||||
throw new Error('HTTP ' + response.status);
|
let isProcessing = false;
|
||||||
}
|
|
||||||
return response.json();
|
// ============================================
|
||||||
})
|
// INITIALISATION
|
||||||
.then(data => {
|
// ============================================
|
||||||
console.log("Apps Response: Found " + (data.entry ? data.entry.length : 0) + " apps");
|
|
||||||
|
require([
|
||||||
if (data.entry && data.entry.length > 0) {
|
'jquery',
|
||||||
const apps = data.entry
|
'splunkjs/mvc',
|
||||||
.filter(item => {
|
'splunkjs/mvc/searchmanager',
|
||||||
// Filtrer les apps système
|
'splunkjs/mvc/simplexml/ready!'
|
||||||
const isHidden = item.content && item.content.is_visible === 0;
|
], function($, mvc, SearchManager) {
|
||||||
const appName = item.name;
|
|
||||||
// Exclure les apps système
|
console.log("Git Pusher v2.0 initializing...");
|
||||||
const systemApps = ['launcher', 'splunk_monitoring_console', 'introspection'];
|
|
||||||
return !isHidden && !systemApps.includes(appName);
|
// Initialiser le système de licence
|
||||||
})
|
if (typeof initializeLicense === 'function') {
|
||||||
.map(item => ({
|
initializeLicense();
|
||||||
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 {
|
} else {
|
||||||
console.warn("No apps found");
|
console.warn("License system not loaded");
|
||||||
showAppsEmpty();
|
}
|
||||||
|
|
||||||
|
// 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) {
|
// Exposer les fonctions globalement
|
||||||
console.log("populateAppsList called with", apps.length, "apps");
|
window.pushDashboards = pushDashboards;
|
||||||
|
window.resetForm = resetForm;
|
||||||
const container = document.getElementById('dashboard-list');
|
window.toggleSelectAll = toggleSelectAll;
|
||||||
|
});
|
||||||
|
|
||||||
if (!container) {
|
// Attacher l'événement au bouton après chargement du DOM
|
||||||
console.error("dashboard-list container not found");
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
return;
|
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) {
|
console.log("Push button event attached");
|
||||||
showAppsEmpty();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}, 2000); // Attendre 2 secondes que tout soit chargé
|
||||||
|
});
|
||||||
|
|
||||||
let html = '<div class="app-header">';
|
// ============================================
|
||||||
html += '<span>Select Applications</span>';
|
// RENDU DE LA LISTE DES APPLICATIONS
|
||||||
html += '<input type="checkbox" id="select-all">';
|
// ============================================
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
apps.forEach((app, index) => {
|
function renderAppsList(rows, fields) {
|
||||||
const checkboxId = 'app-' + index;
|
const container = document.getElementById('dashboard-list');
|
||||||
html += '<div class="app-item">';
|
if (!container) return;
|
||||||
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>';
|
console.log("Fields:", fields);
|
||||||
if (app.description) {
|
console.log("First row:", rows[0]);
|
||||||
html += '<div style="font-size: 11px; color: #999; margin-top: 3px;">' + app.description + '</div>';
|
|
||||||
|
// 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;
|
container.innerHTML = html;
|
||||||
console.log('Successfully populated ' + apps.length + ' apps');
|
|
||||||
|
|
||||||
// Ajouter les event listeners
|
console.log(`Rendered ${validApps} valid applications out of ${rows.length}`);
|
||||||
addCheckboxListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCheckboxListeners() {
|
// ============================================
|
||||||
console.log("addCheckboxListeners called");
|
// GESTION DE LA SÉLECTION
|
||||||
|
// ============================================
|
||||||
|
|
||||||
const selectAllCheckbox = document.getElementById('select-all');
|
function updateSelectedApps() {
|
||||||
if (selectAllCheckbox) {
|
const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app-id]');
|
||||||
selectAllCheckbox.addEventListener('change', function() {
|
selectedApps = [];
|
||||||
toggleSelectAll(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const appCheckboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app]');
|
checkboxes.forEach(cb => {
|
||||||
appCheckboxes.forEach(checkbox => {
|
if (cb.checked) {
|
||||||
checkbox.addEventListener('change', function() {
|
selectedApps.push({
|
||||||
console.log("App checkbox changed");
|
id: cb.getAttribute('data-app-id'),
|
||||||
});
|
label: cb.getAttribute('data-app-label')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function showAppsEmpty() {
|
// Mettre à jour le "Select All"
|
||||||
const container = document.getElementById('dashboard-list');
|
const selectAll = document.getElementById('select-all');
|
||||||
if (container) {
|
if (selectAll) {
|
||||||
container.innerHTML = '<div class="dashboard-empty">No apps found</div>';
|
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
||||||
}
|
const someChecked = Array.from(checkboxes).some(cb => cb.checked);
|
||||||
console.log("Displayed empty state");
|
selectAll.checked = allChecked;
|
||||||
|
selectAll.indeterminate = someChecked && !allChecked;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attendre que le DOM soit chargé
|
console.log(`Selected ${selectedApps.length} apps`);
|
||||||
function initScript() {
|
}
|
||||||
console.log("initScript called");
|
|
||||||
|
|
||||||
// INITIALISER LA LICENCE EN PREMIER
|
function toggleSelectAll(checked) {
|
||||||
initializeLicense();
|
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
|
async function pushDashboards() {
|
||||||
loadSavedCredentials();
|
console.log("Starting push process...");
|
||||||
|
|
||||||
// Attacher les event listeners au bouton
|
// Vérifier si déjà en cours
|
||||||
const pushBtn = document.getElementById('push-btn');
|
if (isProcessing) {
|
||||||
if (pushBtn) {
|
console.log("Push already in progress");
|
||||||
console.log("Push button found, attaching listener");
|
return;
|
||||||
pushBtn.addEventListener('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
console.log("Push button clicked!");
|
|
||||||
pushDashboards();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.warn("Push button not found");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attacher l'event listener pour sauvegarder les credentials
|
// Récupérer les apps sélectionnées directement depuis le DOM
|
||||||
const saveCheckbox = document.getElementById('save-credentials');
|
var apps = [];
|
||||||
if (saveCheckbox) {
|
var checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app-id]:checked');
|
||||||
saveCheckbox.addEventListener('change', function() {
|
checkboxes.forEach(function(cb) {
|
||||||
if (this.checked) {
|
apps.push({
|
||||||
saveCredentials();
|
id: cb.getAttribute('data-app-id'),
|
||||||
} else {
|
label: cb.getAttribute('data-app-label') || cb.getAttribute('data-app-id')
|
||||||
clearSavedCredentials();
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadSavedCredentials() {
|
console.log("Apps selected from DOM:", apps);
|
||||||
console.log("Loading saved credentials...");
|
|
||||||
|
|
||||||
try {
|
// Mettre à jour selectedApps
|
||||||
const savedUrl = getCookie('git_pusher_url');
|
selectedApps = apps;
|
||||||
const savedToken = getCookie('git_pusher_token');
|
|
||||||
const savedBranch = getCookie('git_pusher_branch');
|
|
||||||
|
|
||||||
if (savedUrl) {
|
// Vérifier si déjà en cours
|
||||||
document.getElementById('git-url').value = decodeURIComponent(savedUrl);
|
if (isProcessing) {
|
||||||
console.log("Loaded saved URL from cookie");
|
console.log("Push already in progress");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedToken) {
|
// Vérifier la licence AVANT tout
|
||||||
document.getElementById('git-token').value = decodeURIComponent(savedToken);
|
if (typeof checkLicenseBeforePush === 'function') {
|
||||||
console.log("Loaded saved token from cookie");
|
const licenseOk = await checkLicenseBeforePush();
|
||||||
|
if (!licenseOk) {
|
||||||
|
console.log("License check failed");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedBranch) {
|
|
||||||
document.getElementById('git-branch').value = decodeURIComponent(savedBranch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedUrl && savedToken) {
|
// Récupérer les valeurs du formulaire
|
||||||
document.getElementById('save-credentials').checked = true;
|
const gitUrl = document.getElementById('git-url')?.value?.trim();
|
||||||
}
|
const gitBranch = document.getElementById('git-branch')?.value?.trim() || 'main';
|
||||||
} catch (e) {
|
const gitToken = document.getElementById('git-token')?.value?.trim();
|
||||||
console.warn("Could not load saved credentials:", e);
|
const commitMessage = document.getElementById('commit-message')?.value?.trim();
|
||||||
}
|
const saveCredentials = document.getElementById('save-credentials')?.checked;
|
||||||
}
|
|
||||||
|
|
||||||
function saveCredentials() {
|
// Mettre à jour la liste des apps sélectionnées
|
||||||
console.log("Saving credentials...");
|
updateSelectedApps();
|
||||||
|
|
||||||
try {
|
// Validation
|
||||||
const gitUrl = document.getElementById('git-url').value;
|
if (!gitUrl) {
|
||||||
const gitToken = document.getElementById('git-token').value;
|
showMessage('error', 'Please enter a Git repository URL');
|
||||||
const gitBranch = document.getElementById('git-branch').value;
|
return;
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSavedCredentials() {
|
if (!gitToken) {
|
||||||
console.log("Clearing saved credentials...");
|
showMessage('error', 'Please enter a Git token or password');
|
||||||
|
return;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fonctions utilitaires pour les cookies
|
if (!commitMessage) {
|
||||||
function setCookie(name, value, days) {
|
showMessage('error', 'Please enter a commit message');
|
||||||
const d = new Date();
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCookie(name) {
|
if (selectedApps.length === 0) {
|
||||||
const nameEQ = name + "=";
|
showMessage('error', 'Please select at least one application to deploy');
|
||||||
const ca = document.cookie.split(';');
|
return;
|
||||||
for (let i = 0; i < ca.length; i++) {
|
|
||||||
let c = ca[i].trim();
|
|
||||||
if (c.indexOf(nameEQ) === 0) {
|
|
||||||
return c.substring(nameEQ.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteCookie(name) {
|
// Sauvegarder les credentials si demandé
|
||||||
setCookie(name, "", -1);
|
if (saveCredentials) {
|
||||||
console.log("Cookie deleted: " + name);
|
saveCredentialsToStorage(gitUrl, gitBranch, gitToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
// Démarrer le push
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
isProcessing = true;
|
||||||
console.log("DOM Ready - Initializing script...");
|
showLoading(true);
|
||||||
setTimeout(function() {
|
hideMessages();
|
||||||
initScript();
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log("DOM already ready - Initializing script...");
|
|
||||||
setTimeout(function() {
|
|
||||||
initScript();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFormKeyValue() {
|
|
||||||
try {
|
try {
|
||||||
// Chercher dans les meta tags du DOM
|
// Récupérer l'utilisateur courant
|
||||||
const metaTag = document.querySelector('meta[name="splunk_form_key"]');
|
const currentUser = await getCurrentUser();
|
||||||
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');
|
// 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.log(`Pushing ${selectedApps.length} apps to ${gitUrl}`);
|
||||||
console.error("dashboard-list container not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!apps || apps.length === 0) {
|
// Appeler le serveur
|
||||||
showAppsEmpty();
|
const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/push?${params.toString()}`, {
|
||||||
return;
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let html = '<div class="select-all-group">';
|
const result = await response.json();
|
||||||
html += '<input type="checkbox" id="select-all" onchange="toggleSelectAll(this)">';
|
console.log("Push result:", result);
|
||||||
html += '<label for="select-all">Select All (' + apps.length + ' apps)</label>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
apps.forEach((app, index) => {
|
if (result.status === 'success') {
|
||||||
const checkboxId = 'app-' + index;
|
showMessage('success', `✅ Successfully deployed ${result.apps_pushed || selectedApps.length} application(s) to Git!`);
|
||||||
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;
|
// Reset la sélection après succès
|
||||||
console.log('Successfully populated ' + apps.length + ' apps');
|
setTimeout(() => {
|
||||||
}
|
toggleSelectAll(false);
|
||||||
|
}, 2000);
|
||||||
|
} else if (result.error_code === 'LICENSE_ERROR') {
|
||||||
|
showMessage('error', `🔐 ${result.message}`);
|
||||||
|
|
||||||
function showAppsEmpty() {
|
// Afficher le modal de licence
|
||||||
const container = document.getElementById('dashboard-list');
|
if (typeof showLicenseModal === 'function') {
|
||||||
if (container) {
|
showLicenseModal(result.message, result.error_code);
|
||||||
container.innerHTML = '<div class="dashboard-empty">No apps found</div>';
|
|
||||||
}
|
}
|
||||||
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) {
|
} catch (error) {
|
||||||
const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app]');
|
console.error("Push error:", error);
|
||||||
checkboxes.forEach(cb => cb.checked = checkbox.checked);
|
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 (loading) {
|
||||||
if (!checkLicenseBeforePush()) {
|
loading.classList.toggle('active', show);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
console.log("pushDashboards called");
|
|
||||||
|
|
||||||
const gitUrl = document.getElementById('git-url').value;
|
if (pushBtn) {
|
||||||
const gitBranch = document.getElementById('git-branch').value;
|
pushBtn.disabled = show;
|
||||||
const gitToken = document.getElementById('git-token').value;
|
pushBtn.textContent = show ? '⏳ Deploying...' : '✈️ Deploy to Git';
|
||||||
const commitMessage = document.getElementById('commit-message').value;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Git URL:", gitUrl);
|
function showMessage(type, text) {
|
||||||
console.log("Git Branch:", gitBranch);
|
hideMessages();
|
||||||
console.log("Commit Message:", commitMessage);
|
|
||||||
|
|
||||||
const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"]:not(#select-all):checked');
|
const successMsg = document.getElementById('success-msg');
|
||||||
const selectedApps = Array.from(checkboxes).map(cb => ({
|
const errorMsg = document.getElementById('error-msg');
|
||||||
id: cb.value,
|
const successText = document.getElementById('success-text');
|
||||||
name: cb.getAttribute('data-name')
|
const errorText = document.getElementById('error-text');
|
||||||
}));
|
|
||||||
|
|
||||||
console.log("Selected apps:", selectedApps);
|
if (type === 'success' && successMsg && successText) {
|
||||||
|
successText.textContent = text;
|
||||||
|
successMsg.classList.add('active');
|
||||||
|
|
||||||
// Validation
|
// Auto-hide après 5 secondes
|
||||||
if (!gitUrl.trim()) {
|
setTimeout(() => {
|
||||||
console.warn("Validation failed: No Git URL");
|
successMsg.classList.remove('active');
|
||||||
showError('Please enter a Git repository URL');
|
}, 5000);
|
||||||
return;
|
} 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()) {
|
function hideMessages() {
|
||||||
console.warn("Validation failed: No commit message");
|
const successMsg = document.getElementById('success-msg');
|
||||||
showError('Please enter a commit message');
|
const errorMsg = document.getElementById('error-msg');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedApps.length === 0) {
|
if (successMsg) successMsg.classList.remove('active');
|
||||||
console.warn("Validation failed: No apps selected");
|
if (errorMsg) errorMsg.classList.remove('active');
|
||||||
showError('Please select at least one application');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
if (clearCredentials) {
|
||||||
document.getElementById('loading').style.display = 'block';
|
const gitUrl = document.getElementById('git-url');
|
||||||
document.getElementById('success-msg').style.display = 'none';
|
const gitToken = document.getElementById('git-token');
|
||||||
document.getElementById('error-msg').style.display = 'none';
|
const saveCredentials = document.getElementById('save-credentials');
|
||||||
document.getElementById('push-btn').disabled = true;
|
|
||||||
|
|
||||||
// Sauvegarder les credentials si la case est cochée
|
if (gitUrl) gitUrl.value = '';
|
||||||
if (document.getElementById('save-credentials').checked) {
|
if (gitToken) gitToken.value = '';
|
||||||
try {
|
if (saveCredentials) saveCredentials.checked = false;
|
||||||
setCookie('git_pusher_url', gitUrl, 30);
|
|
||||||
setCookie('git_pusher_token', gitToken, 30);
|
// Supprimer les credentials sauvegardés
|
||||||
setCookie('git_pusher_branch', gitBranch, 30);
|
localStorage.removeItem(GIT_PUSHER_CONFIG.credentialsKey);
|
||||||
console.log("Credentials auto-saved to cookies");
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Could not auto-save credentials:", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 = {
|
// GESTION DES CREDENTIALS
|
||||||
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);
|
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
|
localStorage.setItem(GIT_PUSHER_CONFIG.credentialsKey, JSON.stringify(credentials));
|
||||||
callPushScript(payload);
|
console.log("Credentials saved");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving credentials:", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function callPushScript(payload) {
|
function loadSavedCredentials() {
|
||||||
console.log("callPushScript called");
|
try {
|
||||||
console.log("Payload:", payload);
|
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 credentials = JSON.parse(saved);
|
||||||
const hostname = window.location.hostname;
|
|
||||||
const baseUrl = `http://${hostname}:9999`;
|
|
||||||
|
|
||||||
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
|
if (gitUrl && credentials.gitUrl) {
|
||||||
url.searchParams.append('git_url', payload.git_url);
|
gitUrl.value = credentials.gitUrl;
|
||||||
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
|
if (gitBranch && credentials.gitBranch) {
|
||||||
const appsJson = JSON.stringify(payload.apps);
|
gitBranch.value = credentials.gitBranch;
|
||||||
console.log("Apps JSON:", appsJson);
|
}
|
||||||
url.searchParams.append('apps', appsJson);
|
|
||||||
|
|
||||||
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(), {
|
console.log("Credentials loaded from storage");
|
||||||
method: 'POST',
|
} catch (error) {
|
||||||
mode: 'no-cors'
|
console.error("Error loading credentials:", error);
|
||||||
})
|
}
|
||||||
.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() {
|
// ============================================
|
||||||
|
// RÉCUPÉRATION DE L'UTILISATEUR SPLUNK
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
async function getCurrentUser() {
|
||||||
try {
|
try {
|
||||||
// Essayer plusieurs méthodes
|
const response = await fetch('/en-US/splunkd/__raw/services/authentication/current-context?output_mode=json');
|
||||||
if (Splunk && Splunk.util && typeof Splunk.util.getCurrentUser === 'function') {
|
const data = await response.json();
|
||||||
return Splunk.util.getCurrentUser();
|
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';
|
// VÉRIFICATION DU SERVEUR
|
||||||
} catch (e) {
|
// ============================================
|
||||||
console.warn("Could not get current user:", e);
|
|
||||||
return 'unknown_user';
|
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) {
|
// Vérifier la santé du serveur au démarrage
|
||||||
const successMsg = document.getElementById('success-msg');
|
setTimeout(async () => {
|
||||||
document.getElementById('success-text').textContent = message;
|
const healthy = await checkServerHealth();
|
||||||
successMsg.style.display = 'block';
|
if (!healthy) {
|
||||||
setTimeout(() => {
|
console.warn("Git Pusher server may not be running");
|
||||||
successMsg.style.display = 'none';
|
} else {
|
||||||
}, 5000);
|
console.log("Git Pusher server is healthy");
|
||||||
}
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
function showError(message) {
|
// ============================================
|
||||||
const errorMsg = document.getElementById('error-msg');
|
// EXPORT POUR DEBUG
|
||||||
document.getElementById('error-text').textContent = 'X ' + message;
|
// ============================================
|
||||||
errorMsg.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetForm(showConfirm = false) {
|
window.GitPusher = {
|
||||||
document.getElementById('git-url').value = '';
|
config: GIT_PUSHER_CONFIG,
|
||||||
document.getElementById('git-branch').value = 'main';
|
getSelectedApps: () => selectedApps,
|
||||||
document.getElementById('git-token').value = '';
|
checkServer: checkServerHealth,
|
||||||
document.getElementById('commit-message').value = '';
|
version: GIT_PUSHER_CONFIG.version
|
||||||
document.querySelectorAll('#dashboard-list input[type="checkbox"]').forEach(cb => cb.checked = false);
|
};
|
||||||
|
|
||||||
// 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)
|
// FIX: Attacher les événements aux boutons
|
||||||
if (showConfirm && document.getElementById('save-credentials').checked) {
|
// ============================================
|
||||||
const confirmClear = confirm('Do you want to clear saved credentials?');
|
(function attachButtonEvents() {
|
||||||
if (confirmClear) {
|
function tryAttach() {
|
||||||
clearSavedCredentials();
|
var pushBtn = document.getElementById('push-btn');
|
||||||
document.getElementById('save-credentials').checked = false;
|
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
|
#!/bin/bash
|
||||||
export SPLUNK_USERNAME=admin
|
# ============================================
|
||||||
export SPLUNK_PASSWORD='2312Jocpam!?'
|
# Git Pusher - Start Script
|
||||||
python3 /opt/splunk/etc/apps/pusher_app/bin/git_pusher.py > /opt/splunk/var/log/splunk/git_pusher_startup.log 2>&1 &
|
# Version 2.0 avec système de licence
|
||||||
echo $! > /opt/splunk/etc/apps/pusher_app/bin/git_pusher.pid
|
# ============================================
|
||||||
|
|
||||||
|
# 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