From de7a691861f60cecc6347ff49755447e5a53eb99 Mon Sep 17 00:00:00 2001 From: hcornet Date: Thu, 10 Sep 2026 11:32:03 +0200 Subject: [PATCH] Enclume : editeur CLAPI, catalogue de 26 packs, empaquetage Docker --- .dockerignore | 14 + .env | 1 - .env.example | 20 + .gitattributes | 4 + .gitea/workflows/build.yaml | 100 +++ .gitea/workflows/ci-develop.yml | 70 -- .gitea/workflows/ci-main.yml | 51 -- .gitignore | 23 +- Dockerfile | 50 ++ Dockerfile.catalog-sync | 22 + README.md | 120 ++- catalog/application/app-apache.yml | 49 ++ catalog/application/app-docker.yml | 38 + catalog/application/app-mssql.yml | 56 ++ catalog/application/app-mysql.yml | 65 ++ catalog/application/app-nginx.yml | 41 + catalog/application/app-oracle.yml | 48 ++ catalog/application/app-postgresql.yml | 57 ++ catalog/application/app-protocols.yml | 68 ++ catalog/application/app-vmware-esx.yml | 54 ++ catalog/exemple-pack.yml.example | 21 + catalog/hardware/hw-apc-ups-snmp.yml | 37 + catalog/hardware/hw-cisco-snmp.yml | 58 ++ catalog/hardware/hw-dell-idrac-snmp.yml | 53 ++ .../hardware/hw-fortinet-fortigate-snmp.yml | 60 ++ catalog/hardware/hw-hp-proliant-snmp.yml | 44 ++ catalog/hardware/hw-juniper-junos-snmp.yml | 45 ++ catalog/hardware/hw-netapp-ontap-snmp.yml | 42 + catalog/hardware/hw-printer-rfc3805.yml | 37 + catalog/hardware/hw-synology-snmp.yml | 46 ++ catalog/system/os-aix-snmp.yml | 54 ++ catalog/system/os-freebsd-snmp.yml | 54 ++ catalog/system/os-generic-snmp.yml | 58 ++ catalog/system/os-linux-nrpe.yml | 56 ++ catalog/system/os-linux-snmp.yml | 86 ++ catalog/system/os-solaris-snmp.yml | 54 ++ catalog/system/os-windows-nsclient.yml | 49 ++ catalog/system/os-windows-snmp.yml | 78 ++ compose.dev.yaml | 24 + compose.yaml | 100 +++ docker-compose.yml | 42 - docker/catalog-sync.sh | 42 + docs/EXPLOITATION.md | 91 +++ docs/INSTALLATION-INITIALE.md | 86 ++ enclume/__init__.py | 0 enclume/catalog.py | 180 +++++ enclume/cli.py | 97 +++ enclume/generator.py | 266 +++++++ enclume/model.py | 346 ++++++++ enclume/packs.py | 60 ++ enclume/static/editeur.js | 744 ++++++++++++++++++ enclume/static/enclume.css | 366 +++++++++ enclume/static/enclume.svg | 6 + enclume/templates/base.html | 29 + enclume/templates/editeur.html | 46 ++ enclume/webapp.py | 163 ++++ requirements.txt | 3 + run.py | 19 + tests/test_generation.py | 78 ++ 59 files changed, 4466 insertions(+), 205 deletions(-) create mode 100644 .dockerignore delete mode 100644 .env create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .gitea/workflows/build.yaml delete mode 100644 .gitea/workflows/ci-develop.yml delete mode 100644 .gitea/workflows/ci-main.yml create mode 100644 Dockerfile create mode 100644 Dockerfile.catalog-sync create mode 100644 catalog/application/app-apache.yml create mode 100644 catalog/application/app-docker.yml create mode 100644 catalog/application/app-mssql.yml create mode 100644 catalog/application/app-mysql.yml create mode 100644 catalog/application/app-nginx.yml create mode 100644 catalog/application/app-oracle.yml create mode 100644 catalog/application/app-postgresql.yml create mode 100644 catalog/application/app-protocols.yml create mode 100644 catalog/application/app-vmware-esx.yml create mode 100644 catalog/exemple-pack.yml.example create mode 100644 catalog/hardware/hw-apc-ups-snmp.yml create mode 100644 catalog/hardware/hw-cisco-snmp.yml create mode 100644 catalog/hardware/hw-dell-idrac-snmp.yml create mode 100644 catalog/hardware/hw-fortinet-fortigate-snmp.yml create mode 100644 catalog/hardware/hw-hp-proliant-snmp.yml create mode 100644 catalog/hardware/hw-juniper-junos-snmp.yml create mode 100644 catalog/hardware/hw-netapp-ontap-snmp.yml create mode 100644 catalog/hardware/hw-printer-rfc3805.yml create mode 100644 catalog/hardware/hw-synology-snmp.yml create mode 100644 catalog/system/os-aix-snmp.yml create mode 100644 catalog/system/os-freebsd-snmp.yml create mode 100644 catalog/system/os-generic-snmp.yml create mode 100644 catalog/system/os-linux-nrpe.yml create mode 100644 catalog/system/os-linux-snmp.yml create mode 100644 catalog/system/os-solaris-snmp.yml create mode 100644 catalog/system/os-windows-nsclient.yml create mode 100644 catalog/system/os-windows-snmp.yml create mode 100644 compose.dev.yaml create mode 100644 compose.yaml delete mode 100644 docker-compose.yml create mode 100644 docker/catalog-sync.sh create mode 100644 docs/EXPLOITATION.md create mode 100644 docs/INSTALLATION-INITIALE.md create mode 100644 enclume/__init__.py create mode 100644 enclume/catalog.py create mode 100644 enclume/cli.py create mode 100644 enclume/generator.py create mode 100644 enclume/model.py create mode 100644 enclume/packs.py create mode 100644 enclume/static/editeur.js create mode 100644 enclume/static/enclume.css create mode 100644 enclume/static/enclume.svg create mode 100644 enclume/templates/base.html create mode 100644 enclume/templates/editeur.html create mode 100644 enclume/webapp.py create mode 100644 requirements.txt create mode 100644 run.py create mode 100644 tests/test_generation.py 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 -Buy Me a Coffee at ko-fi.com \ 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 %} +
+ Projet indépendant, sans lien avec Centreon SAS. Centreon est une marque déposée de son propriétaire. + + Code source + État du service +
+ + 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 %} +
+ + + + + + + + + +
+ +
+ +
+
+ + + + + + + + + +{% 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"