diff --git a/license_svlctmlogpub03.unit-c.edf.fr_I0H6UP70WDRV.lic b/license_svlctmlogpub03.unit-c.edf.fr_I0H6UP70WDRV.lic new file mode 100644 index 00000000..6607716d --- /dev/null +++ b/license_svlctmlogpub03.unit-c.edf.fr_I0H6UP70WDRV.lic @@ -0,0 +1,20 @@ +# ============================================= +# Git Pusher License File +# ============================================= +# Customer: EDF +# Email: jocelyn-j-externe.pamphile@edf.fr +# Type: Professional +# Hostname: svlctmlogpub03.unit-c.edf.fr +# Issued: 2026-03-06 +# Expires: 2027-03-06 +# License ID: I0H6UP70WDRV +# ============================================= +# DO NOT MODIFY THIS FILE +# Signature: RSA-PSS with SHA-256 +# ============================================= + +eyJsaWNlbnNlIjogImV5SmpkWE4wYjIxbGNpSTZleUpsYldGcGJDSTZJbXB2WTJWc2VXNHRhaTFsZUhSbGNtNWxMbkJoYlhCb2FXeGxRR1ZrWmk1bWNpSXNJbTVoYldVaU9pSkZSRVlpZlN3aVpYaHdhWEpsY3lJNklqSXdNamN0TURNdE1EWWlMQ0ptWldGMGRYSmxjeUk2V3lKaVlYTnBZMTl3ZFhOb0lpd2ljMk5vWldSMWJHVmtYM0IxYzJnaUxDSnRkV3gwYVY5eVpYQnZJaXdpY0hKcGIzSnBkSGxmYzNWd2NHOXlkQ0pkTENKb2IzTjBibUZ0WlNJNkluTjJiR04wYld4dlozQjFZakF6TG5WdWFYUXRZeTVsWkdZdVpuSWlMQ0pwYzNOMVpXUWlPaUl5TURJMkxUQXpMVEEySWl3aWJHbGpaVzV6WlY5cFpDSTZJa2t3U0RaVlVEY3dWMFJTVmlJc0lteHBiV2wwY3lJNmV5SnRZWGhmWVhCd2N5STZMVEVzSW0xaGVGOXdkWE5vWlhOZmNHVnlYMlJoZVNJNkxURjlMQ0owZVhCbElqb2ljSEp2Wm1WemMybHZibUZzSWl3aWRIbHdaVjl1WVcxbElqb2lVSEp2Wm1WemMybHZibUZzSWl3aWRtVnljMmx2YmlJNklqSXVNQ0o5IiwgInNpZ25hdHVyZSI6ICJFcEFwaXQvRjdNaVovZTJETTNrWHVGbURtMEoxUFBrMXpxWnl1NXphUVdlM3NqYVllaEFtb0d3R3RYV0ErSGR2UWN3cUNwZkJPSjhyenJuaXYvb0JXQk1ZUnl4TDB6ZWY5amZXOEgvWjR2c3FaQ0ZFY3VVeVQ3cDdxSTN1NTMrSlIrcWJ6a0hGMGZwMWFXUVVtSDZ2K1drYy80cDVvVXBEc3ZiVVpTbHFyWExFNXBuVkRhTXZHQ3B0RDhIZ1A0TlNzMkp2aXZoYSsyNy9xTHhFOUNFQTJXNEl5b3ZXUlVpV0tJc0lWeEE1RU5pQ1N4VWdDY0lsaG14aUdrdFV5L1QyWXJqQlB2S3BrSk13Y0RxSjV2SUVWOUFpZ25rditoakRBMElVNkZCNVZ0a2xTaTE4dXdOZmkxYzlWczJFUUNjaXg1OXhlNCtObHZSbms2TE9vdms0T0dDcmhySnVOV1E3eklxejlDUCttc3ZMV084TjBzSm9tYVdKeEs3R2crczlqUlpQeTFBQnJXNzBUV2hvUGtuTzNIMklzYUJNQzRNemZ6WGpBQ1NWOWxIQW43SElDYXgwMzNUd0VpMW9BNktoUUg5M3NCZ3FRdkxMY1QrdnZqd01nbVorQTByQk9rNlM1dG54VjBPUXhKaW9wRkREdHdLdDEraVNJN0hDa0Z0VmVYeTNHc3hyNUMvUElxOG9UZTVoY3V0V0pEQzg2OFpjYW1xVUZCSUlqL0lldDgrS3dPUmRvZU1UWE5Rb1BnV0ZGaUd2aHJHa2hHaldMYTZRUXYyQlVwdi85RGZ1TnV1aFFxbm0rMFNDazQ1Qks4TWNEK3NtakcyZWRLVHhqVTZ5dXR0RWk4c2xLSjZKWWcvRWl3YWlJNjJoTnF2RkFaamE3V3ZNTWNldGF5VT0ifQ== + +# ============================================= +# END OF LICENSE +# ============================================= diff --git a/license_validation.obfuscated.js b/license_validation.obfuscated.js new file mode 100644 index 00000000..10295584 --- /dev/null +++ b/license_validation.obfuscated.js @@ -0,0 +1,972 @@ + +// 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 _najFGrRZ='CFlkfBN6FXi7xvrI9lxTRjPk5ayyfp3u'; +var _LqOLCkTR='UAdqa8B6WlWQYxyqZGUGGV72kYuAo55M'; +var _UVRZPlaI='YEOgo63Pu6vBJ9SCw8DmH2G7au4aANp3'; +var _ZttjEvgs='GaVNDZ0bowpwkdvoq4ikRbaHicWJ3Bt9'; +var _AMOlLLcG='8gYmF906LNO3YLm5DmggG3XJj9r5hjJC'; +var _FlEKpulW='NWo5IGBx798ZLGqrTgWmW5sE5MOhFszd'; +var _BTvdGPpD='9DEroxPzwwfTgaj0ySdKrzp1OjHyWgt2'; +var _OfiXTjhE='zZ5T5uUQo0EDoMF0Lciv51T9ddNbUJ8y'; +var _SaAhdxlg='cbhLzknmCaBT2aVnf9wnzjz87jWzaxWD'; +var _vZNPDdbI='uZIef7bSlbAWekGDHJYoqmHeExuvUpUR'; + +function _jJjWlCZQ(a,b){ +var c=a^b;var d=c.toString(16); +return d.split('').reverse().join(''); +} + + +function _uxhLujlj(a,b){ +var c=a^b;var d=c.toString(16); +return d.split('').reverse().join(''); +} + + +function _COQLRmjC(a,b){ +var c=a^b;var d=c.toString(16); +return d.split('').reverse().join(''); +} + + +function _pSKvCgap(a,b){ +var c=a^b;var d=c.toString(16); +return d.split('').reverse().join(''); +} + + +function _VfdNScNh(a,b){ +var c=a^b;var d=c.toString(16); +return d.split('').reverse().join(''); +} + +var _lgKuvRnA=_d([165,165,165,165,165,202,205,207,193,198,168,216,221,202,196,193,203,168,195,205,209,165,165,165,165,165,130],136); +var _XHprWuGb=_d([254,250,250,240,250,217,242,253,241,212,216,194,219,216,218,244,138,196,131,241,242,226,246,245,242,242,252,240,242,212,139,242,254,250,250,240,240,212,248,240,242,212,246,242,192,212,196,242,192,219,133,217,221,194,203,134,228,227,201,254,199],179); +var _HWIxlWsA=_d([238,214,164,196,164,228,217,215,160,228,253,253,208,254,162,250,199,238,195,229,220,253,198,194,196,191,173,198,231,221,231,198,205,227,230,166,252,227,238,241,160,163,196,196,191,255,172,224,224,238,213,242,204,228,199,243,230,204,196,236,196],148); +var _nkvdbmbX=_d([90,1,14,65,80,116,75,124,13,115,93,14,11,114,113,106,92,77,64,64,109,13,83,91,115,96,111,81,12,109,124,13,84,19,127,72,83,84,124,104,74,118,104,78,109,73,90,110,65,1,90,93,12,15,95,9,80,114,106,10,98],56); +var _pOAwFcgG=_d([242,211,232,227,230,252,175,192,183,209,197,206,189,200,238,192,208,230,230,245,241,194,177,197,183,171,221,242,192,197,195,202,182,171,200,240,195,231,254,211,231,181,212,222,193,197,205,227,202,176,253,247,192,246,238,194,197,241,226,221,213],132); +var _WBDYjMlI=_d([41,37,8,52,75,17,40,61,40,37,74,29,30,40,43,53,80,54,74,18,37,56,44,51,24,17,61,62,39,61,50,78,43,27,22,60,58,76,41,53,24,15,46,51,57,11,30,28,70,54,50,11,40,71,5,19,19,14,61,21,27],127); +var _gwpNFFtR=_d([243,246,151,203,206,214,239,150,198,215,147,212,208,207,202,236,198,242,145,151,212,206,149,213,203,230,212,198,193,227,225,212,199,230,199,145,228,208,205,192,248,250,149,244,205,208,236,235,238,146,148,245,251,137,231,192,225,199,230,237,239],162); +var _MNoiuUEI=_d([97,115,64,69,83,36,85,87,65,87,117,80,84,110,95,38,108,113,66,121,94,126,94,67,94,102,67,124,126,67,96,97,80,126,108,39,101,38,83,33,112,100,113,76,46,76,85,64,112,93,91,85,126,116,124,69,111,111,116,78,96],22); +var _orgyGVNE=_d([110,86,70,90,104,87,15,76,21,80,67,115,78,84,110,75,120,116,70,84,103,122,24,88,79,19,78,66,108,121,79,83,87,22,103,87,99,72,23,77,90,67,106,101,71,110,101,20,78,15,72,22,87,24,109,110,103,82,119,67,67],32); +var _oPLuUiyP=_d([22,118,1,119,1,38,23,55,62,111,33,40,117,46,52,46,8,61,43,5,55,52,49,40,62,28,117,114,7,46,30,53,30,17,62,12,23,13,55,111,51,41,117,1,22,60,3,20,46,48,49,111,35,47,10,61,48,113,1,2,42],68); +var _cTCXSuRh=_d([79,42,41,21,18,63,28,16,37,82,42,41,39,18,28,10,42,8,21,17,8,5,47,37,24,31,69,55,23,50,19,20,41,52,19,69,56,60,60,58,10,68,20,72,75,27,28,45,31,43,24,27,9,37,75,5,42,54,40,55,45],125); +var _HVcTITdT=_d([38,9,42,48,15,59,35,35,32,59,85,8,27,91,54,53,18,0,77,59,47,21,73,90,59,33,27,5,73,83,86,55,21,27,18,49,3,36,9,40,11,90,87,82,16,58,80,8,54,50,56,59,14,35,26,11,26,84,91,73,32],98); +var _eAnnlkpp=_d([116,19,8,50,6,17,27,106,60,8,29,102,30,57,20,24,56,26,116,26,110,108,60,47,13,116,56,15,40,29,13,23,14,105,37,40,116,16,102,59,103,18,50,21,107,37,111,14,49,24,54,109,11,53,45,52,28,30,40,26,30,30,14,98,98],95); +var _TokCrOUO=_d([187,156,156,156,156,156,244,255,245,145,225,228,243,253,248,242,145,250,244,232,156,156,156,156,156],177); +var _zTugPqre=_lgKuvRnA+_XHprWuGb+_HWIxlWsA+_nkvdbmbX+_pOAwFcgG+_WBDYjMlI+_gwpNFFtR+_MNoiuUEI+_orgyGVNE+_oPLuUiyP+_cTCXSuRh+_HVcTITdT+_eAnnlkpp+_TokCrOUO; +var PUBLIC_KEY_PEM=_zTugPqre; + +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 (hostname === 'myprivspldev.jp-engineering.fr') { + return protocol + '//myprivspldev-api.jp-engineering.fr'; + } + if (hostname === 'myprivspldev-api.jp-engineering.fr') { + return protocol + '//' + hostname; + } + if (/^(\d{1,3}\.){3}\d{1,3}$/.test(hostname) || hostname === 'localhost') { + return protocol + '//' + hostname + ':9999'; + } + 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 = ` +
Chargement...
+ | ID | ${validation.license_id} |
| Type | ${validation.type_name} |
| Client | ${validation.customer?.name || 'N/A'} |
| ${validation.customer?.email || 'N/A'} | |
| Hostname | ${validation.hostname} |
| Émise le | ${validation.issued} |
| Expire le | ${validation.expires} |
| Jours restants | ${validation.days_remaining} |
| Apps max | ${maxApps} |
| Pushes/jour | ${maxPushes} |