diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..968569a
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,14 @@
+.git
+.gitea
+.venv
+__pycache__
+**/__pycache__
+*.pyc
+docs
+docker-compose*.yaml
+compose*.yaml
+.env
+.env.*
+!.env.example
+README.md
+sortie
diff --git a/.env b/.env
deleted file mode 100644
index 630b323..0000000
--- a/.env
+++ /dev/null
@@ -1 +0,0 @@
-# Template
\ No newline at end of file
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..796adc5
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,20 @@
+# Copier en .env et renseigner. Ce fichier ne doit pas partir dans Git.
+
+# Image et version deployee. C'est la seule ligne a changer pour un retour arriere.
+ENCLUME_REGISTRY=harbor.tips-of-mine.com
+ENCLUME_TAG=1.0.0
+
+# Publication
+ENCLUME_DOMAINE=clapi.tips-of-mine.com
+TRAEFIK_RESOLVER=cloudflare
+TZ=Europe/Paris
+GUNICORN_WORKERS=2
+
+# Catalogue synchronise depuis Git (laisser vide pour n'utiliser que celui de l'image)
+CATALOG_REPO=https://gitea.tips-of-mine.com/hubert/enclume-catalogue.git
+CATALOG_BRANCH=main
+CATALOG_SUBDIR=catalog
+SYNC_INTERVAL=300
+
+# Base de donnees (chantier 2)
+POSTGRES_PASSWORD=a-remplacer
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..46c79e6
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+* text=auto eol=lf
+*.sh text eol=lf
+*.yml text eol=lf
+*.yaml text eol=lf
\ No newline at end of file
diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml
new file mode 100644
index 0000000..69635fb
--- /dev/null
+++ b/.gitea/workflows/build.yaml
@@ -0,0 +1,100 @@
+# Construction, verification et publication des images sur Harbor.
+#
+# Une image par evolution : le tag reprend la version du depot et le commit.
+# Rien n'est jamais publie sous "latest" pour la production : le compose
+# pointe une version explicite, ce qui rend le retour arriere immediat.
+
+name: Construction et publication
+
+on:
+ push:
+ branches: [main]
+ tags: ["v*"]
+ pull_request:
+
+env:
+ REGISTRY: harbor.tips-of-mine.com
+ PROJET: enclume
+
+jobs:
+ verifier:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+ - name: Dependances
+ run: pip install -r requirements.txt pytest
+ - name: Tests
+ run: python -m pytest -q
+ - name: Coherence du catalogue
+ run: python -m enclume.cli --list > /dev/null
+
+ publier:
+ needs: verifier
+ if: gitea.event_name == 'push'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Calcul des tags
+ id: version
+ run: |
+ COURT=$(echo "${{ gitea.sha }}" | cut -c1-8)
+ if [ "${{ gitea.ref_type }}" = "tag" ]; then
+ VERSION="${{ gitea.ref_name }}"
+ VERSION="${VERSION#v}"
+ else
+ VERSION="0.0.0-main.${COURT}"
+ fi
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+ echo "commit=$COURT" >> "$GITHUB_OUTPUT"
+ echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
+
+ - uses: docker/setup-buildx-action@v3
+ - uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ secrets.HARBOR_USER }}
+ password: ${{ secrets.HARBOR_TOKEN }}
+
+ - name: Image de l'application
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: Dockerfile
+ push: true
+ build-args: |
+ ENCLUME_VERSION=${{ steps.version.outputs.version }}
+ ENCLUME_COMMIT=${{ steps.version.outputs.commit }}
+ BUILD_DATE=${{ steps.version.outputs.date }}
+ tags: |
+ ${{ env.REGISTRY }}/${{ env.PROJET }}/web:${{ steps.version.outputs.version }}
+ ${{ env.REGISTRY }}/${{ env.PROJET }}/web:sha-${{ steps.version.outputs.commit }}
+ cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.PROJET }}/web:cache
+ cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.PROJET }}/web:cache,mode=max
+
+ - name: Image du synchroniseur de catalogue
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: Dockerfile.catalog-sync
+ push: true
+ tags: |
+ ${{ env.REGISTRY }}/${{ env.PROJET }}/catalog-sync:${{ steps.version.outputs.version }}
+ ${{ env.REGISTRY }}/${{ env.PROJET }}/catalog-sync:sha-${{ steps.version.outputs.commit }}
+
+ - name: Analyse de vulnerabilites
+ uses: aquasecurity/trivy-action@master
+ with:
+ image-ref: ${{ env.REGISTRY }}/${{ env.PROJET }}/web:${{ steps.version.outputs.version }}
+ severity: HIGH,CRITICAL
+ ignore-unfixed: true
+ exit-code: "0" # passer a 1 pour bloquer la publication
+ format: table
+
+ - name: Recapitulatif
+ run: |
+ echo "Publie : ${{ env.REGISTRY }}/${{ env.PROJET }}/web:${{ steps.version.outputs.version }}"
+ echo "Sur le serveur : ajuster ENCLUME_TAG dans .env puis docker compose up -d"
diff --git a/.gitea/workflows/ci-develop.yml b/.gitea/workflows/ci-develop.yml
deleted file mode 100644
index d9160d4..0000000
--- a/.gitea/workflows/ci-develop.yml
+++ /dev/null
@@ -1,70 +0,0 @@
-# Template
-name: Deployment Verification
-
-on:
- push:
- branches:
- - develop
- tags:
- - "v[0-9]+.[0-9]+.[0-9]"
- pull_request:
- branches:
- - main
- workflow_dispatch:
-
-jobs:
- deploy-and-test:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- # https://github.com/docker/setup-qemu-action#usage
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3.2.0
-
- # https://github.com/marketplace/actions/docker-setup-buildx
- - name: Set up Docker Buildx
- id: buildx
- uses: docker/setup-buildx-action@v3.6.1
-
- # https://github.com/docker/build-push-action
- - name: Build and push
- uses: docker/build-push-action@v6
-
- #
- - name: Create necessary Docker networks
- run: |
- docker network create back_network || true
- docker network create traefik_front_network || true
-
- - name: Start up services using Docker Compose
- run: docker compose -f docker-compose.yml up -d
-
-# - name: Modify /etc/hosts for internal routing
-# run: |
-# echo "127.0.0.1 gitea.tips-of-mine.com" | sudo tee -a /etc/hosts
-
- - name: Print Docker Compose services status
- run: docker ps
-
- - name: Wait for the application to be ready via Traefik
- run: |
- echo "Checking the routing and availability of application via Traefik..."
- timeout 5m bash -c 'while ! curl -fsSLk "https://metabase.tips-of-mine.com"; do echo "Waiting for the application to be ready..."; sleep 10; done'
-
- - name: Inspect Network Configuration
- run: |
- docker network inspect back_network
- docker network inspect traefik_front_network
-
- - name: Shutdown Docker Compose services
- if: always()
- run: docker compose -f docker-compose.yml down
-
- - name: Cleanup
- if: always()
- run: |
- docker compose --profile setup down
- rm -rf /workspace/tips-of-mine/metabase/*
\ No newline at end of file
diff --git a/.gitea/workflows/ci-main.yml b/.gitea/workflows/ci-main.yml
deleted file mode 100644
index bf77843..0000000
--- a/.gitea/workflows/ci-main.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-# Template
-name: Deployment Verification
-
-on:
- push:
- branches:
- - main
- pull_request:
- branches:
- - main
-
-jobs:
- deploy-and-test:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- - name: Create necessary Docker networks
- run: |
- docker network create back_network_{ vars.APPLICATION_NAME } || true
- docker network create traefik_front_network || true
-
- - name: Start up services using Docker Compose
- run: docker compose -f docker-compose.yml up -d
-
-# - name: Modify /etc/hosts for internal routing
-# run: |
-# echo "127.0.0.1 { vars.APPLICATION_URL }" | sudo tee -a /etc/hosts
-# echo "127.0.0.1 dashboard.tips-of-mine.com" | sudo tee -a /etc/hosts
-
- - name: Print Docker Compose services status
- run: docker ps
-
- - name: Wait for the application to be ready via Traefik
- run: |
- echo "Checking the routing and availability of application via Traefik..."
- timeout 5m bash -c 'while ! curl -fsSLk "https://{ vars.APPLICATION_URL }"; do echo "Waiting for the application to be ready..."; sleep 10; done'
-
- - name: Inspect Network Configuration
- run: |
- docker network inspect back_network_{ vars.APPLICATION_NAME }
- docker network inspect traefik_front_network
-
- - name: Shutdown Docker Compose services
- if: always()
- run: docker compose -f docker-compose.yml down
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 8c2b884..cd720f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,9 @@
-# ---> VisualStudioCode
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-!.vscode/*.code-snippets
-
-# Local History for Visual Studio Code
-.history/
-
-# Built Visual Studio Code Extensions
-*.vsix
-
+.venv/
+__pycache__/
+*.pyc
+.pytest_cache/
+.env
+.env.*
+!.env.example
+sortie/
+*.zip
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..092e3ea
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,50 @@
+# syntax=docker/dockerfile:1
+
+# ---------------------------------------------------------------- construction
+FROM python:3.12-slim AS build
+
+ENV PIP_NO_CACHE_DIR=1 PIP_DISABLE_PIP_VERSION_CHECK=1
+WORKDIR /build
+
+COPY requirements.txt ./
+RUN python -m venv /opt/venv \
+ && /opt/venv/bin/pip install --no-cache-dir -r requirements.txt
+
+# ------------------------------------------------------------------- execution
+FROM python:3.12-slim AS runtime
+
+ARG ENCLUME_VERSION=dev
+ARG ENCLUME_COMMIT=inconnu
+ARG BUILD_DATE=inconnue
+
+LABEL org.opencontainers.image.title="Enclume" \
+ org.opencontainers.image.description="Generateur de configuration CLAPI pour Centreon" \
+ org.opencontainers.image.version="${ENCLUME_VERSION}" \
+ org.opencontainers.image.revision="${ENCLUME_COMMIT}" \
+ org.opencontainers.image.created="${BUILD_DATE}" \
+ org.opencontainers.image.licenses="MIT"
+
+ENV PYTHONUNBUFFERED=1 \
+ PYTHONDONTWRITEBYTECODE=1 \
+ PATH="/opt/venv/bin:$PATH" \
+ ENCLUME_VERSION="${ENCLUME_VERSION}" \
+ ENCLUME_COMMIT="${ENCLUME_COMMIT}" \
+ ENCLUME_CATALOG=/app/catalog \
+ GUNICORN_WORKERS=2 \
+ GUNICORN_TIMEOUT=30
+
+RUN groupadd --gid 10001 enclume \
+ && useradd --uid 10001 --gid enclume --home /app --no-create-home --shell /usr/sbin/nologin enclume
+
+COPY --from=build /opt/venv /opt/venv
+WORKDIR /app
+COPY --chown=root:root enclume/ /app/enclume/
+COPY --chown=root:root catalog/ /app/catalog/
+
+USER enclume
+EXPOSE 8080
+
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
+ CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8080/sante', timeout=4).status==200 else 1)"
+
+CMD ["sh", "-c", "exec gunicorn --bind 0.0.0.0:8080 --workers ${GUNICORN_WORKERS} --timeout ${GUNICORN_TIMEOUT} --access-logfile - --error-logfile - --forwarded-allow-ips '*' 'enclume.webapp:create_app()'"]
diff --git a/Dockerfile.catalog-sync b/Dockerfile.catalog-sync
new file mode 100644
index 0000000..61af961
--- /dev/null
+++ b/Dockerfile.catalog-sync
@@ -0,0 +1,22 @@
+# syntax=docker/dockerfile:1
+# Synchronise le depot du catalogue dans un volume partage avec l'application,
+# puis demande a celle-ci de le relire. Le catalogue evolue ainsi sans nouvelle
+# image de l'application.
+FROM alpine:3.20
+
+RUN apk add --no-cache git curl tini \
+ && addgroup -g 10001 enclume \
+ && adduser -u 10001 -G enclume -D -H enclume
+
+COPY docker/catalog-sync.sh /usr/local/bin/catalog-sync
+RUN chmod +x /usr/local/bin/catalog-sync
+
+ENV CATALOG_REPO="" \
+ CATALOG_BRANCH=main \
+ CATALOG_SUBDIR=catalog \
+ CATALOG_DEST=/catalogue \
+ SYNC_INTERVAL=300 \
+ RELOAD_URL=http://web:8080/recharger
+
+USER enclume
+ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/catalog-sync"]
diff --git a/README.md b/README.md
index 627884c..42fbec2 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,107 @@
# Enclume
-Générateur de configuration CLAPI pour Centreon : créez vos commandes, modèles de service et modèles d'hôte, repartez avec les fichiers prêts à jouer. Sans inscription. Projet indépendant de Centreon SAS.
+Site public de génération de configuration CLAPI pour Centreon. On y construit ses commandes
+de contrôle, ses modèles de service et ses modèles d'hôte — de zéro ou en partant d'un pack
+du catalogue — et on repart avec les fichiers à jouer sur son central.
-# A propos de
+Projet indépendant, sans lien avec Centreon SAS.
-# Prérequis
+## Ce que fait le site
-Vous avez besoin d'une installation docker fonctionnelle et de docker-compose sur votre machine.
+- **Éditeur libre** : commandes, modèles de service, modèles d'hôte, hôtes. Héritage,
+ duplication, rattachement des services aux modèles d'hôte.
+- **Catalogue de départ** : 26 packs, environ 124 sondes, importables puis modifiables.
+- **Validation** avant génération : noms en double, références manquantes, point-virgule
+ interdit, services rattachés à aucun modèle d'hôte, mots de passe en clair.
+- **Quatre fichiers** : `deploy.sh` (avec `--dry-run`), `deploy.clapi` (import CLAPI),
+ et les deux `rollback` correspondants.
+- **Version de Centreon** au niveau du projet : les macros sortent en 3 ou 5 champs selon
+ la cible.
-# Démarrage
+## Ce que le serveur ne fait pas
-~~~bash
- git clone https://gitea.tips-of-mine.com/Tips-Of-Mine/Enclume.git
- cd Enclume
-~~~
+Le projet vit dans le navigateur du visiteur (`localStorage`), s'exporte en JSON et se
+réimporte. Le serveur valide et génère, sans rien conserver. Les macros marquées « mot de
+passe » ne sont jamais stockées ni partagées : elles sont vidées avant toute écriture côté
+serveur.
-## Personnalisation
+## Architecture
-Le fichier .env pour les variable d'environnement
-~~~bash
- nano .env
-~~~
+```
+Cloudflare Tunnel → Traefik ─┬→ web (image maison, sans état)
+ │
+ volume catalogue ┴← catalog-sync (git → volume, puis /recharger)
+ db (postgres, à partir du chantier 2)
+```
-Modification des labels pour traefik
-~~~bash
- nano docker-compose.yml
-~~~
+Chaque évolution produit une image versionnée. Le déploiement et le retour arrière se font
+en changeant une ligne dans `.env` : voir [docs/EXPLOITATION.md](docs/EXPLOITATION.md).
-## Lancement
+## Développement
-~~~bash
- docker compose up -d
-~~~
+```bash
+python3 -m venv .venv && . .venv/bin/activate
+pip install -r requirements.txt
+ENCLUME_DEBUG=1 python3 run.py # http://127.0.0.1:8080
+python3 -m pytest -q
+```
-~~~bash
- docker compose logs -f
-~~~
+Ou en conteneur, avec le code monté en direct :
-# Buy me a coffe
-
\ No newline at end of file
+```bash
+docker compose -f compose.dev.yaml up --build
+```
+
+## Ligne de commande et API
+
+Le même moteur sert au site, à la CLI et à la CI :
+
+```bash
+python3 -m enclume.cli --list
+python3 -m enclume.cli --pack os-linux-snmp --prefix ACME- --out ./sortie --strict
+python3 -m enclume.cli --projet mon-projet.json --out ./sortie
+```
+
+| Route | Usage |
+|---|---|
+| `GET /api/packs` | catalogue |
+| `GET /api/packs//projet` | un pack converti en objets de projet |
+| `POST /api/valider` | validation seule |
+| `POST /api/generer` | validation et génération des quatre fichiers |
+| `POST /telecharger` | archive zip |
+| `GET /sante` | version, commit, état du catalogue |
+| `POST /recharger` | relecture du catalogue (appelé par `catalog-sync`) |
+
+## Ajouter un pack au catalogue
+
+Un pack est un fichier YAML déposé dans `catalog//`. Le format et un exemple
+commenté sont dans `catalog/exemple-pack.yml.example`. Aucun code à modifier, aucune image à
+reconstruire : `catalog-sync` le prend en charge au prochain cycle.
+
+## Structure
+
+```
+run.py point d'entrée de développement
+enclume/model.py modèle de projet, validation, retrait des secrets
+enclume/generator.py projet → opérations CLAPI → quatre fichiers
+enclume/catalog.py lecture du catalogue YAML
+enclume/packs.py pack du catalogue → objets de projet
+enclume/webapp.py routes Flask
+enclume/cli.py usage en ligne de commande et en CI
+enclume/static/editeur.js éditeur, sans dépendance ni étape de build
+tests/ garde-fous sur la génération
+Dockerfile image de l'application
+Dockerfile.catalog-sync image du synchroniseur
+compose.yaml production, derrière Traefik
+compose.dev.yaml développement local
+.gitea/workflows/build.yaml construction, tests, publication sur Harbor
+```
+
+## Chantiers
+
+1. Éditeur libre — **fait**
+2. Empaquetage Docker, CI, exploitation — **fait**
+3. Partage par lien et projets enregistrés (PostgreSQL)
+4. Connexion optionnelle via Authentik
+5. Soumission de packs et modération, écriture dans Gitea
+6. Habillage public : page d'accueil, mentions, documentation d'usage
diff --git a/catalog/application/app-apache.yml b/catalog/application/app-apache.yml
new file mode 100644
index 0000000..2311bc6
--- /dev/null
+++ b/catalog/application/app-apache.yml
@@ -0,0 +1,49 @@
+id: app-apache
+name: Apache HTTP Server
+category: application
+status: verified
+plugin: centreon_apache_serverstatus.pl
+description: Supervision d'un serveur Apache via la page mod_status (charge CPU, requetes, slots, temps de reponse).
+host_template:
+ name: App-Apache-custom
+ alias: Applicatif Apache
+ parents: [generic-active-host-custom]
+ macros:
+ - {name: APACHEPORT, value: "80", description: "Port d'ecoute"}
+ - {name: APACHEPROTO, value: "http", description: "Protocole (http ou https)"}
+ - {name: APACHEURLPATH, value: "/server-status/?auto", description: "Chemin de la page mod_status"}
+ - {name: APACHEEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Cpu-Load
+ alias: Cpu-Load
+ description: Charge CPU declaree par Apache
+ line: "$CENTREONPLUGINS$/centreon_apache_serverstatus.pl --plugin=apps::apache::serverstatus::plugin --mode=cpuload --hostname=$HOSTADDRESS$ --port='$_HOSTAPACHEPORT$' --proto='$_HOSTAPACHEPROTO$' --urlpath='$_HOSTAPACHEURLPATH$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTAPACHEEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Requests
+ alias: Requests
+ description: Requetes et debit par seconde
+ line: "$CENTREONPLUGINS$/centreon_apache_serverstatus.pl --plugin=apps::apache::serverstatus::plugin --mode=requests --hostname=$HOSTADDRESS$ --port='$_HOSTAPACHEPORT$' --proto='$_HOSTAPACHEPROTO$' --urlpath='$_HOSTAPACHEURLPATH$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTAPACHEEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning"}
+ - {name: CRITICAL, value: "", description: "Seuil critical"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Slot-States
+ alias: Slot-States
+ description: Occupation des slots de traitement
+ line: "$CENTREONPLUGINS$/centreon_apache_serverstatus.pl --plugin=apps::apache::serverstatus::plugin --mode=slotstates --hostname=$HOSTADDRESS$ --port='$_HOSTAPACHEPORT$' --proto='$_HOSTAPACHEPROTO$' --urlpath='$_HOSTAPACHEURLPATH$' --warning-busy='$_SERVICEWARNING$' --critical-busy='$_SERVICECRITICAL$' $_HOSTAPACHEEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning slots occupes (%)"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical slots occupes (%)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Response-Time
+ alias: Response-Time
+ description: Temps de reponse de la page de statut
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_apache_serverstatus.pl --plugin=apps::apache::serverstatus::plugin --mode=responsetime --hostname=$HOSTADDRESS$ --port='$_HOSTAPACHEPORT$' --proto='$_HOSTAPACHEPROTO$' --urlpath='$_HOSTAPACHEURLPATH$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTAPACHEEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "1", description: "Seuil warning en secondes"}
+ - {name: CRITICAL, value: "3", description: "Seuil critical en secondes"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/application/app-docker.yml b/catalog/application/app-docker.yml
new file mode 100644
index 0000000..6eb3f1f
--- /dev/null
+++ b/catalog/application/app-docker.yml
@@ -0,0 +1,38 @@
+id: app-docker
+name: Docker (API REST)
+category: application
+status: to-verify
+plugin: centreon_docker_restapi.pl
+description: Supervision d'un hote Docker via son API REST (etat des conteneurs, consommation CPU et memoire). Verifier le nom du plugin selon la version installee.
+host_template:
+ name: App-Docker-custom
+ alias: Applicatif Docker
+ parents: [generic-active-host-custom]
+ macros:
+ - {name: DOCKERPORT, value: "2376", description: "Port de l'API Docker"}
+ - {name: DOCKERPROTO, value: "https", description: "Protocole d'acces a l'API"}
+ - {name: DOCKEREXTRAOPTIONS, value: "", description: "Options supplementaires (certificats, api-version)"}
+services:
+ - name: Container-Status
+ alias: Container-Status
+ description: Etat des conteneurs
+ line: "$CENTREONPLUGINS$/centreon_docker_restapi.pl --plugin=cloud::docker::restapi::plugin --mode=container-status --hostname=$HOSTADDRESS$ --port='$_HOSTDOCKERPORT$' --proto='$_HOSTDOCKERPROTO$' --container-name='$_SERVICECONTAINERNAME$' $_HOSTDOCKEREXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: CONTAINERNAME, value: "", description: "Filtre sur le nom du conteneur"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Container-Usage
+ alias: Container-Usage
+ description: Consommation CPU et memoire des conteneurs
+ line: "$CENTREONPLUGINS$/centreon_docker_restapi.pl --plugin=cloud::docker::restapi::plugin --mode=container-usage --hostname=$HOSTADDRESS$ --port='$_HOSTDOCKERPORT$' --proto='$_HOSTDOCKERPROTO$' --container-name='$_SERVICECONTAINERNAME$' --warning-cpu='$_SERVICEWARNINGCPU$' --critical-cpu='$_SERVICECRITICALCPU$' $_HOSTDOCKEREXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: CONTAINERNAME, value: "", description: "Filtre sur le nom du conteneur"}
+ - {name: WARNINGCPU, value: "80", description: "Seuil warning CPU en %"}
+ - {name: CRITICALCPU, value: "90", description: "Seuil critical CPU en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Node-Status
+ alias: Node-Status
+ description: Etat du noeud Docker
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_docker_restapi.pl --plugin=cloud::docker::restapi::plugin --mode=node-status --hostname=$HOSTADDRESS$ --port='$_HOSTDOCKERPORT$' --proto='$_HOSTDOCKERPROTO$' $_HOSTDOCKEREXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/application/app-mssql.yml b/catalog/application/app-mssql.yml
new file mode 100644
index 0000000..a4fa4e8
--- /dev/null
+++ b/catalog/application/app-mssql.yml
@@ -0,0 +1,56 @@
+id: app-mssql
+name: Microsoft SQL Server
+category: application
+status: verified
+plugin: centreon_mssql.pl
+description: Supervision d'une instance SQL Server (connexion, taille des bases, sauvegardes, jobs, sessions, verrous).
+host_template:
+ name: App-DB-MSSQL-custom
+ alias: Applicatif MSSQL
+ parents: [generic-active-host-custom]
+ macros:
+ - {name: MSSQLPORT, value: "1433", description: "Port d'ecoute de l'instance"}
+ - {name: MSSQLUSERNAME, value: "centreon", description: "Compte de supervision"}
+ - {name: MSSQLPASSWORD, value: "", description: "Mot de passe du compte de supervision", is_password: true}
+ - {name: MSSQLEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Connection-Time
+ alias: Connection-Time
+ description: Temps d'etablissement de la connexion
+ line: "$CENTREONPLUGINS$/centreon_mssql.pl --plugin=database::mssql::plugin --mode=connection-time --host=$HOSTADDRESS$ --port='$_HOSTMSSQLPORT$' --username='$_HOSTMSSQLUSERNAME$' --password='$_HOSTMSSQLPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTMSSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "1000", description: "Seuil warning en ms"}
+ - {name: CRITICAL, value: "5000", description: "Seuil critical en ms"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Databases-Size
+ alias: Databases-Size
+ description: Taille et occupation des bases
+ line: "$CENTREONPLUGINS$/centreon_mssql.pl --plugin=database::mssql::plugin --mode=databases-size --host=$HOSTADDRESS$ --port='$_HOSTMSSQLPORT$' --username='$_HOSTMSSQLUSERNAME$' --password='$_HOSTMSSQLPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTMSSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning"}
+ - {name: CRITICAL, value: "", description: "Seuil critical"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Backup-Age
+ alias: Backup-Age
+ description: Anciennete de la derniere sauvegarde
+ line: "$CENTREONPLUGINS$/centreon_mssql.pl --plugin=database::mssql::plugin --mode=backup-age --host=$HOSTADDRESS$ --port='$_HOSTMSSQLPORT$' --username='$_HOSTMSSQLUSERNAME$' --password='$_HOSTMSSQLPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTMSSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "86400", description: "Seuil warning en secondes"}
+ - {name: CRITICAL, value: "172800", description: "Seuil critical en secondes"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Failed-Jobs
+ alias: Failed-Jobs
+ description: Jobs de l'agent SQL en echec
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_mssql.pl --plugin=database::mssql::plugin --mode=failed-jobs --host=$HOSTADDRESS$ --port='$_HOSTMSSQLPORT$' --username='$_HOSTMSSQLUSERNAME$' --password='$_HOSTMSSQLPASSWORD$' $_HOSTMSSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Connected-Users
+ alias: Connected-Users
+ description: Nombre de sessions ouvertes
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_mssql.pl --plugin=database::mssql::plugin --mode=connected-users --host=$HOSTADDRESS$ --port='$_HOSTMSSQLPORT$' --username='$_HOSTMSSQLUSERNAME$' --password='$_HOSTMSSQLPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTMSSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning"}
+ - {name: CRITICAL, value: "", description: "Seuil critical"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/application/app-mysql.yml b/catalog/application/app-mysql.yml
new file mode 100644
index 0000000..34f5998
--- /dev/null
+++ b/catalog/application/app-mysql.yml
@@ -0,0 +1,65 @@
+id: app-mysql
+name: MySQL / MariaDB
+category: application
+status: verified
+plugin: centreon_mysql.pl
+description: Supervision d'une instance MySQL ou MariaDB (connexion, requetes, threads, replication, taille des bases).
+host_template:
+ name: App-DB-MySQL-custom
+ alias: Applicatif MySQL
+ parents: [generic-active-host-custom]
+ macros:
+ - {name: MYSQLPORT, value: "3306", description: "Port d'ecoute du serveur"}
+ - {name: MYSQLUSERNAME, value: "centreon", description: "Compte de supervision"}
+ - {name: MYSQLPASSWORD, value: "", description: "Mot de passe du compte de supervision", is_password: true}
+ - {name: MYSQLEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Connection-Time
+ alias: Connection-Time
+ description: Temps d'etablissement de la connexion
+ line: "$CENTREONPLUGINS$/centreon_mysql.pl --plugin=database::mysql::plugin --mode=connection-time --host=$HOSTADDRESS$ --port='$_HOSTMYSQLPORT$' --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTMYSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "1000", description: "Seuil warning en ms"}
+ - {name: CRITICAL, value: "5000", description: "Seuil critical en ms"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Queries
+ alias: Queries
+ description: Nombre de requetes par seconde
+ line: "$CENTREONPLUGINS$/centreon_mysql.pl --plugin=database::mysql::plugin --mode=queries --host=$HOSTADDRESS$ --port='$_HOSTMYSQLPORT$' --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' $_HOSTMYSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Connections
+ alias: Connections
+ description: Nombre de connexions clientes ouvertes
+ line: "$CENTREONPLUGINS$/centreon_mysql.pl --plugin=database::mysql::plugin --mode=threads-connected --host=$HOSTADDRESS$ --port='$_HOSTMYSQLPORT$' --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTMYSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Slave-Status
+ alias: Slave-Status
+ description: Etat de la replication cote replica
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_mysql.pl --plugin=database::mysql::plugin --mode=slave-status --host=$HOSTADDRESS$ --port='$_HOSTMYSQLPORT$' --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTMYSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "60", description: "Retard warning en secondes"}
+ - {name: CRITICAL, value: "300", description: "Retard critical en secondes"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Databases-Size
+ alias: Databases-Size
+ description: Taille des bases de donnees
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_mysql.pl --plugin=database::mysql::plugin --mode=databases-size --host=$HOSTADDRESS$ --port='$_HOSTMYSQLPORT$' --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTMYSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning en octets"}
+ - {name: CRITICAL, value: "", description: "Seuil critical en octets"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Uptime
+ alias: Uptime
+ description: Duree de fonctionnement de l'instance
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_mysql.pl --plugin=database::mysql::plugin --mode=uptime --host=$HOSTADDRESS$ --port='$_HOSTMYSQLPORT$' --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTMYSQLEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning en secondes"}
+ - {name: CRITICAL, value: "@0:600", description: "Seuil critical en secondes"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/application/app-nginx.yml b/catalog/application/app-nginx.yml
new file mode 100644
index 0000000..c4338a1
--- /dev/null
+++ b/catalog/application/app-nginx.yml
@@ -0,0 +1,41 @@
+id: app-nginx
+name: Nginx
+category: application
+status: verified
+plugin: centreon_nginx_serverstatus.pl
+description: Supervision d'un serveur Nginx via le module stub_status (connexions, requetes, temps de reponse).
+host_template:
+ name: App-Nginx-custom
+ alias: Applicatif Nginx
+ parents: [generic-active-host-custom]
+ macros:
+ - {name: NGINXPORT, value: "80", description: "Port d'ecoute"}
+ - {name: NGINXPROTO, value: "http", description: "Protocole (http ou https)"}
+ - {name: NGINXURLPATH, value: "/nginx_status", description: "Chemin de la page stub_status"}
+ - {name: NGINXEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Connections
+ alias: Connections
+ description: Connexions actives, en lecture et en attente
+ line: "$CENTREONPLUGINS$/centreon_nginx_serverstatus.pl --plugin=apps::nginx::serverstatus::plugin --mode=connections --hostname=$HOSTADDRESS$ --port='$_HOSTNGINXPORT$' --proto='$_HOSTNGINXPROTO$' --urlpath='$_HOSTNGINXURLPATH$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTNGINXEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning"}
+ - {name: CRITICAL, value: "", description: "Seuil critical"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Requests
+ alias: Requests
+ description: Requetes traitees par seconde
+ line: "$CENTREONPLUGINS$/centreon_nginx_serverstatus.pl --plugin=apps::nginx::serverstatus::plugin --mode=requests --hostname=$HOSTADDRESS$ --port='$_HOSTNGINXPORT$' --proto='$_HOSTNGINXPROTO$' --urlpath='$_HOSTNGINXURLPATH$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTNGINXEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning"}
+ - {name: CRITICAL, value: "", description: "Seuil critical"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Response-Time
+ alias: Response-Time
+ description: Temps de reponse de la page de statut
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_nginx_serverstatus.pl --plugin=apps::nginx::serverstatus::plugin --mode=responsetime --hostname=$HOSTADDRESS$ --port='$_HOSTNGINXPORT$' --proto='$_HOSTNGINXPROTO$' --urlpath='$_HOSTNGINXURLPATH$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTNGINXEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "1", description: "Seuil warning en secondes"}
+ - {name: CRITICAL, value: "3", description: "Seuil critical en secondes"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/application/app-oracle.yml b/catalog/application/app-oracle.yml
new file mode 100644
index 0000000..6df6d73
--- /dev/null
+++ b/catalog/application/app-oracle.yml
@@ -0,0 +1,48 @@
+id: app-oracle
+name: Oracle Database
+category: application
+status: to-verify
+plugin: centreon_oracle.pl
+description: Supervision d'une instance Oracle (connexion, tablespaces, sessions, processus, sauvegardes RMAN). Necessite un client Oracle sur le collecteur.
+host_template:
+ name: App-DB-Oracle-custom
+ alias: Applicatif Oracle
+ parents: [generic-active-host-custom]
+ macros:
+ - {name: ORACLEPORT, value: "1521", description: "Port d'ecoute du listener"}
+ - {name: ORACLESID, value: "", description: "SID ou service name"}
+ - {name: ORACLEUSERNAME, value: "centreon", description: "Compte de supervision"}
+ - {name: ORACLEPASSWORD, value: "", description: "Mot de passe du compte de supervision", is_password: true}
+ - {name: ORACLEEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Connection-Time
+ alias: Connection-Time
+ description: Temps d'etablissement de la connexion
+ line: "$CENTREONPLUGINS$/centreon_oracle.pl --plugin=database::oracle::plugin --mode=connection-time --hostname=$HOSTADDRESS$ --port='$_HOSTORACLEPORT$' --sid='$_HOSTORACLESID$' --username='$_HOSTORACLEUSERNAME$' --password='$_HOSTORACLEPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTORACLEEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "1000", description: "Seuil warning en ms"}
+ - {name: CRITICAL, value: "5000", description: "Seuil critical en ms"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Tablespace-Usage
+ alias: Tablespace-Usage
+ description: Occupation des tablespaces
+ line: "$CENTREONPLUGINS$/centreon_oracle.pl --plugin=database::oracle::plugin --mode=tablespace-usage --hostname=$HOSTADDRESS$ --port='$_HOSTORACLEPORT$' --sid='$_HOSTORACLESID$' --username='$_HOSTORACLEUSERNAME$' --password='$_HOSTORACLEPASSWORD$' --warning-usage='$_SERVICEWARNING$' --critical-usage='$_SERVICECRITICAL$' $_HOSTORACLEEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Sessions-Usage
+ alias: Sessions-Usage
+ description: Sessions ouvertes par rapport au maximum
+ line: "$CENTREONPLUGINS$/centreon_oracle.pl --plugin=database::oracle::plugin --mode=sessions-usage --hostname=$HOSTADDRESS$ --port='$_HOSTORACLEPORT$' --sid='$_HOSTORACLESID$' --username='$_HOSTORACLEUSERNAME$' --password='$_HOSTORACLEPASSWORD$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTORACLEEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Rman-Backup-Problems
+ alias: Rman-Backup-Problems
+ description: Erreurs remontees par RMAN
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_oracle.pl --plugin=database::oracle::plugin --mode=rman-backup-problems --hostname=$HOSTADDRESS$ --port='$_HOSTORACLEPORT$' --sid='$_HOSTORACLESID$' --username='$_HOSTORACLEUSERNAME$' --password='$_HOSTORACLEPASSWORD$' $_HOSTORACLEEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/application/app-postgresql.yml b/catalog/application/app-postgresql.yml
new file mode 100644
index 0000000..1aa654d
--- /dev/null
+++ b/catalog/application/app-postgresql.yml
@@ -0,0 +1,57 @@
+id: app-postgresql
+name: PostgreSQL
+category: application
+status: verified
+plugin: centreon_postgres.pl
+description: Supervision d'une instance PostgreSQL (connexion, sessions, taille des bases, verrous, temps de requete).
+host_template:
+ name: App-DB-PostgreSQL-custom
+ alias: Applicatif PostgreSQL
+ parents: [generic-active-host-custom]
+ macros:
+ - {name: PGPORT, value: "5432", description: "Port d'ecoute du serveur"}
+ - {name: PGUSERNAME, value: "centreon", description: "Compte de supervision"}
+ - {name: PGPASSWORD, value: "", description: "Mot de passe du compte de supervision", is_password: true}
+ - {name: PGDATABASE, value: "postgres", description: "Base de connexion"}
+ - {name: PGEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Connection-Time
+ alias: Connection-Time
+ description: Temps d'etablissement de la connexion
+ line: "$CENTREONPLUGINS$/centreon_postgres.pl --plugin=database::postgres::plugin --mode=connection-time --host=$HOSTADDRESS$ --port='$_HOSTPGPORT$' --username='$_HOSTPGUSERNAME$' --password='$_HOSTPGPASSWORD$' --database='$_HOSTPGDATABASE$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTPGEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "1000", description: "Seuil warning en ms"}
+ - {name: CRITICAL, value: "5000", description: "Seuil critical en ms"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Backends
+ alias: Backends
+ description: Nombre de connexions actives
+ line: "$CENTREONPLUGINS$/centreon_postgres.pl --plugin=database::postgres::plugin --mode=backends --host=$HOSTADDRESS$ --port='$_HOSTPGPORT$' --username='$_HOSTPGUSERNAME$' --password='$_HOSTPGPASSWORD$' --database='$_HOSTPGDATABASE$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTPGEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Database-Size
+ alias: Database-Size
+ description: Taille des bases de donnees
+ line: "$CENTREONPLUGINS$/centreon_postgres.pl --plugin=database::postgres::plugin --mode=database-size --host=$HOSTADDRESS$ --port='$_HOSTPGPORT$' --username='$_HOSTPGUSERNAME$' --password='$_HOSTPGPASSWORD$' --database='$_HOSTPGDATABASE$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTPGEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning en octets"}
+ - {name: CRITICAL, value: "", description: "Seuil critical en octets"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Locks
+ alias: Locks
+ description: Verrous poses sur l'instance
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_postgres.pl --plugin=database::postgres::plugin --mode=locks --host=$HOSTADDRESS$ --port='$_HOSTPGPORT$' --username='$_HOSTPGUSERNAME$' --password='$_HOSTPGPASSWORD$' --database='$_HOSTPGDATABASE$' $_HOSTPGEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Query-Time
+ alias: Query-Time
+ description: Duree de la requete la plus longue
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_postgres.pl --plugin=database::postgres::plugin --mode=query-time --host=$HOSTADDRESS$ --port='$_HOSTPGPORT$' --username='$_HOSTPGUSERNAME$' --password='$_HOSTPGPASSWORD$' --database='$_HOSTPGDATABASE$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTPGEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "60", description: "Seuil warning en secondes"}
+ - {name: CRITICAL, value: "300", description: "Seuil critical en secondes"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/application/app-protocols.yml b/catalog/application/app-protocols.yml
new file mode 100644
index 0000000..fb84cde
--- /dev/null
+++ b/catalog/application/app-protocols.yml
@@ -0,0 +1,68 @@
+id: app-protocols
+name: Services reseau standards (HTTP, TCP, DNS, NTP, SSH)
+category: application
+status: verified
+plugin: check_http
+description: Sondes generiques basees sur les plugins Nagios livres avec Centreon. Utile pour tout service expose sans pack dedie.
+host_template:
+ name: App-Protocols-custom
+ alias: Applicatif Protocoles reseau
+ parents: [generic-active-host-custom]
+ macros: []
+services:
+ - name: Http
+ alias: Http
+ description: Disponibilite et temps de reponse d'une URL
+ line: "$USER1$/check_http -H $HOSTADDRESS$ -p $_SERVICEPORT$ -u '$_SERVICEURLPATH$' -w $_SERVICEWARNING$ -c $_SERVICECRITICAL$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: PORT, value: "80", description: "Port d'ecoute"}
+ - {name: URLPATH, value: "/", description: "Chemin a interroger"}
+ - {name: WARNING, value: "1", description: "Temps de reponse warning (s)"}
+ - {name: CRITICAL, value: "3", description: "Temps de reponse critical (s)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires (ex: -S pour https)"}
+ - name: Https-Certificate
+ alias: Https-Certificate
+ description: Validite du certificat TLS
+ line: "$USER1$/check_http -H $_SERVICEHOSTNAME$ -I $HOSTADDRESS$ -p $_SERVICEPORT$ -S -C $_SERVICEDAYS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: HOSTNAME, value: "$HOSTADDRESS$", description: "Nom presente dans le certificat (SNI)"}
+ - {name: PORT, value: "443", description: "Port TLS"}
+ - {name: DAYS, value: "30,14", description: "Jours restants warning,critical"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Tcp
+ alias: Tcp
+ description: Ouverture d'un port TCP
+ line: "$USER1$/check_tcp -H $HOSTADDRESS$ -p $_SERVICEPORT$ -w $_SERVICEWARNING$ -c $_SERVICECRITICAL$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: PORT, value: "22", description: "Port TCP a tester"}
+ - {name: WARNING, value: "1", description: "Temps de connexion warning (s)"}
+ - {name: CRITICAL, value: "3", description: "Temps de connexion critical (s)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Dns
+ alias: Dns
+ description: Resolution d'un nom par le serveur
+ default: false
+ line: "$USER1$/check_dns -H $_SERVICELOOKUP$ -s $HOSTADDRESS$ -w $_SERVICEWARNING$ -c $_SERVICECRITICAL$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: LOOKUP, value: "www.google.com", description: "Nom a resoudre"}
+ - {name: WARNING, value: "1", description: "Temps de reponse warning (s)"}
+ - {name: CRITICAL, value: "3", description: "Temps de reponse critical (s)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Ntp
+ alias: Ntp
+ description: Derive de l'horloge par rapport au serveur
+ default: false
+ line: "$USER1$/check_ntp_time -H $HOSTADDRESS$ -w $_SERVICEWARNING$ -c $_SERVICECRITICAL$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "1", description: "Derive warning (s)"}
+ - {name: CRITICAL, value: "3", description: "Derive critical (s)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Ssh
+ alias: Ssh
+ description: Disponibilite du service SSH
+ default: false
+ line: "$USER1$/check_ssh -H $HOSTADDRESS$ -p $_SERVICEPORT$ -t $_SERVICETIMEOUT$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: PORT, value: "22", description: "Port SSH"}
+ - {name: TIMEOUT, value: "10", description: "Timeout en secondes"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/application/app-vmware-esx.yml b/catalog/application/app-vmware-esx.yml
new file mode 100644
index 0000000..b9502f1
--- /dev/null
+++ b/catalog/application/app-vmware-esx.yml
@@ -0,0 +1,54 @@
+id: app-vmware-esx
+name: VMware ESXi (connecteur Centreon VMware)
+category: application
+status: to-verify
+plugin: centreon_vmware_connector_client.pl
+description: Supervision d'un hote ESXi via le connecteur Centreon VMware. Le connecteur doit etre installe et configure avec l'acces vCenter.
+host_template:
+ name: App-VMware-ESX-custom
+ alias: Applicatif VMware ESX
+ parents: [generic-active-host-custom]
+ macros:
+ - {name: VMWARECONNECTORHOSTNAME, value: "localhost", description: "Adresse du connecteur Centreon VMware"}
+ - {name: VMWARECONNECTORPORT, value: "5700", description: "Port du connecteur"}
+ - {name: VMWARECONTAINER, value: "default", description: "Nom du container declare dans le connecteur"}
+ - {name: VMWAREESXHOSTNAME, value: "", description: "Nom de l'hote ESXi dans le vCenter"}
+ - {name: VMWAREEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Host-Status
+ alias: Host-Status
+ description: Etat general de l'hote ESXi
+ line: "$CENTREONPLUGINS$/centreon_vmware_connector_client.pl --plugin=apps::vmware::connector::plugin --mode=status-host --custommode=connector --connector-hostname='$_HOSTVMWARECONNECTORHOSTNAME$' --connector-port='$_HOSTVMWARECONNECTORPORT$' --container='$_HOSTVMWARECONTAINER$' --esx-hostname='$_HOSTVMWAREESXHOSTNAME$' $_HOSTVMWAREEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Host-Cpu
+ alias: Host-Cpu
+ description: Consommation CPU de l'hote
+ line: "$CENTREONPLUGINS$/centreon_vmware_connector_client.pl --plugin=apps::vmware::connector::plugin --mode=cpu-host --custommode=connector --connector-hostname='$_HOSTVMWARECONNECTORHOSTNAME$' --connector-port='$_HOSTVMWARECONNECTORPORT$' --container='$_HOSTVMWARECONTAINER$' --esx-hostname='$_HOSTVMWAREESXHOSTNAME$' --warning-total-cpu='$_SERVICEWARNING$' --critical-total-cpu='$_SERVICECRITICAL$' $_HOSTVMWAREEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Host-Memory
+ alias: Host-Memory
+ description: Consommation memoire de l'hote
+ line: "$CENTREONPLUGINS$/centreon_vmware_connector_client.pl --plugin=apps::vmware::connector::plugin --mode=memory-host --custommode=connector --connector-hostname='$_HOSTVMWARECONNECTORHOSTNAME$' --connector-port='$_HOSTVMWARECONNECTORPORT$' --container='$_HOSTVMWARECONTAINER$' --esx-hostname='$_HOSTVMWAREESXHOSTNAME$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTVMWAREEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Host-Health
+ alias: Host-Health
+ description: Etat materiel remonte par l'hyperviseur
+ line: "$CENTREONPLUGINS$/centreon_vmware_connector_client.pl --plugin=apps::vmware::connector::plugin --mode=health-host --custommode=connector --connector-hostname='$_HOSTVMWARECONNECTORHOSTNAME$' --connector-port='$_HOSTVMWARECONNECTORPORT$' --container='$_HOSTVMWARECONTAINER$' --esx-hostname='$_HOSTVMWAREESXHOSTNAME$' $_HOSTVMWAREEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Datastore-Usage
+ alias: Datastore-Usage
+ description: Occupation des banques de donnees
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_vmware_connector_client.pl --plugin=apps::vmware::connector::plugin --mode=datastore-usage --custommode=connector --connector-hostname='$_HOSTVMWARECONNECTORHOSTNAME$' --connector-port='$_HOSTVMWARECONNECTORPORT$' --container='$_HOSTVMWARECONTAINER$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTVMWAREEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/exemple-pack.yml.example b/catalog/exemple-pack.yml.example
new file mode 100644
index 0000000..7042483
--- /dev/null
+++ b/catalog/exemple-pack.yml.example
@@ -0,0 +1,21 @@
+id: app-rabbitmq
+name: RabbitMQ
+category: application
+status: to-verify
+plugin: centreon_rabbitmq_restapi.pl
+description: Supervision d'un broker RabbitMQ via son API de management.
+host_template:
+ name: App-RabbitMQ-custom
+ alias: Applicatif RabbitMQ
+ parents: [generic-active-host-custom]
+ macros:
+ - {name: RABBITPORT, value: "15672", description: "Port de l'API"}
+ - {name: RABBITPASSWORD, value: "", description: "Mot de passe", is_password: true}
+services:
+ - name: Queues
+ alias: Queues
+ description: Messages en attente dans les files
+ default: true # décoché par défaut si false
+ line: "$CENTREONPLUGINS$/centreon_rabbitmq_restapi.pl --plugin=… --mode=queues …"
+ macros:
+ - {name: WARNING, value: "1000", description: "Seuil warning"}
diff --git a/catalog/hardware/hw-apc-ups-snmp.yml b/catalog/hardware/hw-apc-ups-snmp.yml
new file mode 100644
index 0000000..1dfcdd1
--- /dev/null
+++ b/catalog/hardware/hw-apc-ups-snmp.yml
@@ -0,0 +1,37 @@
+id: hw-apc-ups-snmp
+name: Onduleur APC (SNMP)
+category: hardware
+status: to-verify
+plugin: centreon_apc_ups_snmp.pl
+description: Supervision d'un onduleur APC en SNMP (batterie, lignes d'entree et de sortie). Verifier les modes disponibles selon le modele.
+host_template:
+ name: HW-APC-UPS-SNMP-custom
+ alias: Materiel APC UPS SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Battery-Status
+ alias: Battery-Status
+ description: Etat, charge et autonomie de la batterie
+ line: "$CENTREONPLUGINS$/centreon_apc_ups_snmp.pl --plugin=hardware::ups::apc::snmp::plugin --mode=battery-status --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-charge-remaining='$_SERVICEWARNINGCHARGE$' --critical-charge-remaining='$_SERVICECRITICALCHARGE$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNINGCHARGE, value: "50:", description: "Seuil warning sur la charge restante (%)"}
+ - {name: CRITICALCHARGE, value: "25:", description: "Seuil critical sur la charge restante (%)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Input-Lines
+ alias: Input-Lines
+ description: Tension et frequence des lignes d'entree
+ line: "$CENTREONPLUGINS$/centreon_apc_ups_snmp.pl --plugin=hardware::ups::apc::snmp::plugin --mode=input-lines --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Output-Lines
+ alias: Output-Lines
+ description: Charge et tension des lignes de sortie
+ line: "$CENTREONPLUGINS$/centreon_apc_ups_snmp.pl --plugin=hardware::ups::apc::snmp::plugin --mode=output-lines --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-load='$_SERVICEWARNINGLOAD$' --critical-load='$_SERVICECRITICALLOAD$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNINGLOAD, value: "70", description: "Seuil warning sur la charge (%)"}
+ - {name: CRITICALLOAD, value: "85", description: "Seuil critical sur la charge (%)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/hardware/hw-cisco-snmp.yml b/catalog/hardware/hw-cisco-snmp.yml
new file mode 100644
index 0000000..6f1e7fc
--- /dev/null
+++ b/catalog/hardware/hw-cisco-snmp.yml
@@ -0,0 +1,58 @@
+id: hw-cisco-snmp
+name: Switch / routeur Cisco (SNMP)
+category: hardware
+status: verified
+plugin: centreon_cisco_standard_snmp.pl
+description: Supervision d'un equipement Cisco IOS / IOS-XE en SNMP (CPU, memoire, environnement materiel, interfaces).
+host_template:
+ name: HW-Cisco-SNMP-custom
+ alias: Materiel Cisco SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU du chassis
+ line: "$CENTREONPLUGINS$/centreon_cisco_standard_snmp.pl --plugin=network::cisco::standard::snmp::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-average='$_SERVICEWARNING$' --critical-average='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Memory
+ alias: Memory
+ description: Utilisation memoire des pools
+ line: "$CENTREONPLUGINS$/centreon_cisco_standard_snmp.pl --plugin=network::cisco::standard::snmp::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Hardware
+ alias: Hardware
+ description: Alimentations, ventilateurs, sondes et modules
+ line: "$CENTREONPLUGINS$/centreon_cisco_standard_snmp.pl --plugin=network::cisco::standard::snmp::plugin --mode=environment --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='$_SERVICECOMPONENT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: COMPONENT, value: ".*", description: "Composants a verifier (regex)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Interfaces
+ alias: Interfaces
+ description: Etat et trafic des interfaces
+ line: "$CENTREONPLUGINS$/centreon_cisco_standard_snmp.pl --plugin=network::cisco::standard::snmp::plugin --mode=interfaces --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --interface='$_SERVICEINTERFACE$' --add-status --add-traffic --add-errors --warning-in-traffic='$_SERVICEWARNINGIN$' --critical-in-traffic='$_SERVICECRITICALIN$' --warning-out-traffic='$_SERVICEWARNINGOUT$' --critical-out-traffic='$_SERVICECRITICALOUT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: INTERFACE, value: "", description: "Filtre sur le nom de l'interface"}
+ - {name: WARNINGIN, value: "80", description: "Seuil warning trafic entrant (%)"}
+ - {name: CRITICALIN, value: "90", description: "Seuil critical trafic entrant (%)"}
+ - {name: WARNINGOUT, value: "80", description: "Seuil warning trafic sortant (%)"}
+ - {name: CRITICALOUT, value: "90", description: "Seuil critical trafic sortant (%)"}
+ - {name: EXTRAOPTIONS, value: "--interface-use-adminstatus --name", description: "Options supplementaires"}
+ - name: Uptime
+ alias: Uptime
+ description: Duree depuis le dernier redemarrage
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_cisco_standard_snmp.pl --plugin=network::cisco::standard::snmp::plugin --mode=uptime --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-uptime='$_SERVICEWARNING$' --critical-uptime='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning (secondes)"}
+ - {name: CRITICAL, value: "@0:600", description: "Seuil critical (secondes)"}
+ - {name: EXTRAOPTIONS, value: "--unit=s", description: "Options supplementaires"}
diff --git a/catalog/hardware/hw-dell-idrac-snmp.yml b/catalog/hardware/hw-dell-idrac-snmp.yml
new file mode 100644
index 0000000..f85ce9b
--- /dev/null
+++ b/catalog/hardware/hw-dell-idrac-snmp.yml
@@ -0,0 +1,53 @@
+id: hw-dell-idrac-snmp
+name: Serveur Dell (iDRAC SNMP)
+category: hardware
+status: verified
+plugin: centreon_dell_idrac_snmp.pl
+description: Etat materiel d'un serveur Dell PowerEdge via la carte iDRAC en SNMP (global, alimentations, ventilateurs, temperatures, disques, memoire).
+host_template:
+ name: HW-Dell-iDRAC-SNMP-custom
+ alias: Materiel Dell iDRAC SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Hardware
+ alias: Hardware
+ description: Etat global de tous les composants materiels
+ line: "$CENTREONPLUGINS$/centreon_dell_idrac_snmp.pl --plugin=hardware::server::dell::idrac::snmp::plugin --mode=hardware --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='$_SERVICECOMPONENT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: COMPONENT, value: ".*", description: "Composants a verifier (regex)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Hardware-Psu
+ alias: Hardware-Psu
+ description: Etat des alimentations
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_dell_idrac_snmp.pl --plugin=hardware::server::dell::idrac::snmp::plugin --mode=hardware --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='psu' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Hardware-Fan
+ alias: Hardware-Fan
+ description: Etat des ventilateurs
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_dell_idrac_snmp.pl --plugin=hardware::server::dell::idrac::snmp::plugin --mode=hardware --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='fan' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Hardware-Temperature
+ alias: Hardware-Temperature
+ description: Sondes de temperature
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_dell_idrac_snmp.pl --plugin=hardware::server::dell::idrac::snmp::plugin --mode=hardware --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='temperature' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning (ex: temperature,.*,40)"}
+ - {name: CRITICAL, value: "", description: "Seuil critical (ex: temperature,.*,50)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Hardware-Disk
+ alias: Hardware-Disk
+ description: Etat des disques physiques et des volumes logiques
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_dell_idrac_snmp.pl --plugin=hardware::server::dell::idrac::snmp::plugin --mode=hardware --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='$_SERVICECOMPONENT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: COMPONENT, value: "pdisk,vdisk", description: "Composants disques"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/hardware/hw-fortinet-fortigate-snmp.yml b/catalog/hardware/hw-fortinet-fortigate-snmp.yml
new file mode 100644
index 0000000..1d030d6
--- /dev/null
+++ b/catalog/hardware/hw-fortinet-fortigate-snmp.yml
@@ -0,0 +1,60 @@
+id: hw-fortinet-fortigate-snmp
+name: Firewall Fortinet FortiGate (SNMP)
+category: hardware
+status: verified
+plugin: centreon_fortinet_fortigate.pl
+description: Supervision d'un FortiGate en SNMP (CPU, memoire, disque, sessions, tunnels VPN, cluster HA, interfaces).
+host_template:
+ name: HW-Fortinet-FortiGate-SNMP-custom
+ alias: Materiel Fortinet FortiGate SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU du boitier
+ line: "$CENTREONPLUGINS$/centreon_fortinet_fortigate.pl --plugin=network::fortinet::fortigate::snmp::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-average='$_SERVICEWARNING$' --critical-average='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Memory
+ alias: Memory
+ description: Utilisation memoire
+ line: "$CENTREONPLUGINS$/centreon_fortinet_fortigate.pl --plugin=network::fortinet::fortigate::snmp::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Sessions
+ alias: Sessions
+ description: Nombre de sessions actives
+ line: "$CENTREONPLUGINS$/centreon_fortinet_fortigate.pl --plugin=network::fortinet::fortigate::snmp::plugin --mode=sessions --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning sur le nombre de sessions"}
+ - {name: CRITICAL, value: "", description: "Seuil critical sur le nombre de sessions"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Vpn
+ alias: Vpn
+ description: Etat des tunnels VPN IPSec
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_fortinet_fortigate.pl --plugin=network::fortinet::fortigate::snmp::plugin --mode=vpn --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Ha
+ alias: Ha
+ description: Etat du cluster haute disponibilite
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_fortinet_fortigate.pl --plugin=network::fortinet::fortigate::snmp::plugin --mode=ha --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Interfaces
+ alias: Interfaces
+ description: Etat et trafic des interfaces
+ line: "$CENTREONPLUGINS$/centreon_fortinet_fortigate.pl --plugin=network::fortinet::fortigate::snmp::plugin --mode=interfaces --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --interface='$_SERVICEINTERFACE$' --add-status --add-traffic $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: INTERFACE, value: "", description: "Filtre sur le nom de l'interface"}
+ - {name: EXTRAOPTIONS, value: "--interface-use-adminstatus --name", description: "Options supplementaires"}
diff --git a/catalog/hardware/hw-hp-proliant-snmp.yml b/catalog/hardware/hw-hp-proliant-snmp.yml
new file mode 100644
index 0000000..75bad05
--- /dev/null
+++ b/catalog/hardware/hw-hp-proliant-snmp.yml
@@ -0,0 +1,44 @@
+id: hw-hp-proliant-snmp
+name: Serveur HP ProLiant (iLO SNMP)
+category: hardware
+status: verified
+plugin: centreon_hp_proliant_snmp.pl
+description: Etat materiel d'un serveur HP ProLiant via les agents Insight / iLO en SNMP.
+host_template:
+ name: HW-HP-Proliant-SNMP-custom
+ alias: Materiel HP ProLiant SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Hardware
+ alias: Hardware
+ description: Etat global de tous les composants materiels
+ line: "$CENTREONPLUGINS$/centreon_hp_proliant_snmp.pl --plugin=hardware::server::hp::proliant::snmp::plugin --mode=hardware --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='$_SERVICECOMPONENT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: COMPONENT, value: ".*", description: "Composants a verifier (regex)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Hardware-Psu
+ alias: Hardware-Psu
+ description: Etat des alimentations
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_hp_proliant_snmp.pl --plugin=hardware::server::hp::proliant::snmp::plugin --mode=hardware --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='psu' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Hardware-Fan
+ alias: Hardware-Fan
+ description: Etat des ventilateurs
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_hp_proliant_snmp.pl --plugin=hardware::server::hp::proliant::snmp::plugin --mode=hardware --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='fan' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Hardware-Storage
+ alias: Hardware-Storage
+ description: Controleurs, disques physiques et volumes logiques
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_hp_proliant_snmp.pl --plugin=hardware::server::hp::proliant::snmp::plugin --mode=hardware --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='$_SERVICECOMPONENT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: COMPONENT, value: "pdrive,ldrive,ctrl", description: "Composants stockage"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/hardware/hw-juniper-junos-snmp.yml b/catalog/hardware/hw-juniper-junos-snmp.yml
new file mode 100644
index 0000000..275c28e
--- /dev/null
+++ b/catalog/hardware/hw-juniper-junos-snmp.yml
@@ -0,0 +1,45 @@
+id: hw-juniper-junos-snmp
+name: Juniper JunOS (SNMP)
+category: hardware
+status: to-verify
+plugin: centreon_juniper_junos_snmp.pl
+description: Supervision d'un equipement Juniper sous JunOS en SNMP. Verifier le nom du script (centreon_juniper_junos_snmp.pl) sur l'installation cible.
+host_template:
+ name: HW-Juniper-JunOS-SNMP-custom
+ alias: Materiel Juniper JunOS SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU du routing engine
+ line: "$CENTREONPLUGINS$/centreon_juniper_junos_snmp.pl --plugin=network::juniper::common::junos::snmp::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Memory
+ alias: Memory
+ description: Utilisation memoire
+ line: "$CENTREONPLUGINS$/centreon_juniper_junos_snmp.pl --plugin=network::juniper::common::junos::snmp::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Hardware
+ alias: Hardware
+ description: Etat des composants materiels
+ line: "$CENTREONPLUGINS$/centreon_juniper_junos_snmp.pl --plugin=network::juniper::common::junos::snmp::plugin --mode=hardware --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='$_SERVICECOMPONENT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: COMPONENT, value: ".*", description: "Composants a verifier (regex)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Interfaces
+ alias: Interfaces
+ description: Etat et trafic des interfaces
+ line: "$CENTREONPLUGINS$/centreon_juniper_junos_snmp.pl --plugin=network::juniper::common::junos::snmp::plugin --mode=interfaces --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --interface='$_SERVICEINTERFACE$' --add-status --add-traffic $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: INTERFACE, value: "", description: "Filtre sur le nom de l'interface"}
+ - {name: EXTRAOPTIONS, value: "--interface-use-adminstatus --name", description: "Options supplementaires"}
diff --git a/catalog/hardware/hw-netapp-ontap-snmp.yml b/catalog/hardware/hw-netapp-ontap-snmp.yml
new file mode 100644
index 0000000..72efd69
--- /dev/null
+++ b/catalog/hardware/hw-netapp-ontap-snmp.yml
@@ -0,0 +1,42 @@
+id: hw-netapp-ontap-snmp
+name: Baie NetApp ONTAP (SNMP)
+category: hardware
+status: to-verify
+plugin: centreon_netapp_ontap_snmp.pl
+description: Supervision d'une baie NetApp ONTAP en SNMP (etat global, volumes, disques, replication). Verifier les modes selon la version ONTAP.
+host_template:
+ name: HW-NetApp-ONTAP-SNMP-custom
+ alias: Materiel NetApp ONTAP SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Global-Status
+ alias: Global-Status
+ description: Etat global de la baie
+ line: "$CENTREONPLUGINS$/centreon_netapp_ontap_snmp.pl --plugin=storage::netapp::ontap::snmp::plugin --mode=global-status --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Volume-Usage
+ alias: Volume-Usage
+ description: Occupation des volumes
+ line: "$CENTREONPLUGINS$/centreon_netapp_ontap_snmp.pl --plugin=storage::netapp::ontap::snmp::plugin --mode=volume-usage --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Disk-Failed
+ alias: Disk-Failed
+ description: Nombre de disques en defaut
+ line: "$CENTREONPLUGINS$/centreon_netapp_ontap_snmp.pl --plugin=storage::netapp::ontap::snmp::plugin --mode=disk-failed --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Snapmirror-Status
+ alias: Snapmirror-Status
+ description: Etat des relations SnapMirror
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_netapp_ontap_snmp.pl --plugin=storage::netapp::ontap::snmp::plugin --mode=snapmirror-status --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/hardware/hw-printer-rfc3805.yml b/catalog/hardware/hw-printer-rfc3805.yml
new file mode 100644
index 0000000..b2f5fdd
--- /dev/null
+++ b/catalog/hardware/hw-printer-rfc3805.yml
@@ -0,0 +1,37 @@
+id: hw-printer-rfc3805
+name: Imprimante reseau (RFC 3805)
+category: hardware
+status: to-verify
+plugin: centreon_printer_standard_rfc3805.pl
+description: Supervision d'une imprimante compatible RFC 3805 (niveaux de consommables, bacs papier, etat general). Verifier les modes disponibles.
+host_template:
+ name: HW-Printer-RFC3805-custom
+ alias: Materiel Imprimante RFC3805
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Marker-Supply
+ alias: Marker-Supply
+ description: Niveau des consommables (toner, tambour)
+ line: "$CENTREONPLUGINS$/centreon_printer_standard_rfc3805.pl --plugin=hardware::printers::standard::rfc3805::plugin --mode=marker-supply --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "20:", description: "Seuil warning sur le niveau restant (%)"}
+ - {name: CRITICAL, value: "10:", description: "Seuil critical sur le niveau restant (%)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Papertray
+ alias: Papertray
+ description: Niveau des bacs a papier
+ line: "$CENTREONPLUGINS$/centreon_printer_standard_rfc3805.pl --plugin=hardware::printers::standard::rfc3805::plugin --mode=papertray --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "20:", description: "Seuil warning sur le niveau restant (%)"}
+ - {name: CRITICAL, value: "5:", description: "Seuil critical sur le niveau restant (%)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Printer-Status
+ alias: Printer-Status
+ description: Etat general de l'imprimante
+ line: "$CENTREONPLUGINS$/centreon_printer_standard_rfc3805.pl --plugin=hardware::printers::standard::rfc3805::plugin --mode=printer --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/hardware/hw-synology-snmp.yml b/catalog/hardware/hw-synology-snmp.yml
new file mode 100644
index 0000000..9f13546
--- /dev/null
+++ b/catalog/hardware/hw-synology-snmp.yml
@@ -0,0 +1,46 @@
+id: hw-synology-snmp
+name: NAS Synology (SNMP)
+category: hardware
+status: to-verify
+plugin: centreon_synology_snmp.pl
+description: Supervision d'un NAS Synology en SNMP (composants materiels, CPU, memoire, volumes). Verifier les modes selon la version DSM.
+host_template:
+ name: HW-Synology-SNMP-custom
+ alias: Materiel Synology SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Hardware
+ alias: Hardware
+ description: Etat des composants (ventilateurs, disques, alimentation)
+ line: "$CENTREONPLUGINS$/centreon_synology_snmp.pl --plugin=storage::synology::snmp::plugin --mode=components --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --component='$_SERVICECOMPONENT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: COMPONENT, value: ".*", description: "Composants a verifier (regex)"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU du NAS
+ line: "$CENTREONPLUGINS$/centreon_synology_snmp.pl --plugin=storage::synology::snmp::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-average='$_SERVICEWARNING$' --critical-average='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Memory
+ alias: Memory
+ description: Utilisation memoire
+ line: "$CENTREONPLUGINS$/centreon_synology_snmp.pl --plugin=storage::synology::snmp::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Storage
+ alias: Storage
+ description: Occupation des volumes
+ line: "$CENTREONPLUGINS$/centreon_synology_snmp.pl --plugin=storage::synology::snmp::plugin --mode=storage --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
diff --git a/catalog/system/os-aix-snmp.yml b/catalog/system/os-aix-snmp.yml
new file mode 100644
index 0000000..32e1650
--- /dev/null
+++ b/catalog/system/os-aix-snmp.yml
@@ -0,0 +1,54 @@
+id: os-aix-snmp
+name: AIX (SNMP)
+category: system
+status: to-verify
+plugin: centreon_aix_snmp.pl
+description: Supervision d'un serveur AIX via SNMP. Verifier le nom exact du plugin sur la version de centreon-plugins installee.
+host_template:
+ name: OS-AIX-SNMP-custom
+ alias: OS AIX SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU globale
+ line: "$CENTREONPLUGINS$/centreon_aix_snmp.pl --plugin=os::aix::snmp::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-average='$_SERVICEWARNING$' --critical-average='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Memory
+ alias: Memory
+ description: Utilisation de la memoire physique
+ line: "$CENTREONPLUGINS$/centreon_aix_snmp.pl --plugin=os::aix::snmp::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Swap
+ alias: Swap
+ description: Utilisation du swap
+ line: "$CENTREONPLUGINS$/centreon_aix_snmp.pl --plugin=os::aix::snmp::plugin --mode=swap --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "50", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "75", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Storage
+ alias: Storage
+ description: Occupation des systemes de fichiers
+ line: "$CENTREONPLUGINS$/centreon_aix_snmp.pl --plugin=os::aix::snmp::plugin --mode=storage --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Interfaces
+ alias: Interfaces
+ description: Etat et trafic des interfaces reseau
+ line: "$CENTREONPLUGINS$/centreon_aix_snmp.pl --plugin=os::aix::snmp::plugin --mode=interfaces --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --interface='$_SERVICEINTERFACE$' --add-status --add-traffic $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: INTERFACE, value: "", description: "Filtre sur le nom de l'interface"}
+ - {name: EXTRAOPTIONS, value: "--interface-use-adminstatus --name", description: "Options supplementaires"}
diff --git a/catalog/system/os-freebsd-snmp.yml b/catalog/system/os-freebsd-snmp.yml
new file mode 100644
index 0000000..f128dbc
--- /dev/null
+++ b/catalog/system/os-freebsd-snmp.yml
@@ -0,0 +1,54 @@
+id: os-freebsd-snmp
+name: FreeBSD (SNMP)
+category: system
+status: to-verify
+plugin: centreon_freebsd_snmp.pl
+description: Supervision d'un serveur FreeBSD via SNMP. Verifier le nom exact du plugin sur la version de centreon-plugins installee.
+host_template:
+ name: OS-FreeBSD-SNMP-custom
+ alias: OS FreeBSD SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU globale
+ line: "$CENTREONPLUGINS$/centreon_freebsd_snmp.pl --plugin=os::freebsd::snmp::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-average='$_SERVICEWARNING$' --critical-average='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Memory
+ alias: Memory
+ description: Utilisation de la memoire physique
+ line: "$CENTREONPLUGINS$/centreon_freebsd_snmp.pl --plugin=os::freebsd::snmp::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Swap
+ alias: Swap
+ description: Utilisation du swap
+ line: "$CENTREONPLUGINS$/centreon_freebsd_snmp.pl --plugin=os::freebsd::snmp::plugin --mode=swap --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "50", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "75", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Storage
+ alias: Storage
+ description: Occupation des systemes de fichiers
+ line: "$CENTREONPLUGINS$/centreon_freebsd_snmp.pl --plugin=os::freebsd::snmp::plugin --mode=storage --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Interfaces
+ alias: Interfaces
+ description: Etat et trafic des interfaces reseau
+ line: "$CENTREONPLUGINS$/centreon_freebsd_snmp.pl --plugin=os::freebsd::snmp::plugin --mode=interfaces --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --interface='$_SERVICEINTERFACE$' --add-status --add-traffic $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: INTERFACE, value: "", description: "Filtre sur le nom de l'interface"}
+ - {name: EXTRAOPTIONS, value: "--interface-use-adminstatus --name", description: "Options supplementaires"}
diff --git a/catalog/system/os-generic-snmp.yml b/catalog/system/os-generic-snmp.yml
new file mode 100644
index 0000000..a4f9cee
--- /dev/null
+++ b/catalog/system/os-generic-snmp.yml
@@ -0,0 +1,58 @@
+id: os-generic-snmp
+name: Equipement generique (SNMP standard)
+category: system
+status: verified
+plugin: centreon_generic_snmp.pl
+description: Supervision de tout equipement repondant aux MIB standard (HOST-RESOURCES, UCD, IF-MIB). Base de repli quand aucun pack constructeur ne correspond.
+host_template:
+ name: OS-Generic-SNMP-custom
+ alias: Generique SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU via HOST-RESOURCES-MIB
+ line: "$CENTREONPLUGINS$/centreon_generic_snmp.pl --plugin=snmp_standard::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-average='$_SERVICEWARNING$' --critical-average='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Memory
+ alias: Memory
+ description: Memoire via UCD-SNMP-MIB
+ line: "$CENTREONPLUGINS$/centreon_generic_snmp.pl --plugin=snmp_standard::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Storage
+ alias: Storage
+ description: Occupation des volumes declares en HOST-RESOURCES
+ line: "$CENTREONPLUGINS$/centreon_generic_snmp.pl --plugin=snmp_standard::plugin --mode=storage --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Interfaces
+ alias: Interfaces
+ description: Etat et trafic des interfaces (IF-MIB)
+ line: "$CENTREONPLUGINS$/centreon_generic_snmp.pl --plugin=snmp_standard::plugin --mode=interfaces --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --interface='$_SERVICEINTERFACE$' --add-status --add-traffic --warning-in-traffic='$_SERVICEWARNINGIN$' --critical-in-traffic='$_SERVICECRITICALIN$' --warning-out-traffic='$_SERVICEWARNINGOUT$' --critical-out-traffic='$_SERVICECRITICALOUT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: INTERFACE, value: "", description: "Filtre sur le nom de l'interface"}
+ - {name: WARNINGIN, value: "80", description: "Seuil warning trafic entrant (%)"}
+ - {name: CRITICALIN, value: "90", description: "Seuil critical trafic entrant (%)"}
+ - {name: WARNINGOUT, value: "80", description: "Seuil warning trafic sortant (%)"}
+ - {name: CRITICALOUT, value: "90", description: "Seuil critical trafic sortant (%)"}
+ - {name: EXTRAOPTIONS, value: "--interface-use-adminstatus --name", description: "Options supplementaires"}
+ - name: Uptime
+ alias: Uptime
+ description: Duree depuis le dernier redemarrage
+ line: "$CENTREONPLUGINS$/centreon_generic_snmp.pl --plugin=snmp_standard::plugin --mode=uptime --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-uptime='$_SERVICEWARNING$' --critical-uptime='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning (secondes)"}
+ - {name: CRITICAL, value: "@0:600", description: "Seuil critical (secondes)"}
+ - {name: EXTRAOPTIONS, value: "--unit=s", description: "Options supplementaires"}
diff --git a/catalog/system/os-linux-nrpe.yml b/catalog/system/os-linux-nrpe.yml
new file mode 100644
index 0000000..d2a715f
--- /dev/null
+++ b/catalog/system/os-linux-nrpe.yml
@@ -0,0 +1,56 @@
+id: os-linux-nrpe
+name: Linux (NRPE)
+category: system
+status: verified
+plugin: check_centreon_nrpe
+description: Supervision Linux par agent NRPE (commandes definies cote client dans nrpe.cfg).
+host_template:
+ name: OS-Linux-NRPE-custom
+ alias: OS Linux NRPE
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: NRPEPORT, value: "5666", description: "Port d'ecoute de l'agent NRPE"}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU via la commande NRPE distante
+ line: "$USER1$/check_centreon_nrpe -H $HOSTADDRESS$ -p $_HOSTNRPEPORT$ -t $_SERVICETIMEOUT$ -c $_SERVICECOMMAND$ -a '$_SERVICEARGS$'"
+ macros:
+ - {name: COMMAND, value: "check_cpu", description: "Nom de la commande NRPE"}
+ - {name: ARGS, value: "80!90", description: "Arguments passes a la commande"}
+ - {name: TIMEOUT, value: "30", description: "Timeout en secondes"}
+ - name: Load
+ alias: Load
+ description: Charge systeme via NRPE
+ line: "$USER1$/check_centreon_nrpe -H $HOSTADDRESS$ -p $_HOSTNRPEPORT$ -t $_SERVICETIMEOUT$ -c $_SERVICECOMMAND$ -a '$_SERVICEARGS$'"
+ macros:
+ - {name: COMMAND, value: "check_load", description: "Nom de la commande NRPE"}
+ - {name: ARGS, value: "4,3,2!6,5,4", description: "Arguments passes a la commande"}
+ - {name: TIMEOUT, value: "30", description: "Timeout en secondes"}
+ - name: Memory
+ alias: Memory
+ description: Memoire disponible via NRPE
+ line: "$USER1$/check_centreon_nrpe -H $HOSTADDRESS$ -p $_HOSTNRPEPORT$ -t $_SERVICETIMEOUT$ -c $_SERVICECOMMAND$ -a '$_SERVICEARGS$'"
+ macros:
+ - {name: COMMAND, value: "check_memory", description: "Nom de la commande NRPE"}
+ - {name: ARGS, value: "80!90", description: "Arguments passes a la commande"}
+ - {name: TIMEOUT, value: "30", description: "Timeout en secondes"}
+ - name: Disk
+ alias: Disk
+ description: Occupation d'un point de montage via NRPE
+ line: "$USER1$/check_centreon_nrpe -H $HOSTADDRESS$ -p $_HOSTNRPEPORT$ -t $_SERVICETIMEOUT$ -c $_SERVICECOMMAND$ -a '$_SERVICEARGS$'"
+ macros:
+ - {name: COMMAND, value: "check_disk", description: "Nom de la commande NRPE"}
+ - {name: ARGS, value: "80!90!/", description: "Arguments passes a la commande"}
+ - {name: TIMEOUT, value: "30", description: "Timeout en secondes"}
+ - name: Users
+ alias: Users
+ description: Nombre d'utilisateurs connectes
+ default: false
+ line: "$USER1$/check_centreon_nrpe -H $HOSTADDRESS$ -p $_HOSTNRPEPORT$ -t $_SERVICETIMEOUT$ -c $_SERVICECOMMAND$ -a '$_SERVICEARGS$'"
+ macros:
+ - {name: COMMAND, value: "check_users", description: "Nom de la commande NRPE"}
+ - {name: ARGS, value: "5!10", description: "Arguments passes a la commande"}
+ - {name: TIMEOUT, value: "30", description: "Timeout en secondes"}
diff --git a/catalog/system/os-linux-snmp.yml b/catalog/system/os-linux-snmp.yml
new file mode 100644
index 0000000..ae9cee5
--- /dev/null
+++ b/catalog/system/os-linux-snmp.yml
@@ -0,0 +1,86 @@
+id: os-linux-snmp
+name: Linux (SNMP)
+category: system
+status: verified
+plugin: centreon_linux_snmp.pl
+description: Supervision d'un serveur Linux via SNMP avec centreon-plugins (CPU, memoire, swap, systemes de fichiers, charge, interfaces, uptime).
+host_template:
+ name: OS-Linux-SNMP-custom
+ alias: OS Linux SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU moyenne toutes unites confondues
+ line: "$CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-average='$_SERVICEWARNING$' --critical-average='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en % de CPU"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en % de CPU"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Memory
+ alias: Memory
+ description: Utilisation de la memoire physique
+ line: "$CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en % utilise"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en % utilise"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Swap
+ alias: Swap
+ description: Utilisation du swap
+ line: "$CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=swap --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "50", description: "Seuil warning en % utilise"}
+ - {name: CRITICAL, value: "75", description: "Seuil critical en % utilise"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Load
+ alias: Load
+ description: Charge systeme 1/5/15 minutes
+ line: "$CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=load --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-load='$_SERVICEWARNING$' --critical-load='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "4,3,2", description: "Seuils warning load 1,5,15"}
+ - {name: CRITICAL, value: "6,5,4", description: "Seuils critical load 1,5,15"}
+ - {name: EXTRAOPTIONS, value: "--average", description: "Options supplementaires"}
+ - name: Storage
+ alias: Storage
+ description: Occupation de tous les systemes de fichiers locaux
+ line: "$CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=storage --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --filter-storage-type='$_SERVICEFILTERSTORAGETYPE$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en % utilise"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en % utilise"}
+ - {name: FILTERSTORAGETYPE, value: "^(hrStorageFixedDisk|hrStorageNetworkDisk)$", description: "Filtre sur le type de stockage"}
+ - {name: EXTRAOPTIONS, value: "--units=% --free", description: "Options supplementaires"}
+ - name: Interfaces
+ alias: Interfaces
+ description: Etat et trafic des interfaces reseau
+ line: "$CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=interfaces --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --interface='$_SERVICEINTERFACE$' --add-status --add-traffic --warning-in-traffic='$_SERVICEWARNINGIN$' --critical-in-traffic='$_SERVICECRITICALIN$' --warning-out-traffic='$_SERVICEWARNINGOUT$' --critical-out-traffic='$_SERVICECRITICALOUT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: INTERFACE, value: "", description: "Filtre sur le nom de l'interface"}
+ - {name: WARNINGIN, value: "80", description: "Seuil warning trafic entrant (%)"}
+ - {name: CRITICALIN, value: "90", description: "Seuil critical trafic entrant (%)"}
+ - {name: WARNINGOUT, value: "80", description: "Seuil warning trafic sortant (%)"}
+ - {name: CRITICALOUT, value: "90", description: "Seuil critical trafic sortant (%)"}
+ - {name: EXTRAOPTIONS, value: "--interface-use-adminstatus --name", description: "Options supplementaires"}
+ - name: Uptime
+ alias: Uptime
+ description: Duree depuis le dernier redemarrage
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=uptime --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-uptime='$_SERVICEWARNING$' --critical-uptime='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning (secondes)"}
+ - {name: CRITICAL, value: "@0:600", description: "Seuil critical (secondes)"}
+ - {name: EXTRAOPTIONS, value: "--unit=s", description: "Options supplementaires"}
+ - name: Process-Count
+ alias: Process-Count
+ description: Nombre de processus correspondant a un filtre
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=processcount --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --process-name='$_SERVICEPROCESSNAME$' --warning-number='$_SERVICEWARNING$' --critical-number='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: PROCESSNAME, value: "", description: "Nom du processus a compter"}
+ - {name: WARNING, value: "", description: "Seuil warning sur le nombre"}
+ - {name: CRITICAL, value: "1:", description: "Seuil critical sur le nombre"}
+ - {name: EXTRAOPTIONS, value: "--regexp", description: "Options supplementaires"}
diff --git a/catalog/system/os-solaris-snmp.yml b/catalog/system/os-solaris-snmp.yml
new file mode 100644
index 0000000..f141ee9
--- /dev/null
+++ b/catalog/system/os-solaris-snmp.yml
@@ -0,0 +1,54 @@
+id: os-solaris-snmp
+name: Solaris (SNMP)
+category: system
+status: to-verify
+plugin: centreon_solaris_snmp.pl
+description: Supervision d'un serveur Solaris via SNMP. Verifier le nom exact du plugin sur la version de centreon-plugins installee.
+host_template:
+ name: OS-Solaris-SNMP-custom
+ alias: OS Solaris SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU globale
+ line: "$CENTREONPLUGINS$/centreon_solaris_snmp.pl --plugin=os::solaris::snmp::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-average='$_SERVICEWARNING$' --critical-average='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Memory
+ alias: Memory
+ description: Utilisation de la memoire physique
+ line: "$CENTREONPLUGINS$/centreon_solaris_snmp.pl --plugin=os::solaris::snmp::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Swap
+ alias: Swap
+ description: Utilisation du swap
+ line: "$CENTREONPLUGINS$/centreon_solaris_snmp.pl --plugin=os::solaris::snmp::plugin --mode=swap --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "50", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "75", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Storage
+ alias: Storage
+ description: Occupation des systemes de fichiers
+ line: "$CENTREONPLUGINS$/centreon_solaris_snmp.pl --plugin=os::solaris::snmp::plugin --mode=storage --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Interfaces
+ alias: Interfaces
+ description: Etat et trafic des interfaces reseau
+ line: "$CENTREONPLUGINS$/centreon_solaris_snmp.pl --plugin=os::solaris::snmp::plugin --mode=interfaces --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --interface='$_SERVICEINTERFACE$' --add-status --add-traffic $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: INTERFACE, value: "", description: "Filtre sur le nom de l'interface"}
+ - {name: EXTRAOPTIONS, value: "--interface-use-adminstatus --name", description: "Options supplementaires"}
diff --git a/catalog/system/os-windows-nsclient.yml b/catalog/system/os-windows-nsclient.yml
new file mode 100644
index 0000000..e56a80b
--- /dev/null
+++ b/catalog/system/os-windows-nsclient.yml
@@ -0,0 +1,49 @@
+id: os-windows-nsclient
+name: Windows (NSClient++ / check_nt)
+category: system
+status: verified
+plugin: check_nt
+description: Supervision Windows par l'agent NSClient++ en protocole NSC (check_nt), utile quand SNMP n'est pas ouvert.
+host_template:
+ name: OS-Windows-NSClient-custom
+ alias: OS Windows NSClient
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: NSCLIENTPORT, value: "12489", description: "Port d'ecoute NSClient++"}
+ - {name: NSCLIENTPASSWORD, value: "", description: "Mot de passe NSClient++", is_password: true}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU sur 1, 5 et 15 minutes
+ line: "$USER1$/check_nt -H $HOSTADDRESS$ -p $_HOSTNSCLIENTPORT$ -s '$_HOSTNSCLIENTPASSWORD$' -v CPULOAD -l $_SERVICECPULOAD$"
+ macros:
+ - {name: CPULOAD, value: "1,80,90,5,80,90,15,75,85", description: "Triplets minutes,warning,critical"}
+ - name: Memory
+ alias: Memory
+ description: Utilisation de la memoire
+ line: "$USER1$/check_nt -H $HOSTADDRESS$ -p $_HOSTNSCLIENTPORT$ -s '$_HOSTNSCLIENTPASSWORD$' -v MEMUSE -w $_SERVICEWARNING$ -c $_SERVICECRITICAL$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - name: Disk-C
+ alias: Disk-C
+ description: Occupation d'un lecteur
+ line: "$USER1$/check_nt -H $HOSTADDRESS$ -p $_HOSTNSCLIENTPORT$ -s '$_HOSTNSCLIENTPASSWORD$' -v USEDDISKSPACE -l $_SERVICEDRIVE$ -w $_SERVICEWARNING$ -c $_SERVICECRITICAL$"
+ macros:
+ - {name: DRIVE, value: "C", description: "Lettre du lecteur"}
+ - {name: WARNING, value: "80", description: "Seuil warning en %"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en %"}
+ - name: Service
+ alias: Service
+ description: Etat d'un service Windows
+ line: "$USER1$/check_nt -H $HOSTADDRESS$ -p $_HOSTNSCLIENTPORT$ -s '$_HOSTNSCLIENTPASSWORD$' -v SERVICESTATE -l $_SERVICESERVICENAME$"
+ macros:
+ - {name: SERVICENAME, value: "", description: "Nom court du service Windows"}
+ - name: Uptime
+ alias: Uptime
+ description: Duree depuis le dernier redemarrage
+ default: false
+ line: "$USER1$/check_nt -H $HOSTADDRESS$ -p $_HOSTNSCLIENTPORT$ -s '$_HOSTNSCLIENTPASSWORD$' -v UPTIME"
+ macros: []
diff --git a/catalog/system/os-windows-snmp.yml b/catalog/system/os-windows-snmp.yml
new file mode 100644
index 0000000..182b886
--- /dev/null
+++ b/catalog/system/os-windows-snmp.yml
@@ -0,0 +1,78 @@
+id: os-windows-snmp
+name: Windows (SNMP)
+category: system
+status: verified
+plugin: centreon_windows_snmp.pl
+description: Supervision d'un serveur Windows via SNMP (CPU, memoire, disques, services, interfaces, uptime).
+host_template:
+ name: OS-Windows-SNMP-custom
+ alias: OS Windows SNMP
+ parents: [generic-active-host-custom]
+ check_command: check_centreon_ping
+ check_command_args: "!3!200,20%!400,50%"
+ macros:
+ - {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
+services:
+ - name: Cpu
+ alias: Cpu
+ description: Charge CPU globale
+ line: "$CENTREONPLUGINS$/centreon_windows_snmp.pl --plugin=os::windows::snmp::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-average='$_SERVICEWARNING$' --critical-average='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en % de CPU"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en % de CPU"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Memory
+ alias: Memory
+ description: Utilisation de la memoire physique
+ line: "$CENTREONPLUGINS$/centreon_windows_snmp.pl --plugin=os::windows::snmp::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en % utilise"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en % utilise"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Swap
+ alias: Swap
+ description: Utilisation du fichier d'echange
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_windows_snmp.pl --plugin=os::windows::snmp::plugin --mode=swap --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "50", description: "Seuil warning en % utilise"}
+ - {name: CRITICAL, value: "75", description: "Seuil critical en % utilise"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Storage
+ alias: Storage
+ description: Occupation des disques locaux
+ line: "$CENTREONPLUGINS$/centreon_windows_snmp.pl --plugin=os::windows::snmp::plugin --mode=storage --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --filter-storage-type='$_SERVICEFILTERSTORAGETYPE$' --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "80", description: "Seuil warning en % utilise"}
+ - {name: CRITICAL, value: "90", description: "Seuil critical en % utilise"}
+ - {name: FILTERSTORAGETYPE, value: "^(hrStorageFixedDisk)$", description: "Filtre sur le type de stockage"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Service
+ alias: Service
+ description: Etat d'un service Windows
+ line: "$CENTREONPLUGINS$/centreon_windows_snmp.pl --plugin=os::windows::snmp::plugin --mode=service --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --service='$_SERVICESERVICENAME$' --warning-status='$_SERVICEWARNINGSTATUS$' --critical-status='$_SERVICECRITICALSTATUS$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: SERVICENAME, value: "", description: "Nom du service Windows a superviser"}
+ - {name: WARNINGSTATUS, value: "", description: "Regle warning sur le statut"}
+ - {name: CRITICALSTATUS, value: "%{state} !~ /running/i", description: "Regle critical sur le statut"}
+ - {name: EXTRAOPTIONS, value: "", description: "Options supplementaires"}
+ - name: Interfaces
+ alias: Interfaces
+ description: Etat et trafic des interfaces reseau
+ line: "$CENTREONPLUGINS$/centreon_windows_snmp.pl --plugin=os::windows::snmp::plugin --mode=interfaces --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --interface='$_SERVICEINTERFACE$' --add-status --add-traffic --warning-in-traffic='$_SERVICEWARNINGIN$' --critical-in-traffic='$_SERVICECRITICALIN$' --warning-out-traffic='$_SERVICEWARNINGOUT$' --critical-out-traffic='$_SERVICECRITICALOUT$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: INTERFACE, value: "", description: "Filtre sur le nom de l'interface"}
+ - {name: WARNINGIN, value: "80", description: "Seuil warning trafic entrant (%)"}
+ - {name: CRITICALIN, value: "90", description: "Seuil critical trafic entrant (%)"}
+ - {name: WARNINGOUT, value: "80", description: "Seuil warning trafic sortant (%)"}
+ - {name: CRITICALOUT, value: "90", description: "Seuil critical trafic sortant (%)"}
+ - {name: EXTRAOPTIONS, value: "--interface-use-adminstatus --name", description: "Options supplementaires"}
+ - name: Uptime
+ alias: Uptime
+ description: Duree depuis le dernier redemarrage
+ default: false
+ line: "$CENTREONPLUGINS$/centreon_windows_snmp.pl --plugin=os::windows::snmp::plugin --mode=uptime --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' --warning-uptime='$_SERVICEWARNING$' --critical-uptime='$_SERVICECRITICAL$' $_HOSTSNMPEXTRAOPTIONS$ $_SERVICEEXTRAOPTIONS$"
+ macros:
+ - {name: WARNING, value: "", description: "Seuil warning (secondes)"}
+ - {name: CRITICAL, value: "@0:600", description: "Seuil critical (secondes)"}
+ - {name: EXTRAOPTIONS, value: "--unit=s", description: "Options supplementaires"}
diff --git a/compose.dev.yaml b/compose.dev.yaml
new file mode 100644
index 0000000..f851e3b
--- /dev/null
+++ b/compose.dev.yaml
@@ -0,0 +1,24 @@
+# Developpement local : image construite sur place, code monte en direct,
+# rechargement automatique, port publie.
+#
+# docker compose -f compose.dev.yaml up --build
+
+name: enclume-dev
+
+services:
+ web:
+ build:
+ context: .
+ args:
+ ENCLUME_VERSION: dev
+ command: >-
+ sh -c "exec python -m flask --app 'enclume.webapp:create_app()' run
+ --host 0.0.0.0 --port 8080 --debug"
+ environment:
+ ENCLUME_CATALOG: /app/catalog
+ FLASK_DEBUG: "1"
+ volumes:
+ - ./enclume:/app/enclume:ro
+ - ./catalog:/app/catalog:ro
+ ports:
+ - "127.0.0.1:8080:8080"
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000..13c8360
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,100 @@
+# Deploiement de production, derriere le Traefik existant.
+# Aucun port n'est publie : le trafic entre par le tunnel Cloudflare puis Traefik.
+#
+# cp .env.example .env puis renseigner ENCLUME_TAG et ENCLUME_DOMAINE
+# docker compose pull && docker compose up -d
+#
+# Retour arriere : remettre l'ancienne valeur de ENCLUME_TAG, puis up -d.
+
+name: enclume
+
+services:
+ web:
+ image: ${ENCLUME_REGISTRY:-harbor.tips-of-mine.com}/enclume/web:${ENCLUME_TAG:?definir ENCLUME_TAG dans .env}
+ restart: unless-stopped
+ environment:
+ ENCLUME_CATALOG: /catalogue
+ GUNICORN_WORKERS: ${GUNICORN_WORKERS:-2}
+ TZ: ${TZ:-Europe/Paris}
+ volumes:
+ - catalogue:/catalogue:ro
+ networks: [interne, traefik]
+ read_only: true
+ tmpfs:
+ - /tmp:size=32m,mode=1777
+ security_opt: ["no-new-privileges:true"]
+ cap_drop: [ALL]
+ deploy:
+ resources:
+ limits: {cpus: "1.0", memory: 512M}
+ logging:
+ driver: json-file
+ options: {max-size: "10m", max-file: "3"}
+ labels:
+ traefik.enable: "true"
+ traefik.docker.network: traefik
+ traefik.http.routers.enclume.rule: Host(`${ENCLUME_DOMAINE:?definir ENCLUME_DOMAINE}`)
+ traefik.http.routers.enclume.entrypoints: websecure
+ traefik.http.routers.enclume.tls.certresolver: ${TRAEFIK_RESOLVER:-cloudflare}
+ traefik.http.routers.enclume.middlewares: enclume-entetes,enclume-limite
+ traefik.http.services.enclume.loadbalancer.server.port: "8080"
+ traefik.http.middlewares.enclume-entetes.headers.stsSeconds: "31536000"
+ traefik.http.middlewares.enclume-entetes.headers.stsIncludeSubdomains: "true"
+ traefik.http.middlewares.enclume-entetes.headers.frameDeny: "true"
+ traefik.http.middlewares.enclume-entetes.headers.contentTypeNosniff: "true"
+ traefik.http.middlewares.enclume-limite.ratelimit.average: "60"
+ traefik.http.middlewares.enclume-limite.ratelimit.burst: "120"
+
+ catalog-sync:
+ image: ${ENCLUME_REGISTRY:-harbor.tips-of-mine.com}/enclume/catalog-sync:${ENCLUME_TAG:?definir ENCLUME_TAG dans .env}
+ restart: unless-stopped
+ environment:
+ CATALOG_REPO: ${CATALOG_REPO:-}
+ CATALOG_BRANCH: ${CATALOG_BRANCH:-main}
+ CATALOG_SUBDIR: ${CATALOG_SUBDIR:-catalog}
+ CATALOG_DEST: /catalogue
+ SYNC_INTERVAL: ${SYNC_INTERVAL:-300}
+ RELOAD_URL: http://web:8080/recharger
+ volumes:
+ - catalogue:/catalogue
+ networks: [interne]
+ security_opt: ["no-new-privileges:true"]
+ deploy:
+ resources:
+ limits: {cpus: "0.25", memory: 128M}
+ logging:
+ driver: json-file
+ options: {max-size: "5m", max-file: "2"}
+
+ # Utilisee a partir du chantier 2 (liens de partage, projets enregistres).
+ # L'application fonctionne sans, tant que ENCLUME_DATABASE_URL n'est pas defini.
+ db:
+ image: postgres:16.4-alpine
+ restart: unless-stopped
+ environment:
+ POSTGRES_DB: enclume
+ POSTGRES_USER: enclume
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?definir POSTGRES_PASSWORD}
+ TZ: ${TZ:-Europe/Paris}
+ volumes:
+ - donnees:/var/lib/postgresql/data
+ networks: [interne]
+ security_opt: ["no-new-privileges:true"]
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U enclume -d enclume"]
+ interval: 20s
+ timeout: 5s
+ retries: 5
+ deploy:
+ resources:
+ limits: {cpus: "1.0", memory: 512M}
+
+volumes:
+ catalogue:
+ donnees:
+
+networks:
+ interne:
+ internal: true
+ traefik:
+ external: true
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 4054566..0000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-#### NETWORKS
-networks:
- traefik_front_network:
- external: true
- back_network_:
- driver: bridge
- attachable: true
-
-#### SERVICES
-services:
-### hello_world
- hello_world:
- container_name: gitea-app
- hostname: gitea-app
- image: hello-world
- environment:
- restart: always
- networks:
-# - back_network_gitea
- - traefik_front_network
- volumes:
- labels:
- - "traefik.enable=true"
- - "traefik.docker.network=traefik_front_network"
-# HTTP
- - "traefik.http.routers.hello-world-http.rule=Host(`hello-world.tips-of-mine.com`)"
- - "traefik.http.routers.hello-world-http.entrypoints=http"
- - "traefik.http.routers.hello-world-http.priority=49"
-# HTTPS
- - "traefik.http.routers.hello-world-https.rule=Host(`hello-world.tips-of-mine.com`)"
- - "traefik.http.routers.hello-world-https.entrypoints=https"
- - "traefik.http.routers.hello-world-https.tls=true"
- - "traefik.http.routers.hello-world-https.priority=50"
- - "traefik.http.routers.gitea.service=gitea-https-service"
-# Middleware
-# Service
-# - "traefik.http.services.gitea-https-service.loadbalancer.server.port=3000"
-# - "traefik.http.services.gitea-https-service.loadbalancer.server.scheme=https"
-# - "traefik.http.services.gitea-https-service.loadbalancer.healthcheck.hostname=gitea.traefik.me"
-# - "traefik.http.services.gitea-https-service.loadbalancer.healthcheck.method=foobar"
-# - "traefik.http.services.gitea-https-service.loadbalancer.healthcheck.timeout=10"
-# - "traefik.http.services.gitea-https-service.loadbalancer.healthcheck.interval=30"
diff --git a/docker/catalog-sync.sh b/docker/catalog-sync.sh
new file mode 100644
index 0000000..ea8c8ea
--- /dev/null
+++ b/docker/catalog-sync.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+# Clone ou met a jour le depot du catalogue, publie son contenu dans le volume
+# partage, puis notifie l'application. Boucle jusqu'a l'arret du conteneur.
+set -eu
+
+if [ -z "${CATALOG_REPO}" ]; then
+ echo "catalog-sync : CATALOG_REPO non defini, rien a synchroniser." >&2
+ exit 0
+fi
+
+TRAVAIL=/tmp/depot
+
+sync_une_fois() {
+ if [ -d "$TRAVAIL/.git" ]; then
+ git -C "$TRAVAIL" fetch --quiet --depth 1 origin "$CATALOG_BRANCH"
+ git -C "$TRAVAIL" reset --quiet --hard "origin/$CATALOG_BRANCH"
+ else
+ git clone --quiet --depth 1 --branch "$CATALOG_BRANCH" "$CATALOG_REPO" "$TRAVAIL"
+ fi
+
+ SOURCE="$TRAVAIL/$CATALOG_SUBDIR"
+ [ -d "$SOURCE" ] || { echo "catalog-sync : $CATALOG_SUBDIR absent du depot." >&2; return 1; }
+
+ # Publication atomique : on ecrit a cote, puis on bascule.
+ NEUF="${CATALOG_DEST}.neuf"
+ rm -rf "$NEUF"
+ mkdir -p "$NEUF"
+ cp -a "$SOURCE"/. "$NEUF"/
+ rm -rf "${CATALOG_DEST}.ancien"
+ [ -d "$CATALOG_DEST" ] && mv "$CATALOG_DEST" "${CATALOG_DEST}.ancien"
+ mv "$NEUF" "$CATALOG_DEST"
+ rm -rf "${CATALOG_DEST}.ancien"
+
+ REVISION=$(git -C "$TRAVAIL" rev-parse --short HEAD)
+ echo "catalog-sync : catalogue a jour ($REVISION)"
+ curl -fsS -X POST "$RELOAD_URL" >/dev/null 2>&1 || echo "catalog-sync : rechargement non confirme"
+}
+
+while true; do
+ sync_une_fois || echo "catalog-sync : echec de la synchronisation, nouvelle tentative dans ${SYNC_INTERVAL}s" >&2
+ sleep "$SYNC_INTERVAL"
+done
diff --git a/docs/EXPLOITATION.md b/docs/EXPLOITATION.md
new file mode 100644
index 0000000..0d4c28e
--- /dev/null
+++ b/docs/EXPLOITATION.md
@@ -0,0 +1,91 @@
+# Exploitation
+
+## Composants
+
+| Conteneur | Image | Rôle | État |
+|---|---|---|---|
+| `web` | `harbor…/enclume/web:` | l'application : éditeur, validation, génération | sans état |
+| `catalog-sync` | `harbor…/enclume/catalog-sync:` | clone le dépôt du catalogue dans un volume, prévient l'application | sans état |
+| `db` | `postgres:16.4-alpine` | partages et projets enregistrés (à partir du chantier 2) | volume `donnees` |
+
+Traefik, Authentik et le tunnel Cloudflare sont ceux du homelab : ils ne font pas partie
+de cette pile. `web` rejoint le réseau `traefik` existant et n'expose aucun port.
+
+Le réseau `interne` est marqué `internal: true` : `db` et `catalog-sync` n'ont aucun accès
+sortant depuis Docker, et personne ne les joint de l'extérieur.
+
+## Première installation
+
+```bash
+git clone https://gitea.tips-of-mine.com/hubert/enclume.git
+cd enclume
+cp .env.example .env # renseigner ENCLUME_TAG, ENCLUME_DOMAINE, POSTGRES_PASSWORD
+docker compose pull
+docker compose up -d
+curl -s https://clapi.tips-of-mine.com/sante
+```
+
+`/sante` renvoie la version et le commit de l'image en cours : c'est la source de vérité
+sur ce qui tourne réellement.
+
+## Mise à jour
+
+Une évolution donne une image, jamais un `docker compose build` sur le serveur.
+
+```bash
+sed -i 's/^ENCLUME_TAG=.*/ENCLUME_TAG=1.4.0/' .env
+docker compose pull && docker compose up -d
+curl -s https://clapi.tips-of-mine.com/sante | grep 1.4.0
+```
+
+## Retour arrière
+
+```bash
+sed -i 's/^ENCLUME_TAG=.*/ENCLUME_TAG=1.3.2/' .env
+docker compose up -d web catalog-sync
+```
+
+Quelques secondes, sans reconstruction. Trois conditions le garantissent :
+
+1. **Aucun `latest` en production.** Le compose refuse de démarrer si `ENCLUME_TAG` est absent.
+2. **Aucun état dans `web`.** Le système de fichiers est en lecture seule, le projet vit dans
+ le navigateur du visiteur. Revenir en arrière ne perd rien.
+3. **Migrations compatibles dans les deux sens.** À partir du chantier 2 : on ajoute une
+ colonne, on ne la supprime jamais dans la même version que le code qui cesse de l'utiliser.
+ La suppression arrive une version plus tard, quand le retour arrière n'est plus envisagé.
+ Sans cette règle, revenir à l'image précédente casse l'application.
+
+Conserver au minimum les trois dernières versions dans Harbor (politique de rétention par
+nombre de tags, pas par date).
+
+## Catalogue
+
+`catalog-sync` clone le dépôt du catalogue toutes les cinq minutes, publie le contenu de
+façon atomique dans le volume partagé, puis appelle `/recharger`. Ajouter un pack ne demande
+donc ni image ni redémarrage : un commit suffit.
+
+Si `CATALOG_REPO` est vide, l'application utilise le catalogue livré dans son image.
+
+## Sauvegarde
+
+Le volume `catalogue` est jetable, il se reconstruit depuis Git. Seul `donnees` compte :
+
+```bash
+docker compose exec -T db pg_dump -U enclume enclume | zstd > enclume-$(date +%F).sql.zst
+```
+
+À brancher sur la sauvegarde existante du homelab.
+
+## Journaux et diagnostic
+
+```bash
+docker compose logs -f web
+docker compose exec web python -m enclume.cli --list # le catalogue vu par l'application
+docker compose ps # état des healthchecks
+```
+
+## Ce qui est verrouillé dans `web`
+
+Utilisateur non root (uid 10001), racine en lecture seule, `/tmp` en tmpfs, toutes les
+capacités retirées, `no-new-privileges`, corps de requête plafonné à 2 Mo, en-têtes CSP,
+`nosniff` et `no-referrer` posés par l'application, limitation de débit au niveau de Traefik.
diff --git a/docs/INSTALLATION-INITIALE.md b/docs/INSTALLATION-INITIALE.md
new file mode 100644
index 0000000..490a928
--- /dev/null
+++ b/docs/INSTALLATION-INITIALE.md
@@ -0,0 +1,86 @@
+# Création des emplacements — Gitea et Harbor
+
+À faire une seule fois, avant le premier `git push`. Les valeurs ci-dessous sont celles
+qu'attendent `compose.yaml` et `.gitea/workflows/build.yaml`.
+
+## Gitea
+
+**Deux dépôts, pas un.**
+
+| Dépôt | Contenu | Visibilité |
+|---|---|---|
+| `enclume` | l'application, les images, la CI | public |
+| `enclume-catalogue` | uniquement `catalog/`, les packs YAML | public |
+
+Le catalogue vit à part parce que `catalog-sync` le tire directement : ajouter un pack devient
+un commit, sans reconstruction d'image. C'est aussi ce qui rendra les contributions par pull
+request lisibles — on relit dix lignes de YAML, pas un diff mêlé au code.
+
+Sur `enclume` :
+
+- branche par défaut `main`, protégée : pas de push direct, fusion par pull request
+- Actions activées, avec un runner ayant accès au démon Docker
+- secrets du dépôt : `HARBOR_USER` et `HARBOR_TOKEN` (le compte robot créé plus bas)
+- licence MIT à la racine
+
+Sur `enclume-catalogue` : même protection de branche. C'est là qu'arriveront les soumissions
+modérées du chantier 5.
+
+## Harbor
+
+Projet **`enclume`**, en accès **public** : n'importe qui peut alors tirer les images pour
+s'auto-héberger, sans compte chez toi. Seule la publication demande une authentification.
+
+Dépôts créés automatiquement au premier push : `enclume/web` et `enclume/catalog-sync`.
+
+### Compte robot
+
+Créer un robot au niveau du projet, nommé `robot$enclume-ci`, avec les droits
+**push** et **pull** sur les dépôts — rien d'autre, pas de suppression. Le secret généré
+va dans `HARBOR_TOKEN` côté Gitea, le nom complet du robot dans `HARBOR_USER`.
+
+### Immuabilité des versions
+
+Règle d'immuabilité sur les tags correspondant à `[0-9]*.[0-9]*.[0-9]*`.
+
+C'est le réglage qui donne sa valeur au retour arrière : une version publiée ne peut plus
+être écrasée, donc `ENCLUME_TAG=1.3.2` désigne aujourd'hui exactement la même image
+qu'au jour de sa publication.
+
+### Rétention
+
+Conserver les **10 derniers tags** correspondant à `[0-9]*.[0-9]*.[0-9]*`, et purger les tags
+`0.0.0-main.*` de plus de 30 jours. Les tags `sha-*` servent au diagnostic, une rétention de
+30 jours suffit.
+
+### Analyse
+
+Scanner automatiquement à la publication. Ne pas activer « empêcher le déploiement d'images
+vulnérables » au démarrage : une CVE sans correctif dans une image de base bloquerait un
+déploiement urgent. Le rapport Trivy dans la CI suffit à surveiller.
+
+## Enchaînement
+
+```bash
+git remote add origin https://gitea.tips-of-mine.com/hubert/enclume.git
+git push -u origin main
+```
+
+La CI construit, teste et publie `0.0.0-main.`. Pour une vraie version :
+
+```bash
+git tag v1.0.0 && git push origin v1.0.0
+```
+
+Puis sur le serveur, dans le répertoire du compose :
+
+```bash
+cp .env.example .env # renseigner ENCLUME_TAG=1.0.0 et le domaine
+docker compose pull && docker compose up -d
+curl -s https:///sante
+```
+
+## Réseau Traefik
+
+Le compose attend un réseau externe nommé `traefik`. S'il porte un autre nom dans ton
+installation, ajuster la section `networks` et l'étiquette `traefik.docker.network`.
diff --git a/enclume/__init__.py b/enclume/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/enclume/catalog.py b/enclume/catalog.py
new file mode 100644
index 0000000..41d6020
--- /dev/null
+++ b/enclume/catalog.py
@@ -0,0 +1,180 @@
+"""Chargement du catalogue de packs de supervision.
+
+Un pack est un fichier YAML decrivant, pour une technologie donnee :
+ - un modele d'hote (host template) et ses macros,
+ - une liste de services, chacun portant sa ligne de commande et ses macros.
+
+Le catalogue est volontairement en donnees et non en code : ajouter un pack
+revient a deposer un fichier YAML dans catalog//.
+"""
+
+from __future__ import annotations
+
+import os
+from dataclasses import dataclass, field
+from typing import Any
+
+import yaml
+
+CATEGORIES = {
+ "system": "Systemes d'exploitation",
+ "hardware": "Materiel",
+ "application": "Applications",
+}
+
+
+@dataclass
+class Macro:
+ name: str
+ value: str = ""
+ description: str = ""
+ is_password: bool = False
+
+ @classmethod
+ def from_dict(cls, raw: dict[str, Any]) -> "Macro":
+ return cls(
+ name=str(raw["name"]).strip().upper(),
+ value="" if raw.get("value") is None else str(raw.get("value")),
+ description=str(raw.get("description", "")),
+ is_password=bool(raw.get("is_password", False)),
+ )
+
+
+@dataclass
+class Service:
+ name: str
+ alias: str
+ description: str
+ line: str
+ macros: list[Macro] = field(default_factory=list)
+ enabled_by_default: bool = True
+ check_interval: int | None = None
+ retry_interval: int | None = None
+ max_attempts: int | None = None
+
+ @classmethod
+ def from_dict(cls, raw: dict[str, Any]) -> "Service":
+ return cls(
+ name=str(raw["name"]).strip(),
+ alias=str(raw.get("alias") or raw["name"]).strip(),
+ description=str(raw.get("description", "")),
+ line=str(raw["line"]).strip(),
+ macros=[Macro.from_dict(m) for m in raw.get("macros") or []],
+ enabled_by_default=bool(raw.get("default", True)),
+ check_interval=raw.get("check_interval"),
+ retry_interval=raw.get("retry_interval"),
+ max_attempts=raw.get("max_attempts"),
+ )
+
+
+@dataclass
+class HostTemplate:
+ name: str
+ alias: str
+ parents: list[str] = field(default_factory=list)
+ check_command: str = ""
+ check_command_args: str = ""
+ macros: list[Macro] = field(default_factory=list)
+
+ @classmethod
+ def from_dict(cls, raw: dict[str, Any]) -> "HostTemplate":
+ return cls(
+ name=str(raw["name"]).strip(),
+ alias=str(raw.get("alias") or raw["name"]).strip(),
+ parents=[str(p).strip() for p in raw.get("parents") or []],
+ check_command=str(raw.get("check_command", "")),
+ check_command_args=str(raw.get("check_command_args", "")),
+ macros=[Macro.from_dict(m) for m in raw.get("macros") or []],
+ )
+
+
+@dataclass
+class Pack:
+ id: str
+ name: str
+ category: str
+ description: str
+ plugin: str
+ status: str
+ host_template: HostTemplate
+ services: list[Service]
+ source_file: str = ""
+
+ @property
+ def prefix(self) -> str:
+ """Racine des noms de modeles de service, deduite du modele d'hote.
+
+ OS-Linux-SNMP-custom -> OS-Linux-SNMP
+ """
+ base = self.host_template.name
+ return base[: -len("-custom")] if base.endswith("-custom") else base
+
+ @property
+ def category_label(self) -> str:
+ return CATEGORIES.get(self.category, self.category)
+
+ def service(self, name: str) -> Service | None:
+ for svc in self.services:
+ if svc.name == name:
+ return svc
+ return None
+
+ @classmethod
+ def from_dict(cls, raw: dict[str, Any], source_file: str = "") -> "Pack":
+ return cls(
+ id=str(raw["id"]).strip(),
+ name=str(raw["name"]).strip(),
+ category=str(raw.get("category", "system")).strip(),
+ description=str(raw.get("description", "")),
+ plugin=str(raw.get("plugin", "")),
+ status=str(raw.get("status", "to-verify")),
+ host_template=HostTemplate.from_dict(raw["host_template"]),
+ services=[Service.from_dict(s) for s in raw.get("services") or []],
+ source_file=source_file,
+ )
+
+
+class Catalog:
+ def __init__(self, root: str):
+ self.root = root
+ self.packs: dict[str, Pack] = {}
+ self.errors: list[str] = []
+ self.load()
+
+ def load(self) -> None:
+ self.packs = {}
+ self.errors = []
+ for dirpath, _dirnames, filenames in os.walk(self.root):
+ for filename in sorted(filenames):
+ if not filename.endswith((".yml", ".yaml")):
+ continue
+ path = os.path.join(dirpath, filename)
+ try:
+ with open(path, encoding="utf-8") as handle:
+ raw = yaml.safe_load(handle)
+ pack = Pack.from_dict(raw, source_file=path)
+ except Exception as exc: # noqa: BLE001 - remonte tel quel a l'UI
+ self.errors.append(f"{path} : {exc}")
+ continue
+ if pack.id in self.packs:
+ self.errors.append(
+ f"{path} : identifiant deja utilise par "
+ f"{self.packs[pack.id].source_file}"
+ )
+ continue
+ self.packs[pack.id] = pack
+
+ def get(self, pack_id: str) -> Pack | None:
+ return self.packs.get(pack_id)
+
+ def by_category(self) -> dict[str, list[Pack]]:
+ grouped: dict[str, list[Pack]] = {key: [] for key in CATEGORIES}
+ for pack in self.packs.values():
+ grouped.setdefault(pack.category, []).append(pack)
+ for packs in grouped.values():
+ packs.sort(key=lambda p: p.name.lower())
+ return grouped
+
+ @property
+ def service_count(self) -> int:
+ return sum(len(p.services) for p in self.packs.values())
diff --git a/enclume/cli.py b/enclume/cli.py
new file mode 100644
index 0000000..ab4dc38
--- /dev/null
+++ b/enclume/cli.py
@@ -0,0 +1,97 @@
+"""Ligne de commande : meme moteur que le site, utilisable en CI.
+
+ python3 -m enclume.cli --list
+ python3 -m enclume.cli --pack os-linux-snmp --pack app-mysql --out ./sortie
+ python3 -m enclume.cli --projet projet.json --out ./sortie
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import sys
+
+from .catalog import Catalog
+from .generator import build_plan, render_bundle
+from .model import Project
+from .packs import pack_to_project
+
+DEFAULT_CATALOG = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "catalog")
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = argparse.ArgumentParser(description="Generateur CLAPI Centreon")
+ parser.add_argument("--catalog", default=os.environ.get("ENCLUME_CATALOG", DEFAULT_CATALOG))
+ parser.add_argument("--list", action="store_true", help="liste les packs disponibles")
+ parser.add_argument("--pack", action="append", default=[], help="pack a importer (repetable)")
+ parser.add_argument("--tout", action="store_true", help="importer toutes les sondes, pas seulement celles par defaut")
+ parser.add_argument("--projet", help="fichier de projet JSON exporte depuis le site")
+ parser.add_argument("--prefix", default="", help="prefixe applique aux objets crees")
+ parser.add_argument("--plugins-dir", default="/usr/lib/centreon/plugins")
+ parser.add_argument("--version-centreon", default="24.x")
+ parser.add_argument("--poller", default="Central")
+ parser.add_argument("--out", help="repertoire de sortie (defaut : stdout)")
+ parser.add_argument("--strict", action="store_true", help="code de retour non nul si le projet contient une erreur")
+ args = parser.parse_args(argv)
+
+ catalog = Catalog(args.catalog)
+ for erreur in catalog.errors:
+ print(f"catalogue : {erreur}", file=sys.stderr)
+
+ if args.list:
+ for pack in sorted(catalog.packs.values(), key=lambda p: (p.category, p.name)):
+ marque = " " if pack.status == "verified" else "!"
+ print(f"{marque} {pack.id:<28} {pack.category:<12} {len(pack.services):>2} sondes {pack.name}")
+ return 0
+
+ if args.projet:
+ with open(args.projet, encoding="utf-8") as handle:
+ project = Project.from_dict(json.load(handle))
+ elif args.pack:
+ project = Project(
+ name="Projet en ligne de commande",
+ centreon_version=args.version_centreon,
+ prefix=args.prefix,
+ plugins_dir=args.plugins_dir,
+ poller=args.poller,
+ )
+ for pack_id in args.pack:
+ pack = catalog.get(pack_id)
+ if pack is None:
+ print(f"pack inconnu : {pack_id}", file=sys.stderr)
+ return 2
+ for message in project.merge(pack_to_project(pack, only_defaults=not args.tout)):
+ print(f"fusion : {message}", file=sys.stderr)
+ else:
+ parser.error("indiquez --list, --pack ou --projet")
+ return 2
+
+ plan = build_plan(project)
+ fichiers = render_bundle(plan)
+
+ for probleme in plan.issues:
+ print(f"{probleme['level']} : {probleme['message']}", file=sys.stderr)
+
+ if not args.out:
+ sys.stdout.write(fichiers["deploy.clapi"])
+ else:
+ os.makedirs(args.out, exist_ok=True)
+ for nom, contenu in fichiers.items():
+ chemin = os.path.join(args.out, nom)
+ with open(chemin, "w", encoding="utf-8") as handle:
+ handle.write(contenu)
+ if nom.endswith(".sh"):
+ os.chmod(chemin, 0o755)
+ with open(os.path.join(args.out, "projet.json"), "w", encoding="utf-8") as handle:
+ json.dump(project.to_dict(), handle, indent=2, ensure_ascii=False)
+ print(f"{args.out} : " + ", ".join(f"{v} {k}" for k, v in plan.summary.items() if v))
+
+ return 1 if (args.strict and plan.has_errors) else 0
+
+
+if __name__ == "__main__":
+ try:
+ raise SystemExit(main())
+ except BrokenPipeError:
+ raise SystemExit(0)
diff --git a/enclume/generator.py b/enclume/generator.py
new file mode 100644
index 0000000..dd16f75
--- /dev/null
+++ b/enclume/generator.py
@@ -0,0 +1,266 @@
+"""Traduction d'un projet en operations CLAPI, puis en fichiers.
+
+Un seul parcours du projet produit la liste d'operations ; les quatre fichiers
+livres a l'utilisateur ne sont que des rendus differents de cette liste.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from datetime import datetime, timezone
+from typing import Any
+
+from .model import Macro, Project, validate
+
+CLAPI_BIN = "/usr/share/centreon/bin/centreon"
+
+
+@dataclass
+class Op:
+ obj: str
+ action: str
+ values: list[str] = field(default_factory=list)
+ comment: str = ""
+
+ def import_line(self) -> str:
+ return ";".join([self.obj, self.action, *self.values])
+
+ def shell_line(self) -> str:
+ # Les valeurs contiennent des macros Centreon ($HOSTADDRESS$, $_SERVICEWARNING$).
+ # Guillemets simples pour que bash ne les interprete pas ; apostrophes echappees.
+ payload = ";".join(self.values).replace("'", "'\\''")
+ return f"clapi -o {self.obj} -a {self.action} -v '{payload}'"
+
+
+@dataclass
+class Plan:
+ project: Project
+ ops: list[Op] = field(default_factory=list)
+ rollback_ops: list[Op] = field(default_factory=list)
+ issues: list[dict[str, str]] = field(default_factory=list)
+
+ @property
+ def summary(self) -> dict[str, int]:
+ return {
+ "commandes": len(self.project.commands),
+ "modeles de service": len(self.project.service_templates),
+ "modeles d'hote": len(self.project.host_templates),
+ "hotes": len(self.project.hosts),
+ "operations": len([o for o in self.ops if o.obj != "#"]),
+ }
+
+ @property
+ def has_errors(self) -> bool:
+ return any(i["level"] == "error" for i in self.issues)
+
+
+def _macro_values(target: str, macro: Macro, fields: int) -> list[str]:
+ if fields == 3:
+ return [target, macro.name, macro.value]
+ return [target, macro.name, macro.value, "1" if macro.is_password else "0", macro.description]
+
+
+def build_plan(project: Project) -> Plan:
+ plan = Plan(project=project, issues=validate(project))
+ prefix = project.prefix
+ fields = project.macro_fields
+
+ def full(name: str) -> str:
+ return f"{prefix}{name}" if name else name
+
+ own_commands = {c.name for c in project.commands}
+ own_htpl = {h.name for h in project.host_templates}
+
+ def ref_command(name: str) -> str:
+ """Un objet du projet est prefixe ; un objet deja present sur le central ne l'est pas."""
+ return full(name) if name in own_commands else name
+
+ def ref_htpl(name: str) -> str:
+ return full(name) if name in own_htpl else name
+
+ del_cmd: list[Op] = []
+ del_stpl: list[Op] = []
+ del_htpl: list[Op] = []
+ del_host: list[Op] = []
+
+ if project.commands:
+ plan.ops.append(Op("#", "#", comment="Commandes de controle"))
+ for command in project.commands:
+ line = command.line.replace("$CENTREONPLUGINS$", project.plugins_dir)
+ plan.ops.append(Op("CMD", "ADD", [full(command.name), command.type, line]))
+ if command.comment:
+ plan.ops.append(Op("CMD", "setparam", [full(command.name), "comment", command.comment]))
+ del_cmd.append(Op("CMD", "DEL", [full(command.name)]))
+
+ if project.service_templates:
+ plan.ops.append(Op("#", "#", comment="Modeles de service"))
+ for stpl in project.service_templates:
+ plan.ops.append(
+ Op(
+ "STPL",
+ "ADD",
+ [
+ full(stpl.name),
+ stpl.alias or stpl.name,
+ stpl.parent,
+ ref_command(stpl.command),
+ stpl.command_args,
+ str(stpl.check_interval),
+ str(stpl.retry_interval),
+ str(stpl.max_attempts),
+ str(stpl.active_checks),
+ str(stpl.passive_checks),
+ ],
+ )
+ )
+ for macro in stpl.macros:
+ plan.ops.append(Op("STPL", "setmacro", _macro_values(full(stpl.name), macro, fields)))
+ del_stpl.append(Op("STPL", "DEL", [full(stpl.name)]))
+
+ if project.host_templates:
+ plan.ops.append(Op("#", "#", comment="Modeles d'hote"))
+ for htpl in project.host_templates:
+ parents = "|".join(htpl.parents)
+ plan.ops.append(Op("HTPL", "ADD", [full(htpl.name), htpl.alias or htpl.name, "", parents, "", ""]))
+ for param, value in (
+ ("check_command", htpl.check_command),
+ ("check_command_arguments", htpl.check_command_args),
+ ("snmp_version", htpl.snmp_version),
+ ("snmp_community", htpl.snmp_community),
+ ("timezone", htpl.timezone),
+ ):
+ if value:
+ plan.ops.append(Op("HTPL", "setparam", [full(htpl.name), param, value]))
+ for macro in htpl.macros:
+ plan.ops.append(Op("HTPL", "setmacro", _macro_values(full(htpl.name), macro, fields)))
+ del_htpl.append(Op("HTPL", "DEL", [full(htpl.name)]))
+
+ links = [(s, h) for s in project.service_templates for h in s.host_templates]
+ if links:
+ plan.ops.append(Op("#", "#", comment="Rattachement des modeles de service aux modeles d'hote"))
+ for stpl, htpl_name in links:
+ plan.ops.append(Op("STPL", "addhosttemplate", [full(stpl.name), ref_htpl(htpl_name)]))
+
+ if project.hosts:
+ plan.ops.append(Op("#", "#", comment="Hotes"))
+ for host in project.hosts:
+ plan.ops.append(
+ Op(
+ "HOST",
+ "ADD",
+ [
+ host.name,
+ host.alias or host.name,
+ host.address,
+ "|".join(ref_htpl(t) for t in host.templates),
+ host.poller or project.poller,
+ host.hostgroups,
+ ],
+ )
+ )
+ for macro in host.macros:
+ plan.ops.append(Op("HOST", "setmacro", _macro_values(host.name, macro, fields)))
+ plan.ops.append(Op("HOST", "applytpl", [host.name]))
+ del_host.append(Op("HOST", "DEL", [host.name]))
+
+ plan.rollback_ops = [*del_host, *del_stpl, *del_htpl, *del_cmd]
+ return plan
+
+
+# --------------------------------------------------------------------------- #
+# Rendus
+# --------------------------------------------------------------------------- #
+
+def _header(title: str, plan: Plan) -> list[str]:
+ stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
+ summary = ", ".join(f"{v} {k}" for k, v in plan.summary.items() if v)
+ return [
+ f"# {title} - {plan.project.name}",
+ f"# Genere le {stamp}",
+ f"# Cible : {plan.project.centreon_version} | {summary}",
+ "#",
+ ]
+
+
+def render_import(plan: Plan, ops: list[Op], title: str) -> str:
+ lines = _header(title, plan)
+ for op in ops:
+ lines.append(f"# {op.comment}" if op.obj == "#" else op.import_line())
+ return "\n".join(lines) + "\n"
+
+
+def render_shell(plan: Plan, ops: list[Op], title: str, apply_config: bool) -> str:
+ lines = [
+ "#!/usr/bin/env bash",
+ "#",
+ *_header(title, plan),
+ "# Usage :",
+ "# export CENTREON_USER=admin",
+ "# ./deploy.sh --dry-run affiche les commandes sans les executer",
+ "# ./deploy.sh applique la configuration",
+ "",
+ "set -euo pipefail",
+ "",
+ f'CLAPI_BIN="${{CLAPI_BIN:-{CLAPI_BIN}}}"',
+ 'CENTREON_USER="${CENTREON_USER:-admin}"',
+ "DRY_RUN=0",
+ '[ "${1:-}" = "--dry-run" ] && DRY_RUN=1',
+ "",
+ 'if [ "$DRY_RUN" -eq 0 ] && [ -z "${CENTREON_PASS:-}" ]; then',
+ ' read -r -s -p "Mot de passe Centreon pour $CENTREON_USER : " CENTREON_PASS',
+ " echo",
+ "fi",
+ "",
+ "clapi() {",
+ ' if [ "$DRY_RUN" -eq 1 ]; then',
+ ' local out="$CLAPI_BIN -u $CENTREON_USER -p *** " arg',
+ ' for arg in "$@"; do',
+ ' case "$arg" in',
+ " *[!A-Za-z0-9_.:/=-]*) out=\"$out '$arg'\" ;;",
+ ' *) out="$out $arg" ;;',
+ " esac",
+ " done",
+ ' echo "$out"',
+ " else",
+ ' "$CLAPI_BIN" -u "$CENTREON_USER" -p "$CENTREON_PASS" "$@"',
+ " fi",
+ "}",
+ "",
+ ]
+ for op in ops:
+ if op.obj == "#":
+ lines.extend(["", f"# {op.comment}"])
+ else:
+ lines.append(op.shell_line())
+ if apply_config:
+ lines.extend(
+ [
+ "",
+ "# Generation et deploiement de la configuration du collecteur",
+ f"clapi -a APPLYCFG -v '{plan.project.poller}'",
+ ]
+ )
+ lines.extend(["", 'echo "Termine."', ""])
+ return "\n".join(lines)
+
+
+def render_bundle(plan: Plan) -> dict[str, str]:
+ apply_config = bool(plan.project.apply_config)
+ return {
+ "deploy.clapi": render_import(plan, plan.ops, "Deploiement CLAPI"),
+ "deploy.sh": render_shell(plan, plan.ops, "Deploiement CLAPI", apply_config),
+ "rollback.clapi": render_import(plan, plan.rollback_ops, "Suppression des objets"),
+ "rollback.sh": render_shell(plan, plan.rollback_ops, "Suppression des objets", apply_config),
+ }
+
+
+def generate(raw_project: dict[str, Any]) -> dict[str, Any]:
+ """Point d'entree unique utilise par l'API et la CLI."""
+ project = Project.from_dict(raw_project)
+ plan = build_plan(project)
+ return {
+ "summary": plan.summary,
+ "issues": plan.issues,
+ "has_errors": plan.has_errors,
+ "files": render_bundle(plan),
+ }
diff --git a/enclume/model.py b/enclume/model.py
new file mode 100644
index 0000000..2f525a0
--- /dev/null
+++ b/enclume/model.py
@@ -0,0 +1,346 @@
+"""Modele de donnees d'un projet.
+
+Un projet est ce que l'utilisateur construit dans l'editeur : des commandes de
+controle, des modeles de service, des modeles d'hote, et facultativement des
+hotes. Les objets se referencent par leur nom, comme dans Centreon.
+
+Le projet est serialisable en JSON : c'est ce que le navigateur conserve, ce
+qu'on exporte, ce qu'on partage et ce que la CI rejoue.
+"""
+
+from __future__ import annotations
+
+from dataclasses import asdict, dataclass, field
+from typing import Any
+
+SCHEMA_VERSION = 1
+
+# Versions de Centreon proposees dans l'editeur et ce qu'elles impliquent.
+CENTREON_VERSIONS: dict[str, dict[str, Any]] = {
+ "24.x": {"label": "Centreon 24.x", "macro_fields": 5},
+ "23.x": {"label": "Centreon 23.x", "macro_fields": 5},
+ "22.x": {"label": "Centreon 22.x", "macro_fields": 5},
+ "21.x": {"label": "Centreon 21.x", "macro_fields": 5},
+ "20.04": {"label": "Centreon 20.04", "macro_fields": 5},
+ "legacy": {"label": "Centreon anterieur a 20.04", "macro_fields": 3},
+}
+
+COMMAND_TYPES = {"check": "Controle", "notif": "Notification", "misc": "Divers", "discovery": "Decouverte"}
+
+FORBIDDEN = ";"
+
+
+def _clean(value: Any) -> str:
+ return "" if value is None else str(value).strip()
+
+
+@dataclass
+class Macro:
+ name: str = ""
+ value: str = ""
+ description: str = ""
+ is_password: bool = False
+
+ @classmethod
+ def from_dict(cls, raw: dict[str, Any]) -> "Macro":
+ return cls(
+ name=_clean(raw.get("name")).upper(),
+ value="" if raw.get("value") is None else str(raw.get("value")),
+ description=_clean(raw.get("description")),
+ is_password=bool(raw.get("is_password")),
+ )
+
+
+@dataclass
+class Command:
+ name: str = ""
+ type: str = "check"
+ line: str = ""
+ comment: str = ""
+
+ @classmethod
+ def from_dict(cls, raw: dict[str, Any]) -> "Command":
+ return cls(
+ name=_clean(raw.get("name")),
+ type=_clean(raw.get("type")) or "check",
+ line=_clean(raw.get("line")),
+ comment=_clean(raw.get("comment")),
+ )
+
+
+@dataclass
+class ServiceTemplate:
+ name: str = ""
+ alias: str = ""
+ parent: str = "generic-active-service-custom"
+ command: str = ""
+ command_args: str = ""
+ check_interval: int = 5
+ retry_interval: int = 1
+ max_attempts: int = 3
+ active_checks: int = 1
+ passive_checks: int = 0
+ host_templates: list[str] = field(default_factory=list)
+ macros: list[Macro] = field(default_factory=list)
+
+ @classmethod
+ def from_dict(cls, raw: dict[str, Any]) -> "ServiceTemplate":
+ def as_int(key: str, default: int) -> int:
+ try:
+ return int(raw.get(key, default))
+ except (TypeError, ValueError):
+ return default
+
+ return cls(
+ name=_clean(raw.get("name")),
+ alias=_clean(raw.get("alias")),
+ parent=_clean(raw.get("parent")),
+ command=_clean(raw.get("command")),
+ command_args=_clean(raw.get("command_args")),
+ check_interval=as_int("check_interval", 5),
+ retry_interval=as_int("retry_interval", 1),
+ max_attempts=as_int("max_attempts", 3),
+ active_checks=as_int("active_checks", 1),
+ passive_checks=as_int("passive_checks", 0),
+ host_templates=[_clean(h) for h in raw.get("host_templates") or [] if _clean(h)],
+ macros=[Macro.from_dict(m) for m in raw.get("macros") or []],
+ )
+
+
+@dataclass
+class HostTemplate:
+ name: str = ""
+ alias: str = ""
+ parents: list[str] = field(default_factory=list)
+ check_command: str = ""
+ check_command_args: str = ""
+ snmp_version: str = ""
+ snmp_community: str = ""
+ timezone: str = ""
+ macros: list[Macro] = field(default_factory=list)
+
+ @classmethod
+ def from_dict(cls, raw: dict[str, Any]) -> "HostTemplate":
+ return cls(
+ name=_clean(raw.get("name")),
+ alias=_clean(raw.get("alias")),
+ parents=[_clean(p) for p in raw.get("parents") or [] if _clean(p)],
+ check_command=_clean(raw.get("check_command")),
+ check_command_args=_clean(raw.get("check_command_args")),
+ snmp_version=_clean(raw.get("snmp_version")),
+ snmp_community=_clean(raw.get("snmp_community")),
+ timezone=_clean(raw.get("timezone")),
+ macros=[Macro.from_dict(m) for m in raw.get("macros") or []],
+ )
+
+
+@dataclass
+class Host:
+ name: str = ""
+ alias: str = ""
+ address: str = ""
+ templates: list[str] = field(default_factory=list)
+ poller: str = "Central"
+ hostgroups: str = ""
+ macros: list[Macro] = field(default_factory=list)
+
+ @classmethod
+ def from_dict(cls, raw: dict[str, Any]) -> "Host":
+ return cls(
+ name=_clean(raw.get("name")),
+ alias=_clean(raw.get("alias")),
+ address=_clean(raw.get("address")),
+ templates=[_clean(t) for t in raw.get("templates") or [] if _clean(t)],
+ poller=_clean(raw.get("poller")) or "Central",
+ hostgroups=_clean(raw.get("hostgroups")),
+ macros=[Macro.from_dict(m) for m in raw.get("macros") or []],
+ )
+
+
+@dataclass
+class Project:
+ name: str = "Nouveau projet"
+ centreon_version: str = "24.x"
+ prefix: str = ""
+ plugins_dir: str = "/usr/lib/centreon/plugins"
+ poller: str = "Central"
+ apply_config: bool = True
+ commands: list[Command] = field(default_factory=list)
+ service_templates: list[ServiceTemplate] = field(default_factory=list)
+ host_templates: list[HostTemplate] = field(default_factory=list)
+ hosts: list[Host] = field(default_factory=list)
+ schema: int = SCHEMA_VERSION
+
+ @property
+ def macro_fields(self) -> int:
+ return CENTREON_VERSIONS.get(self.centreon_version, {}).get("macro_fields", 5)
+
+ @classmethod
+ def from_dict(cls, raw: dict[str, Any] | None) -> "Project":
+ raw = raw or {}
+ version = _clean(raw.get("centreon_version")) or "24.x"
+ if version not in CENTREON_VERSIONS:
+ version = "24.x"
+ return cls(
+ name=_clean(raw.get("name")) or "Nouveau projet",
+ centreon_version=version,
+ prefix=_clean(raw.get("prefix")),
+ plugins_dir=_clean(raw.get("plugins_dir")) or "/usr/lib/centreon/plugins",
+ poller=_clean(raw.get("poller")) or "Central",
+ apply_config=bool(raw.get("apply_config", True)),
+ commands=[Command.from_dict(c) for c in raw.get("commands") or []],
+ service_templates=[ServiceTemplate.from_dict(s) for s in raw.get("service_templates") or []],
+ host_templates=[HostTemplate.from_dict(h) for h in raw.get("host_templates") or []],
+ hosts=[Host.from_dict(h) for h in raw.get("hosts") or []],
+ )
+
+ def to_dict(self) -> dict[str, Any]:
+ return asdict(self)
+
+ def merge(self, other: "Project") -> list[str]:
+ """Ajoute les objets d'un autre projet, en ignorant les noms deja pris."""
+ skipped: list[str] = []
+
+ def add(target: list[Any], items: list[Any], label: str) -> None:
+ existing = {obj.name for obj in target}
+ for item in items:
+ if item.name in existing:
+ skipped.append(f"{label} deja present, ignore : {item.name}")
+ continue
+ target.append(item)
+ existing.add(item.name)
+
+ add(self.commands, other.commands, "Commande")
+ add(self.service_templates, other.service_templates, "Modele de service")
+ add(self.host_templates, other.host_templates, "Modele d'hote")
+ add(self.hosts, other.hosts, "Hote")
+ return skipped
+
+
+# --------------------------------------------------------------------------- #
+# Validation
+# --------------------------------------------------------------------------- #
+
+def validate(project: Project) -> list[dict[str, str]]:
+ """Retourne la liste des problemes, du bloquant au simple avertissement."""
+
+ issues: list[dict[str, str]] = []
+
+ def error(message: str) -> None:
+ issues.append({"level": "error", "message": message})
+
+ def warn(message: str) -> None:
+ issues.append({"level": "warning", "message": message})
+
+ def check_names(items: list[Any], label: str) -> None:
+ seen: set[str] = set()
+ for index, item in enumerate(items, start=1):
+ if not item.name:
+ error(f"{label} n{chr(176)}{index} : le nom est vide.")
+ continue
+ if item.name in seen:
+ error(f"{label} : nom en double, {item.name}")
+ seen.add(item.name)
+ if FORBIDDEN in item.name:
+ error(f"{label} {item.name} : le point-virgule est le separateur CLAPI.")
+
+ check_names(project.commands, "Commande")
+ check_names(project.service_templates, "Modele de service")
+ check_names(project.host_templates, "Modele d'hote")
+ check_names(project.hosts, "Hote")
+
+ command_names = {c.name for c in project.commands}
+ htpl_names = {h.name for h in project.host_templates}
+
+ for command in project.commands:
+ if not command.line:
+ error(f"Commande {command.name} : la ligne de commande est vide.")
+ elif FORBIDDEN in command.line:
+ error(
+ f"Commande {command.name} : la ligne contient un point-virgule, "
+ "ce qui casserait le fichier d'import."
+ )
+
+ for stpl in project.service_templates:
+ if not stpl.command:
+ warn(f"Modele de service {stpl.name} : aucune commande de controle associee.")
+ elif stpl.command not in command_names:
+ warn(
+ f"Modele de service {stpl.name} : la commande {stpl.command} n'est pas dans "
+ "le projet, elle devra exister deja sur le central."
+ )
+ if not stpl.host_templates:
+ warn(
+ f"Modele de service {stpl.name} : rattache a aucun modele d'hote, "
+ "il ne sera deploye sur aucun hote."
+ )
+ for htpl in stpl.host_templates:
+ if htpl not in htpl_names:
+ warn(f"Modele de service {stpl.name} : modele d'hote inconnu, {htpl}")
+ _check_macros(stpl.macros, f"Modele de service {stpl.name}", issues)
+
+ for htpl in project.host_templates:
+ _check_macros(htpl.macros, f"Modele d'hote {htpl.name}", issues)
+
+ for host in project.hosts:
+ if not host.address:
+ error(f"Hote {host.name} : l'adresse est vide.")
+ for template in host.templates:
+ if template not in htpl_names:
+ warn(f"Hote {host.name} : modele d'hote inconnu, {template}")
+ _check_macros(host.macros, f"Hote {host.name}", issues)
+
+ if not project.commands and not project.service_templates and not project.host_templates:
+ warn("Le projet est vide.")
+
+ return issues
+
+
+def _check_macros(macros: list[Macro], label: str, issues: list[dict[str, str]]) -> None:
+ seen: set[str] = set()
+ for macro in macros:
+ if not macro.name:
+ issues.append({"level": "error", "message": f"{label} : une macro n'a pas de nom."})
+ continue
+ if macro.name in seen:
+ issues.append({"level": "error", "message": f"{label} : macro en double, {macro.name}"})
+ seen.add(macro.name)
+ if FORBIDDEN in macro.value or FORBIDDEN in macro.description:
+ issues.append(
+ {
+ "level": "error",
+ "message": f"{label}, macro {macro.name} : le point-virgule est interdit.",
+ }
+ )
+ if macro.is_password and macro.value:
+ issues.append(
+ {
+ "level": "warning",
+ "message": (
+ f"{label}, macro {macro.name} : un mot de passe est renseigne. "
+ "Il apparaitra en clair dans les fichiers generes."
+ ),
+ }
+ )
+
+
+def detect_macros(line: str) -> dict[str, list[str]]:
+ """Repere les macros personnalisees utilisees dans une ligne de commande."""
+ import re
+
+ service = sorted(set(re.findall(r"\$_SERVICE([A-Z0-9_]+)\$", line)))
+ host = sorted(set(re.findall(r"\$_HOST([A-Z0-9_]+)\$", line)))
+ return {"service": service, "host": host}
+
+
+def strip_secrets(project: Project) -> Project:
+ """Vide toutes les macros marquees mot de passe.
+
+ Applique avant tout stockage cote serveur : partage, projet enregistre,
+ soumission de pack. Le site n'a aucune raison de detenir ces valeurs.
+ """
+ for holder in (*project.service_templates, *project.host_templates, *project.hosts):
+ for macro in holder.macros:
+ if macro.is_password:
+ macro.value = ""
+ return project
diff --git a/enclume/packs.py b/enclume/packs.py
new file mode 100644
index 0000000..21a05c8
--- /dev/null
+++ b/enclume/packs.py
@@ -0,0 +1,60 @@
+"""Passerelle entre le catalogue de packs et le modele de projet.
+
+Le catalogue reste un point de depart : importer un pack copie ses objets dans
+le projet de l'utilisateur, qui les modifie ensuite librement.
+"""
+
+from __future__ import annotations
+
+from .catalog import Pack
+from .model import Command, HostTemplate, Macro, Project, ServiceTemplate
+
+
+def _macro(macro) -> Macro:
+ return Macro(
+ name=macro.name,
+ value=macro.value,
+ description=macro.description,
+ is_password=macro.is_password,
+ )
+
+
+def pack_to_project(pack: Pack, only_defaults: bool = True, service_parent: str = "generic-active-service-custom") -> Project:
+ """Construit un projet ne contenant que les objets du pack."""
+
+ project = Project(name=pack.name)
+ project.commands = []
+ project.service_templates = []
+
+ htpl = HostTemplate(
+ name=pack.host_template.name,
+ alias=pack.host_template.alias,
+ parents=list(pack.host_template.parents),
+ check_command=pack.host_template.check_command,
+ check_command_args=pack.host_template.check_command_args,
+ macros=[_macro(m) for m in pack.host_template.macros],
+ )
+ if any("--snmp-" in svc.line for svc in pack.services):
+ htpl.snmp_version = "2c"
+ htpl.snmp_community = "public"
+ project.host_templates = [htpl]
+
+ for svc in pack.services:
+ if only_defaults and not svc.enabled_by_default:
+ continue
+ name = f"{pack.prefix}-{svc.name}"
+ project.commands.append(Command(name=name, type="check", line=svc.line, comment=svc.description))
+ project.service_templates.append(
+ ServiceTemplate(
+ name=name,
+ alias=svc.alias,
+ parent=service_parent,
+ command=name,
+ check_interval=svc.check_interval or 5,
+ retry_interval=svc.retry_interval or 1,
+ max_attempts=svc.max_attempts or 3,
+ host_templates=[htpl.name],
+ macros=[_macro(m) for m in svc.macros],
+ )
+ )
+ return project
diff --git a/enclume/static/editeur.js b/enclume/static/editeur.js
new file mode 100644
index 0000000..f27993a
--- /dev/null
+++ b/enclume/static/editeur.js
@@ -0,0 +1,744 @@
+/* Editeur de projet Enclume.
+ Aucune dependance : le projet vit dans le navigateur, le serveur ne fait que
+ valider et generer. */
+
+'use strict';
+
+var CLE = 'enclume.projet';
+var TYPES = JSON.parse(document.getElementById('donnees-types').textContent);
+
+var projet = charger();
+var selection = { type: 'reglages', index: 0 };
+var packsCache = null;
+
+// ------------------------------------------------------------------ modele
+
+function projetVide() {
+ return {
+ name: 'Nouveau projet',
+ centreon_version: '24.x',
+ prefix: '',
+ plugins_dir: '/usr/lib/centreon/plugins',
+ poller: 'Central',
+ apply_config: true,
+ commands: [],
+ service_templates: [],
+ host_templates: [],
+ hosts: []
+ };
+}
+
+function charger() {
+ try {
+ var brut = localStorage.getItem(CLE);
+ if (!brut) return projetVide();
+ var p = JSON.parse(brut);
+ var vide = projetVide();
+ Object.keys(vide).forEach(function (k) { if (p[k] === undefined) p[k] = vide[k]; });
+ return p;
+ } catch (e) {
+ return projetVide();
+ }
+}
+
+var minuteurSauvegarde = null;
+function sauver() {
+ clearTimeout(minuteurSauvegarde);
+ minuteurSauvegarde = setTimeout(function () {
+ try {
+ localStorage.setItem(CLE, JSON.stringify(projet));
+ etat('Enregistre dans ce navigateur');
+ } catch (e) {
+ etat('Enregistrement impossible : espace du navigateur sature');
+ }
+ }, 250);
+}
+
+function etat(message) {
+ var cible = document.getElementById('etat-sauvegarde');
+ cible.textContent = message;
+ clearTimeout(etat.minuteur);
+ etat.minuteur = setTimeout(function () { cible.textContent = ''; }, 2500);
+}
+
+var LISTES = {
+ command: { cle: 'commands', titre: 'Commandes', singulier: 'commande' },
+ stpl: { cle: 'service_templates', titre: 'Modeles de service', singulier: 'modele de service' },
+ htpl: { cle: 'host_templates', titre: "Modeles d'hote", singulier: "modele d'hote" },
+ host: { cle: 'hosts', titre: 'Hotes', singulier: 'hote' }
+};
+
+function liste(type) { return projet[LISTES[type].cle]; }
+
+function nouvelObjet(type) {
+ if (type === 'command') return { name: '', type: 'check', line: '', comment: '' };
+ if (type === 'stpl') return {
+ name: '', alias: '', parent: 'generic-active-service-custom', command: '', command_args: '',
+ check_interval: 5, retry_interval: 1, max_attempts: 3, active_checks: 1, passive_checks: 0,
+ host_templates: [], macros: []
+ };
+ if (type === 'htpl') return {
+ name: '', alias: '', parents: ['generic-active-host-custom'], check_command: '',
+ check_command_args: '', snmp_version: '', snmp_community: '', timezone: '', macros: []
+ };
+ return { name: '', alias: '', address: '', templates: [], poller: projet.poller, hostgroups: '', macros: [] };
+}
+
+// -------------------------------------------------------------------- rail
+
+function rendreRail() {
+ var rail = document.getElementById('rail');
+ rail.innerHTML = '';
+
+ rail.appendChild(elt('button', {
+ className: 'rail-reglages' + (selection.type === 'reglages' ? ' actif' : ''),
+ type: 'button',
+ textContent: 'Reglages du projet',
+ onclick: function () { selection = { type: 'reglages', index: 0 }; rendre(); }
+ }));
+
+ Object.keys(LISTES).forEach(function (type) {
+ var section = elt('section', { className: 'rail-groupe' });
+ var head = elt('div', { className: 'rail-head' });
+ head.appendChild(elt('h2', { textContent: LISTES[type].titre }));
+ head.appendChild(elt('span', { className: 'count', textContent: liste(type).length }));
+ head.appendChild(elt('button', {
+ className: 'small', type: 'button', textContent: '+', title: 'Ajouter un ' + LISTES[type].singulier,
+ onclick: function () {
+ liste(type).push(nouvelObjet(type));
+ selection = { type: type, index: liste(type).length - 1 };
+ sauver(); rendre();
+ }
+ }));
+ section.appendChild(head);
+
+ liste(type).forEach(function (objet, index) {
+ var actif = selection.type === type && selection.index === index;
+ var item = elt('button', {
+ className: 'rail-item' + (actif ? ' actif' : ''),
+ type: 'button',
+ onclick: function () { selection = { type: type, index: index }; rendre(); }
+ });
+ item.appendChild(elt('span', {
+ className: 'nom', textContent: objet.name || '(sans nom)',
+ dataset: { railNom: type + ':' + index }
+ }));
+ if (type === 'stpl' && objet.host_templates.length === 0) {
+ item.appendChild(elt('span', { className: 'tag warn', textContent: 'non rattache' }));
+ }
+ section.appendChild(item);
+ });
+ rail.appendChild(section);
+ });
+}
+
+// -------------------------------------------------------------------- main
+
+function rendreMain() {
+ var main = document.getElementById('main');
+ main.innerHTML = '';
+
+ if (selection.type === 'generation') return rendreGeneration(main);
+ if (selection.type === 'reglages') return rendreReglages(main);
+
+ var items = liste(selection.type);
+ if (!items.length) {
+ if (projetTotal() === 0) {
+ main.appendChild(document.getElementById('tpl-vide').content.cloneNode(true));
+ } else {
+ main.appendChild(elt('p', { className: 'lede', textContent: 'Aucun objet dans cette liste.' }));
+ }
+ return;
+ }
+ if (selection.index >= items.length) selection.index = items.length - 1;
+ var objet = items[selection.index];
+
+ var entete = elt('div', { className: 'objet-head' });
+ entete.appendChild(elt('h2', { textContent: LISTES[selection.type].titre }));
+ entete.appendChild(elt('span', { className: 'grow' }));
+ entete.appendChild(elt('button', {
+ className: 'small', type: 'button', textContent: 'Dupliquer',
+ onclick: function () {
+ var copie = JSON.parse(JSON.stringify(objet));
+ copie.name = copie.name + '-copie';
+ items.splice(selection.index + 1, 0, copie);
+ selection.index += 1; sauver(); rendre();
+ }
+ }));
+ entete.appendChild(elt('button', {
+ className: 'small danger', type: 'button', textContent: 'Supprimer',
+ onclick: function () {
+ if (!confirm('Supprimer ' + (objet.name || 'cet objet') + ' ?')) return;
+ items.splice(selection.index, 1);
+ selection.index = Math.max(0, selection.index - 1);
+ sauver(); rendre();
+ }
+ }));
+ main.appendChild(entete);
+
+ if (selection.type === 'command') rendreCommande(main, objet);
+ if (selection.type === 'stpl') rendreStpl(main, objet);
+ if (selection.type === 'htpl') rendreHtpl(main, objet);
+ if (selection.type === 'host') rendreHost(main, objet);
+}
+
+function projetTotal() {
+ return projet.commands.length + projet.service_templates.length +
+ projet.host_templates.length + projet.hosts.length;
+}
+
+function rendreReglages(main) {
+ main.appendChild(elt('h2', { textContent: 'Reglages du projet' }));
+ main.appendChild(elt('p', {
+ className: 'lede',
+ textContent: "Ces valeurs s'appliquent a l'ensemble du code genere. Le prefixe evite les collisions " +
+ 'avec les packs officiels deja installes sur votre central.'
+ }));
+ var grille = elt('div', { className: 'grille' });
+ grille.appendChild(champ('Prefixe applique aux objets crees', projet, 'prefix', { placeholder: 'ex. ACME-' }));
+ grille.appendChild(champ('Repertoire des plugins', projet, 'plugins_dir'));
+ grille.appendChild(champ('Collecteur cible', projet, 'poller'));
+ main.appendChild(grille);
+ main.appendChild(caseACocher("Ajouter APPLYCFG a la fin des scripts", projet, 'apply_config'));
+
+ var info = elt('div', { className: 'notice' });
+ info.appendChild(elt('p', {
+ textContent: 'Le projet est conserve dans ce navigateur uniquement. Rien n\u2019est envoye au serveur ' +
+ 'tant que vous ne demandez pas la generation, et aucune macro de type mot de passe n\u2019est stockee ' +
+ 'ni partagee.'
+ }));
+ main.appendChild(info);
+}
+
+function rendreCommande(main, cmd) {
+ var grille = elt('div', { className: 'grille' });
+ grille.appendChild(champ('Nom de la commande', cmd, 'name', { rail: true, placeholder: 'OS-Linux-SNMP-Cpu' }));
+ grille.appendChild(champSelect('Type', cmd, 'type', TYPES));
+ main.appendChild(grille);
+ main.appendChild(champ('Commentaire', cmd, 'comment'));
+ main.appendChild(champZone('Ligne de commande', cmd, 'line', {
+ aide: '$CENTREONPLUGINS$ sera remplace par le repertoire des plugins. ' +
+ 'Utilisez $HOSTADDRESS$, $_HOSTMACRO$ et $_SERVICEMACRO$ comme dans Centreon.'
+ }));
+
+ var barre = elt('div', { className: 'barre' });
+ barre.appendChild(elt('button', {
+ type: 'button', className: 'small', textContent: 'Creer un modele de service depuis cette commande',
+ onclick: function () {
+ var stpl = nouvelObjet('stpl');
+ stpl.name = cmd.name;
+ stpl.alias = cmd.name.split('-').pop();
+ stpl.command = cmd.name;
+ stpl.macros = macrosDepuisLigne(cmd.line).service;
+ projet.service_templates.push(stpl);
+ selection = { type: 'stpl', index: projet.service_templates.length - 1 };
+ sauver(); rendre();
+ }
+ }));
+ main.appendChild(barre);
+}
+
+function macrosDepuisLigne(ligne) {
+ var service = (ligne.match(/\$_SERVICE([A-Z0-9_]+)\$/g) || []).map(function (m) {
+ return m.replace('$_SERVICE', '').replace('$', '');
+ });
+ var host = (ligne.match(/\$_HOST([A-Z0-9_]+)\$/g) || []).map(function (m) {
+ return m.replace('$_HOST', '').replace('$', '');
+ });
+ var uniq = function (noms) {
+ var vus = {}, sortie = [];
+ noms.forEach(function (n) {
+ if (vus[n]) return;
+ vus[n] = 1;
+ sortie.push({ name: n, value: '', description: '', is_password: /(PASSWORD|SECRET|TOKEN|APIKEY)/.test(n) });
+ });
+ return sortie;
+ };
+ return { service: uniq(service), host: uniq(host) };
+}
+
+function rendreStpl(main, stpl) {
+ var grille = elt('div', { className: 'grille' });
+ grille.appendChild(champ('Nom du modele', stpl, 'name', { rail: true }));
+ grille.appendChild(champ('Alias', stpl, 'alias'));
+ grille.appendChild(champ('Modele parent', stpl, 'parent'));
+ grille.appendChild(champSelectListe('Commande de controle', stpl, 'command',
+ projet.commands.map(function (c) { return c.name; })));
+ main.appendChild(grille);
+
+ var grille2 = elt('div', { className: 'grille quatre' });
+ grille2.appendChild(champ('Intervalle', stpl, 'check_interval', { type: 'number' }));
+ grille2.appendChild(champ('Intervalle de reprise', stpl, 'retry_interval', { type: 'number' }));
+ grille2.appendChild(champ('Tentatives', stpl, 'max_attempts', { type: 'number' }));
+ grille2.appendChild(champ('Arguments', stpl, 'command_args', { placeholder: '!80!90' }));
+ main.appendChild(grille2);
+
+ main.appendChild(sousTitre("Rattachement aux modeles d'hote",
+ "Un hote heritant d'un de ces modeles recevra ce service."));
+ if (!projet.host_templates.length) {
+ main.appendChild(elt('p', { className: 'lede', textContent: "Aucun modele d'hote dans le projet." }));
+ } else {
+ var cases = elt('div', { className: 'cases' });
+ projet.host_templates.forEach(function (htpl) {
+ cases.appendChild(caseListe(htpl.name || '(sans nom)', stpl.host_templates, htpl.name));
+ });
+ main.appendChild(cases);
+ }
+
+ main.appendChild(sousTitre('Macros du service', 'Seuils et parametres propres a cette sonde.'));
+ main.appendChild(editeurMacros(stpl, function () {
+ var cmd = projet.commands.filter(function (c) { return c.name === stpl.command; })[0];
+ return cmd ? macrosDepuisLigne(cmd.line).service : [];
+ }));
+}
+
+function rendreHtpl(main, htpl) {
+ var grille = elt('div', { className: 'grille' });
+ grille.appendChild(champ('Nom du modele', htpl, 'name', { rail: true, placeholder: 'OS-Linux-SNMP-custom' }));
+ grille.appendChild(champ('Alias', htpl, 'alias'));
+ grille.appendChild(champListe('Modeles parents', htpl, 'parents'));
+ grille.appendChild(champ('Fuseau horaire', htpl, 'timezone', { placeholder: ':Europe/Paris' }));
+ main.appendChild(grille);
+
+ var grille2 = elt('div', { className: 'grille' });
+ grille2.appendChild(champ('Commande de verification', htpl, 'check_command', { placeholder: 'check_centreon_ping' }));
+ grille2.appendChild(champ('Arguments', htpl, 'check_command_args', { placeholder: '!3!200,20%!400,50%' }));
+ grille2.appendChild(champ('Version SNMP', htpl, 'snmp_version', { placeholder: '2c' }));
+ grille2.appendChild(champ('Communaute SNMP', htpl, 'snmp_community'));
+ main.appendChild(grille2);
+
+ main.appendChild(sousTitre("Macros du modele d'hote",
+ 'Heritees par tous les hotes rattaches : identifiants, ports, options du plugin.'));
+ main.appendChild(editeurMacros(htpl, function () { return []; }));
+
+ var services = projet.service_templates.filter(function (s) {
+ return s.host_templates.indexOf(htpl.name) !== -1;
+ });
+ main.appendChild(sousTitre('Services deployes par ce modele', services.length + ' modele(s) de service'));
+ var liste_ = elt('div', { className: 'puces' });
+ services.forEach(function (s) { liste_.appendChild(elt('span', { className: 'puce', textContent: s.name })); });
+ main.appendChild(liste_);
+}
+
+function rendreHost(main, host) {
+ var grille = elt('div', { className: 'grille' });
+ grille.appendChild(champ("Nom de l'hote", host, 'name', { rail: true }));
+ grille.appendChild(champ('Alias', host, 'alias'));
+ grille.appendChild(champ('Adresse IP ou DNS', host, 'address'));
+ grille.appendChild(champ('Collecteur', host, 'poller'));
+ grille.appendChild(champ("Groupes d'hotes", host, 'hostgroups'));
+ main.appendChild(grille);
+
+ main.appendChild(sousTitre("Modeles d'hote appliques", ''));
+ var cases = elt('div', { className: 'cases' });
+ projet.host_templates.forEach(function (htpl) {
+ cases.appendChild(caseListe(htpl.name || '(sans nom)', host.templates, htpl.name));
+ });
+ main.appendChild(cases);
+
+ main.appendChild(sousTitre("Macros propres a l'hote", 'Elles surchargent celles du modele.'));
+ main.appendChild(editeurMacros(host, function () { return []; }));
+}
+
+// --------------------------------------------------------------- generation
+
+function rendreGeneration(main) {
+ main.appendChild(elt('h2', { textContent: 'Code CLAPI' }));
+ var zone = elt('div', { id: 'zone-generation' });
+ zone.appendChild(elt('p', { className: 'lede', textContent: 'Generation en cours...' }));
+ main.appendChild(zone);
+
+ fetch('/api/generer', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ project: projet })
+ }).then(function (r) { return r.json(); }).then(function (data) {
+ zone.innerHTML = '';
+
+ var resume = elt('div', { className: 'resume' });
+ Object.keys(data.summary).forEach(function (cle) {
+ var bloc = elt('div');
+ bloc.appendChild(elt('b', { textContent: data.summary[cle] }));
+ bloc.appendChild(elt('span', { textContent: cle }));
+ resume.appendChild(bloc);
+ });
+ zone.appendChild(resume);
+
+ if (data.issues.length) {
+ var erreurs = data.issues.filter(function (i) { return i.level === 'error'; });
+ var note = elt('div', { className: 'notice' + (erreurs.length ? ' bad' : '') });
+ note.appendChild(elt('b', {
+ textContent: erreurs.length
+ ? erreurs.length + ' erreur(s) a corriger avant de deployer'
+ : data.issues.length + ' point(s) de vigilance'
+ }));
+ var ul = elt('ul');
+ data.issues.forEach(function (i) {
+ ul.appendChild(elt('li', { textContent: i.message, className: i.level }));
+ });
+ note.appendChild(ul);
+ zone.appendChild(note);
+ }
+
+ var noms = Object.keys(data.files);
+ var onglets = elt('div', { className: 'tabs' });
+ var panneaux = {};
+ noms.forEach(function (nom, i) {
+ var bouton = elt('button', {
+ type: 'button', textContent: nom,
+ onclick: function () {
+ noms.forEach(function (n) {
+ panneaux[n].hidden = n !== nom;
+ onglets.querySelectorAll('button').forEach(function (b) {
+ b.setAttribute('aria-selected', String(b.textContent === nom));
+ });
+ });
+ }
+ });
+ bouton.setAttribute('aria-selected', String(i === 0));
+ onglets.appendChild(bouton);
+ });
+ zone.appendChild(onglets);
+ noms.forEach(function (nom, i) {
+ var pre = elt('pre', { className: 'code', textContent: data.files[nom] });
+ pre.hidden = i !== 0;
+ panneaux[nom] = pre;
+ zone.appendChild(pre);
+ });
+
+ var barre = elt('div', { className: 'barre' });
+ noms.forEach(function (nom) {
+ barre.appendChild(elt('button', {
+ type: 'button', textContent: nom,
+ onclick: function () { telecharger(nom, data.files[nom]); }
+ }));
+ });
+ barre.appendChild(elt('button', {
+ type: 'button', className: 'primary auto', textContent: 'Archive complete (.zip)',
+ onclick: function () {
+ document.getElementById('form-zip-project').value = JSON.stringify(projet);
+ document.getElementById('form-zip').submit();
+ }
+ }));
+ zone.appendChild(barre);
+ }).catch(function () {
+ zone.innerHTML = '';
+ zone.appendChild(elt('div', { className: 'notice bad', textContent: 'La generation a echoue. Reessayez.' }));
+ });
+}
+
+function telecharger(nom, contenu) {
+ var blob = new Blob([contenu], { type: 'text/plain;charset=utf-8' });
+ var url = URL.createObjectURL(blob);
+ var a = elt('a', { href: url, download: nom });
+ document.body.appendChild(a); a.click(); a.remove();
+ setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
+}
+
+// ------------------------------------------------------------- composants
+
+function elt(tag, props) {
+ var noeud = document.createElement(tag);
+ Object.keys(props || {}).forEach(function (cle) {
+ if (cle === 'dataset') Object.assign(noeud.dataset, props[cle]);
+ else if (cle === 'onclick') noeud.addEventListener('click', props[cle]);
+ else noeud[cle] = props[cle];
+ });
+ return noeud;
+}
+
+function champ(libelle, objet, cle, options) {
+ options = options || {};
+ var label = elt('label', { className: 'field' });
+ label.appendChild(elt('span', { textContent: libelle }));
+ var input = elt('input', { type: options.type || 'text', value: objet[cle] });
+ if (options.placeholder) input.placeholder = options.placeholder;
+ input.addEventListener('input', function () {
+ objet[cle] = options.type === 'number' ? parseInt(input.value || '0', 10) : input.value;
+ if (options.rail) majNomRail();
+ sauver();
+ });
+ label.appendChild(input);
+ return label;
+}
+
+function champZone(libelle, objet, cle, options) {
+ options = options || {};
+ var label = elt('label', { className: 'field' });
+ label.appendChild(elt('span', { textContent: libelle }));
+ var zone = elt('textarea', { rows: 5, value: objet[cle] });
+ zone.addEventListener('input', function () { objet[cle] = zone.value; sauver(); });
+ label.appendChild(zone);
+ if (options.aide) label.appendChild(elt('small', { textContent: options.aide }));
+ return label;
+}
+
+function champSelect(libelle, objet, cle, choix) {
+ var label = elt('label', { className: 'field' });
+ label.appendChild(elt('span', { textContent: libelle }));
+ var select = elt('select');
+ Object.keys(choix).forEach(function (valeur) {
+ var option = elt('option', { value: valeur, textContent: choix[valeur] });
+ if (objet[cle] === valeur) option.selected = true;
+ select.appendChild(option);
+ });
+ select.addEventListener('change', function () { objet[cle] = select.value; sauver(); });
+ label.appendChild(select);
+ return label;
+}
+
+function champSelectListe(libelle, objet, cle, valeurs) {
+ var label = elt('label', { className: 'field' });
+ label.appendChild(elt('span', { textContent: libelle }));
+ var input = elt('input', { type: 'text', value: objet[cle], list: 'liste-' + cle });
+ var datalist = elt('datalist', { id: 'liste-' + cle });
+ valeurs.forEach(function (v) { datalist.appendChild(elt('option', { value: v })); });
+ input.addEventListener('input', function () { objet[cle] = input.value; sauver(); });
+ label.appendChild(input);
+ label.appendChild(datalist);
+ return label;
+}
+
+function champListe(libelle, objet, cle) {
+ var label = elt('label', { className: 'field' });
+ label.appendChild(elt('span', { textContent: libelle + ' (separes par une virgule)' }));
+ var input = elt('input', { type: 'text', value: (objet[cle] || []).join(', ') });
+ input.addEventListener('input', function () {
+ objet[cle] = input.value.split(',').map(function (v) { return v.trim(); }).filter(Boolean);
+ sauver();
+ });
+ label.appendChild(input);
+ return label;
+}
+
+function caseACocher(libelle, objet, cle) {
+ var label = elt('label', { className: 'check' });
+ var input = elt('input', { type: 'checkbox', checked: !!objet[cle] });
+ input.addEventListener('change', function () { objet[cle] = input.checked; sauver(); });
+ label.appendChild(input);
+ label.appendChild(elt('span', { textContent: libelle }));
+ return label;
+}
+
+function caseListe(libelle, tableau, valeur) {
+ var label = elt('label', { className: 'check' });
+ var input = elt('input', { type: 'checkbox', checked: tableau.indexOf(valeur) !== -1 });
+ input.addEventListener('change', function () {
+ var i = tableau.indexOf(valeur);
+ if (input.checked && i === -1) tableau.push(valeur);
+ if (!input.checked && i !== -1) tableau.splice(i, 1);
+ sauver(); rendreRail();
+ });
+ label.appendChild(input);
+ label.appendChild(elt('span', { textContent: libelle }));
+ return label;
+}
+
+function sousTitre(titre, aide) {
+ var bloc = elt('div', { className: 'sous-titre' });
+ bloc.appendChild(elt('h3', { textContent: titre }));
+ if (aide) bloc.appendChild(elt('span', { textContent: aide }));
+ return bloc;
+}
+
+function editeurMacros(objet, suggestions) {
+ var bloc = elt('div');
+ var table = elt('div', { className: 'macros-table' });
+ objet.macros.forEach(function (macro, index) {
+ var ligne = elt('div', { className: 'macro-ligne' });
+ var nom = elt('input', { type: 'text', value: macro.name, placeholder: 'NOM' });
+ nom.addEventListener('input', function () { macro.name = nom.value.toUpperCase(); nom.value = macro.name; sauver(); });
+ var valeur = elt('input', { type: 'text', value: macro.value, placeholder: 'valeur' });
+ valeur.addEventListener('input', function () { macro.value = valeur.value; sauver(); });
+ var desc = elt('input', { type: 'text', value: macro.description, placeholder: 'description' });
+ desc.addEventListener('input', function () { macro.description = desc.value; sauver(); });
+ var mdp = elt('label', { className: 'check', title: 'Macro de type mot de passe' });
+ var coche = elt('input', { type: 'checkbox', checked: !!macro.is_password });
+ coche.addEventListener('change', function () { macro.is_password = coche.checked; sauver(); rendreMain(); });
+ mdp.appendChild(coche);
+ mdp.appendChild(elt('span', { textContent: 'mdp' }));
+ var sup = elt('button', {
+ type: 'button', className: 'small danger', textContent: 'x',
+ onclick: function () { objet.macros.splice(index, 1); sauver(); rendreMain(); }
+ });
+ ligne.appendChild(nom); ligne.appendChild(valeur); ligne.appendChild(desc);
+ ligne.appendChild(mdp); ligne.appendChild(sup);
+ if (macro.is_password && macro.value) {
+ ligne.appendChild(elt('small', { className: 'avert', textContent: 'sortira en clair dans les fichiers' }));
+ }
+ table.appendChild(ligne);
+ });
+ bloc.appendChild(table);
+
+ var barre = elt('div', { className: 'barre' });
+ barre.appendChild(elt('button', {
+ type: 'button', className: 'small', textContent: 'Ajouter une macro',
+ onclick: function () {
+ objet.macros.push({ name: '', value: '', description: '', is_password: false });
+ sauver(); rendreMain();
+ }
+ }));
+ var manquantes = suggestions().filter(function (m) {
+ return !objet.macros.some(function (existante) { return existante.name === m.name; });
+ });
+ if (manquantes.length) {
+ barre.appendChild(elt('button', {
+ type: 'button', className: 'small', textContent: 'Creer les macros de la commande (' + manquantes.length + ')',
+ onclick: function () { manquantes.forEach(function (m) { objet.macros.push(m); }); sauver(); rendreMain(); }
+ }));
+ }
+ bloc.appendChild(barre);
+ return bloc;
+}
+
+function majNomRail() {
+ var cible = document.querySelector('[data-rail-nom="' + selection.type + ':' + selection.index + '"]');
+ if (cible) cible.textContent = liste(selection.type)[selection.index].name || '(sans nom)';
+}
+
+// ------------------------------------------------------------------ modale
+
+function ouvrirModale(titre, construire) {
+ document.getElementById('modal-titre').textContent = titre;
+ var corps = document.getElementById('modal-corps');
+ corps.innerHTML = '';
+ construire(corps);
+ document.getElementById('modal').hidden = false;
+}
+
+function fermerModale() { document.getElementById('modal').hidden = true; }
+
+function ouvrirCataloguePacks() {
+ ouvrirModale('Importer un pack', function (corps) {
+ var recherche = elt('input', { type: 'text', placeholder: 'Filtrer par nom, technologie, plugin...' });
+ corps.appendChild(recherche);
+ var resultats = elt('div', { className: 'packs' });
+ corps.appendChild(resultats);
+
+ function afficher(packs) {
+ resultats.innerHTML = '';
+ packs.forEach(function (pack) {
+ var ligne = elt('div', { className: 'pack-ligne' });
+ var texte = elt('div');
+ texte.appendChild(elt('b', { textContent: pack.name }));
+ if (pack.status !== 'verified') {
+ texte.appendChild(elt('span', { className: 'tag warn', textContent: 'a valider' }));
+ }
+ texte.appendChild(elt('div', { className: 'desc', textContent: pack.description }));
+ texte.appendChild(elt('div', { className: 'plugin', textContent: pack.plugin + ' | ' + pack.services.length + ' sondes' }));
+ ligne.appendChild(texte);
+ var actions = elt('div', { className: 'barre' });
+ actions.appendChild(elt('button', {
+ type: 'button', className: 'small', textContent: 'Sondes par defaut',
+ onclick: function () { importerPack(pack.id, false); }
+ }));
+ actions.appendChild(elt('button', {
+ type: 'button', className: 'small', textContent: 'Toutes les sondes',
+ onclick: function () { importerPack(pack.id, true); }
+ }));
+ ligne.appendChild(actions);
+ resultats.appendChild(ligne);
+ });
+ if (!packs.length) resultats.appendChild(elt('p', { className: 'lede', textContent: 'Aucun pack ne correspond.' }));
+ }
+
+ function filtrer() {
+ var q = recherche.value.toLowerCase();
+ afficher(packsCache.filter(function (p) {
+ return (p.name + ' ' + p.description + ' ' + p.plugin + ' ' + p.category_label).toLowerCase().indexOf(q) !== -1;
+ }));
+ }
+ recherche.addEventListener('input', filtrer);
+
+ if (packsCache) { afficher(packsCache); }
+ else {
+ resultats.appendChild(elt('p', { className: 'lede', textContent: 'Chargement du catalogue...' }));
+ fetch('/api/packs').then(function (r) { return r.json(); }).then(function (packs) {
+ packsCache = packs; afficher(packs);
+ });
+ }
+ });
+}
+
+function importerPack(id, tout) {
+ fetch('/api/packs/' + id + '/projet' + (tout ? '?tout=1' : ''))
+ .then(function (r) { return r.json(); })
+ .then(function (fragment) {
+ var ignores = [];
+ ['commands', 'service_templates', 'host_templates'].forEach(function (cle) {
+ var existants = {};
+ projet[cle].forEach(function (o) { existants[o.name] = true; });
+ fragment[cle].forEach(function (o) {
+ if (existants[o.name]) { ignores.push(o.name); return; }
+ projet[cle].push(o);
+ });
+ });
+ fermerModale();
+ selection = { type: 'htpl', index: Math.max(0, projet.host_templates.length - 1) };
+ sauver(); rendre();
+ etat(ignores.length ? ignores.length + ' objet(s) deja present(s), ignore(s)' : 'Pack importe');
+ });
+}
+
+// ------------------------------------------------------------------ actions
+
+function rendre() {
+ rendreRail();
+ rendreMain();
+ document.getElementById('projet-nom').value = projet.name;
+ document.getElementById('projet-version').value = projet.centreon_version;
+}
+
+document.getElementById('projet-nom').addEventListener('input', function (e) {
+ projet.name = e.target.value; sauver();
+});
+document.getElementById('projet-version').addEventListener('change', function (e) {
+ projet.centreon_version = e.target.value; sauver();
+});
+document.getElementById('btn-pack').addEventListener('click', ouvrirCataloguePacks);
+document.getElementById('btn-generer').addEventListener('click', function () {
+ selection = { type: 'generation', index: 0 }; rendre();
+});
+document.getElementById('btn-export').addEventListener('click', function () {
+ telecharger((projet.name || 'projet').replace(/[^\w-]+/g, '-') + '.json', JSON.stringify(projet, null, 2));
+});
+document.getElementById('btn-import').addEventListener('click', function () {
+ var input = elt('input', { type: 'file', accept: '.json,application/json' });
+ input.addEventListener('change', function () {
+ var fichier = input.files[0];
+ if (!fichier) return;
+ var lecteur = new FileReader();
+ lecteur.onload = function () {
+ try {
+ var charge = JSON.parse(lecteur.result);
+ var vide = projetVide();
+ Object.keys(vide).forEach(function (k) { if (charge[k] === undefined) charge[k] = vide[k]; });
+ projet = charge;
+ selection = { type: 'reglages', index: 0 };
+ sauver(); rendre();
+ etat('Projet ouvert');
+ } catch (e) {
+ alert("Ce fichier n'est pas un projet valide.");
+ }
+ };
+ lecteur.readAsText(fichier);
+ });
+ input.click();
+});
+document.getElementById('btn-vider').addEventListener('click', function () {
+ if (!confirm('Vider le projet en cours ? Cette action est definitive.')) return;
+ projet = projetVide();
+ selection = { type: 'reglages', index: 0 };
+ sauver(); rendre();
+});
+document.querySelectorAll('[data-fermer-modal]').forEach(function (b) {
+ b.addEventListener('click', fermerModale);
+});
+document.getElementById('modal').addEventListener('click', function (e) {
+ if (e.target.id === 'modal') fermerModale();
+});
+document.addEventListener('keydown', function (e) {
+ if (e.key === 'Escape') fermerModale();
+});
+
+rendre();
diff --git a/enclume/static/enclume.css b/enclume/static/enclume.css
new file mode 100644
index 0000000..453f652
--- /dev/null
+++ b/enclume/static/enclume.css
@@ -0,0 +1,366 @@
+:root {
+ --bg: #0f151c;
+ --panel: #161f29;
+ --panel-2: #1b2532;
+ --line: #253243;
+ --ink: #dde5ee;
+ --ink-dim: #8ca0b6;
+ --accent: #e7a93f;
+ --ok: #46b98a;
+ --alert: #e0575b;
+ --radius: 3px;
+ --mono: "JetBrains Mono", "SFMono-Regular", Menlo, Consolas, monospace;
+ --sans: "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
+ --h-top: 52px;
+ --h-tool: 52px;
+}
+
+* { box-sizing: border-box; }
+
+body {
+ margin: 0;
+ background: var(--bg);
+ color: var(--ink);
+ font-family: var(--sans);
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+h1, h2, h3 { margin: 0; font-weight: 600; }
+h1 { font-size: 16px; }
+h2 { font-size: 14px; }
+h3 { font-size: 13px; }
+small { color: var(--ink-dim); font-size: 12px; display: block; margin-top: 4px; }
+
+/* --------------------------------------------------------------- bandeaux */
+
+.topbar {
+ display: flex;
+ align-items: baseline;
+ gap: 14px;
+ height: var(--h-top);
+ padding: 0 20px;
+ border-bottom: 1px solid var(--line);
+ background: var(--panel);
+}
+.topbar .meta { color: var(--ink-dim); font-size: 12.5px; }
+
+.toolbar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: var(--h-tool);
+ padding: 0 20px;
+ border-bottom: 1px solid var(--line);
+ background: var(--panel-2);
+ position: sticky;
+ top: 0;
+ z-index: 4;
+}
+.toolbar .titre {
+ width: 260px;
+ font-family: var(--sans);
+ font-size: 14px;
+ font-weight: 600;
+}
+.toolbar .sep { width: 1px; height: 22px; background: var(--line); margin: 0 4px; }
+.toolbar .grow { flex: 1; text-align: right; color: var(--ok); font-size: 12.5px; }
+
+/* ---------------------------------------------------------------- atelier */
+
+.atelier {
+ display: grid;
+ grid-template-columns: 280px minmax(0, 1fr);
+ min-height: calc(100vh - var(--h-top) - var(--h-tool));
+ align-items: start;
+}
+
+#rail {
+ border-right: 1px solid var(--line);
+ background: var(--panel);
+ padding: 10px 0 30px;
+ position: sticky;
+ top: var(--h-tool);
+ max-height: calc(100vh - var(--h-tool));
+ overflow-y: auto;
+}
+.rail-reglages {
+ display: block;
+ width: calc(100% - 20px);
+ margin: 0 10px 14px;
+ text-align: left;
+}
+.rail-reglages.actif { border-color: var(--accent); color: var(--accent); }
+
+.rail-groupe { margin-bottom: 14px; }
+.rail-head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 4px 10px 4px 14px;
+ border-bottom: 1px solid var(--line);
+}
+.rail-head h2 { font-size: 12.5px; color: var(--ink-dim); font-weight: 600; }
+.rail-head .count { color: var(--ink-dim); font-size: 12px; margin-left: auto; }
+
+.rail-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ width: 100%;
+ text-align: left;
+ background: none;
+ border: none;
+ border-left: 2px solid transparent;
+ border-radius: 0;
+ padding: 6px 10px 6px 12px;
+ color: var(--ink);
+ font-family: var(--mono);
+ font-size: 12px;
+ cursor: pointer;
+}
+.rail-item:hover { background: var(--panel-2); }
+.rail-item.actif { background: var(--panel-2); border-left-color: var(--accent); }
+.rail-item .nom { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+
+/* ------------------------------------------------------------------- main */
+
+main {
+ padding: 22px 26px 60px;
+ max-width: 1000px;
+}
+p.lede { color: var(--ink-dim); margin: 0 0 18px; max-width: 70ch; }
+
+.objet-head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid var(--line);
+ margin-bottom: 18px;
+}
+.objet-head .grow { flex: 1; }
+
+.sous-titre {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+ margin: 22px 0 10px;
+ padding-bottom: 6px;
+ border-bottom: 1px solid var(--line);
+}
+.sous-titre span { color: var(--ink-dim); font-size: 12.5px; }
+
+.grille { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px 16px; }
+.grille.quatre { grid-template-columns: repeat(4, minmax(0, 1fr)); }
+
+/* ------------------------------------------------------------ formulaires */
+
+label.field { display: block; margin-bottom: 12px; }
+label.field > span { display: block; font-size: 12px; color: var(--ink-dim); margin-bottom: 3px; }
+
+input[type="text"], input[type="number"], select, textarea {
+ width: 100%;
+ background: var(--bg);
+ border: 1px solid var(--line);
+ border-radius: var(--radius);
+ color: var(--ink);
+ font-family: var(--mono);
+ font-size: 12.5px;
+ padding: 6px 8px;
+}
+textarea { line-height: 1.55; resize: vertical; }
+input:focus, select:focus, textarea:focus, button:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 1px;
+}
+input[type="checkbox"] { accent-color: var(--accent); width: 15px; height: 15px; }
+
+.check { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; margin: 0 16px 8px 0; }
+.check span { color: var(--ink); }
+.cases { display: flex; flex-wrap: wrap; gap: 0 4px; }
+
+button {
+ font-family: var(--sans);
+ font-size: 13px;
+ border-radius: var(--radius);
+ border: 1px solid var(--line);
+ background: var(--panel);
+ color: var(--ink);
+ padding: 6px 11px;
+ cursor: pointer;
+}
+button:hover { border-color: var(--accent); }
+button.primary { background: var(--accent); border-color: var(--accent); color: #17202b; font-weight: 600; }
+button.small { padding: 3px 8px; font-size: 12px; }
+button.danger:hover { border-color: var(--alert); color: var(--alert); }
+button.auto { width: auto; }
+
+.barre { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
+
+/* ----------------------------------------------------------------- macros */
+
+.macros-table { display: flex; flex-direction: column; gap: 6px; }
+.macro-ligne {
+ display: grid;
+ grid-template-columns: 180px minmax(0, 1fr) minmax(0, 1fr) 74px 30px;
+ gap: 8px;
+ align-items: center;
+}
+.macro-ligne .check { margin: 0; font-size: 12px; }
+.macro-ligne .avert { grid-column: 1 / -1; color: var(--accent); margin: 0 0 4px; }
+
+.puces { display: flex; flex-wrap: wrap; gap: 6px; }
+.puce {
+ font-family: var(--mono);
+ font-size: 11.5px;
+ color: var(--ink-dim);
+ border: 1px solid var(--line);
+ border-radius: var(--radius);
+ padding: 2px 7px;
+}
+
+.tag {
+ font-size: 11px;
+ padding: 1px 6px;
+ border-radius: var(--radius);
+ border: 1px solid var(--line);
+ color: var(--ink-dim);
+}
+.tag.warn { color: var(--accent); border-color: rgba(231, 169, 63, 0.4); }
+
+/* ------------------------------------------------------------- generation */
+
+.resume { display: flex; gap: 24px; margin: 14px 0 18px; }
+.resume div { display: flex; flex-direction: column; }
+.resume b { font-family: var(--mono); font-size: 20px; font-weight: 500; }
+.resume span { color: var(--ink-dim); font-size: 12px; }
+
+.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); }
+.tabs button {
+ border-bottom: none;
+ border-radius: var(--radius) var(--radius) 0 0;
+ background: var(--panel);
+ color: var(--ink-dim);
+ font-family: var(--mono);
+ font-size: 12.5px;
+}
+.tabs button[aria-selected="true"] { background: var(--panel-2); color: var(--ink); }
+
+pre.code {
+ background: var(--panel-2);
+ border: 1px solid var(--line);
+ border-top: none;
+ margin: 0;
+ padding: 14px;
+ font-family: var(--mono);
+ font-size: 12px;
+ line-height: 1.55;
+ max-height: 58vh;
+ overflow: auto;
+ white-space: pre;
+}
+
+.notice {
+ border-left: 2px solid var(--accent);
+ background: var(--panel);
+ padding: 10px 14px;
+ margin: 14px 0;
+ font-size: 13px;
+}
+.notice.bad { border-left-color: var(--alert); }
+.notice p { margin: 0; color: var(--ink-dim); }
+.notice ul { margin: 8px 0 0; padding-left: 18px; }
+.notice li { color: var(--ink-dim); margin-bottom: 3px; }
+.notice li.error { color: var(--alert); }
+
+.vide { padding: 40px 0; max-width: 60ch; }
+.vide h2 { margin-bottom: 8px; }
+.vide p { color: var(--ink-dim); }
+
+/* ----------------------------------------------------------------- modale */
+
+.modal {
+ position: fixed;
+ inset: 0;
+ background: rgba(8, 12, 17, 0.72);
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ padding: 60px 20px;
+ z-index: 20;
+}
+.modal[hidden] { display: none; }
+.modal-box {
+ background: var(--panel);
+ border: 1px solid var(--line);
+ border-radius: var(--radius);
+ width: min(760px, 100%);
+ max-height: 78vh;
+ display: flex;
+ flex-direction: column;
+}
+.modal-box > header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 16px;
+ border-bottom: 1px solid var(--line);
+}
+.modal-box > header h2 { flex: 1; }
+#modal-corps { padding: 14px 16px; overflow-y: auto; }
+#modal-corps > input { margin-bottom: 12px; }
+
+.packs { display: flex; flex-direction: column; }
+.pack-ligne {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 10px 0;
+ border-bottom: 1px solid var(--line);
+}
+.pack-ligne > div:first-child { flex: 1; min-width: 0; }
+.pack-ligne b { margin-right: 8px; }
+.pack-ligne .desc { color: var(--ink-dim); font-size: 12.5px; }
+.pack-ligne .plugin { font-family: var(--mono); font-size: 11.5px; color: var(--ink-dim); margin-top: 2px; }
+.pack-ligne .barre { margin: 0; flex-wrap: nowrap; }
+
+/* -------------------------------------------------------------- adaptatif */
+
+@media (max-width: 900px) {
+ .atelier { grid-template-columns: 1fr; }
+ #rail { position: static; max-height: none; border-right: none; border-bottom: 1px solid var(--line); }
+ .grille, .grille.quatre { grid-template-columns: 1fr; }
+ .macro-ligne { grid-template-columns: 1fr 1fr; }
+ .toolbar { flex-wrap: wrap; height: auto; padding: 10px 16px; }
+ .toolbar .titre { width: 100%; }
+ main { padding: 18px 16px 50px; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ * { transition: none !important; animation: none !important; }
+}
+
+/* -------------------------------------------------------------------- pied */
+
+.topbar .version {
+ margin-left: auto;
+ font-family: var(--mono);
+ font-size: 11.5px;
+ color: var(--ink-dim);
+}
+
+.pied {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 14px;
+ padding: 14px 20px;
+ border-top: 1px solid var(--line);
+ background: var(--panel);
+ color: var(--ink-dim);
+ font-size: 12px;
+}
+.pied .grow { flex: 1; }
+.pied a { color: var(--ink-dim); }
+.pied a:hover { color: var(--accent); }
diff --git a/enclume/static/enclume.svg b/enclume/static/enclume.svg
new file mode 100644
index 0000000..de6f4b1
--- /dev/null
+++ b/enclume/static/enclume.svg
@@ -0,0 +1,6 @@
+
diff --git a/enclume/templates/base.html b/enclume/templates/base.html
new file mode 100644
index 0000000..484f5e4
--- /dev/null
+++ b/enclume/templates/base.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+{% block title %}Enclume — Générateur de configuration CLAPI pour Centreon{% endblock %}
+
+
+
+
+
+
+
+
+
+
+
Enclume
+ Générateur de configuration CLAPI pour Centreon
+ {{ version }}
+
+{% block body %}{% endblock %}
+
+
+
diff --git a/enclume/templates/editeur.html b/enclume/templates/editeur.html
new file mode 100644
index 0000000..d6d2d2e
--- /dev/null
+++ b/enclume/templates/editeur.html
@@ -0,0 +1,46 @@
+{% extends "base.html" %}
+{% block body %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Importer un pack
+
+
+
+
+
+
+
+
+
+
+
Un projet vide, trois facons de le remplir
+
Partez d'un pack du catalogue ({{ pack_count }} packs, {{ service_count }} sondes) et modifiez-le,
+ rouvrez un projet enregistre, ou creez vos objets un par un dans la colonne de gauche.
+
+
+
+
+
+{% endblock %}
diff --git a/enclume/webapp.py b/enclume/webapp.py
new file mode 100644
index 0000000..725a47b
--- /dev/null
+++ b/enclume/webapp.py
@@ -0,0 +1,163 @@
+"""Routes de l'application.
+
+Le serveur est volontairement sans etat a ce stade : il valide, il genere, il
+ne conserve rien. Le projet vit dans le navigateur. Le partage, les projets
+enregistres et la connexion viendront s'ajouter par-dessus sans changer ce
+noyau.
+"""
+
+from __future__ import annotations
+
+import io
+import json
+import os
+import zipfile
+
+from flask import Flask, Response, jsonify, render_template, request
+
+from .catalog import CATEGORIES, Catalog
+from .generator import build_plan, generate, render_bundle
+from .model import CENTREON_VERSIONS, COMMAND_TYPES, Project, detect_macros, strip_secrets
+from .packs import pack_to_project
+
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+CATALOG_DIR = os.environ.get("ENCLUME_CATALOG", os.path.join(BASE_DIR, "catalog"))
+MAX_BODY = 2 * 1024 * 1024 # un projet honnete pese quelques dizaines de ko
+
+
+def create_app() -> Flask:
+ app = Flask(__name__)
+ app.config["MAX_CONTENT_LENGTH"] = MAX_BODY
+ app.config["CATALOG"] = Catalog(CATALOG_DIR)
+
+ def catalog() -> Catalog:
+ return app.config["CATALOG"]
+
+ def project_from_request() -> Project:
+ payload = request.get_json(force=True, silent=True) or {}
+ return Project.from_dict(payload.get("project", payload))
+
+ @app.after_request
+ def security_headers(response: Response) -> Response:
+ response.headers.setdefault(
+ "Content-Security-Policy",
+ "default-src 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'; "
+ "frame-ancestors 'none'; base-uri 'none'; form-action 'self'",
+ )
+ response.headers.setdefault("X-Content-Type-Options", "nosniff")
+ response.headers.setdefault("Referrer-Policy", "no-referrer")
+ return response
+
+ # ----------------------------------------------------------------- pages
+
+ @app.context_processor
+ def contexte_commun() -> dict:
+ return {"version": os.environ.get("ENCLUME_VERSION", "dev")}
+
+ @app.get("/")
+ def editor() -> str:
+ cat = catalog()
+ return render_template(
+ "editeur.html",
+ versions=CENTREON_VERSIONS,
+ command_types=COMMAND_TYPES,
+ categories=CATEGORIES,
+ grouped=cat.by_category(),
+ pack_count=len(cat.packs),
+ service_count=cat.service_count,
+ )
+
+ @app.get("/sante")
+ def health() -> Response:
+ cat = catalog()
+ return jsonify(
+ {
+ "statut": "ok",
+ "version": os.environ.get("ENCLUME_VERSION", "dev"),
+ "revision": os.environ.get("ENCLUME_COMMIT", "inconnu"),
+ "packs": len(cat.packs),
+ "erreurs_catalogue": cat.errors,
+ }
+ )
+
+ # ------------------------------------------------------------------- api
+
+ @app.get("/api/packs")
+ def api_packs() -> Response:
+ return jsonify(
+ [
+ {
+ "id": p.id,
+ "name": p.name,
+ "category": p.category,
+ "category_label": p.category_label,
+ "status": p.status,
+ "plugin": p.plugin,
+ "description": p.description,
+ "host_template": p.host_template.name,
+ "services": [
+ {"name": s.name, "description": s.description, "default": s.enabled_by_default}
+ for s in p.services
+ ],
+ }
+ for p in sorted(catalog().packs.values(), key=lambda p: (p.category, p.name))
+ ]
+ )
+
+ @app.get("/api/packs//projet")
+ def api_pack_project(pack_id: str) -> Response:
+ pack = catalog().get(pack_id)
+ if pack is None:
+ return jsonify({"erreur": "pack inconnu"}), 404
+ only_defaults = request.args.get("tout") != "1"
+ return jsonify(pack_to_project(pack, only_defaults=only_defaults).to_dict())
+
+ @app.post("/api/valider")
+ def api_validate() -> Response:
+ plan = build_plan(project_from_request())
+ return jsonify({"summary": plan.summary, "issues": plan.issues, "has_errors": plan.has_errors})
+
+ @app.post("/api/generer")
+ def api_generate() -> Response:
+ payload = request.get_json(force=True, silent=True) or {}
+ return jsonify(generate(payload.get("project", payload)))
+
+ @app.post("/api/macros")
+ def api_macros() -> Response:
+ payload = request.get_json(force=True, silent=True) or {}
+ return jsonify(detect_macros(str(payload.get("line", ""))))
+
+ @app.post("/telecharger")
+ def download_zip() -> Response:
+ raw = request.form.get("project") or "{}"
+ project = Project.from_dict(json.loads(raw))
+ plan = build_plan(project)
+ files = render_bundle(plan)
+ buffer = io.BytesIO()
+ with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as archive:
+ for name, content in files.items():
+ archive.writestr(name, content)
+ # Le projet joint a l'archive ne contient aucun mot de passe.
+ archive.writestr(
+ "projet.json",
+ json.dumps(
+ strip_secrets(Project.from_dict(json.loads(raw))).to_dict(),
+ indent=2,
+ ensure_ascii=False,
+ ),
+ )
+ buffer.seek(0)
+ slug = "".join(c if c.isalnum() or c in "-_" else "-" for c in project.name).strip("-") or "projet"
+ return Response(
+ buffer.read(),
+ mimetype="application/zip",
+ headers={"Content-Disposition": f'attachment; filename="{slug}-clapi.zip"'},
+ )
+
+ @app.post("/recharger")
+ def reload_catalog() -> Response:
+ app.config["CATALOG"] = Catalog(CATALOG_DIR)
+ cat = catalog()
+ return jsonify({"packs": len(cat.packs), "erreurs": cat.errors})
+
+ return app
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..488937f
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+Flask>=3.0
+PyYAML>=6.0
+gunicorn>=22.0
diff --git a/run.py b/run.py
new file mode 100644
index 0000000..b1979a3
--- /dev/null
+++ b/run.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+"""Point d'entree de Centreon Forge.
+
+ python3 run.py # http://127.0.0.1:8080
+ ENCLUME_PORT=9000 python3 run.py
+"""
+
+import os
+
+from enclume.webapp import create_app
+
+app = create_app()
+
+if __name__ == "__main__":
+ app.run(
+ host=os.environ.get("ENCLUME_HOST", "127.0.0.1"),
+ port=int(os.environ.get("ENCLUME_PORT", "8080")),
+ debug=os.environ.get("ENCLUME_DEBUG") == "1",
+ )
diff --git a/tests/test_generation.py b/tests/test_generation.py
new file mode 100644
index 0000000..1e2d29c
--- /dev/null
+++ b/tests/test_generation.py
@@ -0,0 +1,78 @@
+"""Garde-fous minimaux : ce qui casserait silencieusement le code genere."""
+
+import json
+
+from enclume.catalog import Catalog
+from enclume.generator import build_plan, render_bundle
+from enclume.model import Project, strip_secrets, validate
+from enclume.packs import pack_to_project
+from enclume.webapp import create_app
+
+
+def projet_exemple() -> Project:
+ catalog = Catalog("catalog")
+ return pack_to_project(catalog.get("os-linux-snmp"))
+
+
+def test_catalogue_lisible():
+ catalog = Catalog("catalog")
+ assert not catalog.errors
+ assert len(catalog.packs) >= 20
+
+
+def test_plan_complet():
+ plan = build_plan(projet_exemple())
+ lignes = plan.ops
+ assert any(o.obj == "CMD" and o.action == "ADD" for o in lignes)
+ assert any(o.obj == "STPL" and o.action == "addhosttemplate" for o in lignes)
+ assert not plan.has_errors
+
+
+def test_prefixe_applique_aux_references():
+ projet = projet_exemple()
+ projet.prefix = "ACME-"
+ lignes = [o.import_line() for o in build_plan(projet).ops if o.obj != "#"]
+ liaison = [l for l in lignes if l.startswith("STPL;addhosttemplate")][0]
+ assert liaison.count("ACME-") == 2
+
+
+def test_macros_trois_champs_sur_ancienne_version():
+ projet = projet_exemple()
+ projet.centreon_version = "legacy"
+ macro = [o for o in build_plan(projet).ops if o.action == "setmacro"][0]
+ assert len(macro.values) == 3
+
+
+def test_echappement_shell_des_macros_centreon():
+ fichiers = render_bundle(build_plan(projet_exemple()))
+ ligne = [l for l in fichiers["deploy.sh"].splitlines() if "HOSTADDRESS" in l][0]
+ assert ligne.startswith("clapi -o CMD -a ADD -v '")
+
+
+def test_point_virgule_refuse():
+ projet = projet_exemple()
+ projet.commands[0].line += "; rm -rf /"
+ erreurs = [i for i in validate(projet) if i["level"] == "error"]
+ assert erreurs
+
+
+def test_les_mots_de_passe_ne_sont_pas_conserves():
+ projet = projet_exemple()
+ projet.host_templates[0].macros.append(
+ type(projet.host_templates[0].macros[0])(name="PASSWORD", value="secret", is_password=True)
+ )
+ assert all(not m.value for m in strip_secrets(projet).host_templates[0].macros if m.is_password)
+
+
+def test_api_de_generation():
+ client = create_app().test_client()
+ reponse = client.post("/api/generer", json={"project": projet_exemple().to_dict()})
+ donnees = reponse.get_json()
+ assert reponse.status_code == 200
+ assert donnees["files"]["deploy.clapi"].startswith("#")
+ assert donnees["summary"]["operations"] > 0
+
+
+def test_sante():
+ reponse = create_app().test_client().get("/sante")
+ assert reponse.get_json()["statut"] == "ok"