Etat/Environnement dropdowns alignes strict iTop SANEF
Etat: 6 valeurs lifecycle uniquement (Production, Implémentation, Stock, Obsolète, prêt, tests). Suppression des valeurs condition (Nouveau, Cassé, En panne, etc.) et de EOL qui n'existent pas dans iTop SANEF. Environnement: 7 valeurs iTop (Développement, Intégration, Pré-Prod, Production, Recette, Test, Formation). Filtre env bascule de e.code (legacy) vers s.environnement. tools/import_etat_itop.py: - CHECK 6 valeurs lifecycle + NULL - Migration mappe les anciennes condition/EOL -> NULL - Lit Status en priorite dans le CSV (lifecycle), fallback Etat - Fix format print pour None tools/import_sanef_*.py: ITOP_ETATS reduit a 6 valeurs
This commit is contained in:
parent
753d4076c9
commit
1c2d0b958e
@ -92,7 +92,7 @@ def update_server_ips(db, server_id, ip_reelle, ip_connexion):
|
||||
|
||||
SORT_COLS = {
|
||||
"hostname": "s.hostname",
|
||||
"env": "e.name",
|
||||
"env": "s.environnement",
|
||||
"domaine": "d.name",
|
||||
"tier": "s.tier",
|
||||
"etat": "s.etat",
|
||||
@ -111,7 +111,7 @@ def list_servers(db, filters, page=1, per_page=50, sort="hostname", sort_dir="as
|
||||
if filters.get("domain"):
|
||||
where.append("d.code = :domain"); params["domain"] = filters["domain"]
|
||||
if filters.get("env"):
|
||||
where.append("e.code = :env"); params["env"] = filters["env"]
|
||||
where.append("s.environnement = :env"); params["env"] = filters["env"]
|
||||
if filters.get("tier"):
|
||||
where.append("s.tier = :tier"); params["tier"] = filters["tier"]
|
||||
if filters.get("etat"):
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">Etat</label>
|
||||
<select name="etat" class="w-full">
|
||||
{% for e in ['Production','Implémentation','Stock','Obsolète','EOL','prêt','tests','Nouveau','En panne','Cassé','Cédé','A récupérer','Perdu','Recyclé','Occasion','A détruire','Volé'] %}<option value="{{ e }}" {% if e == s.etat %}selected{% endif %}>{{ e }}</option>{% endfor %}
|
||||
{% for e in ['Production','Implémentation','Stock','Obsolète','prêt','tests'] %}<option value="{{ e }}" {% if e == s.etat %}selected{% endif %}>{{ e }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@ -30,13 +30,13 @@
|
||||
{% for d in domains_list %}<option value="{{ d.code }}" {% if filters.domain == d.code %}selected{% endif %}>{{ d.name }}</option>{% endfor %}
|
||||
</select>
|
||||
<select name="env" onchange="this.form.submit()"><option value="">Env</option>
|
||||
{% for e in envs_list %}<option value="{{ e.code }}" {% if filters.env == e.code %}selected{% endif %}>{{ e.name }}</option>{% endfor %}
|
||||
{% for e in ['Développement','Intégration','Pré-Prod','Production','Recette','Test','Formation'] %}<option value="{{ e }}" {% if filters.env == e %}selected{% endif %}>{{ e }}</option>{% endfor %}
|
||||
</select>
|
||||
<select name="tier" onchange="this.form.submit()"><option value="">Tier</option>
|
||||
{% for t in ['tier0','tier1','tier2','tier3'] %}<option value="{{ t }}" {% if filters.tier == t %}selected{% endif %}>{{ t }}</option>{% endfor %}
|
||||
</select>
|
||||
<select name="etat" onchange="this.form.submit()"><option value="">Etat</option>
|
||||
{% for e in ['Production','Implémentation','Stock','Obsolète','EOL','prêt','tests','Nouveau','En panne','Cassé','Cédé','A récupérer','Perdu','Recyclé','Occasion','A détruire','Volé'] %}<option value="{{ e }}" {% if filters.etat == e %}selected{% endif %}>{{ e }}</option>{% endfor %}
|
||||
{% for e in ['Production','Implémentation','Stock','Obsolète','prêt','tests'] %}<option value="{{ e }}" {% if filters.etat == e %}selected{% endif %}>{{ e }}</option>{% endfor %}
|
||||
</select>
|
||||
<select name="os" onchange="this.form.submit()"><option value="">OS</option>
|
||||
<option value="linux" {% if filters.os == 'linux' %}selected{% endif %}>Linux</option>
|
||||
@ -82,7 +82,7 @@ const bulkValues = {
|
||||
domain_code: [{% for d in domains_list %}{v:"{{ d.code }}", l:"{{ d.name }}"},{% endfor %}],
|
||||
env_code: [{% for e in envs_list %}{v:"{{ e.code }}", l:"{{ e.name }}"},{% endfor %}],
|
||||
tier: [{v:"tier0",l:"tier0"},{v:"tier1",l:"tier1"},{v:"tier2",l:"tier2"},{v:"tier3",l:"tier3"}],
|
||||
etat: [{v:"Production",l:"Production"},{v:"Implémentation",l:"Implémentation"},{v:"Stock",l:"Stock"},{v:"Obsolète",l:"Obsolète"},{v:"EOL",l:"EOL"},{v:"prêt",l:"prêt"},{v:"tests",l:"tests"},{v:"Nouveau",l:"Nouveau"},{v:"En panne",l:"En panne"},{v:"Cassé",l:"Cassé"},{v:"Cédé",l:"Cédé"},{v:"A récupérer",l:"A récupérer"},{v:"Perdu",l:"Perdu"},{v:"Recyclé",l:"Recyclé"},{v:"Occasion",l:"Occasion"},{v:"A détruire",l:"A détruire"},{v:"Volé",l:"Volé"}],
|
||||
etat: [{v:"Production",l:"Production"},{v:"Implémentation",l:"Implémentation"},{v:"Stock",l:"Stock"},{v:"Obsolète",l:"Obsolète"},{v:"prêt",l:"prêt"},{v:"tests",l:"tests"}],
|
||||
patch_os_owner: [{v:"secops",l:"secops"},{v:"ipop",l:"ipop"},{v:"na",l:"na"}],
|
||||
licence_support: [{v:"active",l:"active"},{v:"obsolete",l:"obsolete"},{v:"els",l:"els"}],
|
||||
};
|
||||
|
||||
@ -21,49 +21,51 @@ from sqlalchemy import create_engine, text
|
||||
DATABASE_URL = os.getenv("DATABASE_URL_DEMO") or os.getenv("DATABASE_URL") \
|
||||
or "postgresql://patchcenter:PatchCenter2026!@localhost:5432/patchcenter_demo"
|
||||
|
||||
# Etats lifecycle iTop (dropdown unique de PatchCenter)
|
||||
ITOP_ETATS = [
|
||||
# Lifecycle
|
||||
"Production", "Implémentation", "Stock", "Obsolète", "EOL", "prêt", "tests",
|
||||
# Condition
|
||||
"Nouveau", "A récupérer", "Cassé", "Cédé", "En panne",
|
||||
"Perdu", "Recyclé", "Occasion", "A détruire", "Volé",
|
||||
"Production", "Implémentation", "Stock", "Obsolète", "prêt", "tests",
|
||||
]
|
||||
|
||||
# Migration valeurs existantes (lowercase) -> labels iTop verbatim
|
||||
# Migration anciennes valeurs (lowercase OU condition iTop) -> NULL ou lifecycle verbatim
|
||||
LEGACY_MAP = {
|
||||
# lowercase codes (ancien schema)
|
||||
"production": "Production",
|
||||
"implementation": "Implémentation",
|
||||
"stock": "Stock",
|
||||
"obsolete": "Obsolète",
|
||||
"eol": "EOL",
|
||||
"pret": "prêt",
|
||||
"tests": "tests",
|
||||
"nouveau": "Nouveau",
|
||||
"casse": "Cassé",
|
||||
"cede": "Cédé",
|
||||
"en_panne": "En panne",
|
||||
"a_recuperer": "A récupérer",
|
||||
"perdu": "Perdu",
|
||||
"recycle": "Recyclé",
|
||||
"occasion": "Occasion",
|
||||
"a_detruire": "A détruire",
|
||||
"vole": "Volé",
|
||||
# Verbatim (idempotent)
|
||||
"Production": "Production",
|
||||
"Implémentation": "Implémentation",
|
||||
"Stock": "Stock",
|
||||
"Obsolète": "Obsolète",
|
||||
"prêt": "prêt",
|
||||
# Les valeurs "condition" iTop ne sont pas des lifecycle -> NULL
|
||||
# (Nouveau, Cassé, En panne, ... : import_etat_itop les mettra a NULL
|
||||
# et ces serveurs seront re-syncs depuis la colonne Status du CSV)
|
||||
"eol": None, "EOL": None,
|
||||
"nouveau": None, "Nouveau": None,
|
||||
"casse": None, "Cassé": None,
|
||||
"cede": None, "Cédé": None,
|
||||
"en_panne": None, "En panne": None,
|
||||
"a_recuperer": None, "A récupérer": None,
|
||||
"perdu": None, "Perdu": None,
|
||||
"recycle": None, "Recyclé": None,
|
||||
"occasion": None, "Occasion": None,
|
||||
"a_detruire": None, "A détruire": None,
|
||||
"vole": None, "Volé": None,
|
||||
}
|
||||
|
||||
|
||||
def norm_etat(raw):
|
||||
"""Retourne l'etat lifecycle iTop verbatim, ou None si unknown/condition."""
|
||||
if not raw:
|
||||
return None
|
||||
s = raw.strip()
|
||||
if not s or s in ("-", "(null)"):
|
||||
return None
|
||||
if s in ITOP_ETATS:
|
||||
return s
|
||||
# Tolerance case-insensitive + sans accent pour les alias courants
|
||||
low = s.lower()
|
||||
if low in LEGACY_MAP:
|
||||
return LEGACY_MAP[low]
|
||||
return None
|
||||
return s if s in ITOP_ETATS else None
|
||||
|
||||
|
||||
def main():
|
||||
@ -81,11 +83,12 @@ def main():
|
||||
if not args.dry_run:
|
||||
conn.execute(text("ALTER TABLE servers DROP CONSTRAINT IF EXISTS servers_etat_check"))
|
||||
|
||||
print("[INFO] Migration lowercase -> iTop verbatim...")
|
||||
print("[INFO] Migration valeurs existantes -> lifecycle iTop ou NULL...")
|
||||
for old, new in LEGACY_MAP.items():
|
||||
cnt = conn.execute(text("SELECT COUNT(*) FROM servers WHERE etat=:o"), {"o": old}).scalar()
|
||||
if cnt:
|
||||
print(f" {old:18s} -> {new:18s} : {cnt}")
|
||||
label = new if new is not None else "NULL"
|
||||
print(f" {old:18s} -> {label:18s} : {cnt}")
|
||||
if not args.dry_run:
|
||||
conn.execute(text("UPDATE servers SET etat=:n WHERE etat=:o"), {"n": new, "o": old})
|
||||
|
||||
@ -113,7 +116,8 @@ def main():
|
||||
hostname = (r.get("Nom") or r.get("Hostname") or "").strip()
|
||||
if not hostname or not any(c.isalpha() for c in hostname):
|
||||
continue
|
||||
raw = (r.get("Etat") or r.get("État") or "").strip()
|
||||
# Lit Status (lifecycle iTop) en priorite, fallback Etat
|
||||
raw = (r.get("Status") or r.get("Etat") or r.get("État") or "").strip()
|
||||
new_etat = norm_etat(raw)
|
||||
if raw and new_etat is None and raw not in ("-", "(null)"):
|
||||
unknown.add(raw); continue
|
||||
|
||||
@ -26,11 +26,7 @@ def norm_os_family(famille):
|
||||
return None
|
||||
|
||||
|
||||
ITOP_ETATS = {
|
||||
"Production", "Implémentation", "Stock", "Obsolète", "EOL", "prêt", "tests",
|
||||
"Nouveau", "A récupérer", "Cassé", "Cédé", "En panne",
|
||||
"Perdu", "Recyclé", "Occasion", "A détruire", "Volé",
|
||||
}
|
||||
ITOP_ETATS = {"Production", "Implémentation", "Stock", "Obsolète", "prêt", "tests"}
|
||||
|
||||
|
||||
def norm_etat(status, etat):
|
||||
|
||||
@ -25,11 +25,7 @@ def norm_os_family(famille):
|
||||
return None
|
||||
|
||||
|
||||
ITOP_ETATS = {
|
||||
"Production", "Implémentation", "Stock", "Obsolète", "EOL", "prêt", "tests",
|
||||
"Nouveau", "A récupérer", "Cassé", "Cédé", "En panne",
|
||||
"Perdu", "Recyclé", "Occasion", "A détruire", "Volé",
|
||||
}
|
||||
ITOP_ETATS = {"Production", "Implémentation", "Stock", "Obsolète", "prêt", "tests"}
|
||||
|
||||
|
||||
def norm_etat(status, etat):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user