You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Splunk_Deploiement/apps/pusher_app_prem/appserver/static/license_validation.js

972 lines
35 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Git Pusher License Module v2.1
// (c) 2026 - Proprietary Software
function _d(a,k){return a.map(function(c){return String.fromCharCode(c^k)}).join('')}
var _yBOwzrHa='aC2WIHCyrXL55sdaZYmCeiGoDUD9uxbA';
var _iywTgOKM='c5cHGMalLoMFhLSNJzoSUFIKqfvwwSDA';
var _FqwiCUSy='D4lcGNtxgGEzBo0CmkIr0of2wuXCQvw1';
var _GzuzHWdA='UtReFKpwkgsosYxpdVpaVy7rxx4g0x5X';
var _JDwUeTZv='Z74KgA9iCjfNpZcExT8bY0mNeVOCAvHl';
var _bykrOplk='plqB89Gu7XPaC9jLlCgQi2yJadINDbeA';
var _TmuBSDob='w2D8yUMpFY6U5jzQLU46kNLNu2DNTmFJ';
var _oRPXAMTn='YeI5aaXSJ8f66xg0pq31m4xRTzO1NllD';
var _KNXWivCf='Y3T35X8uSJdTEn0bhpkKdi8jmWFrRF4g';
var _wRwdtsAl='wnFyASFDoyBYmaZw4MKRKnguQ20dsuYa';
function _xbRdZKKL(a,b){
var c=a^b;var d=c.toString(16);
return d.split('').reverse().join('');
}
function _hBTVdjAC(a,b){
var c=a^b;var d=c.toString(16);
return d.split('').reverse().join('');
}
function _FMolrKWt(a,b){
var c=a^b;var d=c.toString(16);
return d.split('').reverse().join('');
}
function _kyanRbHm(a,b){
var c=a^b;var d=c.toString(16);
return d.split('').reverse().join('');
}
function _aLzzRfEs(a,b){
var c=a^b;var d=c.toString(16);
return d.split('').reverse().join('');
}
var _htINRRRo=_d([149,149,149,149,149,250,253,255,241,246,152,232,237,250,244,241,251,152,243,253,225,149,149,149,149,149,178],184);
var _ntgdrTCI=_d([65,69,69,79,69,102,77,66,78,107,103,125,100,103,101,75,53,123,60,78,77,93,73,74,77,77,67,79,77,107,52,77,65,69,69,79,79,107,71,79,77,107,73,77,98,102,62,100,67,107,58,61,93,53,103,53,101,118,56,89,57],12);
var _jnIqxkUd=_d([203,186,196,223,233,236,199,255,253,193,217,202,166,226,255,247,189,162,198,253,239,247,191,197,222,245,239,204,219,213,230,251,193,184,202,251,212,219,235,245,223,194,231,244,189,216,234,245,194,215,203,244,238,215,204,236,202,195,191,236,224],141);
var _yhFFJYjy=_d([166,184,206,201,197,204,201,188,233,217,193,180,198,206,221,229,200,236,221,252,250,185,213,217,203,227,235,187,216,255,184,219,202,189,166,222,235,249,248,234,217,233,217,238,244,245,223,232,187,188,185,215,166,228,187,188,162,191,236,229,230],141);
var _BJwqjLAM=_d([28,69,120,116,10,119,7,88,113,89,7,66,101,7,64,86,7,95,127,88,10,10,0,95,118,82,98,65,124,107,88,107,112,81,105,11,65,92,74,113,6,126,99,86,124,80,91,99,75,105,87,4,96,119,73,92,92,69,118,80,74],51);
var _NVaoHAkC=_d([62,6,13,89,45,10,89,14,50,0,93,62,60,6,9,25,18,6,40,5,33,57,24,13,19,46,61,49,4,13,58,58,18,27,90,34,39,56,83,32,30,56,19,10,26,30,51,29,38,60,6,88,8,51,61,89,32,25,24,68,88],107);
var _wGSgdQKc=_d([232,152,204,213,157,219,239,253,224,223,247,255,225,134,194,251,195,154,130,201,251,195,215,207,226,193,207,196,203,200,194,222,249,244,204,204,201,156,233,225,201,154,227,232,202,222,217,158,226,235,248,219,134,201,254,229,152,197,206,238,238],173);
var _CLlbOFeK=_d([232,184,189,194,195,198,216,253,243,232,193,178,231,188,248,185,224,253,192,233,206,205,219,238,227,190,179,185,193,197,230,217,228,198,198,197,219,217,233,239,160,164,209,221,207,238,193,164,228,201,184,191,191,160,223,255,201,189,221,238,218],139);
var _fKVLCnVQ=_d([117,0,22,62,8,62,40,32,32,46,11,53,62,29,50,45,32,116,16,3,2,52,47,48,11,44,1,48,46,30,0,38,104,61,2,36,51,119,54,52,117,104,15,5,20,118,1,8,6,54,53,11,46,23,35,22,30,13,19,63,108],71);
var _ozzrxkkB=_d([183,141,151,189,141,177,139,140,202,149,214,173,209,215,169,206,215,157,175,134,135,139,142,140,131,164,130,139,221,171,161,208,142,215,156,180,146,215,130,142,209,164,137,169,148,148,215,156,213,212,136,213,143,150,173,129,143,132,170,131,141],229);
var _XhXXlbeC=_d([195,242,242,216,196,241,242,252,185,205,230,167,195,201,230,203,223,194,219,242,202,163,226,253,250,188,242,238,236,191,184,218,188,248,196,220,209,238,233,255,250,186,198,220,254,220,201,224,199,252,225,199,193,226,223,226,187,176,184,231,207],136);
var _elPSQBXU=_d([132,145,140,162,170,167,163,189,216,185,221,173,152,209,176,133,194,166,145,162,165,171,144,221,138,135,188,133,142,191,154,139,153,162,163,156,142,222,174,134,157,129,221,142,222,139,132,170,191,164,129,170,221,139,143,222,163,171,198,128,189],233);
var _KIvMdaiz=_d([60,60,29,118,10,38,3,47,25,47,20,22,97,8,43,8,34,33,3,125,55,23,55,33,97,41,52,35,15,23,127,119,52,61,27,42,31,59,12,62,54,13,57,7,40,97,4,121,31,122,42,23,61,10,37,3,13,15,57,11,15,15,31,115,115],78);
var _LPCNMBgD=_d([18,53,53,53,53,53,93,86,92,56,72,77,90,84,81,91,56,83,93,65,53,53,53,53,53],24);
var _zSFDtZls=_htINRRRo+_ntgdrTCI+_jnIqxkUd+_yhFFJYjy+_BJwqjLAM+_NVaoHAkC+_wGSgdQKc+_CLlbOFeK+_fKVLCnVQ+_ozzrxkkB+_XhXXlbeC+_elPSQBXU+_KIvMdaiz+_LPCNMBgD;
var PUBLIC_KEY_PEM=_zSFDtZls;
const DEFAULT_APP_CONFIG = {
api: {
url: '',
port: 9999,
useProxy: true
}
};
function loadAppConfigForLicense() {
try {
const stored = localStorage.getItem('git_pusher_config');
if (stored) {
return JSON.parse(stored);
}
} catch (e) {
console.warn('Erreur chargement config localStorage:', e);
}
return DEFAULT_APP_CONFIG;
}
function getLicenseServerUrl() {
const config = loadAppConfigForLicense();
const hostname = window.location.hostname;
const protocol = window.location.protocol;
if (config.api && config.api.url) {
let url = config.api.url;
if (!config.api.useProxy && config.api.port) {
url = url.replace(/\/$/, '') + ':' + config.api.port;
}
return url;
}
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(hostname) || hostname === 'localhost') {
return protocol + '//' + hostname + ':9999';
}
const parts = hostname.split('.');
if (parts.length >= 2) {
parts[0] = parts[0] + '-api';
return protocol + '//' + parts.join('.');
}
return protocol + '//' + hostname + ':9999';
}
const LICENSE_CONFIG = {
storageKey: 'git_pusher_license',
usageKey: 'git_pusher_usage',
version: '2.1.0',
serverUrl: getLicenseServerUrl()
};
function pemToArrayBuffer(pem) {
const b64 = pem
.replace(/-----BEGIN PUBLIC KEY-----/, '')
.replace(/-----END PUBLIC KEY-----/, '')
.replace(/\s/g, '');
const binary = atob(b64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}
async function importPublicKey() {
try {
const keyData = pemToArrayBuffer(PUBLIC_KEY_PEM);
const key = await crypto.subtle.importKey(
'spki',
keyData,
{
name: 'RSASSA-PKCS1-v1_5',
hash: 'SHA-256'
},
false,
['verify']
);
return key;
} catch (error) {
console.error('Erreur import clé publique:', error);
return null;
}
}
async function verifySignature(data, signatureB64, publicKey) {
try {
const signature = Uint8Array.from(atob(signatureB64), c => c.charCodeAt(0));
const dataBuffer = new TextEncoder().encode(data);
const isValid = await crypto.subtle.verify(
'RSASSA-PKCS1-v1_5',
publicKey,
signature,
dataBuffer
);
return isValid;
} catch (error) {
console.error('Erreur vérification signature:', error);
return false;
}
}
function base64Decode(str) {
try {
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
} catch (e) {
return atob(str);
}
}
function getCurrentHostname() {
return window.location.hostname.toLowerCase();
}
async function getSplunkHostname() {
try {
const response = await fetch('/en-US/splunkd/__raw/services/server/info?output_mode=json', {
credentials: 'include'
});
if (response.ok) {
const data = await response.json();
const serverName = data.entry?.[0]?.content?.serverName;
if (serverName) {
console.log('Hostname Splunk (server/info):', serverName);
return serverName.toLowerCase();
}
}
} catch (e) {
console.log('Méthode server/info échouée:', e);
}
try {
const response2 = await fetch('/en-US/splunkd/__raw/services/server/settings?output_mode=json', {
credentials: 'include'
});
if (response2.ok) {
const data2 = await response2.json();
const serverName = data2.entry?.[0]?.content?.serverName;
if (serverName) {
console.log('Hostname Splunk (server/settings):', serverName);
return serverName.toLowerCase();
}
}
} catch (e) {
console.log('Méthode server/settings échouée:', e);
}
console.warn('Impossible de récupérer le hostname Splunk, utilisation URL:', getCurrentHostname());
return getCurrentHostname();
}
function daysRemaining(expiryDate) {
const expiry = new Date(expiryDate);
const now = new Date();
const diff = expiry - now;
return Math.ceil(diff / (1000 * 60 * 60 * 24));
}
function parseLicenseFile(content) {
try {
const lines = content.trim().split('\n');
let payloadB64 = null;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#')) {
payloadB64 = trimmed;
break;
}
}
if (!payloadB64) {
return { error: 'Payload non trouvé dans le fichier' };
}
const payloadJson = base64Decode(payloadB64);
const payload = JSON.parse(payloadJson);
if (!payload.license || !payload.signature) {
return { error: 'Format de licence invalide' };
}
const licenseJson = base64Decode(payload.license);
const licenseData = JSON.parse(licenseJson);
return {
success: true,
licenseJson: licenseJson,
licenseData: licenseData,
signatureB64: payload.signature,
rawPayload: payloadB64
};
} catch (error) {
console.error('Erreur parsing licence:', error);
return { error: 'Erreur de lecture du fichier de licence' };
}
}
async function validateLicense(licenseContent = null) {
try {
let parsed;
if (licenseContent) {
parsed = parseLicenseFile(licenseContent);
} else {
const stored = localStorage.getItem(LICENSE_CONFIG.storageKey);
if (!stored) {
return {
valid: false,
error: 'Aucune licence installée',
error_code: 'NO_LICENSE'
};
}
parsed = JSON.parse(stored);
}
if (parsed.error) {
return {
valid: false,
error: parsed.error,
error_code: 'PARSE_ERROR'
};
}
const { licenseJson, licenseData, signatureB64 } = parsed;
console.log('=== DEBUG VALIDATION LICENCE ===');
console.log('License JSON:', licenseJson);
console.log('License JSON length:', licenseJson.length);
console.log('Signature B64:', signatureB64.substring(0, 50) + '...');
console.log('Signature B64 length:', signatureB64.length);
console.log('Vérification de la signature RSA...');
const publicKey = await importPublicKey();
if (!publicKey) {
console.error('Échec import clé publique');
return {
valid: false,
error: 'Impossible de charger la clé publique',
error_code: 'KEY_ERROR'
};
}
console.log('Clé publique importée avec succès');
const signatureValid = await verifySignature(licenseJson, signatureB64, publicKey);
console.log('Résultat vérification signature:', signatureValid);
if (!signatureValid) {
return {
valid: false,
error: 'Signature de licence invalide',
error_code: 'INVALID_SIGNATURE'
};
}
console.log('✓ Signature RSA valide');
const expectedHostname = (licenseData.hostname || '').toLowerCase();
const currentHostname = await getSplunkHostname();
console.log(`Hostname attendu: "${expectedHostname}", actuel: "${currentHostname}"`);
if (expectedHostname && expectedHostname !== currentHostname) {
if (!currentHostname.includes(expectedHostname) && !expectedHostname.includes(currentHostname)) {
return {
valid: false,
error: `Licence non valide pour ce serveur. Attendu: ${expectedHostname}, Actuel: ${currentHostname}`,
error_code: 'HOSTNAME_MISMATCH',
expected_hostname: expectedHostname,
current_hostname: currentHostname
};
}
console.log('✓ Hostname valide (correspondance partielle)');
} else {
console.log('✓ Hostname valide (exact)');
}
const expiryDate = licenseData.expires;
if (expiryDate) {
const days = daysRemaining(expiryDate);
if (days < 0) {
return {
valid: false,
error: `Licence expirée le ${expiryDate}`,
error_code: 'LICENSE_EXPIRED',
expires: expiryDate
};
}
console.log(`✓ Licence valide (${days} jours restants)`);
}
return {
valid: true,
license_id: licenseData.license_id,
type: licenseData.type,
type_name: licenseData.type_name,
customer: licenseData.customer,
hostname: expectedHostname,
issued: licenseData.issued,
expires: expiryDate,
days_remaining: daysRemaining(expiryDate),
limits: licenseData.limits || {},
features: licenseData.features || []
};
} catch (error) {
console.error('Erreur validation licence:', error);
return {
valid: false,
error: error.message,
error_code: 'VALIDATION_ERROR'
};
}
}
async function hasFeature(featureName) {
const validation = await validateLicense();
if (!validation.valid) return false;
return validation.features.includes(featureName);
}
async function saveLicense(licenseContent) {
try {
const parsed = parseLicenseFile(licenseContent);
if (parsed.error) {
return {
success: false,
error: parsed.error
};
}
const validation = await validateLicense(licenseContent);
if (!validation.valid) {
return {
success: false,
error: validation.error,
error_code: validation.error_code
};
}
localStorage.setItem(LICENSE_CONFIG.storageKey, JSON.stringify(parsed));
console.log('✓ Licence sauvegardée dans localStorage');
try {
const response = await fetch(`${LICENSE_CONFIG.serverUrl}/license/save`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
license_content: licenseContent
})
});
const serverResult = await response.json();
if (serverResult.success) {
console.log('✓ Licence sauvegardée sur le serveur');
} else {
console.warn('⚠ Échec sauvegarde serveur:', serverResult.error);
}
} catch (serverError) {
console.warn('⚠ Impossible de sauvegarder sur le serveur:', serverError);
}
return {
success: true,
license: validation
};
} catch (error) {
console.error('Erreur sauvegarde licence:', error);
return {
success: false,
error: error.message
};
}
}
async function loadLicenseFromServer() {
try {
const response = await fetch(`${LICENSE_CONFIG.serverUrl}/license/file`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (result.success && result.content) {
console.log('Licence trouvée sur le serveur, validation en cours...');
const parsed = parseLicenseFile(result.content);
if (parsed.error) {
console.warn('Erreur parsing licence serveur:', parsed.error);
return false;
}
const validation = await validateLicense(result.content);
if (validation.valid) {
localStorage.setItem(LICENSE_CONFIG.storageKey, JSON.stringify(parsed));
console.log('✓ Licence chargée depuis le serveur et stockée localement');
return true;
} else {
console.warn('Licence serveur invalide:', validation.error);
return false;
}
} else {
console.log('Aucune licence sur le serveur');
return false;
}
} catch (error) {
console.log('Impossible de charger la licence depuis le serveur:', error.message);
return false;
}
}
async function removeLicense() {
localStorage.removeItem(LICENSE_CONFIG.storageKey);
localStorage.removeItem(LICENSE_CONFIG.usageKey);
try {
await fetch(`${LICENSE_CONFIG.serverUrl}/license/delete`, {
method: 'POST'
});
console.log('Licence supprimée du serveur');
} catch (e) {
console.warn('Impossible de supprimer la licence du serveur:', e);
}
console.log('Licence supprimée');
}
function getLicenseInfo() {
try {
const stored = localStorage.getItem(LICENSE_CONFIG.storageKey);
if (!stored) return null;
const parsed = JSON.parse(stored);
return parsed.licenseData;
} catch {
return null;
}
}
function getUsageStats() {
try {
const stored = localStorage.getItem(LICENSE_CONFIG.usageKey);
if (stored) {
return JSON.parse(stored);
}
} catch {}
return {
total_pushes: 0,
pushes_today: 0,
last_push_date: null
};
}
function incrementUsage() {
const stats = getUsageStats();
const today = new Date().toISOString().split('T')[0];
if (stats.last_push_date !== today) {
stats.pushes_today = 0;
stats.last_push_date = today;
}
stats.total_pushes = (stats.total_pushes || 0) + 1;
stats.pushes_today = (stats.pushes_today || 0) + 1;
localStorage.setItem(LICENSE_CONFIG.usageKey, JSON.stringify(stats));
return stats;
}
async function checkLimits() {
const validation = await validateLicense();
if (!validation.valid) {
return {
allowed: false,
error: validation.error,
error_code: validation.error_code
};
}
const limits = validation.limits || {};
const maxPushes = limits.max_pushes_per_day || -1;
if (maxPushes > 0) {
const stats = getUsageStats();
const today = new Date().toISOString().split('T')[0];
let pushesToday = stats.pushes_today || 0;
if (stats.last_push_date !== today) {
pushesToday = 0;
}
if (pushesToday >= maxPushes) {
return {
allowed: false,
error: `Limite quotidienne atteinte (${maxPushes} pushes/jour)`,
error_code: 'DAILY_LIMIT_REACHED'
};
}
}
return {
allowed: true,
license_type: validation.type_name,
remaining_today: maxPushes > 0 ? maxPushes - getUsageStats().pushes_today : -1
};
}
async function updateLicenseBadge() {
const container = document.getElementById('license-badge-container');
if (!container) return;
const validation = await validateLicense();
let badgeHtml = '';
if (validation.valid) {
const daysLeft = validation.days_remaining;
let badgeClass = 'license-badge-valid';
let icon = '✓';
if (daysLeft <= 7) {
badgeClass = 'license-badge-expiring';
icon = '⚠️';
} else if (daysLeft <= 30) {
badgeClass = 'license-badge-warning';
icon = '⏳';
}
badgeHtml = `
<div class="license-badge ${badgeClass}" onclick="showLicenseDetails()">
<span class="license-icon">${icon}</span>
<span class="license-type">${validation.type_name}</span>
<span class="license-days">${daysLeft}j</span>
</div>
`;
} else {
badgeHtml = `
<div class="license-badge license-badge-invalid" onclick="showLicenseModal()">
<span class="license-icon">🔐</span>
<span class="license-type">Activer</span>
</div>
`;
}
container.innerHTML = badgeHtml;
}
function showLicenseModal(message = null, errorCode = null) {
const existingModal = document.getElementById('license-modal');
if (existingModal) existingModal.remove();
const modal = document.createElement('div');
modal.id = 'license-modal';
modal.className = 'license-modal-overlay';
let errorMessage = '';
if (message) {
errorMessage = `<div class="license-error-message">${message}</div>`;
}
modal.innerHTML = `
<div class="license-modal">
<div class="license-modal-header">
<h2>🔐 Activation de Licence</h2>
<button class="license-modal-close" onclick="closeLicenseModal()">×</button>
</div>
<div class="license-modal-body">
${errorMessage}
<div class="license-upload-zone" id="license-upload-zone">
<div class="license-upload-icon">📄</div>
<div class="license-upload-text">
Glissez-déposez votre fichier <strong>.lic</strong> ici
<br><small>ou cliquez pour sélectionner</small>
</div>
<input type="file" id="license-file-input" accept=".lic" style="display: none;">
</div>
<div class="license-hostname-info">
<strong>Hostname du serveur:</strong>
<code id="current-hostname-display">Chargement...</code>
<br><small>Communiquez ce hostname pour obtenir votre licence</small>
</div>
<div id="license-validation-result"></div>
</div>
<div class="license-modal-footer">
<button class="btn btn-secondary" onclick="closeLicenseModal()">Fermer</button>
</div>
</div>
`;
document.body.appendChild(modal);
getSplunkHostname().then(hostname => {
const hostnameDisplay = document.getElementById('current-hostname-display');
if (hostnameDisplay) {
hostnameDisplay.textContent = hostname;
}
});
setupLicenseUpload();
}
function setupLicenseUpload() {
const dropZone = document.getElementById('license-upload-zone');
const fileInput = document.getElementById('license-file-input');
if (!dropZone || !fileInput) return;
dropZone.addEventListener('click', () => fileInput.click());
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('dragover');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
handleLicenseFile(files[0]);
}
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleLicenseFile(e.target.files[0]);
}
});
}
async function handleLicenseFile(file) {
const resultDiv = document.getElementById('license-validation-result');
if (!resultDiv) return;
if (!file.name.endsWith('.lic')) {
resultDiv.innerHTML = `
<div class="license-result error">
❌ Le fichier doit avoir l'extension .lic
</div>
`;
return;
}
resultDiv.innerHTML = `
<div class="license-result loading">
⏳ Validation en cours...
</div>
`;
try {
const content = await file.text();
const result = await saveLicense(content);
if (result.success) {
const license = result.license;
resultDiv.innerHTML = `
<div class="license-result success">
<div class="license-success-icon">✅</div>
<div class="license-success-text">
<strong>Licence activée avec succès !</strong><br>
Type: ${license.type_name}<br>
Expire: ${license.expires} (${license.days_remaining} jours)<br>
Client: ${license.customer?.name || 'N/A'}
</div>
</div>
`;
updateLicenseBadge();
setTimeout(() => {
closeLicenseModal();
}, 3000);
} else {
resultDiv.innerHTML = `
<div class="license-result error">
${result.error}
</div>
`;
}
} catch (error) {
resultDiv.innerHTML = `
<div class="license-result error">
❌ Erreur: ${error.message}
</div>
`;
}
}
function closeLicenseModal() {
const modal = document.getElementById('license-modal');
if (modal) modal.remove();
}
async function showLicenseDetails() {
const validation = await validateLicense();
if (!validation.valid) {
showLicenseModal(validation.error, validation.error_code);
return;
}
const existingModal = document.getElementById('license-details-modal');
if (existingModal) existingModal.remove();
const modal = document.createElement('div');
modal.id = 'license-details-modal';
modal.className = 'license-modal-overlay';
const features = validation.features.map(f => `<span class="feature-tag">${f}</span>`).join(' ');
const limits = validation.limits;
const maxApps = limits.max_apps === -1 ? '∞' : limits.max_apps;
const maxPushes = limits.max_pushes_per_day === -1 ? '∞' : limits.max_pushes_per_day;
const usage = getUsageStats();
modal.innerHTML = `
<div class="license-modal">
<div class="license-modal-header">
<h2>📋 Détails de la Licence</h2>
<button class="license-modal-close" onclick="this.closest('.license-modal-overlay').remove()">×</button>
</div>
<div class="license-modal-body">
<table class="license-details-table">
<tr><td><strong>ID</strong></td><td>${validation.license_id}</td></tr>
<tr><td><strong>Type</strong></td><td>${validation.type_name}</td></tr>
<tr><td><strong>Client</strong></td><td>${validation.customer?.name || 'N/A'}</td></tr>
<tr><td><strong>Email</strong></td><td>${validation.customer?.email || 'N/A'}</td></tr>
<tr><td><strong>Hostname</strong></td><td>${validation.hostname}</td></tr>
<tr><td><strong>Émise le</strong></td><td>${validation.issued}</td></tr>
<tr><td><strong>Expire le</strong></td><td>${validation.expires}</td></tr>
<tr><td><strong>Jours restants</strong></td><td>${validation.days_remaining}</td></tr>
<tr><td><strong>Apps max</strong></td><td>${maxApps}</td></tr>
<tr><td><strong>Pushes/jour</strong></td><td>${maxPushes}</td></tr>
</table>
<div style="margin-top: 15px;">
<strong>Fonctionnalités:</strong><br>
<div style="margin-top: 8px;">${features}</div>
</div>
<div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e0e0e0;">
<strong>Utilisation:</strong><br>
<small>Total pushes: ${usage.total_pushes} | Aujourd'hui: ${usage.pushes_today}</small>
</div>
</div>
<div class="license-modal-footer">
<button class="btn btn-secondary" onclick="this.closest('.license-modal-overlay').remove()">Fermer</button>
<button class="btn btn-danger" onclick="confirmRemoveLicense()">Supprimer la licence</button>
</div>
</div>
`;
document.body.appendChild(modal);
}
function confirmRemoveLicense() {
if (confirm('Êtes-vous sûr de vouloir supprimer cette licence ?')) {
removeLicense();
const detailsModal = document.getElementById('license-details-modal');
if (detailsModal) detailsModal.remove();
updateLicenseBadge();
alert('Licence supprimée.');
}
}
async function checkLicenseBeforePush() {
const result = await checkLimits();
if (!result.allowed) {
showLicenseModal(result.error, result.error_code);
return false;
}
return true;
}
const licenseStyles = `
<style>
.license-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 20px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.license-badge:hover {
transform: scale(1.05);
}
.license-badge-valid {
background: linear-gradient(135deg, #4CAF50, #45a049);
color: white;
}
.license-badge-warning {
background: linear-gradient(135deg, #FF9800, #F57C00);
color: white;
}
.license-badge-expiring {
background: linear-gradient(135deg, #f44336, #d32f2f);
color: white;
animation: pulse 2s infinite;
}
.license-badge-invalid {
background: linear-gradient(135deg, #9e9e9e, #757575);
color: white;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.license-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.license-modal {
background: white;
border-radius: 16px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.license-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 25px;
border-bottom: 1px solid #e0e0e0;
}
.license-modal-header h2 {
margin: 0;
font-size: 20px;
color: #333;
}
.license-modal-close {
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #999;
line-height: 1;
}
.license-modal-close:hover {
color: #333;
}
.license-modal-body {
padding: 25px;
}
.license-modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 15px 25px;
border-top: 1px solid #e0e0e0;
}
.license-upload-zone {
border: 2px dashed #ccc;
border-radius: 12px;
padding: 40px 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 20px;
}
.license-upload-zone:hover,
.license-upload-zone.dragover {
border-color: #667eea;
background: #f8f9ff;
}
.license-upload-icon {
font-size: 48px;
margin-bottom: 10px;
}
.license-upload-text {
color: #666;
}
.license-hostname-info {
background: #f5f5f5;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
.license-hostname-info code {
background: #e0e0e0;
padding: 2px 8px;
border-radius: 4px;
font-family: monospace;
}
.license-result {
padding: 15px;
border-radius: 8px;
margin-top: 15px;
}
.license-result.loading {
background: #e3f2fd;
color: #1565c0;
}
.license-result.success {
background: #e8f5e9;
color: #2e7d32;
display: flex;
align-items: center;
gap: 15px;
}
.license-result.error {
background: #ffebee;
color: #c62828;
}
.license-success-icon {
font-size: 32px;
}
.license-error-message {
background: #ffebee;
color: #c62828;
padding: 12px 15px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 4px solid #c62828;
}
.license-details-table {
width: 100%;
border-collapse: collapse;
}
.license-details-table td {
padding: 8px 12px;
border-bottom: 1px solid #eee;
}
.license-details-table td:first-child {
width: 40%;
color: #666;
}
.feature-tag {
display: inline-block;
background: #e3f2fd;
color: #1565c0;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
margin: 2px;
}
.btn-danger {
background: #f44336 !important;
color: white !important;
}
.btn-danger:hover {
background: #d32f2f !important;
}
</style>
`;
async function initializeLicense() {
console.log('Git Pusher License System v' + LICENSE_CONFIG.version + ' (RSA)');
if (!document.getElementById('license-styles')) {
const styleEl = document.createElement('div');
styleEl.id = 'license-styles';
styleEl.innerHTML = licenseStyles;
document.head.appendChild(styleEl);
}
const stored = localStorage.getItem(LICENSE_CONFIG.storageKey);
if (!stored) {
console.log('Aucune licence en cache, tentative de chargement depuis le serveur...');
const loadedFromServer = await loadLicenseFromServer();
if (loadedFromServer) {
console.log('✓ Licence restaurée depuis le serveur');
} else {
console.log('Aucune licence disponible');
}
} else {
console.log('Licence trouvée dans le cache local');
}
updateLicenseBadge();
}
window.initializeLicense = initializeLicense;
window.validateLicense = validateLicense;
window.saveLicense = saveLicense;
window.removeLicense = removeLicense;
window.loadLicenseFromServer = loadLicenseFromServer;
window.checkLicenseBeforePush = checkLicenseBeforePush;
window.showLicenseModal = showLicenseModal;
window.closeLicenseModal = closeLicenseModal;
window.showLicenseDetails = showLicenseDetails;
window.confirmRemoveLicense = confirmRemoveLicense;
window.updateLicenseBadge = updateLicenseBadge;
window.hasFeature = hasFeature;
window.incrementUsage = incrementUsage;
window.getUsageStats = getUsageStats;
window.getLicenseInfo = getLicenseInfo;
if (document.readyState === 'complete') {
setTimeout(initializeLicense, 100);
} else {
window.addEventListener('load', function() {
setTimeout(initializeLicense, 100);
});
}