Portail Infra : CMDB, générateur certificats & mapper VLAN switch

Ajouts :
- Snapshots serveur de la base CMDB (auto + manuel, restauration)
- Import/Export CSV de l'inventaire

Préparation partage public :
- Données réelles (cmdb.db, snapshots, certs/clés privées) exclues via .gitignore
- Suppression des infos en dur (proxy, VLAN, configs switch exemples)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Guillaume DENIS 2026-07-04 16:54:08 +02:00
commit bfc88f4afc
24 changed files with 13551 additions and 0 deletions

20
.gitignore vendored Normal file
View file

@ -0,0 +1,20 @@
# === Données réelles — gardées en local, jamais publiées ===
# Base CMDB (inventaire + mot de passe bind LDAP)
CMDB/cmdb.db
CMDB/cmdb.db.ver
CMDB/cmdb.db.tmp
# Snapshots horodatés de la base (copies des données réelles)
CMDB/snapshots/
# Certificats générés (contiennent des clés privées)
GENERATEUR_CERTIFICATES/LISTS_ALL_CERT/
# === Environnement / build ===
.venv/
__pycache__/
*.pyc
*.pyo
# === Divers ===
.DS_Store
*.swp

1
.python-version Normal file
View file

@ -0,0 +1 @@
3.13

Binary file not shown.

Binary file not shown.

4055
CMDB/cmdb.html Normal file

File diff suppressed because it is too large Load diff

482
CMDB/cmdb_server.py Normal file
View file

@ -0,0 +1,482 @@
#!/usr/bin/env python3
"""Serveur CMDB : sert cmdb.html, la base partagée et la recherche AD.
Remplace `python -m http.server` ET le connecteur séparé :
pip install ldap3
python3 cmdb_server.py # http://0.0.0.0:8000
python3 cmdb_server.py 8080 # port custom
Endpoints :
GET /db base SQLite (bytes) + entête X-DB-Version
GET /db/version {"version": n} (poll léger)
POST /db écrit la base ; entête X-Base-Version pour la
concurrence optimiste (409 si obsolète)
POST /ldap/search recherche annuaire (config envoyée par la page)
La base est un vrai fichier `cmdb.db` partagé : plusieurs personnes
peuvent l'utiliser en même temps, avec détection de conflit.
"""
import json
import os
import re
import ssl
import sys
import threading
import time
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse, parse_qs
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
HERE = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.path.join(HERE, 'cmdb.db')
VER_PATH = DB_PATH + '.ver'
DB_LOCK = threading.Lock()
def db_version():
try:
with open(VER_PATH) as f:
return int(f.read().strip() or 0)
except (OSError, ValueError):
return 1 if os.path.exists(DB_PATH) else 0
def db_bytes():
try:
with open(DB_PATH, 'rb') as f:
return f.read()
except OSError:
return b''
def db_write(data):
"""Écrit la base atomiquement et incrémente la version. Renvoie la version."""
with DB_LOCK:
ver = db_version() + 1
tmp = DB_PATH + '.tmp'
with open(tmp, 'wb') as f:
f.write(data)
os.replace(tmp, DB_PATH)
with open(VER_PATH, 'w') as f:
f.write(str(ver))
return ver
# ---------------------------------------------------------------------------
# Snapshots : copies horodatées de la base dans snapshots/.
# - « auto » : créées après une écriture, au plus 1/heure, rétention 72.
# - « manuel » : créées à la demande (bouton), rétention 100, label libre.
# Restaurer un snapshot crée d'abord un snapshot de sécurité de l'état courant.
# ---------------------------------------------------------------------------
SNAP_DIR = os.path.join(HERE, 'snapshots')
# nom : cmdb-<AAAAMMJJ>-<HHMMSS>-v<version>[-<label>].db (pas de '/' possible → pas de traversal)
SNAP_RE = re.compile(r'^cmdb-(\d{8})-(\d{6})-v(\d+)(?:-([A-Za-z0-9_.-]+))?\.db$')
AUTO_MIN_INTERVAL = 3600 # s : au plus un snapshot auto par heure
AUTO_KEEP = 72 # rétention snapshots auto (~3 jours horaires)
MANUAL_KEEP = 100 # rétention snapshots manuels
def _sanitize_label(label):
label = re.sub(r'[^A-Za-z0-9_.-]+', '-', (label or '').strip()).strip('-')
return label[:40]
def _parse_snap(name):
"""Valide un nom de snapshot et renvoie sa méta, ou None si invalide."""
name = os.path.basename(name or '')
m = SNAP_RE.match(name)
if not m:
return None
return {'name': name, 'date': m.group(1), 'time': m.group(2),
'version': int(m.group(3)), 'label': m.group(4) or '',
'auto': m.group(4) == 'auto'}
def list_snapshots():
"""Liste les snapshots existants, plus récents d'abord."""
out = []
try:
entries = os.listdir(SNAP_DIR)
except OSError:
return out
for n in entries:
meta = _parse_snap(n)
if not meta:
continue
try:
st = os.stat(os.path.join(SNAP_DIR, n))
except OSError:
continue
meta['size'] = st.st_size
meta['mtime'] = int(st.st_mtime * 1000)
out.append(meta)
out.sort(key=lambda s: s['mtime'], reverse=True)
return out
def _prune(auto, keep):
for s in [s for s in list_snapshots() if s['auto'] == auto][keep:]:
try:
os.remove(os.path.join(SNAP_DIR, s['name']))
except OSError:
pass
def create_snapshot(label='manual', auto=False):
"""Copie la base courante dans snapshots/. Renvoie la méta, ou None si base vide."""
with DB_LOCK:
data = db_bytes()
if not data:
return None
ver = db_version()
os.makedirs(SNAP_DIR, exist_ok=True)
lbl = 'auto' if auto else (_sanitize_label(label) or 'manual')
name = 'cmdb-%s-v%d-%s.db' % (time.strftime('%Y%m%d-%H%M%S'), ver, lbl)
path = os.path.join(SNAP_DIR, name)
tmp = path + '.tmp'
with open(tmp, 'wb') as f:
f.write(data)
os.replace(tmp, path)
_prune(auto, AUTO_KEEP if auto else MANUAL_KEEP)
return _parse_snap(name)
def _maybe_auto_snapshot():
"""Snapshot auto si le dernier date de plus d'AUTO_MIN_INTERVAL. Jamais dans DB_LOCK."""
autos = [s for s in list_snapshots() if s['auto']]
if autos and (time.time() - autos[0]['mtime'] / 1000) < AUTO_MIN_INTERVAL:
return
try:
create_snapshot(auto=True)
except OSError:
pass
def db_write_and_snapshot(data):
"""Écrit la base puis déclenche un snapshot auto (throttlé). Renvoie la version."""
ver = db_write(data)
_maybe_auto_snapshot()
return ver
def snapshot_bytes(name):
"""Contenu d'un snapshot. Lève si nom invalide ou fichier absent."""
meta = _parse_snap(name)
if not meta:
raise ValueError('nom de snapshot invalide')
with open(os.path.join(SNAP_DIR, meta['name']), 'rb') as f:
return f.read()
def delete_snapshot(name):
meta = _parse_snap(name)
if not meta:
raise ValueError('nom de snapshot invalide')
path = os.path.join(SNAP_DIR, meta['name'])
if os.path.isfile(path):
os.remove(path)
def restore_snapshot(name):
"""Remplace la base courante par le snapshot `name`.
Crée d'abord un snapshot de sécurité de l'état courant. Renvoie la nouvelle version.
"""
meta = _parse_snap(name)
if not meta:
raise ValueError('nom de snapshot invalide')
path = os.path.join(SNAP_DIR, meta['name'])
if not os.path.isfile(path):
raise FileNotFoundError('snapshot introuvable')
with open(path, 'rb') as f:
data = f.read()
create_snapshot(label='pre-restore') # filet de sécurité avant écrasement
return db_write(data)
try:
from ldap3 import Server, Connection, Tls, SUBTREE, ALL_ATTRIBUTES
from ldap3.utils.conv import escape_filter_chars
HAS_LDAP = True
except ImportError:
HAS_LDAP = False
def _cn_of(dn):
if not dn:
return ''
head = dn.split(',')[0]
return head.split('=', 1)[1] if '=' in head else head
def ldap_connect(cfg):
"""Ouvre et bind une connexion LDAP selon la config de la page."""
use_ssl = cfg.get('method') == 'ssl'
tls = Tls(validate=ssl.CERT_REQUIRED if cfg.get('verify_certificates') else ssl.CERT_NONE)
srv = Server(cfg['host'], port=int(cfg.get('port') or (636 if use_ssl else 389)),
use_ssl=use_ssl, tls=tls, get_info=None, connect_timeout=8)
conn = Connection(srv, user=cfg.get('bind_dn') or None,
password=cfg.get('bind_password') or None,
auto_referrals=False, receive_timeout=15)
if cfg.get('method') == 'start_tls':
conn.open()
conn.start_tls()
if not conn.bind():
raise RuntimeError('Bind LDAP refusé : %s' % conn.result)
return conn
def ldap_computer(cfg, query):
"""Cherche un poste (objectClass=computer) par nom/dNSHostName."""
conn = ldap_connect(cfg)
q = escape_filter_chars((query or '').strip().rstrip('$'))
flt = ('(&(objectClass=computer)(|(cn=*%s*)(name=*%s*)(dNSHostName=*%s*)'
'(sAMAccountName=*%s*)))' % (q, q, q, q))
base = cfg.get('base') or ''
ok = conn.search(base, flt, SUBTREE, attributes=ALL_ATTRIBUTES, size_limit=5)
res = conn.result or {}
if not ok and res.get('result') not in (0, 4):
raise RuntimeError('Recherche LDAP échouée : %s' % (res.get('description') or res))
out = []
for e in conn.entries:
d = e.entry_attributes_as_dict
def val(k):
for kk in d:
if kk.lower() == k.lower():
v = d[kk]
if isinstance(v, list):
v = v[0] if v else ''
return str(v) if v else ''
return ''
def vals(k):
for kk in d:
if kk.lower() == k.lower():
v = d[kk]
return [str(x) for x in (v if isinstance(v, list) else [v]) if x]
return []
uac = val('userAccountControl')
enabled = (not (int(uac) & 2)) if uac.isdigit() else None
out.append({
'name': val('cn') or val('name'),
'dns': val('dNSHostName'),
'os': val('operatingSystem'),
'os_version': val('operatingSystemVersion'),
'description': val('description'),
'location': val('location'),
'managed_by': _cn_of(val('managedBy')),
'when_created': val('whenCreated')[:8],
'enabled': enabled,
'groups': [_cn_of(x) for x in vals('memberOf')][:20],
})
return out
def ldap_search(cfg, query, limit=25):
"""Recherche d'utilisateurs dans l'AD selon la config de la page.
Cherche par username, nom, prénom, nom affiché ou mail.
Lève une exception explicite si le bind ou la recherche échoue
(sinon un problème de config renverrait « aucun résultat » en silence).
"""
conn = ldap_connect(cfg)
uid = cfg.get('uid') or 'userPrincipalName'
raw = cfg.get('filter') or ''
if raw:
flt = raw.replace('{query}', escape_filter_chars(query))
else:
# recherche prioritairement sur displayName, multi-mots (ET) → "jean dupont"
# et "dupont jean" fonctionnent tous les deux, quel que soit l'ordre.
search_attrs = ['displayName', 'cn', 'sAMAccountName', 'givenName', 'sn',
'userPrincipalName', 'mail']
tokens = [escape_filter_chars(t) for t in query.split() if t] or ['']
parts = []
for t in tokens:
ors = ''.join('(%s=*%s*)' % (a, t) for a in search_attrs)
parts.append('(|%s)' % ors)
flt = '(&%s)' % ''.join(parts) if len(parts) > 1 else parts[0]
# personnes uniquement, compatible AD + OpenLDAP
person = ('(|(objectClass=user)(objectClass=inetOrgPerson)'
'(objectClass=organizationalPerson)(objectClass=person))')
base = cfg.get('base') or ''
ok = conn.search(base, '(&%s%s)' % (person, flt), SUBTREE,
attributes=ALL_ATTRIBUTES, size_limit=limit)
res = conn.result or {}
# 0 = success, 4 = sizeLimitExceeded (résultats partiels OK)
if not ok and res.get('result') not in (0, 4):
raise RuntimeError('Recherche LDAP échouée : %s (base=%s)'
% (res.get('description') or res, base or ''))
def val(d, k):
# accès insensible à la casse, première valeur
for kk in d:
if kk.lower() == k.lower():
v = d[kk]
if isinstance(v, list):
v = v[0] if v else ''
return str(v) if v else ''
return ''
def vals(d, k):
for kk in d:
if kk.lower() == k.lower():
v = d[kk]
return [str(x) for x in (v if isinstance(v, list) else [v]) if x]
return []
def cn_of(dn):
# "CN=Jean Dupont,OU=..." -> "Jean Dupont"
if not dn:
return ''
head = dn.split(',')[0]
return head.split('=', 1)[1] if '=' in head else head
out, seen = [], set()
for e in conn.entries:
d = e.entry_attributes_as_dict
name = val(d, 'displayName') or val(d, 'cn') \
or (val(d, 'givenName') + ' ' + val(d, 'sn')).strip()
username = val(d, 'sAMAccountName') or val(d, uid) or val(d, 'userPrincipalName')
key = (name, username)
if not (name or username) or key in seen:
continue
seen.add(key)
uac = val(d, 'userAccountControl')
enabled = (not (int(uac) & 2)) if uac.isdigit() else None
out.append({
'displayName': name or username,
'username': username,
'mail': val(d, 'mail'),
'title': val(d, 'title'),
'department': val(d, 'department'),
'company': val(d, 'company'),
'office': val(d, 'physicalDeliveryOfficeName'),
'phone': val(d, 'telephoneNumber'),
'mobile': val(d, 'mobile') or val(d, 'ipPhone'),
'manager': cn_of(val(d, 'manager')),
'description': val(d, 'description'),
'employee_id': val(d, 'employeeID') or val(d, 'employeeNumber'),
'division': val(d, 'division'),
'city': val(d, 'l'),
'country': val(d, 'co') or val(d, 'c'),
'enabled': enabled,
'groups': [cn_of(x) for x in vals(d, 'memberOf')][:15],
})
out.sort(key=lambda u: u['displayName'].lower())
return out
class Handler(SimpleHTTPRequestHandler):
def log_message(self, *args):
pass
def _json(self, code, payload):
body = json.dumps(payload).encode()
self.send_response(code)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
def _send_db(self, code, data, version):
self.send_response(code)
self.send_header('Content-Type', 'application/octet-stream')
self.send_header('X-DB-Version', str(version))
self.send_header('Cache-Control', 'no-store')
self.send_header('Content-Length', str(len(data)))
self.end_headers()
if data:
self.wfile.write(data)
def _send_download(self, data, filename):
self.send_response(200)
self.send_header('Content-Type', 'application/octet-stream')
self.send_header('Content-Disposition', 'attachment; filename="%s"' % filename)
self.send_header('Cache-Control', 'no-store')
self.send_header('Content-Length', str(len(data)))
self.end_headers()
self.wfile.write(data)
def do_GET(self):
if self.path == '/db':
with DB_LOCK:
return self._send_db(200, db_bytes(), db_version())
if self.path == '/db/version':
return self._json(200, {'version': db_version()})
if self.path == '/db/snapshots':
return self._json(200, {'snapshots': list_snapshots()})
if self.path.startswith('/db/snapshot?'):
name = (parse_qs(urlparse(self.path).query).get('name') or [''])[0]
try:
data = snapshot_bytes(name)
except (ValueError, OSError):
return self._json(404, {'error': 'snapshot introuvable'})
return self._send_download(data, os.path.basename(name))
if self.path in ('/', '/index.html'):
self.path = '/cmdb.html'
return super().do_GET()
def do_POST(self):
if self.path == '/db':
data = self.rfile.read(int(self.headers.get('Content-Length', 0)))
base = int(self.headers.get('X-Base-Version', '0') or 0)
cur = db_version()
if base != cur: # quelqu'un a écrit entre-temps
return self._send_db(409, db_bytes(), cur)
ver = db_write_and_snapshot(data)
return self._send_db(200, b'', ver)
if self.path in ('/db/snapshot', '/db/snapshot/delete', '/db/restore'):
try:
body = json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0)) or 0) or b'{}')
except ValueError:
body = {}
if self.path == '/db/snapshot':
meta = create_snapshot(label=body.get('label') or 'manual')
if not meta:
return self._json(400, {'error': 'base vide — rien à sauvegarder'})
return self._json(200, meta)
if self.path == '/db/snapshot/delete':
try:
delete_snapshot(body.get('name') or '')
except (ValueError, OSError):
return self._json(400, {'error': 'nom de snapshot invalide'})
return self._json(200, {'ok': True})
# /db/restore
try:
restore_snapshot(body.get('name') or '')
except (ValueError, FileNotFoundError):
return self._json(404, {'error': 'snapshot introuvable'})
with DB_LOCK:
return self._send_db(200, db_bytes(), db_version())
if self.path == '/ldap/computer':
if not HAS_LDAP:
return self._json(500, {'error': "module ldap3 manquant — pip install ldap3"})
try:
body = json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0))))
result = ldap_computer(body.get('config') or {}, (body.get('query') or '').strip())
return self._json(200, result)
except Exception as ex:
return self._json(500, {'error': str(ex)})
if self.path != '/ldap/search':
return self._json(404, {'error': 'not found'})
if not HAS_LDAP:
return self._json(500, {'error': "module ldap3 manquant — pip install ldap3"})
try:
body = json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0))))
limit = int(body.get('limit') or 25)
result = ldap_search(body.get('config') or {}, (body.get('query') or '').strip(), limit)
self._json(200, result)
except Exception as ex: # renvoyé tel quel à la page
self._json(500, {'error': str(ex)})
if __name__ == '__main__':
os.chdir(os.path.dirname(os.path.abspath(__file__)))
if not HAS_LDAP:
print("⚠ ldap3 absent : la recherche AD renverra une erreur (pip install ldap3)")
print('CMDB : http://127.0.0.1:%d (Ctrl+C pour arrêter)' % PORT)
ThreadingHTTPServer(('0.0.0.0', PORT), Handler).serve_forever()

3535
CMDB/preferences.html Normal file

File diff suppressed because it is too large Load diff

9
CMDB/pyproject.toml Normal file
View file

@ -0,0 +1,9 @@
[project]
name = "cmdb"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"ldap3>=2.9.1",
]

View file

@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""
Serveur du Générateur de Certificats SSL.
Remplace `python -m http.server` : sert les pages ET écrit les certificats
sur le disque dans GENERATEUR_CERTIFICATES/LISTS_ALL_CERT/ (créé au besoin),
sans aucune sélection de dossier côté navigateur.
Usage :
python3 cert_server.py # http://127.0.0.1:8000/GENERATEUR_CERTIFICATES/dashboard.html
python3 cert_server.py 8080 # port custom
Sert la RACINE du projet (parent), donc les 3 apps + le switcher fonctionnent.
"""
import sys, os, json, base64, shutil, re
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) # .../GENERATEUR_CERTIFICATES
ROOT = os.path.dirname(SCRIPT_DIR) # racine projet
WORK = os.path.join(SCRIPT_DIR, 'LISTS_ALL_CERT') # dossier de travail (disque)
ARCH = os.path.join(WORK, 'archives')
SAFE = re.compile(r'^[A-Za-z0-9._@\- ]+$') # noms de dossier/fichier autorisés
def safe_name(n):
n = (n or '').strip()
if not n or n in ('.', '..') or '/' in n or '\\' in n or not SAFE.match(n):
raise ValueError('nom invalide: %r' % n)
return n
def ensure_work():
os.makedirs(WORK, exist_ok=True)
class Handler(SimpleHTTPRequestHandler):
def _json(self, obj, code=200):
body = json.dumps(obj).encode('utf-8')
self.send_response(code)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-store')
self.end_headers()
self.wfile.write(body)
def _err(self, msg, code=400):
self._json({'ok': False, 'error': str(msg)}, code)
def _body(self):
n = int(self.headers.get('Content-Length', 0))
return json.loads(self.rfile.read(n) or b'{}')
def _api(self):
p = self.path.split('?', 1)[0]
return p.split('/api/', 1)[1] if '/api/' in p else None
# ---------- GET : API list/ping, sinon fichiers statiques ----------
def do_GET(self):
api = self._api()
if api == 'ping':
return self._json({'ok': True, 'workdir': 'LISTS_ALL_CERT'})
if api == 'list':
return self._json(self.list_all())
return super().do_GET()
def do_POST(self):
api = self._api()
try:
if api == 'write': return self.write_text()
if api == 'write_bin': return self.write_bin()
if api == 'delete': return self.delete_dir()
if api == 'delete_file': return self.delete_file()
if api == 'delete_archives': return self.delete_archives()
except Exception as e:
return self._err(e, 500)
self._err('route inconnue: %s' % self.path, 404)
# ---------- implémentations ----------
def list_all(self):
ensure_work()
certs, archives = [], []
for entry in sorted(os.listdir(WORK)):
full = os.path.join(WORK, entry)
if entry == 'archives':
if os.path.isdir(full):
for a in sorted(os.listdir(full)):
af = os.path.join(full, a)
if os.path.isfile(af):
st = os.stat(af)
archives.append({'name': a, 'size': st.st_size,
'mtime': int(st.st_mtime * 1000)})
continue
if not os.path.isdir(full):
continue
files, date = [], 0
for fn in sorted(os.listdir(full)):
ff = os.path.join(full, fn)
if not os.path.isfile(ff):
continue
st = os.stat(ff)
m = int(st.st_mtime * 1000)
date = max(date, m)
files.append({'name': fn, 'ext': (fn.rsplit('.', 1)[-1] if '.' in fn else '').lower(),
'size': st.st_size, 'mtime': m})
if files:
certs.append({'dir': entry, 'files': files, 'date': date})
archives.sort(key=lambda a: -a['mtime'])
certs.sort(key=lambda c: -c['date'])
return {'ok': True, 'certs': certs, 'archives': archives}
def write_text(self):
d = self._body()
dname = safe_name(d['dir'])
target = os.path.join(WORK, dname)
os.makedirs(target, exist_ok=True)
for fname, content in (d.get('files') or {}).items():
fn = safe_name(fname)
with open(os.path.join(target, fn), 'w', encoding='utf-8', newline='') as f:
f.write(content)
self._json({'ok': True, 'path': 'LISTS_ALL_CERT/%s/' % dname})
def write_bin(self):
d = self._body()
# path = "archives/<fichier>.zip" OU "<dir>/<fichier>"
parts = [safe_name(x) for x in d['path'].split('/') if x != '']
if not parts:
raise ValueError('path vide')
target = os.path.join(WORK, *parts)
os.makedirs(os.path.dirname(target), exist_ok=True)
with open(target, 'wb') as f:
f.write(base64.b64decode(d['b64']))
self._json({'ok': True, 'path': 'LISTS_ALL_CERT/%s' % '/'.join(parts)})
def delete_dir(self):
d = self._body()
dname = safe_name(d['dir'])
target = os.path.join(WORK, dname)
if os.path.isdir(target):
shutil.rmtree(target)
if d.get('archives'):
self._rm_archives(dname)
self._json({'ok': True})
def delete_file(self):
d = self._body()
target = os.path.join(WORK, safe_name(d['dir']), safe_name(d['name']))
if os.path.isfile(target):
os.remove(target)
self._json({'ok': True})
def delete_archives(self):
d = self._body()
self._rm_archives(safe_name(d['dir']))
self._json({'ok': True})
def _rm_archives(self, dname):
low = dname.lower()
if os.path.isdir(ARCH):
for a in os.listdir(ARCH):
if low in a.lower():
try: os.remove(os.path.join(ARCH, a))
except OSError: pass
def log_message(self, *a):
pass # silencieux
if __name__ == '__main__':
ensure_work()
os.chdir(ROOT)
url = 'http://127.0.0.1:%d/GENERATEUR_CERTIFICATES/dashboard.html' % PORT
print('Générateur Certificats : %s' % url)
print('Dossier disque : %s (créé automatiquement)' % WORK)
print('Ctrl+C pour arrêter.')
ThreadingHTTPServer(('0.0.0.0', PORT), Handler).serve_forever()

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,48 @@
#26d953 (vert)
#2dd4bf (turquoise)
#22c55e (vert vif)
#4ade80 (vert clair)
#06b6d4 (cyan)
#38bdf8 (bleu ciel)
#3b82f6 (bleu)
#6366f1 (indigo)
#8b5cf6 (violet)
#a855f7 (violet vif)
#d946ef (fuchsia)
#d926c7 (rose magenta)
#ec4899 (rose)
#f43f5e (rose/rouge)
#ef4444 (rouge)
#f97316 (orange)
#fb923c (orange clair)
#facc15 (jaune)
#eab308 (jaune foncé)
#84cc16 (vert citron)
====== NOT USED ======
#38bdf8 (bleu ciel)
#3b82f6 (bleu)
#a855f7 (violet vif)
#d946ef (fuchsia)
#d926c7 (rose magenta)
#ef4444 (rouge)
#84cc16 (vert citron)
IA
Probleme 1 : Quand un switch fait 24 ou 54 port (fibre) , la taille tu tableau n'est pas automatiquement mis a jours.
Probleme 2 : Les numeros des interface ne sont pas centrer dans le rectancle (1,2,3,...) c'est le padding top de la div qui doit etre a 0 ou 1px.
Probleme 3 : Quand j'ajoute les vlan, nom et couleur il ne s'update pas dans la Database. les couleur ne sont pas aleatoir (touours la meme, et rester dasn les meme ton de couleur)
Probleme 4 : Il faut que je puisse utiliser l'application juste avec le html sans ouvrir un serveur html
Probleme 5 : Attention important ! Si et seulement si les switch sont stacker : Exemple : GigabitEthernet1/X/X et GigabitEthernet2/X/X il faut afficher 2 tableaux des 2 switchs stacker l'un en dessous de l'autre et le deuxième tableau reprend a partir de GigabitEthernet2/X/X, avec copier de un tableau 1 ou 2 mais pas les 2.
New Feature 1 : Ajoute des boutons = "Copier l'image" et "Telecharger l'image" pour télécharger ou copier l'image du tableau comme sur le site (une image png).
Probleme 5 : Le tableau, si il est trop grand pour voir la suite doit etre derouler sur l'horizontal, est ce que c'est possible que ca largeur s'auto adapte a la largeur de la page pour pas que on est a scroll.
New Feature 2 : Ajoute les fibre Ten-GigabitEthernet avec comme pour les interface a l'interieur une information rapide de la fibre. (toujours dans le tableau, peut toujours etre copier coller dans excel. Choisi toi meme la facon de les presenter mais a droites des dernieres interface)
New Feature 3: Met les modification de vlan dans un collapse depliant fermer par defaut.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
// Fichier de base de données VLAN - À charger dans le HTML
// Placer ce fichier dans le même dossier que switch_vlan_mapper.html
// Format : <id> = <nom> = <couleur hex> (exemple générique, à adapter)
const VLAN_DATABASE_FILE = `10 = MGMT = #6366f1
20 = SERVERS = #22c55e
30 = USERS = #06b6d4
40 = VOICE = #2dd4bf
50 = DMZ = #8b5cf6
99 = GUEST = #f97316
`;

Binary file not shown.

335
conf_switch.exemple Normal file
View file

@ -0,0 +1,335 @@
**********************************************************
Use is restricted to authorized users
who must comply with the Electronic Communications Policy.
Usage is monitored; unauthorized use will be prosecuted.
**********************************************************
**********************************************************
Connected to 5130-INV-1714 GEMALTO on MEYREUIL (FRANCE)
Use is restricted to authorized users
who must comply with the Electronic Communications Policy.
Usage is monitored; unauthorized use will be prosecuted.
**********************************************************
******************************************************************************
* Copyright (c) 2010-2019 Hewlett Packard Enterprise Development LP *
* Without the owner's prior written consent, *
* no decompiling or reverse-engineering shall be allowed. *
******************************************************************************
<5130-INV-1714>display current-configuration interface
#
interface NULL0
#
interface Vlan-interface17
description SW_MGMT
ip address 10.0.0.2 255.255.255.0
#
interface GigabitEthernet1/0/1
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/2
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/3
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/4
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/5
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/6
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/7
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/8
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/9
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/10
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/11
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/12
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/13
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/14
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/15
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/16
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/17
port access vlan 16
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/18
port access vlan 16
speed 1000
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/19
port access vlan 16
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/20
port access vlan 16
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/21
port access vlan 16
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/22
port access vlan 16
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/23
port access vlan 18
#
interface GigabitEthernet1/0/24
port access vlan 18
#
interface GigabitEthernet1/0/25
port access vlan 18
#
interface GigabitEthernet1/0/26
port access vlan 18
#
interface GigabitEthernet1/0/27
port access vlan 18
#
interface GigabitEthernet1/0/28
port access vlan 18
#
interface GigabitEthernet1/0/29
port access vlan 18
#
interface GigabitEthernet1/0/30
port access vlan 18
#
interface GigabitEthernet1/0/31
port access vlan 18
#
interface GigabitEthernet1/0/32
port access vlan 18
#
interface GigabitEthernet1/0/33
port access vlan 18
#
interface GigabitEthernet1/0/34
port access vlan 18
#
interface GigabitEthernet1/0/35
port access vlan 18
#
interface GigabitEthernet1/0/36
port access vlan 18
#
interface GigabitEthernet1/0/37
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/38
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/39
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/40
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/41
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/42
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/43
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/44
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/45
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/46
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/47
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/48
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface Ten-GigabitEthernet1/0/49
description *** Uplink TRUNK_TO_CORE ***
port link-type trunk
port trunk permit vlan all
#
interface Ten-GigabitEthernet1/0/50
description ###TRUNK_TO_COREXXXX###
port link-type trunk
port trunk permit vlan all
stp instance 0 cost 120
#
interface Ten-GigabitEthernet1/0/51
#
interface Ten-GigabitEthernet1/0/52
#
return
<5130-INV-1714>

686
conf_switch_stack.exemple Normal file
View file

@ -0,0 +1,686 @@
**********************************************************
Use is restricted to authorized users
who must comply with the Electronic Communications Policy.
Usage is monitored; unauthorized use will be prosecuted.
**********************************************************
**********************************************************
Connected to 5130-INV-1714 GEMALTO on MEYREUIL (FRANCE)
Use is restricted to authorized users
who must comply with the Electronic Communications Policy.
Usage is monitored; unauthorized use will be prosecuted.
**********************************************************
******************************************************************************
* Copyright (c) 2010-2019 Hewlett Packard Enterprise Development LP *
* Without the owner's prior written consent, *
* no decompiling or reverse-engineering shall be allowed. *
******************************************************************************
<5130-INV-1714>display current-configuration interface
#
interface NULL0
#
interface Vlan-interface17
description SW_MGMT
ip address 10.0.0.2 255.255.255.0
#
interface GigabitEthernet1/0/1
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/2
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/3
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/4
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/5
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/6
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/7
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/8
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/9
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/10
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/11
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/12
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/13
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/14
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/15
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/16
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet1/0/17
port access vlan 16
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/18
port access vlan 16
speed 1000
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/19
port access vlan 16
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/20
port access vlan 16
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/21
port access vlan 16
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/22
port access vlan 16
duplex full
undo lldp enable
#
interface GigabitEthernet1/0/23
port access vlan 18
#
interface GigabitEthernet1/0/24
port access vlan 18
#
interface GigabitEthernet1/0/25
port access vlan 18
#
interface GigabitEthernet1/0/26
port access vlan 18
#
interface GigabitEthernet1/0/27
port access vlan 18
#
interface GigabitEthernet1/0/28
port access vlan 18
#
interface GigabitEthernet1/0/29
port access vlan 18
#
interface GigabitEthernet1/0/30
port access vlan 18
#
interface GigabitEthernet1/0/31
port access vlan 18
#
interface GigabitEthernet1/0/32
port access vlan 18
#
interface GigabitEthernet1/0/33
port access vlan 18
#
interface GigabitEthernet1/0/34
port access vlan 18
#
interface GigabitEthernet1/0/35
port access vlan 18
#
interface GigabitEthernet1/0/36
port access vlan 18
#
interface GigabitEthernet1/0/37
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/38
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/39
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/40
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/41
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/42
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/43
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/44
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/45
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/46
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/47
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface GigabitEthernet1/0/48
description ** ULAB **
port access vlan 18
undo lldp enable
#
interface Ten-GigabitEthernet1/0/49
description *** Uplink TRUNK_TO_CORE ***
port link-type trunk
port trunk permit vlan all
#
interface Ten-GigabitEthernet1/0/50
description ###TRUNK_TO_COREXXXX###
port link-type trunk
port trunk permit vlan all
stp instance 0 cost 120
#
interface Ten-GigabitEthernet1/0/51
#
interface Ten-GigabitEthernet1/0/52
#
interface GigabitEthernet2/0/1
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/2
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/3
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/4
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/5
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/6
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/7
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/8
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/9
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/10
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/11
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/12
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/13
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/14
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/15
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/16
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/17
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/18
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/19
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/20
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/21
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/22
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/23
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/24
port access vlan 16
duplex full
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/25
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/26
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/27
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/28
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/29
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/30
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/31
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/32
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/33
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/34
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/35
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/36
stp edged-port
undo lldp enable
undo dot1x handshake
dot1x mandatory-domain example_radius
undo dot1x multicast-trigger
dot1x re-authenticate
port-security port-mode userlogin-secure
#
interface GigabitEthernet2/0/37
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/38
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/39
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/40
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/41
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/42
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/43
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/44
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/45
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/46
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/47
port access vlan 18
undo lldp enable
#
interface GigabitEthernet2/0/48
port access vlan 18
undo lldp enable
#
interface Ten-GigabitEthernet2/0/49
description *** Uplink TRUNK_TO_CORE ***
port link-type trunk
port trunk permit vlan all
#
interface Ten-GigabitEthernet2/0/50
description ###TRUNK_TO_COREXXXX###
port link-type trunk
port trunk permit vlan all
stp instance 0 cost 120
#
interface Ten-GigabitEthernet2/0/51
#
interface Ten-GigabitEthernet2/0/52
#
return
<5130-INV-STACK>

11
index.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0; url=CMDB/cmdb.html">
<title>CMDB</title>
</head>
<body>
<a href="CMDB/cmdb.html">Redirection vers la CMDB…</a>
</body>
</html>

5
mise.toml Normal file
View file

@ -0,0 +1,5 @@
[tasks.run]
description = "Run server"
run = '''
uv run server.py
'''

11
proxy.sh Normal file
View file

@ -0,0 +1,11 @@
# Renseignez l'URL de votre proxy d'entreprise (host:port).
PROXY_URL="http://PROXY_HOST:PROXY_PORT/"
export http_proxy="$PROXY_URL"
export https_proxy="$PROXY_URL"
export ftp_proxy="$PROXY_URL"
# For curl
export HTTP_PROXY="$PROXY_URL"
export HTTPS_PROXY="$PROXY_URL"
export FTP_PROXY="$PROXY_URL"

9
pyproject.toml Normal file
View file

@ -0,0 +1,9 @@
[project]
name = "version2"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"ldap3>=2.9.1",
]

262
server.py Normal file
View file

@ -0,0 +1,262 @@
#!/usr/bin/env python3
"""
Serveur unifié du portail Infra (CMDB + Certificats + Switch).
Un seul serveur, lancé à la racine du projet :
python3 server.py # http://127.0.0.1:8000 → CMDB
python3 server.py 8080 # port custom
- http://127.0.0.1:8000/ redirige vers la CMDB
- CMDB : base partagée cmdb.db sourcée automatiquement (/db, versionnée)
- Générateur de certificats : écrit sur le disque dans
GENERATEUR_CERTIFICATES/LISTS_ALL_CERT/ (créé au besoin), aucun prompt
- Sert les 3 applications + le switcher (liens relatifs OK)
- Recherche AD/LDAP de la CMDB conservée (nécessite `pip install ldap3`)
"""
import sys, os, json, base64, shutil, re, threading
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse, parse_qs
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
HERE = os.path.dirname(os.path.abspath(__file__)) # racine projet
CMDB_DIR = os.path.join(HERE, 'CMDB')
CERT_DIR = os.path.join(HERE, 'GENERATEUR_CERTIFICATES')
WORK = os.path.join(CERT_DIR, 'LISTS_ALL_CERT') # dossier de travail certs (disque)
ARCH = os.path.join(WORK, 'archives')
# réutilise la logique CMDB existante (base partagée cmdb.db + LDAP)
sys.path.insert(0, CMDB_DIR)
import cmdb_server as C # ne démarre rien (guarded __main__)
SAFE = re.compile(r'^[A-Za-z0-9._@\- ]+$')
def safe_name(n):
n = (n or '').strip()
if not n or n in ('.', '..') or '/' in n or '\\' in n or not SAFE.match(n):
raise ValueError('nom invalide: %r' % n)
return n
def ensure_work():
os.makedirs(WORK, exist_ok=True)
class Handler(SimpleHTTPRequestHandler):
def log_message(self, *a):
pass
# ---------- helpers ----------
def _json(self, obj, code=200):
body = json.dumps(obj).encode('utf-8')
self.send_response(code)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-store')
self.end_headers()
self.wfile.write(body)
def _err(self, msg, code=400):
self._json({'ok': False, 'error': str(msg)}, code)
def _send_db(self, code, data, version):
self.send_response(code)
self.send_header('Content-Type', 'application/octet-stream')
self.send_header('X-DB-Version', str(version))
self.send_header('Cache-Control', 'no-store')
self.send_header('Content-Length', str(len(data)))
self.end_headers()
if data:
self.wfile.write(data)
def _send_download(self, data, filename):
self.send_response(200)
self.send_header('Content-Type', 'application/octet-stream')
self.send_header('Content-Disposition', 'attachment; filename="%s"' % filename)
self.send_header('Cache-Control', 'no-store')
self.send_header('Content-Length', str(len(data)))
self.end_headers()
self.wfile.write(data)
def _body(self):
n = int(self.headers.get('Content-Length', 0))
return json.loads(self.rfile.read(n) or b'{}')
def _cert_api(self):
p = self.path.split('?', 1)[0]
if '/GENERATEUR_CERTIFICATES/api/' in p:
return p.split('/api/', 1)[1]
return None
# ---------- GET ----------
def do_GET(self):
# racine → CMDB
if self.path in ('/', '/index.html'):
self.send_response(302)
self.send_header('Location', '/CMDB/cmdb.html')
self.end_headers()
return
# base CMDB partagée
if self.path == '/db':
with C.DB_LOCK:
return self._send_db(200, C.db_bytes(), C.db_version())
if self.path == '/db/version':
return self._json({'version': C.db_version()})
if self.path == '/db/snapshots':
return self._json({'snapshots': C.list_snapshots()})
if self.path.startswith('/db/snapshot?'):
name = (parse_qs(urlparse(self.path).query).get('name') or [''])[0]
try:
data = C.snapshot_bytes(name)
except (ValueError, OSError):
return self._json({'error': 'snapshot introuvable'}, 404)
return self._send_download(data, os.path.basename(name))
# API certs (lecture)
api = self._cert_api()
if api == 'ping':
return self._json({'ok': True, 'workdir': 'LISTS_ALL_CERT'})
if api == 'list':
return self._json(self.cert_list())
return super().do_GET()
# ---------- POST ----------
def do_POST(self):
# base CMDB
if self.path == '/db':
data = self.rfile.read(int(self.headers.get('Content-Length', 0)))
base = int(self.headers.get('X-Base-Version', '0') or 0)
cur = C.db_version()
if base != cur:
return self._send_db(409, C.db_bytes(), cur)
return self._send_db(200, b'', C.db_write_and_snapshot(data))
# snapshots CMDB (création / suppression / restauration)
if self.path in ('/db/snapshot', '/db/snapshot/delete', '/db/restore'):
body = self._body()
if self.path == '/db/snapshot':
meta = C.create_snapshot(label=body.get('label') or 'manual')
if not meta:
return self._json({'error': 'base vide — rien à sauvegarder'}, 400)
return self._json(meta)
if self.path == '/db/snapshot/delete':
try:
C.delete_snapshot(body.get('name') or '')
except (ValueError, OSError):
return self._json({'error': 'nom de snapshot invalide'}, 400)
return self._json({'ok': True})
try:
C.restore_snapshot(body.get('name') or '')
except (ValueError, FileNotFoundError):
return self._json({'error': 'snapshot introuvable'}, 404)
with C.DB_LOCK:
return self._send_db(200, C.db_bytes(), C.db_version())
# LDAP (délégué à cmdb_server)
if self.path in ('/ldap/search', '/ldap/computer'):
if not C.HAS_LDAP:
return self._json({'error': "module ldap3 manquant — pip install ldap3"}, 500)
try:
body = json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0))))
fn = C.ldap_computer if self.path == '/ldap/computer' else C.ldap_search
if self.path == '/ldap/search':
return self._json(fn(body.get('config') or {}, (body.get('query') or '').strip(),
int(body.get('limit') or 25)))
return self._json(fn(body.get('config') or {}, (body.get('query') or '').strip()))
except Exception as ex:
return self._json({'error': str(ex)}, 500)
# API certs (écriture)
api = self._cert_api()
try:
if api == 'write': return self.cert_write_text()
if api == 'write_bin': return self.cert_write_bin()
if api == 'delete': return self.cert_delete_dir()
if api == 'delete_archives': return self.cert_delete_archives()
except Exception as e:
return self._err(e, 500)
self._err('route inconnue: %s' % self.path, 404)
# ---------- certificats (disque réel) ----------
def cert_list(self):
ensure_work()
certs, archives = [], []
for entry in sorted(os.listdir(WORK)):
full = os.path.join(WORK, entry)
if entry == 'archives':
if os.path.isdir(full):
for a in sorted(os.listdir(full)):
af = os.path.join(full, a)
if os.path.isfile(af):
st = os.stat(af)
archives.append({'name': a, 'size': st.st_size,
'mtime': int(st.st_mtime * 1000)})
continue
if not os.path.isdir(full):
continue
files, date = [], 0
for fn in sorted(os.listdir(full)):
ff = os.path.join(full, fn)
if not os.path.isfile(ff):
continue
st = os.stat(ff)
m = int(st.st_mtime * 1000)
date = max(date, m)
files.append({'name': fn, 'ext': (fn.rsplit('.', 1)[-1] if '.' in fn else '').lower(),
'size': st.st_size, 'mtime': m})
if files:
certs.append({'dir': entry, 'files': files, 'date': date})
archives.sort(key=lambda a: -a['mtime'])
certs.sort(key=lambda c: -c['date'])
return {'ok': True, 'certs': certs, 'archives': archives}
def cert_write_text(self):
d = self._body()
dname = safe_name(d['dir'])
target = os.path.join(WORK, dname)
os.makedirs(target, exist_ok=True)
for fname, content in (d.get('files') or {}).items():
with open(os.path.join(target, safe_name(fname)), 'w', encoding='utf-8', newline='') as f:
f.write(content)
self._json({'ok': True, 'path': 'LISTS_ALL_CERT/%s/' % dname})
def cert_write_bin(self):
d = self._body()
parts = [safe_name(x) for x in d['path'].split('/') if x != '']
if not parts:
raise ValueError('path vide')
target = os.path.join(WORK, *parts)
os.makedirs(os.path.dirname(target), exist_ok=True)
with open(target, 'wb') as f:
f.write(base64.b64decode(d['b64']))
self._json({'ok': True, 'path': 'LISTS_ALL_CERT/%s' % '/'.join(parts)})
def cert_delete_dir(self):
d = self._body()
dname = safe_name(d['dir'])
target = os.path.join(WORK, dname)
if os.path.isdir(target):
shutil.rmtree(target)
if d.get('archives'):
self._rm_archives(dname)
self._json({'ok': True})
def cert_delete_archives(self):
self._rm_archives(safe_name(self._body()['dir']))
self._json({'ok': True})
def _rm_archives(self, dname):
low = dname.lower()
if os.path.isdir(ARCH):
for a in os.listdir(ARCH):
if low in a.lower():
try: os.remove(os.path.join(ARCH, a))
except OSError: pass
if __name__ == '__main__':
ensure_work()
os.chdir(HERE)
if not C.HAS_LDAP:
print("⚠ ldap3 absent : recherche AD de la CMDB désactivée (pip install ldap3)")
print('Portail Infra : http://127.0.0.1:%d → CMDB' % PORT)
print('CMDB base : %s' % C.DB_PATH)
print('Certificats : %s (créé automatiquement)' % WORK)
print('Ctrl+C pour arrêter.')
ThreadingHTTPServer(('0.0.0.0', PORT), Handler).serve_forever()

35
uv.lock generated Normal file
View file

@ -0,0 +1,35 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "ldap3"
version = "2.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/ac/96bd5464e3edbc61595d0d69989f5d9969ae411866427b2500a8e5b812c0/ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f", size = 398830, upload-time = "2021-07-18T06:34:21.786Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/f6/71d6ec9f18da0b2201287ce9db6afb1a1f637dedb3f0703409558981c723/ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70", size = 432192, upload-time = "2021-07-18T06:34:12.905Z" },
]
[[package]]
name = "pyasn1"
version = "0.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
]
[[package]]
name = "version2"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "ldap3" },
]
[package.metadata]
requires-dist = [{ name = "ldap3", specifier = ">=2.9.1" }]