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 = {
|
SORT_COLS = {
|
||||||
"hostname": "s.hostname",
|
"hostname": "s.hostname",
|
||||||
"env": "e.name",
|
"env": "s.environnement",
|
||||||
"domaine": "d.name",
|
"domaine": "d.name",
|
||||||
"tier": "s.tier",
|
"tier": "s.tier",
|
||||||
"etat": "s.etat",
|
"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"):
|
if filters.get("domain"):
|
||||||
where.append("d.code = :domain"); params["domain"] = filters["domain"]
|
where.append("d.code = :domain"); params["domain"] = filters["domain"]
|
||||||
if filters.get("env"):
|
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"):
|
if filters.get("tier"):
|
||||||
where.append("s.tier = :tier"); params["tier"] = filters["tier"]
|
where.append("s.tier = :tier"); params["tier"] = filters["tier"]
|
||||||
if filters.get("etat"):
|
if filters.get("etat"):
|
||||||
|
|||||||
@ -58,7 +58,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="text-xs text-gray-500">Etat</label>
|
<label class="text-xs text-gray-500">Etat</label>
|
||||||
<select name="etat" class="w-full">
|
<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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<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 %}
|
{% for d in domains_list %}<option value="{{ d.code }}" {% if filters.domain == d.code %}selected{% endif %}>{{ d.name }}</option>{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<select name="env" onchange="this.form.submit()"><option value="">Env</option>
|
<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>
|
||||||
<select name="tier" onchange="this.form.submit()"><option value="">Tier</option>
|
<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 %}
|
{% for t in ['tier0','tier1','tier2','tier3'] %}<option value="{{ t }}" {% if filters.tier == t %}selected{% endif %}>{{ t }}</option>{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<select name="etat" onchange="this.form.submit()"><option value="">Etat</option>
|
<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>
|
||||||
<select name="os" onchange="this.form.submit()"><option value="">OS</option>
|
<select name="os" onchange="this.form.submit()"><option value="">OS</option>
|
||||||
<option value="linux" {% if filters.os == 'linux' %}selected{% endif %}>Linux</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 %}],
|
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 %}],
|
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"}],
|
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"}],
|
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"}],
|
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") \
|
DATABASE_URL = os.getenv("DATABASE_URL_DEMO") or os.getenv("DATABASE_URL") \
|
||||||
or "postgresql://patchcenter:PatchCenter2026!@localhost:5432/patchcenter_demo"
|
or "postgresql://patchcenter:PatchCenter2026!@localhost:5432/patchcenter_demo"
|
||||||
|
|
||||||
|
# Etats lifecycle iTop (dropdown unique de PatchCenter)
|
||||||
ITOP_ETATS = [
|
ITOP_ETATS = [
|
||||||
# Lifecycle
|
"Production", "Implémentation", "Stock", "Obsolète", "prêt", "tests",
|
||||||
"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é",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Migration valeurs existantes (lowercase) -> labels iTop verbatim
|
# Migration anciennes valeurs (lowercase OU condition iTop) -> NULL ou lifecycle verbatim
|
||||||
LEGACY_MAP = {
|
LEGACY_MAP = {
|
||||||
|
# lowercase codes (ancien schema)
|
||||||
"production": "Production",
|
"production": "Production",
|
||||||
"implementation": "Implémentation",
|
"implementation": "Implémentation",
|
||||||
"stock": "Stock",
|
"stock": "Stock",
|
||||||
"obsolete": "Obsolète",
|
"obsolete": "Obsolète",
|
||||||
"eol": "EOL",
|
|
||||||
"pret": "prêt",
|
"pret": "prêt",
|
||||||
"tests": "tests",
|
"tests": "tests",
|
||||||
"nouveau": "Nouveau",
|
# Verbatim (idempotent)
|
||||||
"casse": "Cassé",
|
"Production": "Production",
|
||||||
"cede": "Cédé",
|
"Implémentation": "Implémentation",
|
||||||
"en_panne": "En panne",
|
"Stock": "Stock",
|
||||||
"a_recuperer": "A récupérer",
|
"Obsolète": "Obsolète",
|
||||||
"perdu": "Perdu",
|
"prêt": "prêt",
|
||||||
"recycle": "Recyclé",
|
# Les valeurs "condition" iTop ne sont pas des lifecycle -> NULL
|
||||||
"occasion": "Occasion",
|
# (Nouveau, Cassé, En panne, ... : import_etat_itop les mettra a NULL
|
||||||
"a_detruire": "A détruire",
|
# et ces serveurs seront re-syncs depuis la colonne Status du CSV)
|
||||||
"vole": "Volé",
|
"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):
|
def norm_etat(raw):
|
||||||
|
"""Retourne l'etat lifecycle iTop verbatim, ou None si unknown/condition."""
|
||||||
if not raw:
|
if not raw:
|
||||||
return None
|
return None
|
||||||
s = raw.strip()
|
s = raw.strip()
|
||||||
if not s or s in ("-", "(null)"):
|
if not s or s in ("-", "(null)"):
|
||||||
return None
|
return None
|
||||||
if s in ITOP_ETATS:
|
return s if s in ITOP_ETATS else None
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -81,11 +83,12 @@ def main():
|
|||||||
if not args.dry_run:
|
if not args.dry_run:
|
||||||
conn.execute(text("ALTER TABLE servers DROP CONSTRAINT IF EXISTS servers_etat_check"))
|
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():
|
for old, new in LEGACY_MAP.items():
|
||||||
cnt = conn.execute(text("SELECT COUNT(*) FROM servers WHERE etat=:o"), {"o": old}).scalar()
|
cnt = conn.execute(text("SELECT COUNT(*) FROM servers WHERE etat=:o"), {"o": old}).scalar()
|
||||||
if cnt:
|
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:
|
if not args.dry_run:
|
||||||
conn.execute(text("UPDATE servers SET etat=:n WHERE etat=:o"), {"n": new, "o": old})
|
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()
|
hostname = (r.get("Nom") or r.get("Hostname") or "").strip()
|
||||||
if not hostname or not any(c.isalpha() for c in hostname):
|
if not hostname or not any(c.isalpha() for c in hostname):
|
||||||
continue
|
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)
|
new_etat = norm_etat(raw)
|
||||||
if raw and new_etat is None and raw not in ("-", "(null)"):
|
if raw and new_etat is None and raw not in ("-", "(null)"):
|
||||||
unknown.add(raw); continue
|
unknown.add(raw); continue
|
||||||
|
|||||||
@ -26,11 +26,7 @@ def norm_os_family(famille):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
ITOP_ETATS = {
|
ITOP_ETATS = {"Production", "Implémentation", "Stock", "Obsolète", "prêt", "tests"}
|
||||||
"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é",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def norm_etat(status, etat):
|
def norm_etat(status, etat):
|
||||||
|
|||||||
@ -25,11 +25,7 @@ def norm_os_family(famille):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
ITOP_ETATS = {
|
ITOP_ETATS = {"Production", "Implémentation", "Stock", "Obsolète", "prêt", "tests"}
|
||||||
"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é",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def norm_etat(status, etat):
|
def norm_etat(status, etat):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user