Enclume : editeur CLAPI, catalogue de 26 packs, empaquetage Docker
This commit is contained in:
1 parent
aa71251de0
commit
de7a691861
59 files changed
+4466
-205
No files matched your search
@@ -0,0 +1,14 @@
|
|||||||
|
.git
|
||||||
|
.gitea
|
||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
|
**/__pycache__
|
||||||
|
*.pyc
|
||||||
|
docs
|
||||||
|
docker-compose*.yaml
|
||||||
|
compose*.yaml
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
README.md
|
||||||
|
sortie
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
@@ -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"
|
||||||
@@ -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/[email protected]
|
|
||||||
|
|
||||||
# https://github.com/marketplace/actions/docker-setup-buildx
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: docker/[email protected]
|
|
||||||
|
|
||||||
# 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/*
|
|
||||||
@@ -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
|
|
||||||
+9
-14
@@ -1,14 +1,9 @@
|
|||||||
# ---> VisualStudioCode
|
.venv/
|
||||||
.vscode/*
|
__pycache__/
|
||||||
!.vscode/settings.json
|
*.pyc
|
||||||
!.vscode/tasks.json
|
.pytest_cache/
|
||||||
!.vscode/launch.json
|
.env
|
||||||
!.vscode/extensions.json
|
.env.*
|
||||||
!.vscode/*.code-snippets
|
!.env.example
|
||||||
|
sortie/
|
||||||
# Local History for Visual Studio Code
|
*.zip
|
||||||
.history/
|
|
||||||
|
|
||||||
# Built Visual Studio Code Extensions
|
|
||||||
*.vsix
|
|
||||||
|
|
||||||
+50
@@ -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()'"]
|
||||||
@@ -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"]
|
||||||
@@ -1,41 +1,107 @@
|
|||||||
# Enclume
|
# 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
|
Le projet vit dans le navigateur du visiteur (`localStorage`), s'exporte en JSON et se
|
||||||
git clone https://gitea.tips-of-mine.com/Tips-Of-Mine/Enclume.git
|
réimporte. Le serveur valide et génère, sans rien conserver. Les macros marquées « mot de
|
||||||
cd Enclume
|
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
|
Cloudflare Tunnel → Traefik ─┬→ web (image maison, sans état)
|
||||||
nano .env
|
│
|
||||||
~~~
|
volume catalogue ┴← catalog-sync (git → volume, puis /recharger)
|
||||||
|
db (postgres, à partir du chantier 2)
|
||||||
|
```
|
||||||
|
|
||||||
Modification des labels pour traefik
|
Chaque évolution produit une image versionnée. Le déploiement et le retour arrière se font
|
||||||
~~~bash
|
en changeant une ligne dans `.env` : voir [docs/EXPLOITATION.md](docs/EXPLOITATION.md).
|
||||||
nano docker-compose.yml
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## Lancement
|
## Développement
|
||||||
|
|
||||||
~~~bash
|
```bash
|
||||||
docker compose up -d
|
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
|
Ou en conteneur, avec le code monté en direct :
|
||||||
docker compose logs -f
|
|
||||||
~~~
|
|
||||||
|
|
||||||
# Buy me a coffe
|
```bash
|
||||||
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
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/<id>/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/<catégorie>/`. 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
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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"}
|
||||||
@@ -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: []
|
||||||
@@ -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"}
|
||||||
@@ -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"
|
||||||
+100
@@ -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
|
||||||
@@ -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"
|
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# Exploitation
|
||||||
|
|
||||||
|
## Composants
|
||||||
|
|
||||||
|
| Conteneur | Image | Rôle | État |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `web` | `harbor…/enclume/web:<version>` | l'application : éditeur, validation, génération | sans état |
|
||||||
|
| `catalog-sync` | `harbor…/enclume/catalog-sync:<version>` | 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.
|
||||||
@@ -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.<commit>`. 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://<domaine>/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`.
|
||||||
Whitespace-only changes.
@@ -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/<categorie>/.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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())
|
||||||
@@ -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)
|
||||||
@@ -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),
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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();
|
||||||
@@ -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); }
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Enclume">
|
||||||
|
<rect width="32" height="32" rx="4" fill="#161f29"/>
|
||||||
|
<path d="M7 12h11c0 3.2-1.6 4.6-3.4 5.2H21l4-3.4V12h2v4.3l-4.6 3.9h-3.1l1.6 3.3H10.5l1.6-3.3H10c-2.3 0-4-1.9-4-4.2V12z"
|
||||||
|
fill="#e7a93f"/>
|
||||||
|
<rect x="9" y="8" width="7" height="2.4" rx="1" fill="#8ca0b6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 369 B |
@@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{% block title %}Enclume — Générateur de configuration CLAPI pour Centreon{% endblock %}</title>
|
||||||
|
<meta name="description" content="Créez vos commandes de contrôle, modèles de service et modèles d'hôte Centreon, et repartez avec le code CLAPI prêt à jouer sur votre central. Gratuit, sans inscription.">
|
||||||
|
<link rel="canonical" href="{{ request.url_root }}">
|
||||||
|
<meta property="og:title" content="Enclume — Générateur de configuration CLAPI pour Centreon">
|
||||||
|
<meta property="og:description" content="Créez vos commandes, modèles de service et modèles d'hôte, repartez avec le code CLAPI.">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<link rel="icon" href="{{ url_for('static', filename='enclume.svg') }}" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='enclume.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="topbar">
|
||||||
|
<h1>Enclume</h1>
|
||||||
|
<span class="meta">Générateur de configuration CLAPI pour Centreon</span>
|
||||||
|
<span class="version">{{ version }}</span>
|
||||||
|
</header>
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
<footer class="pied">
|
||||||
|
<span>Projet indépendant, sans lien avec Centreon SAS. Centreon est une marque déposée de son propriétaire.</span>
|
||||||
|
<span class="grow"></span>
|
||||||
|
<a href="https://gitea.tips-of-mine.com/hubert/enclume">Code source</a>
|
||||||
|
<a href="/sante">État du service</a>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block body %}
|
||||||
|
<div class="toolbar">
|
||||||
|
<input type="text" id="projet-nom" class="titre" value="Nouveau projet" aria-label="Nom du projet">
|
||||||
|
<select id="projet-version" aria-label="Version de Centreon ciblee">
|
||||||
|
{% for key, meta in versions.items() %}<option value="{{ key }}">{{ meta.label }}</option>{% endfor %}
|
||||||
|
</select>
|
||||||
|
<span class="sep"></span>
|
||||||
|
<button type="button" id="btn-pack">Importer un pack</button>
|
||||||
|
<button type="button" id="btn-import">Ouvrir un projet</button>
|
||||||
|
<button type="button" id="btn-export">Enregistrer le projet</button>
|
||||||
|
<button type="button" id="btn-vider" class="danger">Vider</button>
|
||||||
|
<span class="grow" id="etat-sauvegarde"></span>
|
||||||
|
<button type="button" id="btn-generer" class="primary">Generer le CLAPI</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="atelier">
|
||||||
|
<nav id="rail" aria-label="Objets du projet"></nav>
|
||||||
|
<main id="main"></main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" id="modal" hidden>
|
||||||
|
<div class="modal-box">
|
||||||
|
<header>
|
||||||
|
<h2 id="modal-titre">Importer un pack</h2>
|
||||||
|
<button type="button" class="small" data-fermer-modal>Fermer</button>
|
||||||
|
</header>
|
||||||
|
<div id="modal-corps"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="form-zip" method="post" action="/telecharger" hidden>
|
||||||
|
<input type="hidden" name="project" id="form-zip-project">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<template id="tpl-vide">
|
||||||
|
<div class="vide">
|
||||||
|
<h2>Un projet vide, trois facons de le remplir</h2>
|
||||||
|
<p>Partez d'un pack du catalogue ({{ pack_count }} packs, {{ service_count }} sondes) et modifiez-le,
|
||||||
|
rouvrez un projet enregistre, ou creez vos objets un par un dans la colonne de gauche.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script id="donnees-types" type="application/json">{{ command_types|tojson }}</script>
|
||||||
|
<script src="{{ url_for('static', filename='editor.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
@@ -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/<pack_id>/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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Flask>=3.0
|
||||||
|
PyYAML>=6.0
|
||||||
|
gunicorn>=22.0
|
||||||
@@ -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",
|
||||||
|
)
|
||||||
@@ -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"
|
||||||
Reference in new issue
Block a user