diff --git a/.DS_Store b/.DS_Store index ca6b235e..d2ba66c7 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 23ea782a..2d69d1c4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.txt *.ini *.xsl +.DS_Store passwd !apps/ /anonymizer/ diff --git a/apps/.DS_Store b/apps/.DS_Store index 32486670..d8464d34 100644 Binary files a/apps/.DS_Store and b/apps/.DS_Store differ diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/README.md b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/README.md new file mode 100644 index 00000000..da92895d --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/README.md @@ -0,0 +1,766 @@ +# 🚀 Git Pusher for Splunk + +**Version 2.1** | Application Splunk pour déployer vos applications vers Git et le Search Head Cluster + +--- + +## 📋 Table des matières + +- [Présentation](#-présentation) +- [Fonctionnalités](#-fonctionnalités) +- [Architecture](#-architecture) +- [Installation - Serveur Source](#-installation---serveur-source) +- [Installation - SH Deployer Agent](#-installation---sh-deployer-agent) +- [Configuration](#-configuration) +- [Configuration HTTPS](#-configuration-https) +- [Système de Licence](#-système-de-licence) +- [Utilisation](#-utilisation) +- [Sécurité](#-sécurité) +- [Personnalisation du Dashboard](#-personnalisation-du-dashboard) +- [Dépannage](#-dépannage) +- [API Reference](#-api-reference) +- [Changelog](#-changelog) +- [Support](#-support) + +--- + +## 🎯 Présentation + +**Git Pusher** est une application Splunk Enterprise qui permet de : +1. **Versionner** vos applications Splunk dans un repository Git +2. **Déployer automatiquement** vers un Search Head Cluster via le SH Deployer + +Le workflow complet en un clic : +``` +Splunk Source → Push Git → Pull SH Deployer → Apply Bundle → Search Head Cluster +``` + +Idéal pour : +- 📦 Sauvegarder vos configurations Splunk +- 🔄 Versionner vos dashboards et applications +- 👥 Collaborer en équipe sur les développements Splunk +- 🚀 Mettre en place un workflow CI/CD pour Splunk +- 🎯 Déployer automatiquement vers votre Search Head Cluster + +--- + +## ✨ Fonctionnalités + +| Fonctionnalité | Description | +|----------------|-------------| +| **Push vers Git** | Déployez une ou plusieurs applications Splunk vers votre repository Git | +| **Déploiement SH Cluster** | Déploiement automatique vers le Search Head Cluster après le push Git | +| **Interface moderne** | Dashboard intuitif avec sélection visuelle des applications | +| **Multi-repository** | Support de GitHub, GitLab, Gitea, Bitbucket et tout serveur Git | +| **Support HTTPS** | Communication sécurisée avec certificats SSL | +| **Système de licence** | Gestion par fichier `.lic` sécurisé | +| **Credentials sécurisés** | Chiffrement des mots de passe Splunk | +| **Logs détaillés** | Traçabilité complète des opérations | + +--- + +## 🏗 Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ SERVEUR SOURCE │ +│ ┌─────────────┐ ┌─────────────────────────────────────┐ │ +│ │ Splunk │────▶│ Git Pusher Server (:9999) │ │ +│ │ Dashboard │ │ 1. Push apps vers Git │ │ +│ │ (HTTPS) │ │ 2. Appelle le SH Deployer Agent │ │ +│ └─────────────┘ └──────────────────┬──────────────────┘ │ +└─────────────────────────────────────────┼───────────────────────┘ + │ HTTPS (:9998) + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ SH DEPLOYER │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Deployer Agent (:9998) │ │ +│ │ 1. Git pull dans /opt/splunk/etc/shcluster/apps/ │ │ +│ │ 2. Exécute: splunk apply shcluster-bundle │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ SEARCH HEAD CLUSTER │ +│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ +│ │ SH1 │ │ SH2 │ │ SH3 │ │ +│ └───────────┘ └───────────┘ └───────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Structure des fichiers + +``` +SERVEUR SOURCE - pusher_app_prem/ +├── bin/ +│ ├── git_pusher.py # Serveur HTTP/HTTPS principal (port 9999) +│ ├── license_validator.py # Validation des licences +│ ├── license_generator.py # Génération des licences (vendeur) +│ ├── credentials_manager.py # Gestion sécurisée des credentials +│ └── start_git_pusher.sh # Script de démarrage +├── appserver/static/ +│ ├── git_pusher.js # Logique JavaScript principale +│ └── license_validation.js # Interface de gestion des licences +├── default/data/ui/views/ +│ └── git_pusher_-_deploy_applications.xml # Dashboard principal +├── local/ +│ ├── license.lic # Fichier de licence (après activation) +│ ├── .credentials # Credentials chiffrés +│ ├── .key # Clé de chiffrement +│ └── certs/ # Certificats SSL +│ ├── server.crt +│ └── server.key +└── README.md + +SH DEPLOYER - deployer_agent/ +├── bin/ +│ ├── deployer_agent.py # Agent de déploiement (port 9998) +│ └── start_deployer_agent.sh # Script de démarrage +└── local/ + └── certs/ # Certificats SSL + ├── server.crt + └── server.key +``` + +--- + +## 📥 Installation - Serveur Source + +### Prérequis + +- Splunk Enterprise 8.x ou supérieur +- Python 3.7+ +- Git installé sur le serveur (`yum install git` ou `apt install git`) +- OpenSSL (pour la génération des certificats HTTPS) +- Accès réseau vers votre repository Git + +### Étapes d'installation + +#### 1. Extraire l'application + +```bash +# Copier l'application dans Splunk +cp -r pusher_app_prem /opt/splunk/etc/apps/ + +# Définir les permissions +chown -R splunk:splunk /opt/splunk/etc/apps/pusher_app_prem +chmod +x /opt/splunk/etc/apps/pusher_app_prem/bin/*.sh +``` + +#### 2. Configurer les credentials Splunk + +```bash +cd /opt/splunk/etc/apps/pusher_app_prem/bin/ +./start_git_pusher.sh credentials setup +``` + +``` +================================================== +Git Pusher - Credentials Setup +================================================== + +Splunk Username [admin]: admin +Splunk Password: ******** +Confirm Password: ******** + +✓ Credentials saved securely! +``` + +#### 3. Configurer HTTPS (si Splunk est en HTTPS) + +```bash +# Créer le dossier des certificats +mkdir -p /opt/splunk/etc/apps/pusher_app_prem/local/certs + +# Générer un certificat auto-signé (valide 365 jours) +openssl req -x509 -newkey rsa:4096 \ + -keyout /opt/splunk/etc/apps/pusher_app_prem/local/certs/server.key \ + -out /opt/splunk/etc/apps/pusher_app_prem/local/certs/server.crt \ + -days 365 -nodes -subj "/CN=git-pusher" + +# Définir les permissions +chmod 600 /opt/splunk/etc/apps/pusher_app_prem/local/certs/server.key +chown -R splunk:splunk /opt/splunk/etc/apps/pusher_app_prem/local/certs/ +``` + +#### 4. Démarrer le serveur Git Pusher + +```bash +./start_git_pusher.sh start +``` + +#### 5. Ouvrir le firewall (si nécessaire) + +```bash +# FirewallD +sudo firewall-cmd --add-port=9999/tcp --permanent +sudo firewall-cmd --reload + +# UFW +sudo ufw allow 9999/tcp +``` + +#### 6. Redémarrer Splunk + +```bash +/opt/splunk/bin/splunk restart +``` + +#### 7. Accepter le certificat dans le navigateur + +1. Ouvrir : `https://VOTRE_IP_SPLUNK:9999/health` +2. Accepter le certificat auto-signé +3. Rafraîchir le dashboard (Ctrl+Shift+R) + +--- + +## 📥 Installation - SH Deployer Agent + +### Sur le serveur SH Deployer + +#### 1. Créer l'application + +```bash +# Créer les dossiers +mkdir -p /opt/splunk/etc/apps/deployer_agent/bin +mkdir -p /opt/splunk/etc/apps/deployer_agent/local/certs + +# Copier les fichiers +cp deployer_agent.py /opt/splunk/etc/apps/deployer_agent/bin/ +cp start_deployer_agent.sh /opt/splunk/etc/apps/deployer_agent/bin/ + +# Rendre exécutable +chmod +x /opt/splunk/etc/apps/deployer_agent/bin/start_deployer_agent.sh +``` + +#### 2. Configurer le token d'authentification + +```bash +nano /opt/splunk/etc/apps/deployer_agent/bin/deployer_agent.py +``` + +Modifier la ligne `AUTH_TOKEN` : +```python +AUTH_TOKEN = "votre_token_secret_personnalise" +``` + +#### 3. Configurer le Search Head Captain + +Dans le même fichier, trouver la fonction `apply_shcluster_bundle` et modifier l'IP du captain : +```python +target_uri = "https://IP_DU_CAPTAIN:8089" +``` + +#### 4. Générer les certificats SSL + +```bash +cd /opt/splunk/etc/apps/deployer_agent/bin/ +./start_deployer_agent.sh gencerts +``` + +#### 5. Définir les permissions + +```bash +chown -R splunk:splunk /opt/splunk/etc/apps/deployer_agent +``` + +#### 6. Ouvrir le firewall + +```bash +sudo firewall-cmd --add-port=9998/tcp --permanent +sudo firewall-cmd --reload +``` + +#### 7. Démarrer l'agent + +```bash +./start_deployer_agent.sh start +``` + +#### 8. Vérifier le statut + +```bash +./start_deployer_agent.sh status +``` + +#### 9. Accepter le certificat + +Ouvrir dans un navigateur : `https://IP_SH_DEPLOYER:9998/health` +Accepter le certificat auto-signé. + +--- + +## ⚙️ Configuration + +### Commandes Git Pusher (Serveur Source) + +```bash +cd /opt/splunk/etc/apps/pusher_app_prem/bin/ + +# Démarrer le serveur +./start_git_pusher.sh start + +# Arrêter le serveur +./start_git_pusher.sh stop + +# Redémarrer le serveur +./start_git_pusher.sh restart + +# Voir le statut +./start_git_pusher.sh status + +# Voir les logs +./start_git_pusher.sh logs +./start_git_pusher.sh logs -f # Mode follow + +# Gestion des credentials +./start_git_pusher.sh credentials setup +./start_git_pusher.sh credentials status +./start_git_pusher.sh credentials delete +``` + +### Commandes Deployer Agent (SH Deployer) + +```bash +cd /opt/splunk/etc/apps/deployer_agent/bin/ + +# Démarrer l'agent +./start_deployer_agent.sh start + +# Arrêter l'agent +./start_deployer_agent.sh stop + +# Redémarrer l'agent +./start_deployer_agent.sh restart + +# Voir le statut +./start_deployer_agent.sh status + +# Voir les logs +./start_deployer_agent.sh logs +./start_deployer_agent.sh logs -f + +# Générer les certificats SSL +./start_deployer_agent.sh gencerts + +# Tester la connexion +./start_deployer_agent.sh test +``` + +### Configuration du SH Deployer dans l'interface + +1. Ouvrir le dashboard Git Pusher +2. Cliquer sur **⚙️ Configure** dans la section "Deploy to Search Head Cluster" +3. Remplir : + - **Host** : IP du SH Deployer (ex: 10.10.40.14) + - **Port** : 9998 + - **Token** : Le même token que dans `deployer_agent.py` +4. Cliquer sur **Save & Test** + +--- + +## 🔒 Configuration HTTPS + +### Git Pusher Server (Port 9999) + +```bash +mkdir -p /opt/splunk/etc/apps/pusher_app_prem/local/certs + +openssl req -x509 -newkey rsa:4096 \ + -keyout /opt/splunk/etc/apps/pusher_app_prem/local/certs/server.key \ + -out /opt/splunk/etc/apps/pusher_app_prem/local/certs/server.crt \ + -days 365 -nodes -subj "/CN=git-pusher" + +chmod 600 /opt/splunk/etc/apps/pusher_app_prem/local/certs/server.key +chown -R splunk:splunk /opt/splunk/etc/apps/pusher_app_prem/local/certs/ +``` + +### Deployer Agent (Port 9998) + +```bash +mkdir -p /opt/splunk/etc/apps/deployer_agent/local/certs + +openssl req -x509 -newkey rsa:4096 \ + -keyout /opt/splunk/etc/apps/deployer_agent/local/certs/server.key \ + -out /opt/splunk/etc/apps/deployer_agent/local/certs/server.crt \ + -days 365 -nodes -subj "/CN=deployer-agent" + +chmod 600 /opt/splunk/etc/apps/deployer_agent/local/certs/server.key +chown -R splunk:splunk /opt/splunk/etc/apps/deployer_agent/local/certs/ +``` + +### Vérifier HTTPS + +```bash +# Git Pusher +curl -k https://localhost:9999/health + +# Deployer Agent +curl -k https://localhost:9998/health +``` + +--- + +## 🔐 Système de Licence + +Git Pusher utilise un système de licence par fichier `.lic` pour activer les fonctionnalités. + +### Types de licences + +| Type | Durée | Apps max | Pushes/jour | Fonctionnalités | +|------|-------|----------|-------------|-----------------| +| **Trial** | 14 jours | 3 | 5 | Push basique | +| **Starter** | 1 an | 10 | 50 | + Push programmé | +| **Professional** | 1 an | Illimité | Illimité | + Multi-repo, Support prioritaire | +| **Enterprise** | 1 an | Illimité | Illimité | + SH Cluster deployment, API | + +### Activer une licence + +1. Ouvrir le dashboard Git Pusher +2. Glisser-déposer le fichier `.lic` +3. Cliquer sur "Activer" + +--- + +## 📖 Utilisation + +### Workflow complet : Push Git + Déploiement SH Cluster + +1. **Ouvrir le dashboard** Git Pusher + +2. **Sélectionner les applications** à déployer + +3. **Configurer Git** : + - Repository URL : `https://github.com/user/repo.git` + - Branch : `main` + - Token : Personal Access Token + +4. **Activer le déploiement SH Cluster** : + - Cocher "Enable automatic deployment" + - (Optionnel) Remplir les credentials Splunk si différents de l'admin par défaut + +5. **Cliquer sur "Deploy to Git"** + +Le processus automatique : +``` +✅ Push vers Git +✅ Pull sur le SH Deployer +✅ Apply shcluster-bundle +✅ Mise à jour du Search Head Cluster +``` + +### Obtenir un token Git + +#### GitHub +1. Settings → Developer settings → Personal access tokens +2. Generate new token +3. Cocher : `repo` (Full control) + +#### GitLab +1. Preferences → Access Tokens +2. Create personal access token +3. Scopes : `write_repository` + +#### Gitea +1. Settings → Applications → Generate New Token +2. Permissions : `repository: write` + +--- + +## 🔒 Sécurité + +### Credentials Splunk + +- **Chiffrés** avec une clé dérivée de l'ID machine +- **Stockés** dans un fichier avec permissions `600` +- **Jamais visibles** en clair + +### Communication HTTPS + +- SSL/TLS entre tous les composants +- Certificats auto-signés supportés +- Validation du token d'authentification + +### Token Deployer Agent + +- Authentification par token entre Git Pusher et Deployer Agent +- Token configurable dans les deux composants +- Doit être identique des deux côtés + +### Bonnes pratiques + +1. ✅ Utiliser HTTPS pour tous les composants +2. ✅ Changer le token par défaut du Deployer Agent +3. ✅ Utiliser des tokens Git avec permissions minimales +4. ✅ Restreindre l'accès réseau au port 9998/9999 +5. ✅ Renouveler les certificats régulièrement + +--- + +## 🎨 Personnalisation du Dashboard + +### Masquer les boutons d'édition + +```bash +nano /opt/splunk/etc/apps/pusher_app_prem/default/data/ui/views/git_pusher_-_deploy_applications.xml +``` + +Modifier la première ligne : +```xml + +``` + +### Options disponibles + +| Attribut | Description | +|----------|-------------| +| `hideEdit="true"` | Masque le bouton "Modifier" | +| `hideExport="true"` | Masque le bouton "Exporter" | +| `hideTitle="true"` | Masque le titre du dashboard | +| `hideSplunkBar="true"` | Masque la barre Splunk en haut | +| `hideAppBar="true"` | Masque la barre de l'application | +| `hideFooter="true"` | Masque le footer | + +### Appliquer les changements + +```bash +rm -rf /opt/splunk/var/run/splunk/appserver/* +/opt/splunk/bin/splunk restart +``` + +--- + +## 🔧 Dépannage + +### Les boutons ne fonctionnent pas + +Le fichier JavaScript doit exposer les fonctions globalement. Vérifier que le bloc suivant est présent à la fin de `git_pusher.js` : + +```javascript +// Exposer les fonctions globalement +window.showDeployerConfigModal = showDeployerConfigModal; +window.closeDeployerConfigModal = closeDeployerConfigModal; +window.saveDeployerConfigFromModal = saveDeployerConfigFromModal; +window.toggleDeployerAuth = toggleDeployerAuth; + +// Attacher les événements aux boutons +(function attachButtonEvents() { + function tryAttach() { + var pushBtn = document.getElementById('push-btn'); + if (pushBtn) { + pushBtn.addEventListener('click', function(e) { + e.preventDefault(); + pushDashboards(); + }); + } + + var buttons = document.querySelectorAll('button.btn'); + buttons.forEach(function(btn) { + if (btn.textContent.includes('Reset')) { + btn.addEventListener('click', function(e) { + e.preventDefault(); + resetForm(true); + }); + } + }); + + var configBtn = document.querySelector('.deployer-config-btn'); + if (configBtn) { + configBtn.addEventListener('click', function(e) { + e.preventDefault(); + showDeployerConfigModal(); + }); + } + + if (!pushBtn) setTimeout(tryAttach, 500); + } + + if (document.readyState === 'complete') { + setTimeout(tryAttach, 1000); + } else { + window.addEventListener('load', function() { + setTimeout(tryAttach, 1000); + }); + } +})(); +``` + +Puis vider le cache : +```bash +rm -rf /opt/splunk/var/run/splunk/appserver/* +/opt/splunk/bin/splunk restart +``` + +Et dans le navigateur : **Ctrl+Shift+R** + +### Erreur 401 Unauthorized (Deployer) + +Le token ne correspond pas entre les deux composants. + +1. Vérifier le token sur le SH Deployer : +```bash +grep "AUTH_TOKEN" /opt/splunk/etc/apps/deployer_agent/bin/deployer_agent.py +``` + +2. Configurer le même token dans l'interface (⚙️ Configure) + +3. Tester : +```bash +curl -k -H "X-Auth-Token: VOTRE_TOKEN" https://IP_DEPLOYER:9998/status +``` + +### Erreur "tcsetattr: Inappropriate ioctl for device" + +La commande `splunk apply shcluster-bundle` attend une entrée interactive. + +Solution : Modifier la fonction `apply_shcluster_bundle` dans `deployer_agent.py` pour utiliser `echo 'y' |` : + +```python +shell_cmd = f"echo 'y' | {SPLUNK_BIN} apply shcluster-bundle -target {target_uri} -auth {auth_user}:'{escaped_pass}' -preserve-lookups true" +``` + +### Erreur CORS + +Vider le cache Splunk et navigateur : +```bash +rm -rf /opt/splunk/var/run/splunk/appserver/* +/opt/splunk/bin/splunk restart +``` +Puis **Ctrl+Shift+R** dans le navigateur. + +### Le SH Deployer ne répond pas + +```bash +# Vérifier le statut +./start_deployer_agent.sh status + +# Voir les logs +tail -50 /opt/splunk/var/log/splunk/deployer_agent.log + +# Redémarrer +./start_deployer_agent.sh restart +``` + +### Le bundle ne s'applique pas + +1. Vérifier que le pull a fonctionné : +```bash +ls -la /opt/splunk/etc/shcluster/apps/ +``` + +2. Vérifier les logs : +```bash +grep -i "bundle\|error" /opt/splunk/var/log/splunk/deployer_agent.log | tail -30 +``` + +3. Tester manuellement : +```bash +echo 'y' | /opt/splunk/bin/splunk apply shcluster-bundle -target https://CAPTAIN_IP:8089 -auth admin:'password' -preserve-lookups true +``` + +### Vider le cache Splunk + +```bash +rm -rf /opt/splunk/var/run/splunk/appserver/* +/opt/splunk/bin/splunk restart +``` + +--- + +## 📡 API Reference + +### Git Pusher Server (Port 9999) + +| Méthode | Endpoint | Description | +|---------|----------|-------------| +| `GET` | `/health` | Health check | +| `GET` | `/license` | Statut de la licence | +| `GET` | `/license/hostname` | Hostname Splunk | +| `GET` | `/deployer/health` | Santé du SH Deployer | +| `GET` | `/deployer/status` | Statut du SH Deployer | +| `GET` | `/deployer/config` | Configuration du SH Deployer | +| `POST` | `/license/upload` | Uploader une licence | +| `POST` | `/push` | Pousser les applications | + +### Deployer Agent (Port 9998) + +| Méthode | Endpoint | Description | +|---------|----------|-------------| +| `GET` | `/health` | Health check (pas d'auth) | +| `GET` | `/status` | Statut du déploiement | +| `GET` | `/apps` | Liste des apps | +| `GET` | `/history` | Historique des déploiements | +| `POST` | `/pull` | Git pull | +| `POST` | `/deploy` | Apply shcluster-bundle | +| `POST` | `/pull-and-deploy` | Pull + Deploy en une opération | + +### Exemple : Push avec déploiement SH Cluster + +```bash +curl -k -X POST "https://localhost:9999/push?git_url=https://github.com/user/repo.git&git_branch=main&git_token=TOKEN&commit_message=Update&apps=[{\"id\":\"my_app\"}]&user=admin&deploy_to_shcluster=true" +``` + +--- + +## 📝 Changelog + +### Version 2.1.0 (Février 2026) +- 🚀 **Nouveau** : Déploiement automatique vers Search Head Cluster +- 🔧 **Nouveau** : Agent Deployer pour le SH Deployer +- ⚙️ **Nouveau** : Interface de configuration du SH Deployer +- 🔐 **Amélioration** : Gestion des caractères spéciaux dans les mots de passe +- 🛠️ **Correction** : Mode non-interactif pour shcluster-bundle + +### Version 2.0.0 (Février 2026) +- ✨ Nouveau système de licence par fichier `.lic` +- 🔐 Credentials Splunk chiffrés +- 🔒 Support HTTPS avec certificats SSL +- 🎨 Interface utilisateur modernisée +- 📊 Badge de licence en temps réel +- 🔧 Script de gestion amélioré +- 🛡️ Options pour masquer les boutons d'édition du dashboard +- 📝 Logs détaillés +- 🐛 Correction des problèmes CORS + +### Version 1.0.0 +- 🚀 Version initiale +- Push d'applications vers Git +- Interface basique + +--- + +## 📞 Support + +### Obtenir de l'aide + +- 📧 Email : support@gitpusher.com +- 🌐 Site web : https://gitpusher.com +- 📖 Documentation : https://docs.gitpusher.com + +### Signaler un bug + +Incluez dans votre rapport : +1. Version de Git Pusher +2. Version de Splunk +3. Configuration (HTTP/HTTPS) +4. Architecture (standalone, SH Cluster) +5. Logs Git Pusher (`/opt/splunk/var/log/splunk/git_pusher.log`) +6. Logs Deployer Agent (`/opt/splunk/var/log/splunk/deployer_agent.log`) +7. Erreurs de la console navigateur (F12) +8. Étapes pour reproduire le problème + +--- + +## 📄 Licence + +Git Pusher est un logiciel propriétaire. Une licence valide est requise pour son utilisation. + +© 2026 Git Pusher - Tous droits réservés + +--- + +

+ Made with ❤️ for Splunk administrators +

diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/git_pusher.js b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/git_pusher.js new file mode 100644 index 00000000..74e7881c --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/git_pusher.js @@ -0,0 +1,1055 @@ +// ============================================ +// GIT PUSHER - MAIN JAVASCRIPT +// Version 2.1 avec déploiement vers SH Cluster +// ============================================ + +// Configuration par défaut +const DEFAULT_CONFIG = { + api: { + url: '', + port: 9999, + useProxy: true + }, + deployer: { + enabled: false, + host: '', + port: 9998, + token: '', + useSSL: true + } +}; + +// Charger la configuration +function loadAppConfig() { + 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_CONFIG; +} + +// Déterminer l'URL du serveur API +function getServerUrl() { + const config = loadAppConfig(); + const hostname = window.location.hostname; + const protocol = window.location.protocol; + + // Si une URL est configurée, l'utiliser + if (config.api && config.api.url) { + let url = config.api.url; + // Ajouter le port si pas de proxy + if (!config.api.useProxy && config.api.port) { + url = url.replace(/\/$/, '') + ':' + config.api.port; + } + return url; + } + + // Auto-détection basée sur le hostname + // Si accès via le domaine Splunk principal, utiliser le domaine API + if (hostname === 'myprivspldev.jp-engineering.fr') { + return protocol + '//myprivspldev-api.jp-engineering.fr'; + } + + // Si déjà sur le domaine API + if (hostname === 'myprivspldev-api.jp-engineering.fr') { + return protocol + '//' + hostname; + } + + // Si c'est une IP ou localhost, ajouter le port 9999 + if (/^(\d{1,3}\.){3}\d{1,3}$/.test(hostname) || hostname === 'localhost') { + return protocol + '//' + hostname + ':9999'; + } + + // Par défaut, ajouter le port 9999 + return protocol + '//' + hostname + ':9999'; +} + +// Configuration +const GIT_PUSHER_CONFIG = { + serverUrl: getServerUrl(), + credentialsKey: 'git_pusher_credentials', + deployerConfigKey: 'git_pusher_deployer_config', + version: '2.1.0' +}; + +// Configuration SH Deployer (peut être modifiée via l'interface) +let SH_DEPLOYER_CONFIG = { + enabled: false, + host: '10.10.40.14', + port: 9998, + token: '' +}; + +// État global +let selectedApps = []; +let selectedShClusterApps = []; // Apps sélectionnées pour le SH Cluster +let isProcessing = false; +let deployerAvailable = false; + +// ============================================ +// INITIALISATION +// ============================================ + +require([ + 'jquery', + 'splunkjs/mvc', + 'splunkjs/mvc/searchmanager', + 'splunkjs/mvc/simplexml/ready!' +], function($, mvc, SearchManager) { + + console.log("Git Pusher v2.1 initializing..."); + + // Initialiser le système de licence + if (typeof initializeLicense === 'function') { + initializeLicense(); + } else { + console.warn("License system not loaded"); + } + + // Charger les credentials sauvegardés + loadSavedCredentials(); + + // Charger la config du deployer + loadDeployerConfig(); + + // Vérifier la disponibilité du SH Deployer + checkDeployerHealth(); + + // 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); + }); + } + }); + } + + // Exposer les fonctions globalement + window.pushDashboards = pushDashboards; + window.resetForm = resetForm; + window.toggleSelectAll = toggleSelectAll; + window.toggleShClusterAllApps = toggleShClusterAllApps; + window.updateSelectedShClusterApps = updateSelectedShClusterApps; + + // Attacher les événements pour la section SH Cluster + setTimeout(function() { + // Checkbox "Deploy to SH Cluster" + const deployCheckbox = document.getElementById('deploy-to-shcluster'); + if (deployCheckbox) { + deployCheckbox.addEventListener('change', function() { + toggleDeployerOptions(); + }); + } + + // Checkbox "All apps" + const allAppsCheckbox = document.getElementById('shcluster-all-apps'); + if (allAppsCheckbox) { + allAppsCheckbox.addEventListener('change', function() { + toggleShClusterAllApps(); + }); + } + + // Bouton configure + const configBtn = document.getElementById('deployer-config-btn'); + if (configBtn) { + configBtn.addEventListener('click', function() { + showDeployerConfigModal(); + }); + } + }, 500); +}); + +// Afficher/masquer les options du deployer +function toggleDeployerOptions() { + const checkbox = document.getElementById('deploy-to-shcluster'); + const appsSection = document.getElementById('deployer-apps-section'); + const authSection = document.getElementById('deployer-auth'); + + if (checkbox && checkbox.checked) { + if (appsSection) appsSection.classList.add('visible'); + if (authSection) authSection.classList.add('visible'); + } else { + if (appsSection) appsSection.classList.remove('visible'); + if (authSection) authSection.classList.remove('visible'); + } +} + +// ============================================ +// RENDU DE LA LISTE DES APPLICATIONS +// ============================================ + +function renderAppsList(rows, fields) { + const container = document.getElementById('dashboard-list'); + if (!container) return; + + // Trouver les index des colonnes + const nameIdx = fields.indexOf('name'); + const labelIdx = fields.indexOf('label'); + const descIdx = fields.indexOf('description'); + + // Générer le HTML + let html = ` +
+
+ + +
+ ${rows.length} apps +
+ `; + + rows.forEach((row, index) => { + const name = row[nameIdx] || ''; + const label = row[labelIdx] || name; + const desc = row[descIdx] || ''; + + // Ignorer certaines apps système + if (name.startsWith('splunk_') || name === 'learned' || name === 'launcher') { + return; + } + + html += ` +
+ + +
+ `; + }); + + container.innerHTML = html; + + console.log(`Rendered ${rows.length} applications`); +} + +// ============================================ +// GESTION DE LA SÉLECTION +// ============================================ + +function updateSelectedApps() { + const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app-id]'); + selectedApps = []; + + checkboxes.forEach(cb => { + if (cb.checked) { + selectedApps.push({ + id: cb.getAttribute('data-app-id'), + label: cb.getAttribute('data-app-label') + }); + } + }); + + // Mettre à jour le "Select All" + const selectAll = document.getElementById('select-all'); + if (selectAll) { + const allChecked = Array.from(checkboxes).every(cb => cb.checked); + const someChecked = Array.from(checkboxes).some(cb => cb.checked); + selectAll.checked = allChecked; + selectAll.indeterminate = someChecked && !allChecked; + } + + // Mettre à jour la liste des apps pour le SH Cluster + updateShClusterAppsList(); + + console.log(`Selected ${selectedApps.length} apps`); +} + +function updateShClusterAppsList() { + const container = document.getElementById('shcluster-apps-container'); + if (!container) return; + + if (selectedApps.length === 0) { + container.innerHTML = '

Select apps from the left panel first

'; + selectedShClusterApps = []; + return; + } + + // Sauvegarder l'état actuel des checkboxes + const currentState = {}; + const existingCheckboxes = container.querySelectorAll('input[type="checkbox"][data-app-id]'); + existingCheckboxes.forEach(cb => { + currentState[cb.getAttribute('data-app-id')] = cb.checked; + }); + + // Vérifier si la liste a changé (nouvelles apps ajoutées ou apps retirées) + const currentAppIds = Array.from(existingCheckboxes).map(cb => cb.getAttribute('data-app-id')); + const newAppIds = selectedApps.map(app => app.id); + const listChanged = currentAppIds.length !== newAppIds.length || + !currentAppIds.every(id => newAppIds.includes(id)); + + // Ne recréer le HTML que si la liste a changé + if (listChanged || existingCheckboxes.length === 0) { + let html = ''; + selectedApps.forEach((app, index) => { + // Préserver l'état si l'app existait, sinon cocher par défaut + const isChecked = currentState.hasOwnProperty(app.id) ? currentState[app.id] : true; + html += ` +
+ + +
+ `; + }); + + container.innerHTML = html; + } + + // Mettre à jour la liste des apps SH Cluster sélectionnées (sans recréer le HTML) + updateSelectedShClusterApps(); +} + +function updateSelectedShClusterApps() { + const allAppsCheckbox = document.getElementById('shcluster-all-apps'); + + if (allAppsCheckbox && allAppsCheckbox.checked) { + // Toutes les apps sélectionnées pour Git + selectedShClusterApps = [...selectedApps]; + } else { + // Seulement les apps cochées dans la liste SH Cluster + const checkboxes = document.querySelectorAll('#shcluster-apps-container input[type="checkbox"][data-app-id]'); + selectedShClusterApps = []; + + checkboxes.forEach(cb => { + if (cb.checked) { + selectedShClusterApps.push({ + id: cb.getAttribute('data-app-id'), + label: cb.getAttribute('data-app-label') + }); + } + }); + } + + console.log(`Selected ${selectedShClusterApps.length} apps for SH Cluster`); +} + +function toggleShClusterAllApps() { + const allAppsCheckbox = document.getElementById('shcluster-all-apps'); + const appsList = document.getElementById('shcluster-apps-list'); + + if (allAppsCheckbox && appsList) { + if (allAppsCheckbox.checked) { + // Masquer la liste et utiliser toutes les apps + appsList.style.display = 'none'; + selectedShClusterApps = [...selectedApps]; + console.log('SH Cluster: Using all selected apps'); + } else { + // Afficher la liste pour permettre la sélection manuelle + appsList.style.display = 'block'; + // Ne PAS appeler updateSelectedShClusterApps() ici + // L'utilisateur va faire sa sélection manuellement + // La liste garde son état actuel (tous cochés par défaut) + console.log('SH Cluster: Manual selection enabled'); + } + } +} + +function toggleSelectAll(checked) { + const checkboxes = document.querySelectorAll('#dashboard-list input[type="checkbox"][data-app-id]'); + checkboxes.forEach(cb => { + cb.checked = checked; + }); + updateSelectedApps(); +} + +// ============================================ +// PUSH VERS GIT +// ============================================ + +async function pushDashboards() { + console.log("Starting push process..."); + + // Vérifier si déjà en cours + if (isProcessing) { + console.log("Push already in progress"); + return; + } + + // Vérifier la licence AVANT tout + if (typeof checkLicenseBeforePush === 'function') { + const licenseOk = await checkLicenseBeforePush(); + if (!licenseOk) { + console.log("License check failed"); + return; + } + } + + // Récupérer les valeurs du formulaire + const gitUrl = document.getElementById('git-url')?.value?.trim(); + const gitBranch = document.getElementById('git-branch')?.value?.trim() || 'main'; + const gitToken = document.getElementById('git-token')?.value?.trim(); + const commitMessage = document.getElementById('commit-message')?.value?.trim(); + const saveCredentials = document.getElementById('save-credentials')?.checked; + + // Ne PAS rappeler updateSelectedApps() ici car cela réinitialiserait la liste SH Cluster + // La liste selectedApps est déjà à jour grâce aux événements onchange + + // Validation + if (!gitUrl) { + showMessage('error', 'Please enter a Git repository URL'); + return; + } + + if (!gitToken) { + showMessage('error', 'Please enter a Git token or password'); + return; + } + + if (!commitMessage) { + showMessage('error', 'Please enter a commit message'); + return; + } + + if (selectedApps.length === 0) { + showMessage('error', 'Please select at least one application to deploy'); + return; + } + + // Sauvegarder les credentials si demandé + if (saveCredentials) { + saveCredentialsToStorage(gitUrl, gitBranch, gitToken); + } + + // Vérifier si le déploiement vers SH Cluster est activé + const deployToSHCluster = document.getElementById('deploy-to-shcluster')?.checked || false; + const shAuthUser = document.getElementById('sh-auth-user')?.value?.trim() || ''; + const shAuthPass = document.getElementById('sh-auth-pass')?.value?.trim() || ''; + + // La liste selectedShClusterApps est déjà à jour via les événements onchange + // Ne pas rappeler updateSelectedShClusterApps() pour éviter de réinitialiser la sélection + + // Validation des apps SH Cluster si déploiement activé + if (deployToSHCluster && selectedShClusterApps.length === 0) { + showMessage('error', 'Please select at least one application to deploy to SH Cluster'); + return; + } + + // Démarrer le push + isProcessing = true; + showLoading(true, deployToSHCluster); + hideMessages(); + + try { + // Récupérer l'utilisateur courant + const currentUser = await getCurrentUser(); + + // Récupérer les infos de licence depuis le localStorage + const licenseInfo = getLicenseInfo ? getLicenseInfo() : null; + const licenseType = licenseInfo?.type_name || ''; + const licenseId = licenseInfo?.license_id || ''; + + // Construire les paramètres + const params = new URLSearchParams({ + git_url: gitUrl, + git_branch: gitBranch, + git_token: gitToken, + commit_message: commitMessage, + apps: JSON.stringify(selectedApps), + shcluster_apps: JSON.stringify(selectedShClusterApps), // Apps pour le SH Cluster + user: currentUser, + deploy_to_shcluster: deployToSHCluster.toString(), + deployer_host: SH_DEPLOYER_CONFIG.host, + deployer_token: SH_DEPLOYER_CONFIG.token, + license_type: licenseType, + license_id: licenseId + }); + + // Ajouter les credentials SH si fournis + if (shAuthUser) params.append('sh_auth_user', shAuthUser); + if (shAuthPass) params.append('sh_auth_pass', shAuthPass); + + console.log(`Pushing ${selectedApps.length} apps to Git${deployToSHCluster ? `, ${selectedShClusterApps.length} apps to SH Cluster` : ''}`); + + // Appeler le serveur + const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/push?${params.toString()}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + + const result = await response.json(); + console.log("Push result:", result); + + if (result.status === 'success') { + // Incrémenter le compteur d'utilisation côté client + console.log("=== INCREMENT USAGE ==="); + console.log("typeof incrementUsage:", typeof incrementUsage); + console.log("typeof window.incrementUsage:", typeof window.incrementUsage); + + try { + if (typeof window.incrementUsage === 'function') { + const stats = window.incrementUsage(); + console.log("✓ Usage incremented successfully:", stats); + } else if (typeof incrementUsage === 'function') { + const stats = incrementUsage(); + console.log("✓ Usage incremented (local):", stats); + } else { + console.warn("✗ incrementUsage function not available"); + } + } catch (e) { + console.error("✗ Error incrementing usage:", e); + } + + let message = `✅ Successfully deployed ${result.apps_pushed || selectedApps.length} application(s) to Git!`; + + // Ajouter le statut du déploiement SH Cluster + if (deployToSHCluster && result.shcluster_deployment) { + if (result.shcluster_deployment.success) { + message += '\n🚀 SH Cluster deployment triggered successfully!'; + } else { + message += `\n⚠️ SH Cluster deployment failed: ${result.shcluster_deployment.message}`; + } + } + + showMessage('success', message); + + // Reset la sélection après succès + setTimeout(() => { + toggleSelectAll(false); + }, 2000); + } else if (result.error_code === 'LICENSE_ERROR') { + showMessage('error', `🔐 ${result.message}`); + + // Afficher le modal de licence + if (typeof showLicenseModal === 'function') { + showLicenseModal(result.message, result.error_code); + } + } else if (result.error_code === 'APP_LIMIT') { + showMessage('error', `📦 ${result.message}`); + } else { + showMessage('error', result.message || 'Unknown error occurred'); + } + + } catch (error) { + console.error("Push error:", error); + showMessage('error', `Connection error: ${error.message}. Is the Git Pusher server running?`); + } finally { + isProcessing = false; + showLoading(false); + } +} + +// ============================================ +// UTILITAIRES UI +// ============================================ + +function showLoading(show, deployToSHCluster = false) { + const loading = document.getElementById('loading'); + const pushBtn = document.getElementById('push-btn'); + const loadingText = document.querySelector('.loading-text'); + + if (loading) { + loading.classList.toggle('active', show); + } + + if (loadingText && show) { + if (deployToSHCluster) { + loadingText.textContent = 'Deploying to Git and SH Cluster... Please wait'; + } else { + loadingText.textContent = 'Deploying applications to Git... Please wait'; + } + } + + if (pushBtn) { + pushBtn.disabled = show; + if (show) { + pushBtn.textContent = deployToSHCluster ? '⏳ Deploying to Git + SH...' : '⏳ Deploying...'; + } else { + pushBtn.textContent = '✈️ Deploy to Git'; + } + } +} + +function showMessage(type, text) { + hideMessages(); + + const successMsg = document.getElementById('success-msg'); + const errorMsg = document.getElementById('error-msg'); + const successText = document.getElementById('success-text'); + const errorText = document.getElementById('error-text'); + + if (type === 'success' && successMsg && successText) { + successText.textContent = text; + successMsg.classList.add('active'); + + // Auto-hide après 5 secondes + setTimeout(() => { + successMsg.classList.remove('active'); + }, 5000); + } else if (type === 'error' && errorMsg && errorText) { + errorText.textContent = text; + errorMsg.classList.add('active'); + } +} + +function hideMessages() { + const successMsg = document.getElementById('success-msg'); + const errorMsg = document.getElementById('error-msg'); + + if (successMsg) successMsg.classList.remove('active'); + if (errorMsg) errorMsg.classList.remove('active'); +} + +function resetForm(clearCredentials = false) { + // Reset les champs + const commitMessage = document.getElementById('commit-message'); + if (commitMessage) commitMessage.value = ''; + + if (clearCredentials) { + const gitUrl = document.getElementById('git-url'); + const gitToken = document.getElementById('git-token'); + const saveCredentials = document.getElementById('save-credentials'); + + if (gitUrl) gitUrl.value = ''; + if (gitToken) gitToken.value = ''; + if (saveCredentials) saveCredentials.checked = false; + + // Supprimer les credentials sauvegardés + localStorage.removeItem(GIT_PUSHER_CONFIG.credentialsKey); + } + + // Reset la sélection + toggleSelectAll(false); + + // Cacher les messages + hideMessages(); + + console.log("Form reset" + (clearCredentials ? " (with credentials)" : "")); +} + +// ============================================ +// GESTION DES CREDENTIALS +// ============================================ + +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() + }; + + localStorage.setItem(GIT_PUSHER_CONFIG.credentialsKey, JSON.stringify(credentials)); + console.log("Credentials saved"); + } catch (error) { + console.error("Error saving credentials:", error); + } +} + +function loadSavedCredentials() { + try { + const saved = localStorage.getItem(GIT_PUSHER_CONFIG.credentialsKey); + if (!saved) return; + + const credentials = JSON.parse(saved); + + const gitUrl = document.getElementById('git-url'); + const gitBranch = document.getElementById('git-branch'); + const gitToken = document.getElementById('git-token'); + const saveCredentials = document.getElementById('save-credentials'); + + if (gitUrl && credentials.gitUrl) { + gitUrl.value = credentials.gitUrl; + } + + if (gitBranch && credentials.gitBranch) { + gitBranch.value = credentials.gitBranch; + } + + if (gitToken && credentials.gitToken) { + gitToken.value = atob(credentials.gitToken); + } + + if (saveCredentials) { + saveCredentials.checked = true; + } + + console.log("Credentials loaded from storage"); + } catch (error) { + console.error("Error loading credentials:", error); + } +} + +// ============================================ +// RÉCUPÉRATION DE L'UTILISATEUR SPLUNK +// ============================================ + +async function getCurrentUser() { + try { + const response = await fetch('/en-US/splunkd/__raw/services/authentication/current-context?output_mode=json'); + const data = await response.json(); + return data.entry?.[0]?.content?.username || 'unknown'; + } catch (error) { + console.error("Error getting current user:", error); + return 'unknown'; + } +} + +// ============================================ +// VÉRIFICATION DU SERVEUR +// ============================================ + +async function checkServerHealth() { + try { + const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/health`, { + method: 'GET', + timeout: 5000 + }); + const data = await response.json(); + return data.status === 'ok'; + } catch (error) { + console.error("Server health check failed:", error); + return false; + } +} + +// ============================================ +// SH DEPLOYER FUNCTIONS +// ============================================ + +async function checkDeployerHealth() { + try { + const response = await fetch(`${GIT_PUSHER_CONFIG.serverUrl}/deployer/health`, { + method: 'GET', + timeout: 5000 + }); + const data = await response.json(); + + deployerAvailable = data.status === 'ok'; + + // Mettre à jour l'UI + updateDeployerUI(); + + console.log("SH Deployer status:", deployerAvailable ? "Available" : "Unavailable"); + return deployerAvailable; + } catch (error) { + console.error("Deployer health check failed:", error); + deployerAvailable = false; + updateDeployerUI(); + return false; + } +} + +function updateDeployerUI() { + const deployerCheckbox = document.getElementById('deploy-to-shcluster'); + const deployerStatus = document.getElementById('deployer-status'); + const deployerSection = document.getElementById('deployer-section'); + + if (deployerCheckbox) { + deployerCheckbox.disabled = !deployerAvailable; + } + + if (deployerStatus) { + if (deployerAvailable) { + deployerStatus.innerHTML = '● Connected'; + } else { + deployerStatus.innerHTML = '● Disconnected'; + } + } + + if (deployerSection && !deployerAvailable) { + deployerSection.style.opacity = '0.6'; + } +} + +function loadDeployerConfig() { + try { + const saved = localStorage.getItem(GIT_PUSHER_CONFIG.deployerConfigKey); + if (saved) { + const config = JSON.parse(saved); + SH_DEPLOYER_CONFIG = { ...SH_DEPLOYER_CONFIG, ...config }; + console.log("Deployer config loaded"); + } + } catch (error) { + console.error("Error loading deployer config:", error); + } +} + +function saveDeployerConfig() { + try { + localStorage.setItem(GIT_PUSHER_CONFIG.deployerConfigKey, JSON.stringify(SH_DEPLOYER_CONFIG)); + console.log("Deployer config saved"); + } catch (error) { + console.error("Error saving deployer config:", error); + } +} + +function showDeployerConfigModal() { + const modal = document.createElement('div'); + modal.id = 'deployer-config-modal'; + modal.style.cssText = ` + 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; + `; + + modal.innerHTML = ` +
+

⚙️ SH Deployer Configuration

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ `; + + document.body.appendChild(modal); +} + +function closeDeployerConfigModal() { + const modal = document.getElementById('deployer-config-modal'); + if (modal) modal.remove(); +} + +async function saveDeployerConfigFromModal() { + const host = document.getElementById('deployer-config-host')?.value?.trim(); + const port = parseInt(document.getElementById('deployer-config-port')?.value) || 9998; + const token = document.getElementById('deployer-config-token')?.value?.trim(); + const msgEl = document.getElementById('deployer-config-message'); + + if (!host) { + if (msgEl) { + msgEl.style.display = 'block'; + msgEl.style.background = '#ffebee'; + msgEl.style.color = '#c62828'; + msgEl.textContent = 'Please enter a host'; + } + return; + } + + // Mettre à jour la config + SH_DEPLOYER_CONFIG.host = host; + SH_DEPLOYER_CONFIG.port = port; + SH_DEPLOYER_CONFIG.token = token; + + // Sauvegarder + saveDeployerConfig(); + + // Tester la connexion + if (msgEl) { + msgEl.style.display = 'block'; + msgEl.style.background = '#e3f2fd'; + msgEl.style.color = '#1565c0'; + msgEl.textContent = 'Testing connection...'; + } + + const isHealthy = await checkDeployerHealth(); + + if (isHealthy) { + if (msgEl) { + msgEl.style.background = '#e8f5e9'; + msgEl.style.color = '#2e7d32'; + msgEl.textContent = '✓ Connected successfully!'; + } + setTimeout(closeDeployerConfigModal, 1500); + } else { + if (msgEl) { + msgEl.style.background = '#ffebee'; + msgEl.style.color = '#c62828'; + msgEl.textContent = '✗ Connection failed. Check host and port.'; + } + } +} + +// Vérifier la santé du serveur au démarrage +setTimeout(async () => { + const healthy = await checkServerHealth(); + if (!healthy) { + console.warn("Git Pusher server may not be running"); + } else { + console.log("Git Pusher server is healthy"); + } +}, 1000); + +// ============================================ +// EXPORT FONCTIONS GLOBALES +// ============================================ + +// Exposer les fonctions du deployer globalement pour les onclick du HTML +window.showDeployerConfigModal = showDeployerConfigModal; +window.closeDeployerConfigModal = closeDeployerConfigModal; +window.saveDeployerConfigFromModal = saveDeployerConfigFromModal; + +// Exposer les fonctions principales +window.pushDashboards = pushDashboards; +window.resetForm = resetForm; +window.toggleSelectAll = toggleSelectAll; +window.updateSelectedApps = updateSelectedApps; + +// Fonction toggle pour le HTML +window.toggleDeployerAuth = function() { + var checkbox = document.getElementById('deploy-to-shcluster'); + var authSection = document.getElementById('deployer-auth'); + if (checkbox && authSection) { + authSection.classList.toggle('visible', checkbox.checked); + } +}; + +// ============================================ +// ATTACHEMENT DES ÉVÉNEMENTS AUX BOUTONS +// ============================================ + +(function attachButtonEvents() { + function tryAttach() { + console.log("Trying to attach button events..."); + + // Bouton Deploy to Git + var pushBtn = document.getElementById('push-btn'); + if (pushBtn) { + // Supprimer les anciens listeners + pushBtn.replaceWith(pushBtn.cloneNode(true)); + pushBtn = document.getElementById('push-btn'); + + pushBtn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + console.log("Deploy button clicked!"); + pushDashboards(); + }); + console.log("✓ Deploy button event attached"); + } else { + console.log("✗ Deploy button not found yet"); + } + + // Bouton Reset - chercher par classe ou contenu + var buttons = document.querySelectorAll('button.btn, button.btn-secondary'); + buttons.forEach(function(btn) { + if (btn.textContent.includes('Reset') || btn.textContent.includes('🔄')) { + // Supprimer les anciens listeners + var newBtn = btn.cloneNode(true); + btn.parentNode.replaceChild(newBtn, btn); + + newBtn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + console.log("Reset button clicked!"); + resetForm(true); + }); + console.log("✓ Reset button event attached"); + } + }); + + // Bouton Configure Deployer + var configBtn = document.querySelector('.deployer-config-btn'); + if (configBtn) { + // Supprimer les anciens listeners + var newConfigBtn = configBtn.cloneNode(true); + configBtn.parentNode.replaceChild(newConfigBtn, configBtn); + + newConfigBtn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + console.log("Configure button clicked!"); + showDeployerConfigModal(); + }); + console.log("✓ Configure button event attached"); + } + + // Checkbox deploy to shcluster + var deployCheckbox = document.getElementById('deploy-to-shcluster'); + if (deployCheckbox) { + deployCheckbox.addEventListener('change', function() { + var authSection = document.getElementById('deployer-auth'); + if (authSection) { + authSection.classList.toggle('visible', this.checked); + } + }); + console.log("✓ Deploy checkbox event attached"); + } + + // Si le bouton principal n'est pas encore là, réessayer + if (!pushBtn) { + console.log("Retrying in 500ms..."); + setTimeout(tryAttach, 500); + } else { + console.log("=== All button events attached successfully ==="); + } + } + + // Démarrer après un délai pour laisser le DOM se charger + if (document.readyState === 'complete') { + console.log("Document ready, attaching events in 1s..."); + setTimeout(tryAttach, 1000); + } else { + window.addEventListener('load', function() { + console.log("Window loaded, attaching events in 1s..."); + setTimeout(tryAttach, 1000); + }); + } +})(); + +// ============================================ +// EXPORT POUR DEBUG +// ============================================ + +window.GitPusher = { + config: GIT_PUSHER_CONFIG, + deployerConfig: SH_DEPLOYER_CONFIG, + getSelectedApps: () => selectedApps, + checkServer: checkServerHealth, + checkDeployer: checkDeployerHealth, + version: GIT_PUSHER_CONFIG.version +}; \ No newline at end of file diff --git a/apps/pusher_app_prem/appserver/static/git_pusher.js_old b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/git_pusher.js_old similarity index 100% rename from apps/pusher_app_prem/appserver/static/git_pusher.js_old rename to apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/git_pusher.js_old diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/git_pusher_config.js b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/git_pusher_config.js new file mode 100644 index 00000000..3e763d33 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/git_pusher_config.js @@ -0,0 +1,391 @@ +// ============================================ +// GIT PUSHER - CONFIGURATION PAGE +// Version 2.1 - Compatible Splunk +// ============================================ + +require([ + 'jquery', + 'splunkjs/mvc', + 'splunkjs/mvc/simplexml/ready!' +], function($, mvc) { + + console.log('Git Pusher Config v2.1 initializing...'); + + // Configuration par défaut + var DEFAULT_CONFIG = { + api: { + url: '', + port: 9999, + useProxy: true + }, + deployer: { + enabled: false, + host: '', + port: 9998, + token: '', + useSSL: true + }, + license: { + checkInterval: 24 + }, + advanced: { + logLevel: 'INFO', + timeout: 30, + gitTimeout: 120 + } + }; + + // URL de l'API pour la config + function getConfigApiUrl() { + var hostname = window.location.hostname; + var protocol = window.location.protocol; + + // Essayer de charger depuis localStorage + try { + var stored = localStorage.getItem('git_pusher_config'); + if (stored) { + var config = JSON.parse(stored); + if (config.api && config.api.url) { + var url = config.api.url; + if (!config.api.useProxy && config.api.port) { + url = url.replace(/\/$/, '') + ':' + config.api.port; + } + return url; + } + } + } catch(e) {} + + // Auto-détection + 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'; + } + + // ============================================ + // CHARGEMENT DE LA CONFIGURATION + // ============================================ + + function loadConfig() { + console.log('Loading configuration...'); + var apiUrl = getConfigApiUrl(); + + $.ajax({ + url: apiUrl + '/config', + method: 'GET', + dataType: 'json', + success: function(config) { + console.log('Config loaded:', config); + applyConfigToForm(config); + showMessage('Configuration chargée', 'success'); + }, + error: function(xhr, status, error) { + console.log('No server config, using defaults:', error); + applyConfigToForm(DEFAULT_CONFIG); + } + }); + + loadLicenseStatus(); + } + + function applyConfigToForm(config) { + // API + $('#api-url').val(config.api ? config.api.url || '' : ''); + $('#api-port').val(config.api ? config.api.port || 9999 : 9999); + $('#use-proxy').prop('checked', config.api ? config.api.useProxy !== false : true); + + // Deployer + $('#deployer-enabled').prop('checked', config.deployer ? config.deployer.enabled || false : false); + $('#deployer-host').val(config.deployer ? config.deployer.host || '' : ''); + $('#deployer-port').val(config.deployer ? config.deployer.port || 9998 : 9998); + $('#deployer-token').val(config.deployer ? config.deployer.token || '' : ''); + $('#deployer-use-ssl').prop('checked', config.deployer ? config.deployer.useSSL !== false : true); + + // Licence + $('#license-check-interval').val(config.license ? config.license.checkInterval || 24 : 24); + + // Avancé + $('#log-level').val(config.advanced ? config.advanced.logLevel || 'INFO' : 'INFO'); + $('#timeout').val(config.advanced ? config.advanced.timeout || 30 : 30); + $('#git-timeout').val(config.advanced ? config.advanced.gitTimeout || 120 : 120); + } + + function getConfigFromForm() { + return { + api: { + url: $('#api-url').val().trim(), + port: parseInt($('#api-port').val()) || 9999, + useProxy: $('#use-proxy').is(':checked') + }, + deployer: { + enabled: $('#deployer-enabled').is(':checked'), + host: $('#deployer-host').val().trim(), + port: parseInt($('#deployer-port').val()) || 9998, + token: $('#deployer-token').val(), + useSSL: $('#deployer-use-ssl').is(':checked') + }, + license: { + checkInterval: parseInt($('#license-check-interval').val()) || 24 + }, + advanced: { + logLevel: $('#log-level').val(), + timeout: parseInt($('#timeout').val()) || 30, + gitTimeout: parseInt($('#git-timeout').val()) || 120 + } + }; + } + + // ============================================ + // SAUVEGARDE DE LA CONFIGURATION + // ============================================ + + function saveConfig() { + console.log('Saving configuration...'); + var config = getConfigFromForm(); + var apiUrl = getConfigApiUrl(); + + $.ajax({ + url: apiUrl + '/config', + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(config), + dataType: 'json', + success: function(result) { + console.log('Save result:', result); + if (result.success) { + showMessage('✅ Configuration sauvegardée avec succès !', 'success'); + // Sauvegarder aussi dans localStorage + localStorage.setItem('git_pusher_config', JSON.stringify(config)); + } else { + showMessage('❌ Erreur: ' + (result.error || 'Échec de la sauvegarde'), 'error'); + } + }, + error: function(xhr, status, error) { + console.error('Save error:', error); + showMessage('❌ Erreur de connexion au serveur: ' + error, 'error'); + } + }); + } + + function resetConfig() { + if (confirm('Voulez-vous vraiment réinitialiser la configuration ?')) { + applyConfigToForm(DEFAULT_CONFIG); + showMessage('Configuration réinitialisée (non sauvegardée)', 'success'); + } + } + + // ============================================ + // TESTS DE CONNEXION + // ============================================ + + function testApiConnection() { + console.log('Testing API connection...'); + var $status = $('#api-status'); + $status.removeClass('connected disconnected').text('● Test en cours...'); + + var apiUrl = $('#api-url').val().trim(); + + if (!apiUrl) { + apiUrl = getConfigApiUrl(); + } else if (!$('#use-proxy').is(':checked')) { + var port = $('#api-port').val() || 9999; + if (apiUrl.indexOf(':' + port) === -1) { + apiUrl = apiUrl.replace(/\/$/, '') + ':' + port; + } + } + + console.log('Testing URL:', apiUrl); + + $.ajax({ + url: apiUrl + '/health', + method: 'GET', + dataType: 'json', + timeout: 10000, + success: function(data) { + console.log('API health:', data); + $status.addClass('connected').text('● Connecté'); + }, + error: function(xhr, status, error) { + console.error('API test failed:', error); + $status.addClass('disconnected').text('● Échec connexion'); + } + }); + } + + function testDeployerConnection() { + console.log('Testing Deployer connection...'); + var $status = $('#deployer-status'); + $status.removeClass('connected disconnected').text('● Test en cours...'); + + var host = $('#deployer-host').val().trim(); + var port = $('#deployer-port').val() || 9998; + var useSSL = $('#deployer-use-ssl').is(':checked'); + var token = $('#deployer-token').val(); + + if (!host) { + $status.addClass('disconnected').text('● Adresse manquante'); + return; + } + + var protocol = useSSL ? 'https' : 'http'; + var url; + + // Si c'est un nom de domaine (contient des lettres et des points, pas une IP) + // Ne pas ajouter le port (le proxy gère) + if (/^[a-zA-Z]/.test(host) && host.indexOf('.') > -1 && !/^(\d{1,3}\.){3}\d{1,3}$/.test(host)) { + // C'est un domaine, pas de port + url = protocol + '://' + host + '/health'; + } else { + // C'est une IP ou localhost, ajouter le port + url = protocol + '://' + host + ':' + port + '/health'; + } + + console.log('Testing Deployer URL:', url); + + $.ajax({ + url: url, + method: 'GET', + dataType: 'json', + timeout: 10000, + headers: { + 'X-Auth-Token': token + }, + success: function(data) { + console.log('Deployer health:', data); + $status.addClass('connected').text('● Connecté'); + }, + error: function(xhr, status, error) { + console.error('Deployer test failed:', error); + $status.addClass('disconnected').text('● Échec connexion'); + } + }); + } + + // ============================================ + // STATUT DE LA LICENCE + // ============================================ + + function loadLicenseStatus() { + var $status = $('#license-status'); + + try { + var stored = localStorage.getItem('git_pusher_license'); + + if (stored) { + var parsed = JSON.parse(stored); + var licenseData = parsed.licenseData; + + if (licenseData) { + var expires = new Date(licenseData.expires); + var now = new Date(); + var daysRemaining = Math.ceil((expires - now) / (1000 * 60 * 60 * 24)); + + if (daysRemaining > 0) { + $status.html( + '● Active' + + '
Type: ' + licenseData.type_name + ' | Expire: ' + licenseData.expires + ' (' + daysRemaining + 'j)' + ); + } else { + $status.html( + '● Expirée' + + '
Expirée le ' + licenseData.expires + '' + ); + } + return; + } + } + + $status.html('● Non installée'); + + } catch (error) { + console.error('Erreur lecture licence:', error); + $status.html('● Erreur'); + } + } + + // ============================================ + // UTILITAIRES + // ============================================ + + function showMessage(message, type) { + var $msg = $('#config-message'); + $msg.text(message).removeClass('success error').addClass(type).show(); + + setTimeout(function() { + $msg.fadeOut(); + }, 5000); + } + + // ============================================ + // ATTACHER LES ÉVÉNEMENTS + // ============================================ + + function attachEvents() { + console.log('Attaching events...'); + + // Bouton Test API + $('#test-api-btn').on('click', function(e) { + e.preventDefault(); + console.log('Test API clicked'); + testApiConnection(); + }); + + // Bouton Test Deployer + $('#test-deployer-btn').on('click', function(e) { + e.preventDefault(); + console.log('Test Deployer clicked'); + testDeployerConnection(); + }); + + // Bouton Sauvegarder + $('#save-btn').on('click', function(e) { + e.preventDefault(); + console.log('Save clicked'); + saveConfig(); + }); + + // Bouton Réinitialiser + $('#reset-btn').on('click', function(e) { + e.preventDefault(); + console.log('Reset clicked'); + resetConfig(); + }); + + console.log('Events attached to buttons'); + } + + // ============================================ + // INITIALISATION + // ============================================ + + // Attendre que le DOM soit complètement prêt + function init() { + if ($('#api-url').length > 0) { + console.log('DOM ready, initializing...'); + attachEvents(); + loadConfig(); + } else { + console.log('DOM not ready, retrying...'); + setTimeout(init, 300); + } + } + + setTimeout(init, 500); + + // Exposer globalement pour le debug + window.gitPusherConfig = { + saveConfig: saveConfig, + resetConfig: resetConfig, + testApiConnection: testApiConnection, + testDeployerConnection: testDeployerConnection, + loadConfig: loadConfig + }; + + console.log('Git Pusher Config module loaded'); +}); \ No newline at end of file diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_file_management.js b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_file_management.js new file mode 100644 index 00000000..f59a6676 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_file_management.js @@ -0,0 +1,613 @@ +// ============================================ +// SYSTÈME DE GESTION DE LICENCE FICHIER .LIC +// ============================================ + +const LICENSE_FILE_KEY = 'git_pusher_license_file'; +const LICENSE_STATUS_ENDPOINT = '/custom/git_pusher/license_status'; + +let currentLicenseStatus = null; + +function initializeLicenseSystem() { + console.log("Initializing file-based license system..."); + + // Vérifier le statut de la licence + checkLicenseStatus(); +} + +function checkLicenseStatus() { + console.log("Checking license status..."); + + // Appeler le backend pour vérifier la licence + fetch(LICENSE_STATUS_ENDPOINT) + .then(response => response.json()) + .then(data => { + console.log("License status:", data); + currentLicenseStatus = data; + + if (data.licensed) { + // Licence valide + displayLicenseBadge(data.license_info, data.warnings); + } else { + // Pas de licence ou invalide + showLicenseUploadModal(data.errors); + } + }) + .catch(error => { + console.error("Error checking license:", error); + // En cas d'erreur, afficher le modal + showLicenseUploadModal(["Impossible de vérifier la licence"]); + }); +} + +function displayLicenseBadge(licenseInfo, warnings) { + console.log("Displaying license badge..."); + + const container = document.getElementById('license-badge-container'); + if (!container) { + console.error("license-badge-container not found"); + return; + } + + // Créer le badge + const badge = document.createElement('div'); + badge.id = 'license-badge'; + badge.style.cssText = ` + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 12px 20px; + border-radius: 8px; + font-size: 12px; + font-weight: 600; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); + cursor: pointer; + transition: all 0.3s ease; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + text-align: center; + min-width: 200px; + `; + + // Déterminer le texte et la couleur selon le type + let badgeText = '✓ Licence Activée'; + let badgeColor = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'; + + if (licenseInfo.type === 'trial') { + badgeText = `⏱️ Essai: ${licenseInfo.days_remaining} jours`; + badgeColor = 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)'; + } else if (licenseInfo.type === 'standard') { + badgeText = `✓ Standard (${licenseInfo.days_remaining}j)`; + } else if (licenseInfo.type === 'enterprise') { + badgeText = `✓ Enterprise (${licenseInfo.days_remaining}j)`; + badgeColor = 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)'; + } + + // Avertissement si expire bientôt + if (licenseInfo.days_remaining < 30) { + badgeColor = 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)'; + badgeText = `⚠️ Expire dans ${licenseInfo.days_remaining}j`; + } + + badge.style.background = badgeColor; + badge.textContent = badgeText; + + // Clic pour afficher les détails + badge.onclick = function() { + showLicenseDetailsModal(licenseInfo, warnings); + }; + + container.appendChild(badge); + + // Hover effect + badge.addEventListener('mouseenter', function() { + this.style.transform = 'translateY(-3px)'; + this.style.boxShadow = '0 6px 25px rgba(102, 126, 234, 0.5)'; + }); + + badge.addEventListener('mouseleave', function() { + this.style.transform = 'translateY(0)'; + this.style.boxShadow = '0 4px 15px rgba(102, 126, 234, 0.3)'; + }); +} + +function showLicenseUploadModal(errors = []) { + console.log("Showing license upload modal"); + + // Créer le modal + const modal = document.createElement('div'); + modal.id = 'license-modal'; + modal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + `; + + const content = document.createElement('div'); + content.style.cssText = ` + background: white; + border-radius: 16px; + padding: 40px; + max-width: 550px; + width: 90%; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + animation: slideIn 0.3s ease; + `; + + let errorsHtml = ''; + if (errors.length > 0) { + errorsHtml = ` +
+

+ ⚠️ Problèmes détectés: +

+ +
+ `; + } + + content.innerHTML = ` +
+

🔐 Git Pusher

+

Activation de licence requise

+
+ + ${errorsHtml} + +
+

+ 📋 Hostname détecté: Chargement... +

+
+ +
+ + +
+
📄
+

+ Glissez votre fichier .lic ici +

+

+ ou cliquez pour parcourir +

+ +
+ + + + + 💡 Vous n'avez pas de licence? Contactez-nous pour obtenir votre fichier .lic personnalisé + +
+ + + +
+ + + +
+ `; + + modal.appendChild(content); + document.body.appendChild(modal); + + // Ajouter l'animation CSS + const style = document.createElement('style'); + style.textContent = ` + @keyframes slideIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + `; + document.head.appendChild(style); + + // Récupérer et afficher le hostname + getHostname().then(hostname => { + document.getElementById('detected-hostname').textContent = hostname; + }); + + // Configurer le drag & drop et file input + setupFileUpload(); +} + +function setupFileUpload() { + const dropZone = document.getElementById('drop-zone'); + const fileInput = document.getElementById('license-file-input'); + const fileInfo = document.getElementById('file-info'); + const uploadBtn = document.getElementById('upload-btn'); + + let selectedFile = null; + + // Clic sur la zone pour ouvrir le sélecteur + dropZone.onclick = () => fileInput.click(); + + // Hover effect + dropZone.addEventListener('mouseenter', function() { + this.style.background = '#eef1ff'; + this.style.borderColor = '#5568d3'; + }); + + dropZone.addEventListener('mouseleave', function() { + this.style.background = '#f8f9ff'; + this.style.borderColor = '#667eea'; + }); + + // Drag & drop + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.style.background = '#e3e7ff'; + dropZone.style.borderColor = '#5568d3'; + }); + + dropZone.addEventListener('dragleave', () => { + dropZone.style.background = '#f8f9ff'; + dropZone.style.borderColor = '#667eea'; + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.style.background = '#f8f9ff'; + dropZone.style.borderColor = '#667eea'; + + const files = e.dataTransfer.files; + if (files.length > 0) { + handleFileSelection(files[0]); + } + }); + + // Sélection de fichier + fileInput.addEventListener('change', (e) => { + if (e.target.files.length > 0) { + handleFileSelection(e.target.files[0]); + } + }); + + function handleFileSelection(file) { + selectedFile = file; + + // Vérifier l'extension + if (!file.name.endsWith('.lic')) { + showLicenseMessage('⚠️ Veuillez sélectionner un fichier .lic', 'warning'); + return; + } + + // Afficher les infos du fichier + document.getElementById('file-name').textContent = file.name; + document.getElementById('file-size').textContent = formatFileSize(file.size); + fileInfo.style.display = 'block'; + + // Activer le bouton + uploadBtn.disabled = false; + uploadBtn.style.cursor = 'pointer'; + uploadBtn.style.opacity = '1'; + + // Stocker le fichier pour l'upload + window.selectedLicenseFile = file; + + showLicenseMessage('✓ Fichier prêt à être installé', 'success'); + } +} + +function formatFileSize(bytes) { + if (bytes < 1024) return bytes + ' B'; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; + return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; +} + +function uploadLicenseFile() { + const file = window.selectedLicenseFile; + + if (!file) { + showLicenseMessage('❌ Aucun fichier sélectionné', 'error'); + return; + } + + showLicenseMessage('⏳ Installation de la licence...', 'info'); + + // Lire le fichier + const reader = new FileReader(); + + reader.onload = function(e) { + const fileContent = e.target.result; + + // Envoyer au backend pour validation et installation + fetch('/custom/git_pusher/install_license', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + license_file: fileContent, + filename: file.name + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showLicenseMessage('✅ Licence installée avec succès !', 'success'); + + setTimeout(() => { + closeLicenseModal(); + // Recharger pour afficher le badge + checkLicenseStatus(); + }, 1500); + } else { + showLicenseMessage('❌ ' + (data.message || 'Erreur d\'installation'), 'error'); + + if (data.errors && data.errors.length > 0) { + const errorList = data.errors.map(e => `• ${e}`).join('\n'); + console.error('License errors:', errorList); + } + } + }) + .catch(error => { + console.error('Upload error:', error); + showLicenseMessage('❌ Erreur de connexion au serveur', 'error'); + }); + }; + + reader.onerror = function() { + showLicenseMessage('❌ Erreur de lecture du fichier', 'error'); + }; + + reader.readAsText(file); +} + +function requestTrial() { + showLicenseMessage('⏳ Demande d\'essai en cours...', 'info'); + + // Appeler le backend pour créer une licence d'essai + fetch('/custom/git_pusher/request_trial', { + method: 'POST' + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showLicenseMessage('✅ Licence d\'essai activée (7 jours) !', 'success'); + + setTimeout(() => { + closeLicenseModal(); + checkLicenseStatus(); + }, 1500); + } else { + showLicenseMessage('❌ ' + (data.message || 'Erreur'), 'error'); + } + }) + .catch(error => { + console.error('Trial request error:', error); + showLicenseMessage('❌ Erreur de connexion', 'error'); + }); +} + +function showLicenseDetailsModal(licenseInfo, warnings) { + // Créer un modal pour afficher les détails de la licence + const modal = document.createElement('div'); + modal.id = 'license-details-modal'; + modal.style.cssText = ` + 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: 10001; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + `; + + const content = document.createElement('div'); + content.style.cssText = ` + background: white; + border-radius: 16px; + padding: 40px; + max-width: 500px; + width: 90%; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + `; + + let warningsHtml = ''; + if (warnings && warnings.length > 0) { + warningsHtml = ` +
+ ${warnings.map(w => `

${w}

`).join('')} +
+ `; + } + + const featuresHtml = Object.entries(licenseInfo.features || {}) + .map(([feature, enabled]) => { + const icon = enabled ? '✅' : '❌'; + return `
${icon} ${feature}
`; + }).join(''); + + content.innerHTML = ` +
+

🔐 Informations de licence

+
+ +
+
+ 🆔 ID de licence: +
${licenseInfo.license_id}
+
+ +
+ 👤 Client: +
${licenseInfo.customer}
+
+ +
+ 🏷️ Type: +
${licenseInfo.type.toUpperCase()}
+
+ +
+ 📅 Expire le: +
${new Date(licenseInfo.expires).toLocaleDateString('fr-FR')}
+
+ +
+ ⏰ Jours restants: +
${licenseInfo.days_remaining} jours
+
+
+ +
+ ✨ Fonctionnalités: +
+ ${featuresHtml} +
+
+ + ${warningsHtml} + +
+ + + +
+ `; + + modal.appendChild(content); + document.body.appendChild(modal); +} + +function closeDetailsModal() { + const modal = document.getElementById('license-details-modal'); + if (modal) { + modal.remove(); + } +} + +function showLicenseMessage(message, type) { + const messageEl = document.getElementById('license-message'); + if (!messageEl) return; + + messageEl.style.display = 'block'; + messageEl.textContent = message; + + if (type === 'success') { + messageEl.style.background = '#d4edda'; + messageEl.style.color = '#155724'; + messageEl.style.border = '1px solid #c3e6cb'; + } else if (type === 'error') { + messageEl.style.background = '#f8d7da'; + messageEl.style.color = '#721c24'; + messageEl.style.border = '1px solid #f5c6cb'; + } else if (type === 'warning') { + messageEl.style.background = '#fff3cd'; + messageEl.style.color = '#856404'; + messageEl.style.border = '1px solid #ffeaa7'; + } else if (type === 'info') { + messageEl.style.background = '#d1ecf1'; + messageEl.style.color = '#0c5460'; + messageEl.style.border = '1px solid #bee5eb'; + } +} + +function closeLicenseModal() { + const modal = document.getElementById('license-modal'); + if (modal) { + modal.remove(); + } +} + +function getHostname() { + return new Promise((resolve) => { + fetch('/en-US/splunkd/__raw/services/server/info?output_mode=json') + .then(r => r.json()) + .then(d => { + const hostname = d.entry?.[0]?.content?.host || 'unknown'; + resolve(hostname); + }) + .catch(() => resolve('unknown')); + }); +} + +function checkLicenseBeforePush() { + if (!currentLicenseStatus || !currentLicenseStatus.licensed) { + alert('⚠️ Aucune licence valide détectée. Veuillez installer une licence.'); + showLicenseUploadModal([]); + return false; + } + + return true; +} + +// Initialiser au chargement de la page +document.addEventListener('DOMContentLoaded', function() { + initializeLicenseSystem(); +}); diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_validation.js b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_validation.js new file mode 100644 index 00000000..3be600dc --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_validation.js @@ -0,0 +1,1291 @@ +// ============================================ +// GIT PUSHER - LICENSE VALIDATION (RSA) +// Version 2.1 - 100% Client-Side Validation +// ============================================ + +// Configuration par défaut +const DEFAULT_APP_CONFIG = { + api: { + url: '', + port: 9999, + useProxy: true + } +}; + +// Charger la configuration +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; +} + +// Déterminer l'URL du serveur API +function getLicenseServerUrl() { + const config = loadAppConfigForLicense(); + const hostname = window.location.hostname; + const protocol = window.location.protocol; + + // Si une URL est configurée, l'utiliser + if (config.api && config.api.url) { + let url = config.api.url; + // Ajouter le port si pas de proxy + if (!config.api.useProxy && config.api.port) { + url = url.replace(/\/$/, '') + ':' + config.api.port; + } + return url; + } + + // Auto-détection basée sur le hostname + 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'; +} + +// Configuration +const LICENSE_CONFIG = { + storageKey: 'git_pusher_license', + usageKey: 'git_pusher_usage', + version: '2.1.0', + serverUrl: getLicenseServerUrl() +}; + +// ============================================ +// CLÉ PUBLIQUE RSA +// ============================================ +// Cette clé est générée par le vendeur avec license_generator_rsa.py +// Commande: python3 license_generator_rsa.py export-key + +const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7H8v243PKpy4ZcGUI3YX +EiAZaK+VwWSDywCwBOMOJBA5slWorP78cME0bIphrNRvTlA9xpuo0a+8V+VFMb3+ +Uw9AhDtuJKRIEgwJixm/mkKcbjwqSHPmnyBBHPBBX7lO/q2wsEX0y3O/NujByqc3 +dVsB0VVhMFJmgyR3dVy6ZQITgEu/NGs9v/jUc5IT1YzVmOCcL8BZrjlGiF0AXeS3 +/U8khq7wEx5OilhXC7i8w6urd9c4Djjg583WsGtDKk0aZ6xvnfYpmgfTzaFIrUkS +afTxbcZ1h0N3lN9MBvaLbgAui5RgdlbJlbGsgl3uAa9R9xZk+rqTh8VBLVq+KW5I +a6aYOVterUf2hz/hUkNjM8Rolv4/3PQX0mGu6fa4fwoxmjlSUEVxVFh7TdCE/WHj +3kAOybZXWJnws/++urqijP5SmYxyCaVlYAoutdWmz1tTrSXOh74qrou2wv3C8Dmo +8ccVznAhdhHcVs7MSl9Qbyw1fsi1117WigUGkPE5Cxjlrl8EcBQg3G5x91ER95JM +O0SjyhDborT+oMq9947ZL35VllzkKbBELbhDnogXmDMrI3Ij1UBmCtSOZzOLhyHD +FmGf5AB1LWbxcgrzOMcTLoAHduaDalZCzmW4WdV4313CqeawEfqJVj8BJ+0VEFdb +RDk4ZzHpOaGAuCJjN3AuxO8CAwEAAQ== +-----END PUBLIC KEY-----`; + +// ============================================ +// UTILITAIRES CRYPTO +// ============================================ + +/** + * Convertir une chaîne PEM en ArrayBuffer + */ +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; +} + +/** + * Importer la clé publique RSA + */ +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; + } +} + +/** + * Vérifier la signature RSA PKCS#1 v1.5 + */ +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; + } +} + +// ============================================ +// FONCTIONS UTILITAIRES +// ============================================ + +/** + * Décoder Base64 + */ +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); + } +} + +/** + * Obtenir le hostname actuel (depuis l'URL) + */ +function getCurrentHostname() { + return window.location.hostname.toLowerCase(); +} + +/** + * Obtenir le vrai hostname Splunk via l'API REST + */ +async function getSplunkHostname() { + try { + // Méthode 1: Via server/info + 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 { + // Méthode 2: Via server/settings + 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); + } + + // Fallback: hostname de l'URL (pas idéal) + console.warn('Impossible de récupérer le hostname Splunk, utilisation URL:', getCurrentHostname()); + return getCurrentHostname(); +} + +/** + * Calculer les jours restants + */ +function daysRemaining(expiryDate) { + const expiry = new Date(expiryDate); + const now = new Date(); + const diff = expiry - now; + return Math.ceil(diff / (1000 * 60 * 60 * 24)); +} + +// ============================================ +// PARSING DE LICENCE +// ============================================ + +/** + * Parser le contenu d'un fichier .lic + */ +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' }; + } + + // Décoder le payload + const payloadJson = base64Decode(payloadB64); + const payload = JSON.parse(payloadJson); + + if (!payload.license || !payload.signature) { + return { error: 'Format de licence invalide' }; + } + + // Décoder les données de licence + 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' }; + } +} + +// ============================================ +// VALIDATION DE LICENCE +// ============================================ + +/** + * Valider une licence complète (signature RSA + hostname + expiration) + */ +async function validateLicense(licenseContent = null) { + try { + // Si pas de contenu fourni, charger depuis le localStorage + 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; + + // DEBUG: Afficher les données + 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); + + // 1. Vérifier la signature RSA + 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'); + + // 2. Vérifier le hostname + const expectedHostname = (licenseData.hostname || '').toLowerCase(); + const currentHostname = await getSplunkHostname(); + + console.log(`Hostname attendu: "${expectedHostname}", actuel: "${currentHostname}"`); + + // Permettre une correspondance partielle ou exacte + if (expectedHostname && expectedHostname !== currentHostname) { + // Vérifier si c'est une correspondance partielle (le hostname peut être un FQDN) + 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)'); + } + + // 3. Vérifier la date d'expiration + 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)`); + } + + // Licence valide ! + 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' + }; + } +} + +/** + * Vérifier si une fonctionnalité est disponible + */ +async function hasFeature(featureName) { + const validation = await validateLicense(); + if (!validation.valid) return false; + return validation.features.includes(featureName); +} + +// ============================================ +// GESTION DU STOCKAGE +// ============================================ + +/** + * Sauvegarder une licence validée (localStorage + serveur) + */ +async function saveLicense(licenseContent) { + try { + // Parser le fichier + const parsed = parseLicenseFile(licenseContent); + + if (parsed.error) { + return { + success: false, + error: parsed.error + }; + } + + // Valider la licence avant de sauvegarder + const validation = await validateLicense(licenseContent); + + if (!validation.valid) { + return { + success: false, + error: validation.error, + error_code: validation.error_code + }; + } + + // 1. Sauvegarder dans localStorage + localStorage.setItem(LICENSE_CONFIG.storageKey, JSON.stringify(parsed)); + console.log('✓ Licence sauvegardée dans localStorage'); + + // 2. Sauvegarder sur le serveur (pour persistance après vidage cache) + 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); + // On continue quand même car localStorage fonctionne + } + } catch (serverError) { + console.warn('⚠ Impossible de sauvegarder sur le serveur:', serverError); + // On continue quand même car localStorage fonctionne + } + + return { + success: true, + license: validation + }; + + } catch (error) { + console.error('Erreur sauvegarde licence:', error); + return { + success: false, + error: error.message + }; + } +} + +/** + * Charger la licence depuis le serveur (si localStorage vide) + */ +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...'); + + // Parser et valider la licence + const parsed = parseLicenseFile(result.content); + + if (parsed.error) { + console.warn('Erreur parsing licence serveur:', parsed.error); + return false; + } + + // Valider la signature + const validation = await validateLicense(result.content); + + if (validation.valid) { + // Sauvegarder dans localStorage pour les prochaines fois + 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; + } +} + +/** + * Supprimer la licence (localStorage + serveur) + */ +async function removeLicense() { + // Supprimer du localStorage + localStorage.removeItem(LICENSE_CONFIG.storageKey); + localStorage.removeItem(LICENSE_CONFIG.usageKey); + + // Supprimer du serveur + 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'); +} + +/** + * Récupérer les infos de licence (sans revalider la signature) + */ +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; + } +} + +// ============================================ +// GESTION DES LIMITES D'UTILISATION +// ============================================ + +/** + * Obtenir les stats d'utilisation + */ +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 + }; +} + +/** + * Incrémenter le compteur d'utilisation + */ +function incrementUsage() { + const stats = getUsageStats(); + const today = new Date().toISOString().split('T')[0]; + + // Reset si nouveau jour + 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; +} + +/** + * Vérifier les limites avant un push + */ +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]; + + // Reset si nouveau jour + 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 + }; +} + +// ============================================ +// INTERFACE UTILISATEUR +// ============================================ + +/** + * Afficher le badge de licence + */ +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 = ` +
+ ${icon} + ${validation.type_name} + ${daysLeft}j +
+ `; + } else { + badgeHtml = ` +
+ 🔐 + Activer +
+ `; + } + + container.innerHTML = badgeHtml; +} + +/** + * Afficher le modal de licence + */ +function showLicenseModal(message = null, errorCode = null) { + // Supprimer l'ancien modal s'il existe + 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 = `
${message}
`; + } + + modal.innerHTML = ` +
+
+

🔐 Activation de Licence

+ +
+ +
+ ${errorMessage} + +
+
📄
+
+ Glissez-déposez votre fichier .lic ici +
ou cliquez pour sélectionner +
+ +
+ +
+ Hostname du serveur: + Chargement... +
Communiquez ce hostname pour obtenir votre licence +
+ +
+
+ + +
+ `; + + document.body.appendChild(modal); + + // Afficher le hostname + getSplunkHostname().then(hostname => { + const hostnameDisplay = document.getElementById('current-hostname-display'); + if (hostnameDisplay) { + hostnameDisplay.textContent = hostname; + } + }); + + // Configurer le drag & drop + setupLicenseUpload(); +} + +/** + * Configurer l'upload de licence + */ +function setupLicenseUpload() { + const dropZone = document.getElementById('license-upload-zone'); + const fileInput = document.getElementById('license-file-input'); + + if (!dropZone || !fileInput) return; + + // Clic pour sélectionner + dropZone.addEventListener('click', () => fileInput.click()); + + // Drag & drop + 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]); + } + }); + + // Sélection de fichier + fileInput.addEventListener('change', (e) => { + if (e.target.files.length > 0) { + handleLicenseFile(e.target.files[0]); + } + }); +} + +/** + * Traiter le fichier de licence uploadé + */ +async function handleLicenseFile(file) { + const resultDiv = document.getElementById('license-validation-result'); + if (!resultDiv) return; + + // Vérifier l'extension + if (!file.name.endsWith('.lic')) { + resultDiv.innerHTML = ` +
+ ❌ Le fichier doit avoir l'extension .lic +
+ `; + return; + } + + resultDiv.innerHTML = ` +
+ ⏳ Validation en cours... +
+ `; + + try { + // Lire le fichier + const content = await file.text(); + + // Sauvegarder et valider + const result = await saveLicense(content); + + if (result.success) { + const license = result.license; + resultDiv.innerHTML = ` +
+
+
+ Licence activée avec succès !
+ Type: ${license.type_name}
+ Expire: ${license.expires} (${license.days_remaining} jours)
+ Client: ${license.customer?.name || 'N/A'} +
+
+ `; + + // Mettre à jour le badge + updateLicenseBadge(); + + // Fermer le modal après 3 secondes + setTimeout(() => { + closeLicenseModal(); + }, 3000); + + } else { + resultDiv.innerHTML = ` +
+ ❌ ${result.error} +
+ `; + } + + } catch (error) { + resultDiv.innerHTML = ` +
+ ❌ Erreur: ${error.message} +
+ `; + } +} + +/** + * Fermer le modal de licence + */ +function closeLicenseModal() { + const modal = document.getElementById('license-modal'); + if (modal) modal.remove(); +} + +/** + * Afficher les détails de la licence + */ +async function showLicenseDetails() { + const validation = await validateLicense(); + + if (!validation.valid) { + showLicenseModal(validation.error, validation.error_code); + return; + } + + // Supprimer l'ancien modal s'il existe + 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 => `${f}`).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 = ` +
+
+

📋 Détails de la Licence

+ +
+ +
+ + + + + + + + + + + +
ID${validation.license_id}
Type${validation.type_name}
Client${validation.customer?.name || 'N/A'}
Email${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}
+ +
+ Fonctionnalités:
+
${features}
+
+ +
+ Utilisation:
+ Total pushes: ${usage.total_pushes} | Aujourd'hui: ${usage.pushes_today} +
+
+ + +
+ `; + + document.body.appendChild(modal); +} + +/** + * Confirmer la suppression de licence + */ +function confirmRemoveLicense() { + if (confirm('Êtes-vous sûr de vouloir supprimer cette licence ?')) { + removeLicense(); + + // Fermer le modal des détails + const detailsModal = document.getElementById('license-details-modal'); + if (detailsModal) detailsModal.remove(); + + // Mettre à jour le badge + updateLicenseBadge(); + + alert('Licence supprimée.'); + } +} + +/** + * Vérifier la licence avant un push (appelé par git_pusher.js) + */ +async function checkLicenseBeforePush() { + const result = await checkLimits(); + + if (!result.allowed) { + showLicenseModal(result.error, result.error_code); + return false; + } + + return true; +} + +// ============================================ +// STYLES CSS +// ============================================ + +const licenseStyles = ` + +`; + +// ============================================ +// INITIALISATION +// ============================================ + +/** + * Initialiser le système de licence + */ +async function initializeLicense() { + console.log('Git Pusher License System v' + LICENSE_CONFIG.version + ' (RSA)'); + + // Injecter les styles + if (!document.getElementById('license-styles')) { + const styleEl = document.createElement('div'); + styleEl.id = 'license-styles'; + styleEl.innerHTML = licenseStyles; + document.head.appendChild(styleEl); + } + + // Vérifier si une licence existe dans localStorage + const stored = localStorage.getItem(LICENSE_CONFIG.storageKey); + + if (!stored) { + console.log('Aucune licence en cache, tentative de chargement depuis le serveur...'); + + // Essayer de charger 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'); + } + + // Mettre à jour le badge + updateLicenseBadge(); +} + +// Exposer les fonctions globalement +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; + +// Auto-initialiser après le chargement +if (document.readyState === 'complete') { + setTimeout(initializeLicense, 100); +} else { + window.addEventListener('load', function() { + setTimeout(initializeLicense, 100); + }); +} \ No newline at end of file diff --git a/apps/pusher_app_prem/appserver/static/license_validation.js_old b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_validation.js_old similarity index 100% rename from apps/pusher_app_prem/appserver/static/license_validation.js_old rename to apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_validation.js_old diff --git a/apps/pusher_app_prem/appserver/static/license_validation.js_old2 b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_validation.js_old2 similarity index 100% rename from apps/pusher_app_prem/appserver/static/license_validation.js_old2 rename to apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_validation.js_old2 diff --git a/apps/pusher_app_prem/appserver/static/license_validation.js_old3 b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_validation.js_old3 similarity index 100% rename from apps/pusher_app_prem/appserver/static/license_validation.js_old3 rename to apps/Version git_pusheravant_nettoyage/pusher_app_prem/appserver/static/license_validation.js_old3 diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/README b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/README new file mode 100644 index 00000000..9a70db09 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/README @@ -0,0 +1 @@ +This is where you put any scripts you want to add to this app. diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/__pycache__/license_validator.cpython-39.pyc b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/__pycache__/license_validator.cpython-39.pyc new file mode 100644 index 00000000..759b4da7 Binary files /dev/null and b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/__pycache__/license_validator.cpython-39.pyc differ diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/credentials_manager.py b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/credentials_manager.py new file mode 100755 index 00000000..99929aba --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/credentials_manager.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Git Pusher - Credentials Manager +Gère les credentials de manière sécurisée via Splunk storage/passwords +ou via un fichier chiffré local. +""" + +import os +import sys +import json +import base64 +import hashlib +from pathlib import Path + +# Chemin vers le fichier de credentials chiffré +CREDENTIALS_FILE = "/opt/splunk/etc/apps/pusher_app_prem/local/.credentials" +ENCRYPTION_KEY_FILE = "/opt/splunk/etc/apps/pusher_app_prem/local/.key" + + +def get_machine_id(): + """Obtenir un identifiant unique de la machine pour le chiffrement""" + machine_id = "" + + # Essayer différentes sources + try: + with open('/etc/machine-id', 'r') as f: + machine_id = f.read().strip() + except: + pass + + if not machine_id: + try: + import socket + machine_id = socket.gethostname() + except: + machine_id = "default_key" + + return machine_id + + +def derive_key(password, salt=None): + """Dériver une clé de chiffrement à partir d'un mot de passe""" + if salt is None: + salt = get_machine_id().encode() + + # Utiliser PBKDF2-like avec SHA256 + key = hashlib.pbkdf2_hmac( + 'sha256', + password.encode(), + salt, + 100000 + ) + return base64.b64encode(key).decode() + + +def simple_encrypt(data, key): + """Chiffrement simple XOR (pour une sécurité basique)""" + # Pour une vraie sécurité, utiliser cryptography.fernet + key_bytes = key.encode() * (len(data) // len(key) + 1) + encrypted = bytes([a ^ b for a, b in zip(data.encode(), key_bytes[:len(data)])]) + return base64.b64encode(encrypted).decode() + + +def simple_decrypt(encrypted_data, key): + """Déchiffrement simple XOR""" + try: + data = base64.b64decode(encrypted_data) + key_bytes = key.encode() * (len(data) // len(key) + 1) + decrypted = bytes([a ^ b for a, b in zip(data, key_bytes[:len(data)])]) + return decrypted.decode() + except Exception as e: + return None + + +def get_encryption_key(): + """Obtenir ou créer la clé de chiffrement""" + if os.path.exists(ENCRYPTION_KEY_FILE): + with open(ENCRYPTION_KEY_FILE, 'r') as f: + return f.read().strip() + else: + # Générer une nouvelle clé basée sur la machine + key = derive_key(get_machine_id()) + + # Sauvegarder la clé + os.makedirs(os.path.dirname(ENCRYPTION_KEY_FILE), exist_ok=True) + with open(ENCRYPTION_KEY_FILE, 'w') as f: + f.write(key) + + # Protéger le fichier + os.chmod(ENCRYPTION_KEY_FILE, 0o600) + + return key + + +def save_credentials(username, password): + """Sauvegarder les credentials de manière chiffrée""" + key = get_encryption_key() + + credentials = { + 'username': username, + 'password': simple_encrypt(password, key) + } + + os.makedirs(os.path.dirname(CREDENTIALS_FILE), exist_ok=True) + + with open(CREDENTIALS_FILE, 'w') as f: + json.dump(credentials, f) + + # Protéger le fichier + os.chmod(CREDENTIALS_FILE, 0o600) + + print(f"✓ Credentials saved securely to {CREDENTIALS_FILE}") + + +def load_credentials(): + """Charger les credentials chiffrés""" + if not os.path.exists(CREDENTIALS_FILE): + return None, None + + try: + key = get_encryption_key() + + with open(CREDENTIALS_FILE, 'r') as f: + credentials = json.load(f) + + username = credentials.get('username') + encrypted_password = credentials.get('password') + + password = simple_decrypt(encrypted_password, key) + + return username, password + + except Exception as e: + print(f"Error loading credentials: {e}", file=sys.stderr) + return None, None + + +def delete_credentials(): + """Supprimer les credentials stockés""" + if os.path.exists(CREDENTIALS_FILE): + os.remove(CREDENTIALS_FILE) + print("✓ Credentials deleted") + else: + print("No credentials file found") + + +def get_credentials_for_script(): + """ + Fonction utilisée par le script de démarrage pour obtenir les credentials. + Retourne username et password, ou None si non configurés. + """ + username, password = load_credentials() + + if username and password: + return username, password + + # Fallback sur les variables d'environnement + env_user = os.environ.get('SPLUNK_USERNAME') + env_pass = os.environ.get('SPLUNK_PASSWORD') + + if env_user and env_pass: + return env_user, env_pass + + return None, None + + +# ============================================ +# CLI +# ============================================ + +def interactive_setup(): + """Configuration interactive des credentials""" + print("=" * 50) + print("Git Pusher - Credentials Setup") + print("=" * 50) + print() + print("This will securely store your Splunk credentials.") + print(f"Credentials will be encrypted and saved to:") + print(f" {CREDENTIALS_FILE}") + print() + + username = input("Splunk Username [admin]: ").strip() or "admin" + + import getpass + password = getpass.getpass("Splunk Password: ") + + if not password: + print("Error: Password cannot be empty") + return + + # Confirmer + password2 = getpass.getpass("Confirm Password: ") + + if password != password2: + print("Error: Passwords do not match") + return + + save_credentials(username, password) + print() + print("✓ Credentials configured successfully!") + print() + print("You can now start Git Pusher without specifying credentials:") + print(" ./start_git_pusher.sh start") + + +def show_status(): + """Afficher le statut des credentials""" + print("=" * 50) + print("Credentials Status") + print("=" * 50) + + if os.path.exists(CREDENTIALS_FILE): + print(f"✓ Credentials file exists: {CREDENTIALS_FILE}") + + username, password = load_credentials() + if username: + print(f" Username: {username}") + print(f" Password: {'*' * len(password) if password else 'ERROR'}") + else: + print(" Error: Could not decrypt credentials") + else: + print("✗ No credentials file found") + print() + print("Run: python credentials_manager.py setup") + + +if __name__ == '__main__': + if len(sys.argv) < 2: + show_status() + sys.exit(0) + + command = sys.argv[1] + + if command == 'setup': + interactive_setup() + + elif command == 'status': + show_status() + + elif command == 'delete': + delete_credentials() + + elif command == 'get': + # Pour utilisation dans les scripts bash + username, password = get_credentials_for_script() + if username and password: + print(f"{username}") + print(f"{password}") + else: + sys.exit(1) + + elif command == 'export': + # Exporter pour utilisation dans bash + username, password = get_credentials_for_script() + if username and password: + print(f"export SPLUNK_USERNAME='{username}'") + print(f"export SPLUNK_PASSWORD='{password}'") + else: + print("# No credentials found", file=sys.stderr) + sys.exit(1) + + else: + print("Usage: python credentials_manager.py [setup|status|delete|get|export]") + print() + print("Commands:") + print(" setup - Configure credentials interactively") + print(" status - Show credentials status") + print(" delete - Delete stored credentials") + print(" get - Output credentials (for scripts)") + print(" export - Output as bash export commands") \ No newline at end of file diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/git_pusher.pid b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/git_pusher.pid new file mode 100644 index 00000000..36d6e0a2 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/git_pusher.pid @@ -0,0 +1 @@ +2001609 diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/git_pusher.py b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/git_pusher.py new file mode 100755 index 00000000..23aadd1e --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/git_pusher.py @@ -0,0 +1,984 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Git Pusher - Main Server +Serveur HTTP pour pousser les applications Splunk vers Git +et déployer vers le Search Head Cluster via le SH Deployer + +Avec système de licence par fichier .lic +""" + +import sys +import os +import json +import logging +import tempfile +import shutil +import subprocess +import ssl +import urllib.request +import urllib.error +from datetime import datetime +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.parse import parse_qs, urlparse + +# Importer le validateur de licence +# En production, ce fichier sera dans le même dossier +try: + from license_validator import ( + validate_license, + save_license_file, + check_limits, + increment_usage, + get_splunk_hostname, + get_usage_stats, + parse_license_content + ) +except ImportError: + # Fallback pour le développement + print("Warning: license_validator not found, running without license checks") + def validate_license(): return {"valid": True, "type": "dev", "days_remaining": 999} + def save_license_file(c): return {"success": True} + def check_limits(): return {"allowed": True} + def increment_usage(): return {} + def get_splunk_hostname(): return "dev-host" + def get_usage_stats(): return {} + def parse_license_content(c): return {} + +# ============================================ +# CONFIGURATION +# ============================================ + +# Chemins Splunk +SPLUNK_HOME = os.environ.get('SPLUNK_HOME', '/opt/splunk') +APP_HOME = os.path.join(SPLUNK_HOME, 'etc', 'apps', 'pusher_app_prem') +CONFIG_FILE = os.path.join(APP_HOME, 'local', 'config.json') + +# Configuration par défaut +DEFAULT_CONFIG = { + "api": { + "url": "", + "port": 9999, + "useProxy": True + }, + "deployer": { + "enabled": False, + "host": "", + "port": 9998, + "token": "", + "useSSL": True + }, + "license": { + "checkInterval": 24 + }, + "advanced": { + "logLevel": "INFO", + "timeout": 30, + "gitTimeout": 120 + } +} + +def load_config(): + """Charger la configuration depuis le fichier""" + try: + if os.path.exists(CONFIG_FILE): + with open(CONFIG_FILE, 'r') as f: + config = json.load(f) + # Fusionner avec la config par défaut pour les clés manquantes + return {**DEFAULT_CONFIG, **config} + except Exception as e: + logger.error(f"Erreur chargement config: {e}") + return DEFAULT_CONFIG.copy() + +def save_config(config): + """Sauvegarder la configuration dans le fichier""" + try: + local_dir = os.path.join(APP_HOME, 'local') + os.makedirs(local_dir, exist_ok=True) + + with open(CONFIG_FILE, 'w') as f: + json.dump(config, f, indent=2) + + os.chmod(CONFIG_FILE, 0o600) + logger.info(f"Configuration sauvegardée: {CONFIG_FILE}") + return True + except Exception as e: + logger.error(f"Erreur sauvegarde config: {e}") + return False + +# Charger la configuration au démarrage +APP_CONFIG = load_config() + +# Configuration du SH Deployer (depuis la config ou valeurs par défaut) +SH_DEPLOYER_CONFIG = { + "enabled": APP_CONFIG.get("deployer", {}).get("enabled", False), + "host": APP_CONFIG.get("deployer", {}).get("host", ""), + "port": APP_CONFIG.get("deployer", {}).get("port", 9998), + "use_ssl": APP_CONFIG.get("deployer", {}).get("useSSL", True), + "token": APP_CONFIG.get("deployer", {}).get("token", ""), + "timeout": APP_CONFIG.get("advanced", {}).get("timeout", 30) +} + +# Configuration du logging +log_dir = '/opt/splunk/var/log/splunk' +os.makedirs(log_dir, exist_ok=True) + +log_level = getattr(logging, APP_CONFIG.get("advanced", {}).get("logLevel", "INFO"), logging.INFO) + +logging.basicConfig( + level=log_level, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(os.path.join(log_dir, 'git_pusher.log')), + logging.StreamHandler() + ] +) +logger = logging.getLogger('git_pusher') + + +class GitPusherRequestHandler(BaseHTTPRequestHandler): + """Handler pour les requêtes HTTP""" + + def send_cors_headers(self): + """Envoyer les headers CORS complets""" + # Permettre toutes les origines + 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, X-Splunk-Form-Key, X-Auth-Token') + self.send_header('Access-Control-Allow-Credentials', 'true') + self.send_header('Access-Control-Max-Age', '86400') # Cache preflight 24h + + def do_OPTIONS(self): + """Traiter les requêtes OPTIONS (CORS preflight)""" + logger.info(f"OPTIONS request from {self.headers.get('Origin', 'unknown')}") + self.send_response(200) + self.send_cors_headers() + self.end_headers() + # Important: ne rien écrire dans le body pour OPTIONS + return + + def do_GET(self): + """Traiter les requêtes GET""" + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.send_cors_headers() + self.end_headers() + + try: + parsed_url = urlparse(self.path) + path = parsed_url.path + + # ============================================ + # ENDPOINTS LICENCE + # ============================================ + + if path == '/license' or path == '/license/status': + # Récupérer le statut de la licence + 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 + } + self.wfile.write(json.dumps(response).encode()) + + elif path == '/license/hostname': + # Juste le hostname + response = {"hostname": get_splunk_hostname()} + self.wfile.write(json.dumps(response).encode()) + + elif path == '/license/file': + # Charger la licence depuis le fichier sur le serveur + license_file = os.path.join(APP_HOME, 'local', 'license.lic') + + if os.path.exists(license_file): + try: + with open(license_file, 'r') as f: + license_content = f.read() + response = { + "success": True, + "content": license_content + } + except Exception as e: + response = { + "success": False, + "error": f"Erreur lecture fichier: {str(e)}" + } + else: + response = { + "success": False, + "error": "Aucun fichier de licence sur le serveur" + } + + self.wfile.write(json.dumps(response).encode()) + + # ============================================ + # ENDPOINT CONFIGURATION + # ============================================ + + elif path == '/config': + # Retourner la configuration actuelle + config = load_config() + # Masquer le token pour la sécurité + if 'deployer' in config and 'token' in config['deployer']: + config['deployer']['token'] = '***' if config['deployer']['token'] else '' + self.wfile.write(json.dumps(config).encode()) + + elif path == '/health': + # Health check + response = { + "status": "ok", + "service": "git_pusher", + "timestamp": datetime.now().isoformat(), + "sh_deployer": { + "enabled": SH_DEPLOYER_CONFIG.get("enabled", True), + "host": SH_DEPLOYER_CONFIG.get("host"), + "port": SH_DEPLOYER_CONFIG.get("port") + } + } + self.wfile.write(json.dumps(response).encode()) + + # ============================================ + # ENDPOINTS SH DEPLOYER + # ============================================ + + elif path == '/deployer/health': + # Vérifier la santé du SH Deployer + result = call_deployer_agent("/health") + if result.get("success"): + response = { + "status": "ok", + "deployer": result.get("data"), + "config": { + "host": SH_DEPLOYER_CONFIG.get("host"), + "port": SH_DEPLOYER_CONFIG.get("port") + } + } + else: + response = { + "status": "error", + "error": result.get("error"), + "config": { + "host": SH_DEPLOYER_CONFIG.get("host"), + "port": SH_DEPLOYER_CONFIG.get("port") + } + } + self.wfile.write(json.dumps(response).encode()) + + elif path == '/deployer/status': + # Statut du SH Deployer + result = get_deployer_status() + self.wfile.write(json.dumps(result).encode()) + + elif path == '/deployer/config': + # Configuration actuelle du SH Deployer + response = { + "enabled": SH_DEPLOYER_CONFIG.get("enabled", True), + "host": SH_DEPLOYER_CONFIG.get("host"), + "port": SH_DEPLOYER_CONFIG.get("port"), + "use_ssl": SH_DEPLOYER_CONFIG.get("use_ssl", True) + } + self.wfile.write(json.dumps(response).encode()) + + else: + response = {"error": "Unknown endpoint", "path": path} + 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): + """Traiter les requêtes POST""" + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.send_cors_headers() + self.end_headers() + + try: + parsed_url = urlparse(self.path) + path = parsed_url.path + query_params = parse_qs(parsed_url.query) + + logger.info(f"POST request to {path}") + + # ============================================ + # ENDPOINT CONFIGURATION + # ============================================ + + if path == '/config': + # Sauvegarder la configuration + content_length = int(self.headers.get('Content-Length', 0)) + body = self.rfile.read(content_length).decode('utf-8') + + try: + new_config = json.loads(body) + + # Charger la config existante pour préserver le token si masqué + existing_config = load_config() + + # Si le token est masqué (***), garder l'ancien + if new_config.get('deployer', {}).get('token') == '***': + new_config['deployer']['token'] = existing_config.get('deployer', {}).get('token', '') + + # Sauvegarder + if save_config(new_config): + # Recharger la config globale + global APP_CONFIG, SH_DEPLOYER_CONFIG + APP_CONFIG = load_config() + SH_DEPLOYER_CONFIG = { + "enabled": APP_CONFIG.get("deployer", {}).get("enabled", False), + "host": APP_CONFIG.get("deployer", {}).get("host", ""), + "port": APP_CONFIG.get("deployer", {}).get("port", 9998), + "use_ssl": APP_CONFIG.get("deployer", {}).get("useSSL", True), + "token": APP_CONFIG.get("deployer", {}).get("token", ""), + "timeout": APP_CONFIG.get("advanced", {}).get("timeout", 30) + } + + response = {"success": True, "message": "Configuration sauvegardée"} + else: + response = {"success": False, "error": "Erreur lors de la sauvegarde"} + + except json.JSONDecodeError as e: + response = {"success": False, "error": f"JSON invalide: {str(e)}"} + except Exception as e: + logger.error(f"Erreur sauvegarde config: {e}") + response = {"success": False, "error": str(e)} + + self.wfile.write(json.dumps(response).encode()) + return + + # ============================================ + # ENDPOINTS LICENCE + # ============================================ + + elif path == '/license/upload' or path == '/license/save': + # Sauvegarder la licence sur le serveur (fichier) + # La validation RSA est faite côté client + content_length = int(self.headers.get('Content-Length', 0)) + body = self.rfile.read(content_length).decode('utf-8') + + try: + data = json.loads(body) + license_content = data.get('license_content', '') + except: + license_content = body + + if not license_content: + response = {"success": False, "error": "Contenu de licence vide"} + else: + # Sauvegarder le fichier de licence + try: + local_dir = os.path.join(APP_HOME, 'local') + os.makedirs(local_dir, exist_ok=True) + + license_path = os.path.join(local_dir, 'license.lic') + + with open(license_path, 'w') as f: + f.write(license_content) + + os.chmod(license_path, 0o600) + + logger.info(f"Licence sauvegardée: {license_path}") + response = { + "success": True, + "message": "Licence sauvegardée sur le serveur", + "path": license_path + } + except Exception as e: + logger.error(f"Erreur sauvegarde licence: {e}") + response = { + "success": False, + "error": f"Erreur sauvegarde: {str(e)}" + } + + self.wfile.write(json.dumps(response).encode()) + return + + elif path == '/license/delete': + # Supprimer la licence + license_path = os.path.join(APP_HOME, 'local', 'license.lic') + if os.path.exists(license_path): + os.remove(license_path) + logger.info(f"Licence supprimée: {license_path}") + response = {"success": True, "message": "Licence supprimée"} + else: + response = {"success": False, "error": "Aucune licence à supprimer"} + + self.wfile.write(json.dumps(response).encode()) + return + + # ============================================ + # ENDPOINT PUSH GIT + # ============================================ + + elif path == '/push' or path.startswith('/services/'): + # NOTE: La vérification de licence est maintenant faite côté client (JavaScript) + # avec validation RSA. Le serveur fait confiance au client. + # Si vous voulez réactiver la vérification serveur, décommentez le bloc ci-dessous: + # + # license_check = check_limits() + # if not license_check.get("allowed"): + # response = { + # "status": "error", + # "error_code": "LICENSE_ERROR", + # "message": license_check.get("error", "Licence invalide ou limite atteinte") + # } + # self.wfile.write(json.dumps(response).encode()) + # return + + # Traiter le push Git + self.handle_git_push(query_params) + return + + else: + # Traiter comme un push Git (compatibilité) + self.handle_git_push(query_params) + + except Exception as e: + logger.error(f"POST error: {str(e)}", exc_info=True) + response = { + "status": "error", + "message": f"Error: {str(e)}" + } + self.wfile.write(json.dumps(response).encode()) + + def handle_git_push(self, query_params): + """Gérer le push Git et optionnellement le déploiement vers SH Cluster""" + try: + # Extraire les paramètres + git_url = query_params.get('git_url', [''])[0] + git_branch = query_params.get('git_branch', ['main'])[0] + git_token = query_params.get('git_token', [''])[0] + commit_message = query_params.get('commit_message', [''])[0] + apps_json = query_params.get('apps', query_params.get('dashboards', ['[]']))[0] + shcluster_apps_json = query_params.get('shcluster_apps', ['[]'])[0] # Apps pour le SH Cluster + user = query_params.get('user', ['unknown'])[0] + + # Paramètres pour le déploiement SH Cluster + deploy_to_shcluster = query_params.get('deploy_to_shcluster', ['false'])[0].lower() == 'true' + deployer_host = query_params.get('deployer_host', [SH_DEPLOYER_CONFIG.get('host', '')])[0] + deployer_token = query_params.get('deployer_token', [SH_DEPLOYER_CONFIG.get('token', '')])[0] + sh_auth_user = query_params.get('sh_auth_user', [''])[0] + sh_auth_pass = query_params.get('sh_auth_pass', [''])[0] + + # Paramètres de licence (envoyés par le client) + license_type = query_params.get('license_type', [''])[0] + license_id = query_params.get('license_id', [''])[0] + + logger.info(f"Parameters: git_url={git_url}, branch={git_branch}, user={user}, deploy_to_shcluster={deploy_to_shcluster}") + + # Parser les apps pour Git + try: + apps = json.loads(apps_json) if isinstance(apps_json, str) else apps_json + except (json.JSONDecodeError, TypeError) as e: + logger.error(f"JSON parse error for apps: {e}") + apps = [] + + # Parser les apps pour SH Cluster + try: + shcluster_apps = json.loads(shcluster_apps_json) if isinstance(shcluster_apps_json, str) else shcluster_apps_json + except (json.JSONDecodeError, TypeError) as e: + logger.error(f"JSON parse error for shcluster_apps: {e}") + shcluster_apps = apps # Fallback: utiliser toutes les apps + + logger.info(f"Apps for Git: {len(apps)}, Apps for SH Cluster: {len(shcluster_apps)}") + + # NOTE: La vérification des limites est maintenant faite côté client + # Le serveur fait confiance aux informations envoyées par le client + + # Valider les paramètres + if not git_url or not git_token or not commit_message or not apps: + response = { + "status": "error", + "message": "Missing required parameters" + } + self.wfile.write(json.dumps(response).encode()) + return + + # Créer un répertoire temporaire + temp_dir = tempfile.mkdtemp(prefix='splunk_git_') + logger.info(f"Created temp directory: {temp_dir}") + + try: + # Préparer l'URL Git avec le token + git_url_with_token = self.prepare_git_url(git_url, git_token) + + logger.info("Cloning repository...") + self.clone_repository(temp_dir, git_url_with_token, git_branch) + + # Récupérer les applications + logger.info("Fetching applications from Splunk...") + app_directories = self.fetch_apps_directories(apps) + + # Créer le dossier apps + apps_dir = os.path.join(temp_dir, 'apps') + os.makedirs(apps_dir, exist_ok=True) + + # Copier les applications + logger.info("Copying applications to repository...") + apps_copied = 0 + for app_data in app_directories: + app_name = app_data['name'] + app_path = app_data['path'] + dest_path = os.path.join(apps_dir, app_name) + + if os.path.exists(app_path): + # Supprimer l'ancienne version si elle existe + if os.path.exists(dest_path): + logger.info(f"Removing old version of {app_name}") + shutil.rmtree(dest_path) + + # Copier la nouvelle version + shutil.copytree(app_path, dest_path) + apps_copied += 1 + + # Compter les fichiers copiés + file_count = sum(len(files) for _, _, files in os.walk(dest_path)) + logger.info(f"Copied app: {app_name} ({file_count} files)") + else: + logger.warning(f"App path not found: {app_path}") + + logger.info(f"Total apps copied: {apps_copied}") + + # Configurer git + subprocess.run(['git', 'config', 'user.email', 'splunk@splunk.local'], + cwd=temp_dir, capture_output=True) + subprocess.run(['git', 'config', 'user.name', 'Splunk Git Pusher'], + cwd=temp_dir, capture_output=True) + + # Ajouter TOUS les fichiers (y compris les suppressions) + subprocess.run(['git', 'add', '--all'], cwd=temp_dir, capture_output=True) + + # Afficher le statut Git pour debug + status_check = subprocess.run(['git', 'status', '--short'], + cwd=temp_dir, capture_output=True, text=True) + if status_check.stdout.strip(): + logger.info(f"Git changes detected:\n{status_check.stdout[:500]}") + else: + logger.info("No Git changes detected") + + # Message de commit avec infos de licence (envoyées par le client) + full_message = f"{commit_message}\n\n" + full_message += f"Pushed by: {user}\n" + full_message += f"License: {license_id or 'N/A'} ({license_type or 'N/A'})\n" + full_message += f"Timestamp: {datetime.now().isoformat()}" + + # Vérifier s'il y a des changements à committer + status_result = subprocess.run(['git', 'status', '--porcelain'], + cwd=temp_dir, capture_output=True, text=True) + + if status_result.stdout.strip(): + # Il y a des changements, faire le commit + result = subprocess.run(['git', 'commit', '-m', full_message], + cwd=temp_dir, capture_output=True, text=True) + + if result.returncode != 0: + logger.warning(f"Commit warning: {result.stderr}") + else: + logger.info("Commit created successfully") + else: + logger.info("No changes detected, skipping commit") + + logger.info("Pushing to Git...") + result = subprocess.run(['git', 'push', 'origin', git_branch], + cwd=temp_dir, capture_output=True, text=True, timeout=60) + + if result.returncode != 0: + # Vérifier si c'est juste "Everything up-to-date" + if "Everything up-to-date" in result.stderr or "Everything up-to-date" in result.stdout: + logger.info("Repository is already up-to-date") + else: + raise Exception(f"Push failed: {result.stderr}") + + # NOTE: L'incrémentation des stats est maintenant faite côté client (JavaScript) + + logger.info("Git push successful!") + + # ============================================ + # DÉPLOIEMENT VERS SH CLUSTER (optionnel) + # ============================================ + + deployer_result = None + shcluster_apps_deployed = 0 + + if deploy_to_shcluster: + logger.info("Triggering deployment to SH Cluster...") + + # Extraire les IDs des apps à déployer sur le SH Cluster + shcluster_app_ids = [app.get('id') or app.get('name') for app in shcluster_apps] + logger.info(f"Apps to deploy to SH Cluster: {shcluster_app_ids}") + + shcluster_apps_deployed = len(shcluster_app_ids) + + # Configurer le deployer + deployer_config = SH_DEPLOYER_CONFIG.copy() + if deployer_host: + deployer_config["host"] = deployer_host + if deployer_token: + deployer_config["token"] = deployer_token + + # Appeler le SH Deployer pour pull + deploy + # Passer la liste des apps à déployer + deployer_result = trigger_deployer_pull_and_deploy( + git_url=git_url, + git_token=git_token, + auth_user=sh_auth_user if sh_auth_user else None, + auth_pass=sh_auth_pass if sh_auth_pass else None, + config=deployer_config, + apps_to_deploy=shcluster_app_ids # Liste des apps à déployer + ) + + if deployer_result.get("success"): + logger.info(f"SH Cluster deployment triggered successfully for {shcluster_apps_deployed} apps") + else: + logger.error(f"SH Cluster deployment failed: {deployer_result.get('error')}") + + # Préparer la réponse + response = { + "status": "success", + "message": f"Successfully pushed {len(app_directories)} application(s) to Git", + "apps_pushed": len(app_directories), + "license_type": license_type or "N/A" + } + + # Ajouter les infos de déploiement si activé + if deploy_to_shcluster: + response["shcluster_deployment"] = { + "triggered": True, + "apps_count": shcluster_apps_deployed, + "success": deployer_result.get("success", False) if deployer_result else False, + "message": deployer_result.get("data", {}).get("message") if deployer_result and deployer_result.get("success") else deployer_result.get("error") if deployer_result else "Not triggered" + } + + if deployer_result and deployer_result.get("success"): + response["message"] += f" and triggered SH Cluster deployment ({shcluster_apps_deployed} apps)" + else: + response["message"] += " (SH Cluster deployment failed)" + + self.wfile.write(json.dumps(response).encode()) + + finally: + logger.info(f"Cleaning up {temp_dir}") + shutil.rmtree(temp_dir, ignore_errors=True) + + except Exception as e: + logger.error(f"Git push error: {str(e)}", exc_info=True) + response = { + "status": "error", + "message": f"Error: {str(e)}" + } + self.wfile.write(json.dumps(response).encode()) + + def log_message(self, format, *args): + logger.debug(format % args) + + @staticmethod + def prepare_git_url(git_url, token): + """Préparer l'URL Git avec le token""" + if '@' in git_url: + protocol = git_url.split('://')[0] + rest = git_url.split('://', 1)[1] + host_and_path = rest.split('@', 1)[1] if '@' in rest else rest + return f"{protocol}://{token}@{host_and_path}" + + if git_url.startswith('https://') or git_url.startswith('http://'): + protocol = git_url.split('://')[0] + host_and_path = git_url.split('://', 1)[1] + return f"{protocol}://{token}@{host_and_path}" + + return git_url + + @staticmethod + def clone_repository(dest_dir, git_url, branch): + """Cloner le repository""" + try: + cmd = ['git', 'clone', '--depth', '1', '--branch', branch, git_url, dest_dir] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + + if result.returncode != 0: + raise Exception(f"Clone failed: {result.stderr}") + + logger.info("Repository cloned successfully") + except subprocess.TimeoutExpired: + raise Exception("Git clone operation timed out") + except FileNotFoundError: + raise Exception("Git is not installed on this system") + + @staticmethod + def fetch_apps_directories(apps): + """Récupérer les dossiers des applications""" + logger.info(f"Fetching directories for {len(apps)} applications") + + splunk_home = '/opt/splunk' + apps_base_path = os.path.join(splunk_home, 'etc', 'apps') + + app_directories = [] + + for app in apps: + app_id = app.get('id') or app.get('app_id') + app_path = os.path.join(apps_base_path, app_id) + + if os.path.isdir(app_path): + app_directories.append({ + 'name': app_id, + 'path': app_path, + 'size': sum(os.path.getsize(os.path.join(dirpath, filename)) + for dirpath, dirnames, filenames in os.walk(app_path) + for filename in filenames) + }) + logger.info(f"Found app: {app_id}") + else: + logger.warning(f"App directory not found: {app_path}") + + return app_directories + + +# ============================================ +# FONCTIONS SH DEPLOYER +# ============================================ + +def call_deployer_agent(endpoint, method="GET", data=None, config=None): + """ + Appeler l'agent SH Deployer + + Args: + endpoint: Endpoint à appeler (ex: /health, /pull, /deploy) + method: GET ou POST + data: Données à envoyer (dict) + config: Configuration (override SH_DEPLOYER_CONFIG) + + Returns: + dict avec success, data ou error + """ + if config is None: + config = SH_DEPLOYER_CONFIG + + if not config.get("enabled", True): + return {"success": False, "error": "SH Deployer is disabled"} + + host = config.get("host", "10.10.40.14") + port = config.get("port", 9998) + use_ssl = config.get("use_ssl", True) + token = config.get("token", "") + timeout = config.get("timeout", 30) + + protocol = "https" if use_ssl else "http" + + # Si c'est un nom de domaine (commence par une lettre et contient un point) + # Ne pas ajouter le port (le proxy gère) + import re + is_domain = bool(re.match(r'^[a-zA-Z]', host)) and '.' in host and not re.match(r'^(\d{1,3}\.){3}\d{1,3}$', host) + + if is_domain: + url = f"{protocol}://{host}{endpoint}" + else: + url = f"{protocol}://{host}:{port}{endpoint}" + + logger.info(f"Calling SH Deployer: {method} {url}") + + try: + # Créer le contexte SSL (ignorer les certificats auto-signés) + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + # Préparer les données + if data: + json_data = json.dumps(data).encode('utf-8') + else: + json_data = None + + # Créer la requête + req = urllib.request.Request(url, data=json_data, method=method) + req.add_header('Content-Type', 'application/json') + req.add_header('X-Auth-Token', token) + + # Exécuter la requête + with urllib.request.urlopen(req, timeout=timeout, context=ssl_context) as response: + response_data = json.loads(response.read().decode('utf-8')) + logger.info(f"SH Deployer response: {response_data}") + return {"success": True, "data": response_data} + + except urllib.error.HTTPError as e: + error_body = e.read().decode('utf-8') if e.fp else str(e) + logger.error(f"SH Deployer HTTP error {e.code}: {error_body}") + return {"success": False, "error": f"HTTP {e.code}: {error_body}"} + + except urllib.error.URLError as e: + logger.error(f"SH Deployer connection error: {e.reason}") + return {"success": False, "error": f"Connection error: {e.reason}"} + + except Exception as e: + logger.error(f"SH Deployer error: {str(e)}") + return {"success": False, "error": str(e)} + + +def check_deployer_health(config=None): + """Vérifier si l'agent SH Deployer est accessible""" + result = call_deployer_agent("/health", config=config) + return result.get("success", False) + + +def trigger_deployer_pull(git_url, git_token, config=None): + """ + Déclencher un pull sur le SH Deployer + + Args: + git_url: URL du repository Git + git_token: Token Git pour l'authentification + config: Configuration du deployer + """ + data = { + "repo_url": git_url, + "git_token": git_token, + "apps_subdir": "apps" + } + + return call_deployer_agent("/pull", method="POST", data=data, config=config) + + +def trigger_deployer_deploy(target_uri=None, auth_user=None, auth_pass=None, config=None): + """ + Déclencher le déploiement du bundle sur le SH Cluster + + Args: + target_uri: URI du captain du SH Cluster (optionnel) + auth_user: Utilisateur Splunk + auth_pass: Mot de passe Splunk + config: Configuration du deployer + """ + data = {} + if target_uri: + data["target_uri"] = target_uri + if auth_user: + data["auth_user"] = auth_user + if auth_pass: + data["auth_pass"] = auth_pass + + return call_deployer_agent("/deploy", method="POST", data=data, config=config) + + +def trigger_deployer_pull_and_deploy(git_url, git_token, target_uri=None, auth_user=None, auth_pass=None, config=None, apps_to_deploy=None): + """ + Déclencher pull + deploy en une seule opération + + Args: + git_url: URL du repo Git + git_token: Token d'accès Git + target_uri: URI cible pour le déploiement + auth_user: Utilisateur Splunk pour l'authentification + auth_pass: Mot de passe Splunk + config: Configuration du deployer + apps_to_deploy: Liste des IDs d'apps à déployer (si None, toutes les apps) + """ + data = { + "repo_url": git_url, + "git_token": git_token, + "apps_subdir": "apps" + } + if target_uri: + data["target_uri"] = target_uri + if auth_user: + data["auth_user"] = auth_user + if auth_pass: + data["auth_pass"] = auth_pass + if apps_to_deploy: + data["apps_to_deploy"] = apps_to_deploy # Liste des apps à déployer + + return call_deployer_agent("/pull-and-deploy", method="POST", data=data, config=config) + + +def get_deployer_status(config=None): + """Récupérer le statut du SH Deployer""" + return call_deployer_agent("/status", config=config) + + +def start_server(port=9999, use_ssl=True): + """Démarrer le serveur HTTP/HTTPS""" + import ssl + + server = HTTPServer(('0.0.0.0', port), GitPusherRequestHandler) + + ssl_enabled = False + + if use_ssl: + # Chemins possibles pour les certificats (ordre de priorité) + cert_paths = [ + # Certificats dédiés pour Git Pusher (recommandé) + ('/opt/splunk/etc/apps/pusher_app_prem/local/certs/server.crt', + '/opt/splunk/etc/apps/pusher_app_prem/local/certs/server.key'), + # Certificats splunkweb + ('/opt/splunk/etc/auth/splunkweb/cert.pem', + '/opt/splunk/etc/auth/splunkweb/privkey.pem'), + # Autre emplacement splunkweb + ('/opt/splunk/etc/auth/splunkweb/splunkweb.pem', + '/opt/splunk/etc/auth/splunkweb/splunkweb.key'), + ] + + for cert_file, key_file in cert_paths: + logger.info(f"Trying SSL cert: {cert_file}") + if os.path.exists(cert_file) and os.path.exists(key_file): + try: + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + # Charger le certificat et la clé + ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file) + + server.socket = ssl_context.wrap_socket(server.socket, server_side=True) + ssl_enabled = True + logger.info(f"SSL enabled using: {cert_file}") + break + except Exception as e: + logger.warning(f"Could not load SSL cert {cert_file}: {e}") + continue + else: + logger.debug(f"Cert not found: {cert_file} or {key_file}") + + if not ssl_enabled: + logger.error("=" * 60) + logger.error("SSL CERTIFICATES NOT FOUND OR INVALID!") + logger.error("HTTPS requests from browser will fail!") + logger.error("") + logger.error("To fix, run these commands:") + logger.error(" mkdir -p /opt/splunk/etc/apps/pusher_app_prem/local/certs") + logger.error(" openssl req -x509 -newkey rsa:4096 \\") + logger.error(" -keyout /opt/splunk/etc/apps/pusher_app_prem/local/certs/server.key \\") + logger.error(" -out /opt/splunk/etc/apps/pusher_app_prem/local/certs/server.crt \\") + logger.error(" -days 365 -nodes -subj \"/CN=git-pusher\"") + logger.error("=" * 60) + + protocol = "HTTPS" if ssl_enabled else "HTTP" + logger.info(f"Git Pusher server listening on 0.0.0.0:{port} ({protocol})") + + # Afficher le statut de la licence au démarrage + license_status = validate_license() + if license_status.get("valid"): + logger.info(f"License: {license_status.get('type_name')} - {license_status.get('days_remaining')} days remaining") + else: + logger.warning(f"License: {license_status.get('error', 'Invalid')}") + + server.serve_forever() + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(description='Git Pusher Server') + parser.add_argument('--no-ssl', action='store_true', help='Disable SSL/HTTPS') + parser.add_argument('--port', type=int, default=9999, help='Port number (default: 9999)') + args = parser.parse_args() + + port = args.port + use_ssl = not args.no_ssl + + logger.info(f"Starting Git Pusher on port {port} (SSL: {use_ssl})") + start_server(port, use_ssl) \ No newline at end of file diff --git a/apps/pusher_app_prem/bin/git_pusher.py_old b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/git_pusher.py_old similarity index 100% rename from apps/pusher_app_prem/bin/git_pusher.py_old rename to apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/git_pusher.py_old diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/license_endpoints.py b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/license_endpoints.py new file mode 100755 index 00000000..e71d4f8d --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/license_endpoints.py @@ -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 diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/license_validator.py b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/license_validator.py new file mode 100755 index 00000000..8c1ef3ff --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/license_validator.py @@ -0,0 +1,468 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Git Pusher - License Validator (RSA) +Validation des licences avec vérification de signature RSA + +Ce fichier est distribué avec l'application client. +La clé publique peut être visible - seul le vendeur avec la clé privée peut signer. +""" + +import os +import sys +import json +import base64 +import socket +from datetime import datetime + +# ============================================ +# CLÉ PUBLIQUE RSA +# ============================================ +# Cette clé est générée par le vendeur avec license_generator_rsa.py +# Commande: python license_generator_rsa.py export-key +# +# REMPLACEZ CETTE CLÉ PAR VOTRE CLÉ PUBLIQUE ! + +PUBLIC_KEY = '''-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnj2hOg61Q9k9iz4U5F7I +RdaJrpLTG+orz0/Kpbz2HSxbAVXkvL5GvYVfxROjy0UgxOZFycZAaGN2am+5CDHA +D1dTL9KCPhEaPqw4XTFnf6Ur5VG0+SftugTdTcyxRe614Z+i61/2ahk/vKG9D4kB +j4qV4se4lLk993lEaQrOXkXCbZ8royB5MPeOchPxZd7SDzoovEcyUmf2Fa2eYk6U +WmbrymCnJRsfxEVZofQQyp1ILS8KuSxaquXvMWm3cXV2Krs/3E5ax0vBPMrZRL+o +Vn7/dVnzbOlbifeosTYaad1DLd7NEgst3OFUv+dSH5hcCCc36IHMSvxcJ9l7s2kv +KbEFPeh582JNmRoMMNPRbd+/ZVDeJ/oB344+TtB6VeQ2GQyOyoggiLryZujg3WDE +shwLkFwiYGa/zEct0qs2/HBS1FOAqrLiPdQYJTx+RhrXhTni/p3H42L+2xJcbnki +fAgn8ND5k2yQw2gk4AlLqq2y01m0jsHdjaOfhKzzPLyzt1En/KAnCWJSzB+jur4z +fd70R4pLTYfawr2NTvTAhOtiOIjWj380oGmxeKCNJT1P4Dq8Yl+OxKLBy4cnUlgV +sbpKJug7Goth4g7bmCVMhC4bf7JB/iTrrS8DhMaWaZX/FeFloM3yYyo/gzmAY19z +sUdQuBpxCwIf/J7Q4dYsDkMCAwEAAQ== +-----END PUBLIC KEY-----''' + +# ============================================ +# CONFIGURATION +# ============================================ + +# Chemin de l'application +APP_HOME = os.environ.get('SPLUNK_HOME', '/opt/splunk') + '/etc/apps/pusher_app_prem' +LICENSE_FILE = os.path.join(APP_HOME, 'local', 'license.lic') +USAGE_FILE = os.path.join(APP_HOME, 'local', '.usage_stats') + +# ============================================ +# FONCTIONS UTILITAIRES +# ============================================ + +def get_splunk_hostname(): + """Récupérer le hostname du serveur Splunk""" + try: + # Essayer via servername de Splunk + server_conf = os.path.join( + os.environ.get('SPLUNK_HOME', '/opt/splunk'), + 'etc', 'system', 'local', 'server.conf' + ) + if os.path.exists(server_conf): + with open(server_conf, 'r') as f: + for line in f: + if line.strip().startswith('serverName'): + return line.split('=')[1].strip().lower() + except: + pass + + # Fallback sur le hostname système + return socket.gethostname().lower() + + +def load_public_key(): + """Charger la clé publique RSA""" + try: + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.backends import default_backend + + public_key = serialization.load_pem_public_key( + PUBLIC_KEY.encode('utf-8'), + backend=default_backend() + ) + return public_key + except ImportError: + # Fallback si cryptography n'est pas installé + return None + except Exception as e: + print(f"Erreur chargement clé publique: {e}") + return None + + +def verify_rsa_signature(data, signature, public_key): + """Vérifier la signature RSA""" + try: + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import padding + + public_key.verify( + signature, + data, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + return True + except Exception: + return False + + +# ============================================ +# PARSING DU FICHIER .LIC +# ============================================ + +def parse_license_file(filepath=None): + """Parser un fichier de licence .lic""" + if filepath is None: + filepath = LICENSE_FILE + + if not os.path.exists(filepath): + return None + + try: + with open(filepath, 'r') as f: + content = f.read() + return parse_license_content(content) + except Exception as e: + print(f"Erreur lecture licence: {e}") + return None + + +def parse_license_content(content): + """Parser le contenu d'une licence""" + try: + # Extraire le payload (ignorer les lignes de commentaires) + lines = content.strip().split('\n') + payload_b64 = None + + for line in lines: + line = line.strip() + if line and not line.startswith('#'): + payload_b64 = line + break + + if not payload_b64: + return None + + # Décoder le payload + payload = json.loads(base64.b64decode(payload_b64).decode('utf-8')) + + return { + "license_b64": payload.get("license"), + "signature_b64": payload.get("signature"), + "raw_payload": payload_b64 + } + except Exception as e: + print(f"Erreur parsing licence: {e}") + return None + + +# ============================================ +# VALIDATION DE LA LICENCE +# ============================================ + +def validate_license(filepath=None): + """ + Valider une licence + + Returns: + dict avec: + - valid: bool + - error: str (si invalide) + - error_code: str (si invalide) + - license_id, type, type_name, expires, days_remaining, limits, features (si valide) + """ + + # Parser le fichier + parsed = parse_license_file(filepath) + + if not parsed: + return { + "valid": False, + "error": "Fichier de licence non trouvé ou invalide", + "error_code": "NO_LICENSE" + } + + license_b64 = parsed.get("license_b64") + signature_b64 = parsed.get("signature_b64") + + if not license_b64 or not signature_b64: + return { + "valid": False, + "error": "Format de licence invalide", + "error_code": "INVALID_FORMAT" + } + + try: + # Décoder la licence et la signature + license_json = base64.b64decode(license_b64).decode('utf-8') + signature = base64.b64decode(signature_b64) + + # Charger la clé publique + public_key = load_public_key() + + if public_key is None: + # Mode dégradé sans cryptography - vérification basique + print("⚠️ Module cryptography non installé - vérification basique uniquement") + else: + # Vérifier la signature RSA + if not verify_rsa_signature(license_json.encode('utf-8'), signature, public_key): + return { + "valid": False, + "error": "Signature de licence invalide", + "error_code": "INVALID_SIGNATURE" + } + + # Parser les données de licence + license_data = json.loads(license_json) + + # Vérifier le hostname + expected_hostname = license_data.get("hostname", "").lower() + current_hostname = get_splunk_hostname() + + if expected_hostname and expected_hostname != current_hostname: + return { + "valid": False, + "error": f"Licence non valide pour ce serveur. Attendu: {expected_hostname}, Actuel: {current_hostname}", + "error_code": "HOSTNAME_MISMATCH" + } + + # Vérifier la date d'expiration + expires_str = license_data.get("expires", "") + if expires_str: + try: + expires_date = datetime.strptime(expires_str, "%Y-%m-%d") + now = datetime.now() + + if now > expires_date: + return { + "valid": False, + "error": f"Licence expirée le {expires_str}", + "error_code": "LICENSE_EXPIRED" + } + + days_remaining = (expires_date - now).days + except ValueError: + days_remaining = 0 + else: + days_remaining = 999 # Pas d'expiration + + # Licence valide ! + 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", {}), + "hostname": expected_hostname, + "issued": license_data.get("issued"), + "expires": expires_str, + "days_remaining": days_remaining, + "limits": license_data.get("limits", {}), + "features": license_data.get("features", []) + } + + except json.JSONDecodeError: + return { + "valid": False, + "error": "Données de licence corrompues", + "error_code": "CORRUPTED_DATA" + } + except Exception as e: + return { + "valid": False, + "error": f"Erreur de validation: {str(e)}", + "error_code": "VALIDATION_ERROR" + } + + +# ============================================ +# SAUVEGARDE DE LICENCE +# ============================================ + +def save_license_file(content): + """Sauvegarder un fichier de licence uploadé""" + try: + # Créer le dossier local si nécessaire + local_dir = os.path.join(APP_HOME, 'local') + os.makedirs(local_dir, exist_ok=True) + + # Valider le contenu avant de sauvegarder + parsed = parse_license_content(content) + if not parsed: + return { + "success": False, + "error": "Format de licence invalide" + } + + # Valider la licence + # Sauvegarder temporairement pour valider + temp_file = LICENSE_FILE + '.tmp' + with open(temp_file, 'w') as f: + f.write(content) + + validation = validate_license(temp_file) + + if not validation.get("valid"): + os.remove(temp_file) + return { + "success": False, + "error": validation.get("error"), + "error_code": validation.get("error_code") + } + + # Renommer le fichier temporaire + os.rename(temp_file, LICENSE_FILE) + os.chmod(LICENSE_FILE, 0o600) + + return { + "success": True, + "license": validation + } + + except Exception as e: + return { + "success": False, + "error": str(e) + } + + +# ============================================ +# GESTION DES LIMITES D'UTILISATION +# ============================================ + +def get_usage_stats(): + """Récupérer les statistiques d'utilisation""" + try: + if os.path.exists(USAGE_FILE): + with open(USAGE_FILE, 'r') as f: + return json.load(f) + except: + pass + + return { + "total_pushes": 0, + "pushes_today": 0, + "last_push_date": None, + "apps_pushed": [] + } + + +def save_usage_stats(stats): + """Sauvegarder les statistiques d'utilisation""" + try: + local_dir = os.path.join(APP_HOME, 'local') + os.makedirs(local_dir, exist_ok=True) + + with open(USAGE_FILE, 'w') as f: + json.dump(stats, f, indent=2) + os.chmod(USAGE_FILE, 0o600) + except Exception as e: + print(f"Erreur sauvegarde stats: {e}") + + +def increment_usage(): + """Incrémenter le compteur d'utilisation""" + stats = get_usage_stats() + today = datetime.now().strftime("%Y-%m-%d") + + # Reset compteur quotidien si nouveau jour + if stats.get("last_push_date") != today: + stats["pushes_today"] = 0 + stats["last_push_date"] = today + + stats["total_pushes"] = stats.get("total_pushes", 0) + 1 + stats["pushes_today"] = stats.get("pushes_today", 0) + 1 + + save_usage_stats(stats) + return stats + + +def check_limits(): + """Vérifier si les limites de la licence sont respectées""" + license_info = validate_license() + + if not license_info.get("valid"): + return { + "allowed": False, + "error": license_info.get("error"), + "error_code": license_info.get("error_code") + } + + limits = license_info.get("limits", {}) + max_pushes = limits.get("max_pushes_per_day", -1) + + if max_pushes > 0: + 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 + + if stats.get("pushes_today", 0) >= max_pushes: + return { + "allowed": False, + "error": f"Limite quotidienne atteinte ({max_pushes} pushes/jour)", + "error_code": "DAILY_LIMIT_REACHED" + } + + return { + "allowed": True, + "license_type": license_info.get("type_name"), + "remaining_today": max_pushes - get_usage_stats().get("pushes_today", 0) if max_pushes > 0 else -1 + } + + +def has_feature(feature_name): + """Vérifier si une fonctionnalité est disponible dans la licence""" + license_info = validate_license() + + if not license_info.get("valid"): + return False + + features = license_info.get("features", []) + return feature_name in features + + +# ============================================ +# CLI POUR TESTS +# ============================================ + +if __name__ == "__main__": + if len(sys.argv) > 1: + if sys.argv[1] == "status": + result = validate_license() + if result.get("valid"): + print("✅ Licence valide") + print(f" ID: {result.get('license_id')}") + print(f" Type: {result.get('type_name')}") + print(f" Hostname: {result.get('hostname')}") + print(f" Expire: {result.get('expires')} ({result.get('days_remaining')} jours)") + print(f" Features: {', '.join(result.get('features', []))}") + else: + print(f"❌ Licence invalide: {result.get('error')}") + + elif sys.argv[1] == "hostname": + print(f"Hostname: {get_splunk_hostname()}") + + elif sys.argv[1] == "usage": + stats = get_usage_stats() + print(f"Total pushes: {stats.get('total_pushes', 0)}") + print(f"Pushes aujourd'hui: {stats.get('pushes_today', 0)}") + + elif sys.argv[1] == "check": + result = check_limits() + if result.get("allowed"): + print(f"✅ Push autorisé ({result.get('license_type')})") + else: + print(f"❌ Push refusé: {result.get('error')}") + + else: + print("Usage: python license_validator.py [status|hostname|usage|check]") + else: + result = validate_license() + print(json.dumps(result, indent=2)) diff --git a/apps/pusher_app_prem/bin/license_validator.py_old b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/license_validator.py_old similarity index 100% rename from apps/pusher_app_prem/bin/license_validator.py_old rename to apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/license_validator.py_old diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/start_git_pusher.sh b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/start_git_pusher.sh new file mode 100755 index 00000000..5f700770 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/start_git_pusher.sh @@ -0,0 +1,354 @@ +#!/bin/bash +# ============================================ +# Git Pusher - Start Script +# Version 2.0 avec credentials sécurisés +# ============================================ + +# Configuration +SPLUNK_HOME=${SPLUNK_HOME:-/opt/splunk} +APP_NAME="pusher_app_prem" +APP_HOME="${SPLUNK_HOME}/etc/apps/${APP_NAME}" +BIN_DIR="${APP_HOME}/bin" +LOG_DIR="${SPLUNK_HOME}/var/log/splunk" +PID_FILE="${BIN_DIR}/git_pusher.pid" +CREDENTIALS_MANAGER="${BIN_DIR}/credentials_manager.py" + +# Couleurs pour les logs +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +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" +} + +log_debug() { + echo -e "${BLUE}[DEBUG]${NC} $1" +} + +# Charger les credentials de manière sécurisée +load_credentials() { + # Méthode 1: Utiliser le credentials manager (recommandé) + if [ -f "$CREDENTIALS_MANAGER" ]; then + CREDS=$(python3 "$CREDENTIALS_MANAGER" get 2>/dev/null) + if [ $? -eq 0 ]; then + export SPLUNK_USERNAME=$(echo "$CREDS" | head -1) + export SPLUNK_PASSWORD=$(echo "$CREDS" | tail -1) + log_info "Credentials loaded from secure storage" + return 0 + fi + fi + + # Méthode 2: Variables d'environnement déjà définies + if [ -n "$SPLUNK_USERNAME" ] && [ -n "$SPLUNK_PASSWORD" ]; then + log_info "Using credentials from environment variables" + return 0 + fi + + # Méthode 3: Fichier .env (moins sécurisé mais pratique) + ENV_FILE="${APP_HOME}/local/.env" + if [ -f "$ENV_FILE" ]; then + source "$ENV_FILE" + if [ -n "$SPLUNK_USERNAME" ] && [ -n "$SPLUNK_PASSWORD" ]; then + log_warn "Using credentials from .env file (consider using 'credentials setup' for better security)" + return 0 + fi + fi + + # Aucune credential trouvée + log_error "No credentials found!" + log_error "Please run: $0 credentials setup" + return 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 +} + +# Configurer les credentials +setup_credentials() { + if [ -f "$CREDENTIALS_MANAGER" ]; then + python3 "$CREDENTIALS_MANAGER" setup + else + log_error "Credentials manager not found at $CREDENTIALS_MANAGER" + exit 1 + fi +} + +# Afficher le statut des credentials +credentials_status() { + if [ -f "$CREDENTIALS_MANAGER" ]; then + python3 "$CREDENTIALS_MANAGER" status + else + log_error "Credentials manager not found" + fi +} + +# Supprimer les credentials +delete_credentials() { + if [ -f "$CREDENTIALS_MANAGER" ]; then + python3 "$CREDENTIALS_MANAGER" delete + else + log_error "Credentials manager not found" + fi +} + +# 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 + + # Charger les credentials + if ! load_credentials; then + exit 1 + fi + + # Créer le répertoire de logs + mkdir -p "$LOG_DIR" + + # 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 - activation required" + 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 "Server Status: ${GREEN}RUNNING${NC}" + echo "PID: $PID" + echo "Port: 9999" + else + echo -e "Server 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 des credentials + echo "Credentials:" + if [ -f "${APP_HOME}/local/.credentials" ]; then + echo -e " Secure storage: ${GREEN}Configured${NC}" + else + echo -e " Secure storage: ${YELLOW}Not configured${NC}" + fi + + 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') +try: + 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')}\") +except Exception as e: + print(f' Unable to read license: {e}') +" 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 v2.0" + echo "" + echo "Usage: $0 {command} [options]" + echo "" + echo "Server 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 [-f] Show recent logs (-f for follow)" + echo "" + echo "Credentials Commands:" + echo " credentials setup Configure Splunk credentials securely" + echo " credentials status Show credentials status" + echo " credentials delete Delete stored credentials" + echo "" + echo "Other:" + echo " help Show this help message" + echo "" + echo "Examples:" + echo " $0 credentials setup # First time setup" + echo " $0 start # Start server" + echo " $0 logs -f # Follow logs" +} + +# Main +case "$1" in + start) + start_server + ;; + stop) + stop_server + ;; + restart) + restart_server + ;; + status) + show_status + ;; + logs) + show_logs "$2" + ;; + credentials) + case "$2" in + setup) + setup_credentials + ;; + status) + credentials_status + ;; + delete) + delete_credentials + ;; + *) + echo "Usage: $0 credentials {setup|status|delete}" + ;; + esac + ;; + help|--help|-h) + show_help + ;; + *) + if [ -z "$1" ]; then + show_help + else + echo "Unknown command: $1" + echo "" + show_help + exit 1 + fi + ;; +esac \ No newline at end of file diff --git a/apps/pusher_app_prem/bin/start_git_pusher.sh.V1 b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/start_git_pusher.sh.V1 similarity index 100% rename from apps/pusher_app_prem/bin/start_git_pusher.sh.V1 rename to apps/Version git_pusheravant_nettoyage/pusher_app_prem/bin/start_git_pusher.sh.V1 diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/certs/server.crt b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/certs/server.crt new file mode 100644 index 00000000..11fa39a7 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/certs/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUCuKo8SLloS5cjBOR04+X6ayZ40cwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDEyMzIyMTIxOFoXDTI3MDEy +MzIyMTIxOFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAs0vF6sFTseKgZC1nZ6CVZdw45yk1Ni0W9Mc24KZ9NKCJ +rP0tHy0hs6mME/sq8DV1fh0YtqIvBCxcKEE84/cVXmUfZF9JRXO95734+JGPmo07 +zpiu7p3r4WyIWmCXX5VB0UkMEXsPQmonqG1Kwtz+R1cfgis2lUk+xsC2zSjER8l4 +2UODjHvtD25usgxKjpwPrCuZt43miArnVnwfB8OLbAqpwQeYIf18bPt/TrnQsdgd +ZZiQdE6UTaJ5xhqztwpYJO9pvZA24Bi3bGNfBciITds5RCGY2wQo8yxbeJsidTuW +7Z64DK9t33oVnB2PqlP6hVGD5Agthsv9ehRPxdd3MwIDAQABo1MwUTAdBgNVHQ4E +FgQUy0dni+ogqC7YuvfD/Pn0AuebsXQwHwYDVR0jBBgwFoAUy0dni+ogqC7YuvfD +/Pn0AuebsXQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAlyxg +vR15lsYp4TxJPi1WPzLZl1e6ewTl8GhyE1saxS8LRtyTyr8sa9EFRLQ0OIsqYrUw +zZi7FIDoDPZDKpd0/+U94UKlhUuPUyufQwl5vNu0A+SEpwKeznUMaj4Y98tHvVGd +1SCndZBWn/v2U4nXqHoTd6Y0xEOga0jUEsUMBckNC236BTo88Zk65/oa9Gncyb27 +9vGVCbmPyzE70H4KFoVtxkoZrKywn+0ajHhgH5gqZNRPWpe6i8xTbMAeIXkCjmWL +LmOA7MkjeQBBEWewu4vMOXsvf+gCtxUj5owsAcOQlZ3g72Sng4MeMjuVx4ZRVxX9 +fj+vCP9EFI8rX48tjQ== +-----END CERTIFICATE----- diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/certs/server.key b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/certs/server.key new file mode 100644 index 00000000..8b790585 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/certs/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCzS8XqwVOx4qBk +LWdnoJVl3DjnKTU2LRb0xzbgpn00oIms/S0fLSGzqYwT+yrwNXV+HRi2oi8ELFwo +QTzj9xVeZR9kX0lFc73nvfj4kY+ajTvOmK7unevhbIhaYJdflUHRSQwRew9Caieo +bUrC3P5HVx+CKzaVST7GwLbNKMRHyXjZQ4OMe+0Pbm6yDEqOnA+sK5m3jeaICudW +fB8Hw4tsCqnBB5gh/Xxs+39OudCx2B1lmJB0TpRNonnGGrO3Clgk72m9kDbgGLds +Y18FyIhN2zlEIZjbBCjzLFt4myJ1O5btnrgMr23fehWcHY+qU/qFUYPkCC2Gy/16 +FE/F13czAgMBAAECggEAMrEMrvej0xpQ4KHZp3nGY3sk9242JjAPWntsb42CvrtY +0XjvJe5bpfEcspWDqVBj/Jj7YL9v7Y0hLRxsu8Mi3oJWoskx7RnxKjES0CxPXpHp +w9p1Mu+hPiWyU2MVySdo6WPuro6NXOiod70WswtKNR9TwDi5gPGpdwYLaOvKusSp +Rncm0m0H3IBhgVA691X0AUIomAW3Wmh+5If1XHfjrNHTB8cjcNf6koPMkCqHCEZ9 +wtINxOJior+gGkjMXaDszqzNlicVBXFEFjaXWcp38xAif1uimpqKsRzZEF6RAUzi +H7cI3aF2dXG3C9l6Byi7OSgd8X4JUnE0dlCpC7qweQKBgQDgvoavo8G0kYruCUIQ +6vcSs1YBByOkl6yZBCZWk10NgRpU1wyu9zmlvEwNVlUfALs5eoLxnhe8Wklq0ckQ +r/Rl+r/lj/MZUFn49TgUCsUOIi/G7nWQG0bPo4bCB2QXsAiKdY+KZeC56620uyom +1VY+nS3y8O4EP0YHX0qHFfmIZwKBgQDMOywO0DSrZMDyvmbwL0ISzHRcNpRn0jk7 +pEtzM/VOx+v0O93E+5OygzmXlBKjF0MwMXBidf8IZu4xO8qWqAM4EP2DD0cpoS1Z +WiHHkc5NZhjgeG6C4XaCXR++7CuY25VKKe01yz/+j51linDD8OAibKUspkjVufEN +R/AT0GFLVQKBgBxMYTEkcXOHD/NA/yyaKVoVcrLWb0p+PqFVwG4OSB03MFWWbmZp +gry3pOvY/wbUVL68CljaCysQQ0ZL/AE55pAgrqD9KyL41xtd5R3A7WcGLvXheLQY +eyYR9RnhTF0fMTQd8WD/yvgeENU86+XP3vgrWmnIpG+sd+jdusifn7fpAn9QkwfO +0FX3SMjW/EegewSWZhOCTgY+77Gk1izuRpGBg16T/QqBrL+Yri0KoGC593OKj/bG +4ca8id9vjSdgSOj8NbfO/TgWNICvv9+T3PKHlsA5z0nKWSloRVVA/ew1YmyD1gbA +MnAM/pwac4QJyf6jljmUZAZYTAPOOZN+PbglAoGBAJ9cOGDgT+BCOoNc0T1GJDAk +xOR8d+tD+j4JH5IVxB51DXjJOZxw9U3XhNH1OcE0x3fRzKJOtlQLxP6fHYVtMVFq +VpeekmTtJ9OfMg68ELOlf7ykA3GhMJ3FarM6e8+X+KliGf6ND4HBMb112FlMgIi6 +yYi7sfSL53Dzp1Q2DxXV +-----END PRIVATE KEY----- diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/default/app.conf b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/default/app.conf new file mode 100644 index 00000000..d2212d20 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/default/app.conf @@ -0,0 +1,16 @@ +# +# Splunk app configuration file +# + +[install] +is_configured = 0 + +[ui] +is_visible = true +label = Pusher Premium + +[launcher] +author = +description = +version = 2.0.0 + diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/default/data/ui/nav/default.xml b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/default/data/ui/nav/default.xml new file mode 100644 index 00000000..b95a0b8e --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/default/data/ui/nav/default.xml @@ -0,0 +1,4 @@ + diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/default/data/ui/views/README b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/default/data/ui/views/README new file mode 100644 index 00000000..6cf74f0b --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/default/data/ui/views/README @@ -0,0 +1 @@ +Add all the views that your app needs in this directory diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/.credentials b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/.credentials new file mode 100644 index 00000000..388185fd --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/.credentials @@ -0,0 +1 @@ +{"username": "admin", "password": "VgNoQiAmDzgUO3NQ"} \ No newline at end of file diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/.key b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/.key new file mode 100644 index 00000000..1da59971 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/.key @@ -0,0 +1 @@ +d0YpjIlHuVRowV+y4yQh+VuqDhq7QikC3qepdfLE9+o= \ No newline at end of file diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/.usage_stats b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/.usage_stats new file mode 100644 index 00000000..e029a484 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/.usage_stats @@ -0,0 +1,6 @@ +{ + "total_pushes": 21, + "pushes_today": 6, + "last_push_date": "2026-02-21", + "apps_pushed": [] +} \ No newline at end of file diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/app.conf b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/app.conf new file mode 100644 index 00000000..c82e287e --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/app.conf @@ -0,0 +1,6 @@ +[ui] + +[launcher] + +[install] +state = enabled diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.crt b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.crt new file mode 100644 index 00000000..a477884e --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.crt @@ -0,0 +1,49 @@ +-----BEGIN CERTIFICATE----- +MIIDxTCCA0ugAwIBAgISBXu55+97HBg/V5v9p1wKdygbMAoGCCqGSM49BAMDMDIx +CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF +ODAeFw0yNjAyMTcxMzU5MDRaFw0yNjA1MTgxMzU5MDNaMCkxJzAlBgNVBAMTHm15 +cHJpdnNwbGRldi5qcC1lbmdpbmVlcmluZy5mcjB2MBAGByqGSM49AgEGBSuBBAAi +A2IABOLi1mN0uYVlBILGpDZDyk1wmGPgXklxm7E2Zn/Nswn54qsHJcSW8gSJDHyE +GbqFcc3wTomzFHa73aPxF+/8bTX281VkKjABfNPFSu83qmVcDZ35VT4bF1zbDOES +280Kq6OCAiswggInMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD +ATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTq3jD3pSEI3y4olRyu1I/aK5sGDDAf +BgNVHSMEGDAWgBSPDROi9i5+0VBsMxg4XVmOI3KRyjAyBggrBgEFBQcBAQQmMCQw +IgYIKwYBBQUHMAKGFmh0dHA6Ly9lOC5pLmxlbmNyLm9yZy8wKQYDVR0RBCIwIIIe +bXlwcml2c3BsZGV2LmpwLWVuZ2luZWVyaW5nLmZyMBMGA1UdIAQMMAowCAYGZ4EM +AQIBMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHA6Ly9lOC5jLmxlbmNyLm9yZy8yOC5j +cmwwggENBgorBgEEAdZ5AgQCBIH+BIH7APkAfwBxfpXzwjiKbbHjhEk9MeFaqWII +di1CAOAFDNBntaZh4gAAAZxsG05YAAgAAAUACa6ZQgQDAEgwRgIhAIichDr9Q2CB +AXzXmEbKjiO9qSAUXE7s8IgBXw8BfjluAiEAyjAyAc2I4hCjco4FLp7+y0nmeElH +yl4BBAaCEsOFK3AAdgAWgy2r8KklDw/wOqVF/8i/yCPQh0v2BCkn+OcfMxP1+gAA +AZxsG1XXAAAEAwBHMEUCIE7uCd4jXwCcTGuqR3ThJm0Zms0U7tAJcuhnR2F0syZJ +AiEAj06LtXnMoYEN6wvfgFUVd4zekUUaNJlJ1TK0HcMMlNQwCgYIKoZIzj0EAwMD +aAAwZQIwZaVb5NEYfUrGirhaFV9eohbjDY7YLzaJ5dIxH+Jt78o+GMfypoFc6Rc1 +S2FPNCJsAjEAphAhfBmVN1ASp3JENib1t12TuEVaTNHdNlzzuFE4s3z+z0cp6je4 +MmgQpu/hgcn3 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEVjCCAj6gAwIBAgIQY5WTY8JOcIJxWRi/w9ftVjANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFy +Y2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNDAzMTMwMDAwMDBa +Fw0yNzAzMTIyMzU5NTlaMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBF +bmNyeXB0MQswCQYDVQQDEwJFODB2MBAGByqGSM49AgEGBSuBBAAiA2IABNFl8l7c +S7QMApzSsvru6WyrOq44ofTUOTIzxULUzDMMNMchIJBwXOhiLxxxs0LXeb5GDcHb +R6EToMffgSZjO9SNHfY9gjMy9vQr5/WWOrQTZxh7az6NSNnq3u2ubT6HTKOB+DCB +9TAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB +MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFI8NE6L2Ln7RUGwzGDhdWY4j +cpHKMB8GA1UdIwQYMBaAFHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEB +BCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzATBgNVHSAE +DDAKMAgGBmeBDAECATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veDEuYy5sZW5j +ci5vcmcvMA0GCSqGSIb3DQEBCwUAA4ICAQBnE0hGINKsCYWi0Xx1ygxD5qihEjZ0 +RI3tTZz1wuATH3ZwYPIp97kWEayanD1j0cDhIYzy4CkDo2jB8D5t0a6zZWzlr98d +AQFNh8uKJkIHdLShy+nUyeZxc5bNeMp1Lu0gSzE4McqfmNMvIpeiwWSYO9w82Ob8 +otvXcO2JUYi3svHIWRm3+707DUbL51XMcY2iZdlCq4Wa9nbuk3WTU4gr6LY8MzVA +aDQG2+4U3eJ6qUF10bBnR1uuVyDYs9RhrwucRVnfuDj29CMLTsplM5f5wSV5hUpm +Uwp/vV7M4w4aGunt74koX71n4EdagCsL/Yk5+mAQU0+tue0JOfAV/R6t1k+Xk9s2 +HMQFeoxppfzAVC04FdG9M+AC2JWxmFSt6BCuh3CEey3fE52Qrj9YM75rtvIjsm/1 +Hl+u//Wqxnu1ZQ4jpa+VpuZiGOlWrqSP9eogdOhCGisnyewWJwRQOqK16wiGyZeR +xs/Bekw65vwSIaVkBruPiTfMOo0Zh4gVa8/qJgMbJbyrwwG97z/PRgmLKCDl8z3d +tA0Z7qq7fta0Gl24uyuB05dqI5J1LvAzKuWdIjT1tP8qCoxSE/xpix8hX2dt3h+/ +jujUgFPFZ0EVZ0xSyBNRF3MboGZnYXFUxpNjTWPKpagDHJQmqrAcDmWJnMsFY3jS +u1igv3OefnWjSQ== +-----END CERTIFICATE----- diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.crt.bak b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.crt.bak new file mode 100644 index 00000000..72166c19 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.crt.bak @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCzCCAvOgAwIBAgIUIkJeLpRn6wfN8VbsMrPdFAl/x7swDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKZ2l0LXB1c2hlcjAeFw0yNjAyMDExOTM1MzNaFw0yNzAy +MDExOTM1MzNaMBUxEzARBgNVBAMMCmdpdC1wdXNoZXIwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDsTH5H+OD27yXIFz4MvNrpmpNgYly6QYUukJO8FI/f +unBe4N5mfOTZ/WrBeR5KoBOS3FV8VWIH7ShXIzWBpollmIz9+jlPyftawXyiSnSX +rwLBdHc8gjVRZf1H2U0E191v9z0oaXApy8d7E0Pw+i4odoGHOX5Ix89s0DJrG5UP +LrLr9OHlyMCC0D2QVp0wqFGkIXXv/cyYwlcGPACvFhE/fWUazC4AEImJhXypOfQZ +h57SoldKQWwW7BZdGmnbSqeG2lq7KFUow0sie4KzRPLPXrIGdKQbPAKDKcQ7MYlG +9bfgsmM1Rr6klRAmO/e4w1HRHSHUetmFBYDJ5MYNdeddYDfIVwjgwfDvy1i+ojRl +/viywW3ONXR9rUcx/nGGc8UJTjJAAaAaTj6UMzn40ltLYmiymhj6mWFNiuGwDxas +YTxA/3i62pGlbC8s1ZSXtdCYsEN4+W3N5CSadHdVtAIIEc6OyQ2mRa3v4oeZlyzt +1mk9j9oChxi5r74ujUmjNHxpawWG5wmnRy3b9ABxcivESEuXIbjBXrQ+RCmuQ8Dp +5Asa7iede7iEPkhGw3Es3uVdk4s3/OlS6o+1F+rG9qZr3aTtJrciVSLofbrSNtFF +MERip+VBwIdBofVzS6GUhnLjjdhfu/kdzsqrTDDFan4NX1IyC+zBL90DPQ8SoIOg +owIDAQABo1MwUTAdBgNVHQ4EFgQUe6KibB+U4yTq9/pwf6C4JeoPQo0wHwYDVR0j +BBgwFoAUe6KibB+U4yTq9/pwf6C4JeoPQo0wDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAgEA12Y7N6iLOtRAt+xD3B5eIdJtPnFclhmy8FCd2+SdnF/K +zWVdzTsWYKzZ4TZ08aMOh2+H2vDf/ZTG+gryCob28QVy0sRxbpA4WJMI6zBfZSKr +Raudh5Gql/CO28gN4k6EGgNAn1S02NkvhZP4FokfXwkY4CIReRECJzws0gCyMZto +wLfYEVyvN9NYQf1xoJHaozGghA0FADQrr6pQsl1Ek4bkrvb8/L8gWeT6lgny0ZcI +1UEW9lkx7pc0dRDOjUjVzlHYzAf53xEslontihQcOoE/Uj3Wsmi1V/lPm+Pi0Ml0 +msMUkvMZP6Y8lnvRT4625YKR6L3ibuQxlXGtKHvu7eJdcOnW5IeVmg5vsH2xoVaF +4L0zBPpA6CBzsfZPgLJ9odD10nRVx8bh0b555/CUP5WIVHvLkPgp+NptJAoKah7Q +PkI5OKZWGjrJGiV7BWx5HIB5TX/w0PCjMA4ce3yVRNs7poUzR4PKgohrp94cIDFa +m+K4rfBmeMwVuuJZIuYpye80dcdsiFPrIZuA+hbP7tGlrwBKKMzpB3BEDIFTPb2b +hUq1TJCcHffFqxpRia+L/MMI8RpaEwzW9hghQB/LiPG0UqJycNpuo8cnggx+bsop +0ZXB68cW6K1UWLo1qLSz7/0+65PrWRkJisbjtmpKRDJlB72yIxlsZmBucxBy3II= +-----END CERTIFICATE----- diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.key b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.key new file mode 100644 index 00000000..a9b00593 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDQfw7eesJcJWzoV0AL +EJtevZRBdfNTKjceiNAfFoc/2bKN5pKU3n17/ZuJGGWFGNOhZANiAATi4tZjdLmF +ZQSCxqQ2Q8pNcJhj4F5JcZuxNmZ/zbMJ+eKrByXElvIEiQx8hBm6hXHN8E6JsxR2 +u92j8Rfv/G019vNVZCowAXzTxUrvN6plXA2d+VU+Gxdc2wzhEtvNCqs= +-----END PRIVATE KEY----- diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.key.bak b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.key.bak new file mode 100644 index 00000000..465e812b --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/certs/server.key.bak @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDsTH5H+OD27yXI +Fz4MvNrpmpNgYly6QYUukJO8FI/funBe4N5mfOTZ/WrBeR5KoBOS3FV8VWIH7ShX +IzWBpollmIz9+jlPyftawXyiSnSXrwLBdHc8gjVRZf1H2U0E191v9z0oaXApy8d7 +E0Pw+i4odoGHOX5Ix89s0DJrG5UPLrLr9OHlyMCC0D2QVp0wqFGkIXXv/cyYwlcG +PACvFhE/fWUazC4AEImJhXypOfQZh57SoldKQWwW7BZdGmnbSqeG2lq7KFUow0si +e4KzRPLPXrIGdKQbPAKDKcQ7MYlG9bfgsmM1Rr6klRAmO/e4w1HRHSHUetmFBYDJ +5MYNdeddYDfIVwjgwfDvy1i+ojRl/viywW3ONXR9rUcx/nGGc8UJTjJAAaAaTj6U +Mzn40ltLYmiymhj6mWFNiuGwDxasYTxA/3i62pGlbC8s1ZSXtdCYsEN4+W3N5CSa +dHdVtAIIEc6OyQ2mRa3v4oeZlyzt1mk9j9oChxi5r74ujUmjNHxpawWG5wmnRy3b +9ABxcivESEuXIbjBXrQ+RCmuQ8Dp5Asa7iede7iEPkhGw3Es3uVdk4s3/OlS6o+1 +F+rG9qZr3aTtJrciVSLofbrSNtFFMERip+VBwIdBofVzS6GUhnLjjdhfu/kdzsqr +TDDFan4NX1IyC+zBL90DPQ8SoIOgowIDAQABAoICABEB7HDns+FyEwkUyy2Fhkgc +DRF54uyw/JH+a/O0kypqM95QVxGPWbVq7P0h55E9dksyuqBpUNX7NtUWvqonP2pl +kXhSQz+/7Ox6Uqsnqr6kJRGhfVeIk6fZLGK4fDemBdUiOW+oLx+DAEeWemRkzV+y +L954v+MjJoXRcl+NK6xdExmylXPBgEGqFVRHN6ch5kZm9iMg5FH2Yuca+H2hm/oy +300PdxwgFJYmWnOfrTcNMNw+PQQmM05vDakD1qym8end23vvCjoV3FmOBDk89DEC +wtN+H7WqGxAvuGT+SsAlvWdZz5QtFFmqNPBbjpfozwG7FA1EDlXpsHxXj/22B8Ho +XAPQ0ZeEJv4ZDbLs7AM/ofpNaitYcE0po2W7dlMqb1so3hK+eCPpVfos8mxlrkPV +sq2nFzVtl7V3MeipGw/MixDZZz6LDX0M1tk60a+4zAbDJaht1dS4SLfhygdQM+/m +OEAWyRAT7TDhCB+F474/G8PM1PnKmps5gV0X5jkqjQBCterBRwVcTI+ewNxUz7+Q +f4iN6sO8+Ihf94sr0YwaaIFyEetxTWBjevqWXpZ7fmdVbAmD5Z7WN8nvuARyXYdd +xOfXYA0mv0gtF1RDFV69lsMD6yPIGYdYIVd50r6P0VPQK+yjDQM3Pn3tVyLBmwqK +smKGCvkpZ92UvTsR3L/BAoIBAQD3w2RgKg3qVUeuJgbFDCRS/AkF1mkI4PMY5WLc +BHGa9q/9LucYTpZ60Pq/t9cNdDrEY/wdGvS23shWsAN3XGnj3m9FCCyuEZojmOkO +kNL51afhJ4h7r0yy4oPuXhQ5V860PuC3xv1QiBpxvKsod+KwiJI7O49dNxCr5dGk +RRdAHJEm73O2EmiTFHOZ3oGPoZ5SGtYxurVd3MNptiuKQYLA4+2i/P439TbCrh7E +0dxW9bZRLTygA77HLMA5suHKR4q6DS0HlpY9Cba2CxxIh/4xJRSQHOgiamNtYLeH +PnrXjyQVblf09j34YMc1K0A4R91CI74BYe5MwCptRSOIer6DAoIBAQD0J4g6F01m +26EqLhszq3n4jVroqmdB+oQ7q+yclrpjjPkwYhKZmXVrA+eNavBuMdLNv+50eqg4 +9RW8H+GBwzhA+CWbZCFJ3Ad3e7jcE6QJVMjNIe6sZ15KF0IvYJA3B1lSsuK1jGga +eOFQZG+nIqpH58iowJt0wUTuiehf2xRaEhVkQn+PvIriPAmTlI3xME7ehctxFm8C +ZqFODfXsh0bLMx/pWVKDKYNh6WOst3k6/CUt4vN/PlPVvnNLMtbFQWyn1p5LhvvZ +xVlFBtobkaGUEjFYoQTwzl3s566f2anMa2Slhz0uopTttkvd3rXyPHm4guNiN/CN +ohW534tkPfthAoIBAFsBIfVQfRv9hv6oaQQnmZABky7ZumrQdXpHhzBZUYEh6zKL +78Y113/1EqUo2YzPjGZmc0wdgpVI7z0oGZ3WC+7u3N/2SLMHNB6vI6t99oBdwfQp +mTAVC48JNHxxgewuHHaIQfI+3PyfgVcVfai3oERHZa7sCZSrjSwWlhJIbmnWFFrA +yTevO0oK0QtLdztSmdx+jv5lHgkD9aL2jreRqH1BOyAK3TWglCSd4B9bFhu61OSs +QQBlX8W44kJPOjAaZxI/lLKc1UJGNx5WpmTdzrgubocglwNNIIgkZkT+5hAXO6HD +jfskF08L/R/CayxA+Tw59Kh9WBJI40yPgKW4sBECggEBAIdk0MeeGn86tnIEpXMO +2ZG7GbnCnYZaHTBWE912PKBuEdYB3Nyu3A1fWe3zaqdBG+ybTensBxOm3cm4SD7E +epKUyY4VhdxGlyFsS8RHZAUErmILOicDH6eopDxPqUnK2n7g0pXo6eYcOJ5zQ/OE +Zrd/Uqg6PzsM3mQFuAZIIE4ejxxNQB3+aWox7wGXNOuWZXZC7eGlliPXtAXr+f+T +uO+AR2cI8Jfp0oDegzbJfAH4x8ldfLiIYMc8WQVPiQhUUqP0gU3S6iEGro130kXN +ibPqLtE+YdYEKtPwWscsVlwVBfhBOe19nWcBW6sLEQzm+n0WoG/cI5r3UmMEE3Gg +aaECggEAJBcLJKp4WEz69qQbvV15HrEgsctIswUoaRsF0r0qnCAJoySG0mX2rGWR +Wmf8I2rw5hARLR5BFa3YtBPj0doL2mSxLOuH5tKdqthMai+B7K2J/YTPh8xOKoQd +2HcAoh1zxT2oA1Fj7BwzpvLTWwJEfsWzcOM+2ApxzLXkmgF90TBHoeCvlbfzdZ99 +Ni/yj7tRpK1LoJYCkUHRspWwHB8DIagohG8tCtu4Bb5NdogtfaNeQ/BrgnZj77kf +FHu59hzsQQzkplCOnwJuT/Y6iqZuuJgPiESprUxHUz6aesySgcL1tuEq1GmB6O6U +eiDRzM4++dQGm7osCy1NRbPGxdUyhw== +-----END PRIVATE KEY----- diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/config.json b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/config.json new file mode 100644 index 00000000..41802687 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/config.json @@ -0,0 +1,22 @@ +{ + "api": { + "url": "https://myprivspldev-api.jp-engineering.fr", + "port": 9999, + "useProxy": true + }, + "deployer": { + "enabled": true, + "host": "myprivspldev-shdp-api.jp-engineering.fr", + "port": 9998, + "token": "bc2564e5a885d49ac3811dc946ca5620da24da19b8a8f5c5fdfcd7c07a241688", + "useSSL": true + }, + "license": { + "checkInterval": 24 + }, + "advanced": { + "logLevel": "INFO", + "timeout": 30, + "gitTimeout": 120 + } +} \ No newline at end of file diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/data/ui/views/git_pusher_-_deploy_applicationsV1.xml b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/data/ui/views/git_pusher_-_deploy_applicationsV1.xml new file mode 100644 index 00000000..b44287f2 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/data/ui/views/git_pusher_-_deploy_applicationsV1.xml @@ -0,0 +1,572 @@ + + + Push Splunk applications to Git repository and deploy to SH Cluster + + + | rest /services/apps/local | search disabled=0 | table title, label, description | rename title as name | sort label + -1m + now + + + + + + + +
+ +
+
+

🚀 Git Pusher

+ v2.2 +
+
+ +
+
+ + +
+ +
+

📦 Applications

+
+ +

Loading applications...

+
+
+ + +
+

⚙️ Git Configuration

+ +
+ + +
HTTPS URL of your Git repository
+
+ +
+ + +
+ +
+ + +
Personal access token with write permissions
+
+ +
+ + +
+ +
+ + +
+ + +
+
+
+ 🎯 Deploy to Search Head Cluster + + ● Checking... + +
+ +
+ +
+ + +
+ +
+
+ Splunk credentials for applying shcluster-bundle (optional if using default) +
+
+ + +
+
+
+ + +
+ + +
+ + +
+
+ Deploying applications to Git... Please wait +
+ +
+
+
+
+
+ + + +
+
+
\ No newline at end of file diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/data/ui/views/git_pusher_-_push_applications_to_git.xml b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/data/ui/views/git_pusher_-_push_applications_to_git.xml new file mode 100644 index 00000000..eca8b17e --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/data/ui/views/git_pusher_-_push_applications_to_git.xml @@ -0,0 +1,256 @@ + + + + Push Splunk applications to Git repository + + + | rest /services/apps/local | search disabled=0 | fields name, label, description | sort label + -4h@h + now + + + + Configuration & Application Selection + + + +
+
+ ℹ️ Configure your Git settings and select the applications you want to push to your repository. +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+ Loading applications... +
+
+ Select one or more applications to push +
+ +
+ + +
+ +
+ + +
+ +
+
+ Pushing dashboards to Git... +
+ +
+ ✓ Dashboards successfully pushed to Git! +
+ +
+ ✗ Error occurred while pushing dashboards +
+
+ + + + + + + Push History + + + index=_internal source=*git_pusher* action=push_attempt | table _time, user, dashboards, commit_message, status, error_msg | reverse | rename _time as "Timestamp", user as "User", dashboards as "Dashboards", commit_message as "Message", status as "Status", error_msg as "Error" | head 20 + -30d@d + now + + + + {"success": "#28a745", "error": "#dc3545", "pending": "#ffc107"} + +
+
+
+ + \ No newline at end of file diff --git a/apps/pusher_app_prem/local/data/ui/views/git_pusher_config.xml b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/data/ui/views/git_pusher_config.xml similarity index 100% rename from apps/pusher_app_prem/local/data/ui/views/git_pusher_config.xml rename to apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/data/ui/views/git_pusher_config.xml diff --git a/apps/pusher_app_prem/local/data/ui/views/git_pusher_dashboard.xml b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/data/ui/views/git_pusher_dashboard.xml similarity index 100% rename from apps/pusher_app_prem/local/data/ui/views/git_pusher_dashboard.xml rename to apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/data/ui/views/git_pusher_dashboard.xml diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/usage_stats.json b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/usage_stats.json new file mode 100644 index 00000000..bcf35e59 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/local/usage_stats.json @@ -0,0 +1 @@ +{"pushes_today": 1, "pushes_total": 47, "last_push_date": "2026-02-14", "apps_pushed": []} \ No newline at end of file diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/metadata/default.meta b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/metadata/default.meta new file mode 100644 index 00000000..b77b8cb9 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/metadata/default.meta @@ -0,0 +1,35 @@ + +# Application-level permissions + +[] +access = read : [ * ], write : [ admin, power ] + +### EVENT TYPES + +[eventtypes] +export = system + + +### PROPS + +[props] +export = system + + +### TRANSFORMS + +[transforms] +export = system + + +### LOOKUPS + +[lookups] +export = system + + +### VIEWSTATES: even normal users should be able to create shared viewstates + +[viewstates] +access = read : [ * ], write : [ * ] +export = system diff --git a/apps/Version git_pusheravant_nettoyage/pusher_app_prem/metadata/local.meta b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/metadata/local.meta new file mode 100644 index 00000000..76aeeb15 --- /dev/null +++ b/apps/Version git_pusheravant_nettoyage/pusher_app_prem/metadata/local.meta @@ -0,0 +1,30 @@ +[app/ui] +version = 10.0.2 +modtime = 1769115948.043388000 + +[app/launcher] +version = 10.0.2 +modtime = 1769115948.046389000 + +[views/git_pusher_-_push_dashboards_to_git] +access = read : [ admin ], write : [ admin ] +export = system +owner = admin +version = 10.0.2 +modtime = 1769276443.812957000 + +[views/git_pusher_-_push_applications_to_git] +owner = admin +version = 10.0.2 +modtime = 1769361925.808816000 + +[views/git_pusher_-_deploy_applications] +access = read : [ * ], write : [ * ] +export = none +owner = admin +version = 10.0.2 +modtime = 1769976365.099941000 + +[app/install/state] +version = 10.0.2 +modtime = 1771702401.801159000 diff --git a/apps/pusher_app_prem/default/app.conf b/apps/pusher_app_prem/default/app.conf index d2212d20..5d7d315c 100644 --- a/apps/pusher_app_prem/default/app.conf +++ b/apps/pusher_app_prem/default/app.conf @@ -10,7 +10,7 @@ is_visible = true label = Pusher Premium [launcher] -author = -description = -version = 2.0.0 +author = Jocelyn PAMPHILE +description = Application Splunk pour déployer vos applications vers Git et le Search Head Cluster +version = 2.1.0 diff --git a/apps/pusher_app_prem/default/data/ui/views/git_pusher_config.xml b/apps/pusher_app_prem/default/data/ui/views/git_pusher_config.xml new file mode 100644 index 00000000..b1fde3b1 --- /dev/null +++ b/apps/pusher_app_prem/default/data/ui/views/git_pusher_config.xml @@ -0,0 +1,311 @@ + + + Configuration de l'application Git Pusher + + + + + + +
+
+

⚙️ Configuration Git Pusher

+

Configurez les paramètres de l'application

+
+ +
+ + +
+

🌐 Configuration API

+
+ + + L'URL du serveur Git Pusher (sans le port si proxy) + + + + Port utilisé si accès direct par IP (ignoré si URL de domaine) + + + + Coché si vous utilisez un reverse proxy (Nginx, etc.) + + +
+ + ● Non testé +
+
+
+ + +
+

🚀 Configuration SH Deployer

+
+ + + Activer le déploiement automatique vers le Search Head Cluster + + + + Adresse IP ou hostname du serveur SH Deployer + + + + + + + Token configuré dans deployer_agent.py + + + + + +
+ + ● Non testé +
+
+
+ + +
+

🔐 Configuration Licence

+
+ +
Chargement...
+ + + + Fréquence de revalidation de la licence +
+
+ + +
+

🔧 Paramètres avancés

+
+ + + + + + + + +
+
+ + +
+ + +
+
+ +
Git Pusher v2.1
+ +
+
+
\ No newline at end of file diff --git a/apps/pusher_app_prem/default/data/ui/views/git_pusher_dashboard.xml b/apps/pusher_app_prem/default/data/ui/views/git_pusher_dashboard.xml new file mode 100644 index 00000000..aba9d9e5 --- /dev/null +++ b/apps/pusher_app_prem/default/data/ui/views/git_pusher_dashboard.xml @@ -0,0 +1,661 @@ + + + Push Splunk applications to Git repository and deploy to SH Cluster + + + | rest /services/apps/local | search disabled=0 | table title, label, description | rename title as name | sort label + -1m + now + + + + + + + +
+ +
+
+

🚀 Git Pusher

+ v2.2 +
+
+ +
+
+ + +
+ +
+

📦 Applications

+
+ +

Loading applications...

+
+
+ + +
+

⚙️ Git Configuration

+ +
+ + +
HTTPS URL of your Git repository
+
+ +
+ + +
+ +
+ + +
Personal access token with write permissions
+
+ +
+ + +
+ +
+ + +
+ + +
+
+
+ 🎯 Deploy to Search Head Cluster + + ● Checking... + +
+ +
+ +
+ + +
+ + +
+
+ +
+ + +
+ +
+
+ Splunk credentials for applying shcluster-bundle (optional if using default) +
+
+ + +
+
+
+ + +
+ + +
+ + +
+
+ Deploying applications to Git... Please wait +
+ +
+
+
+
+
+ + + +
+
+