Modification internationalisation
This commit is contained in:
parent
99ae3123e9
commit
f4a28db6f7
@ -31,11 +31,11 @@ def preparer_graphe(G):
|
||||
|
||||
def selectionner_niveaux():
|
||||
"""Interface pour sélectionner les niveaux de départ et d'arrivée."""
|
||||
st.markdown(f"## {str(_('pages.analyse.selection_nodes', 'Sélection des nœuds de départ et d\'arrivée'))}")
|
||||
valeur_defaut = str(_("pages.analyse.select_level", "-- Sélectionner un niveau --"))
|
||||
st.markdown(f"## {str(_('pages.analyse.selection_nodes'))}")
|
||||
valeur_defaut = str(_("pages.analyse.select_level"))
|
||||
niveau_choix = [valeur_defaut] + list(niveau_labels.values())
|
||||
|
||||
niveau_depart = st.selectbox(str(_("pages.analyse.start_level", "Niveau de départ")), niveau_choix, key="analyse_niveau_depart")
|
||||
niveau_depart = st.selectbox(str(_("pages.analyse.start_level")), niveau_choix, key="analyse_niveau_depart")
|
||||
if niveau_depart == valeur_defaut:
|
||||
return None, None
|
||||
|
||||
@ -43,7 +43,7 @@ def selectionner_niveaux():
|
||||
niveaux_arrivee_possibles = [v for k, v in niveau_labels.items() if k > niveau_depart_int]
|
||||
niveaux_arrivee_choix = [valeur_defaut] + niveaux_arrivee_possibles
|
||||
|
||||
analyse_niveau_arrivee = st.selectbox(str(_("pages.analyse.end_level", "Niveau d'arrivée")), niveaux_arrivee_choix, key="analyse_niveau_arrivee")
|
||||
analyse_niveau_arrivee = st.selectbox(str(_("pages.analyse.end_level")), niveaux_arrivee_choix, key="analyse_niveau_arrivee")
|
||||
if analyse_niveau_arrivee == valeur_defaut:
|
||||
return niveau_depart_int, None
|
||||
|
||||
@ -55,7 +55,7 @@ def selectionner_minerais(G, niveau_depart, niveau_arrivee):
|
||||
"""Interface pour sélectionner les minerais si nécessaire."""
|
||||
minerais_selection = None
|
||||
if niveau_depart < 2 < niveau_arrivee:
|
||||
st.markdown(f"### {str(_('pages.analyse.select_minerals', 'Sélectionner un ou plusieurs minerais'))}")
|
||||
st.markdown(f"### {str(_('pages.analyse.select_minerals'))}")
|
||||
# Tous les nœuds de niveau 2 (minerai)
|
||||
minerais_nodes = sorted([
|
||||
n for n, d in G.nodes(data=True)
|
||||
@ -63,7 +63,7 @@ def selectionner_minerais(G, niveau_depart, niveau_arrivee):
|
||||
])
|
||||
|
||||
minerais_selection = st.multiselect(
|
||||
str(_("pages.analyse.filter_by_minerals", "Filtrer par minerais (optionnel)")),
|
||||
str(_("pages.analyse.filter_by_minerals")),
|
||||
minerais_nodes,
|
||||
key="analyse_minerais"
|
||||
)
|
||||
@ -74,15 +74,15 @@ def selectionner_minerais(G, niveau_depart, niveau_arrivee):
|
||||
def selectionner_noeuds(G, niveaux_temp, niveau_depart, niveau_arrivee):
|
||||
"""Interface pour sélectionner les nœuds spécifiques de départ et d'arrivée."""
|
||||
st.markdown("---")
|
||||
st.markdown(f"## {str(_('pages.analyse.fine_selection', 'Sélection fine des items'))}")
|
||||
st.markdown(f"## {str(_('pages.analyse.fine_selection'))}")
|
||||
|
||||
depart_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_depart]
|
||||
arrivee_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_arrivee]
|
||||
|
||||
noeuds_depart = st.multiselect(str(_("pages.analyse.filter_start_nodes", "Filtrer par noeuds de départ (optionnel)")),
|
||||
noeuds_depart = st.multiselect(str(_("pages.analyse.filter_start_nodes")),
|
||||
sorted(depart_nodes),
|
||||
key="analyse_noeuds_depart")
|
||||
noeuds_arrivee = st.multiselect(str(_("pages.analyse.filter_end_nodes", "Filtrer par noeuds d'arrivée (optionnel)")),
|
||||
noeuds_arrivee = st.multiselect(str(_("pages.analyse.filter_end_nodes")),
|
||||
sorted(arrivee_nodes),
|
||||
key="analyse_noeuds_arrivee")
|
||||
|
||||
@ -95,26 +95,26 @@ def selectionner_noeuds(G, niveaux_temp, niveau_depart, niveau_arrivee):
|
||||
def configurer_filtres_vulnerabilite():
|
||||
"""Interface pour configurer les filtres de vulnérabilité."""
|
||||
st.markdown("---")
|
||||
st.markdown(f"## {str(_('pages.analyse.vulnerability_filters', 'Sélection des filtres pour identifier les vulnérabilités'))}")
|
||||
st.markdown(f"## {str(_('pages.analyse.vulnerability_filters'))}")
|
||||
|
||||
filtrer_ics = st.checkbox(str(_("pages.analyse.filter_ics", "Filtrer les chemins contenant au moins minerai critique pour un composant (ICS > 66 %)")),
|
||||
filtrer_ics = st.checkbox(str(_("pages.analyse.filter_ics")),
|
||||
key="analyse_filtrer_ics")
|
||||
filtrer_ivc = st.checkbox(str(_("pages.analyse.filter_ivc", "Filtrer les chemins contenant au moins un minerai critique par rapport à la concurrence sectorielle (IVC > 30)")),
|
||||
filtrer_ivc = st.checkbox(str(_("pages.analyse.filter_ivc")),
|
||||
key="analyse_filtrer_ivc")
|
||||
filtrer_ihh = st.checkbox(str(_("pages.analyse.filter_ihh", "Filtrer les chemins contenant au moins une opération critique par rapport à la concentration géographique ou industrielle (IHH pays ou acteurs > 25)")),
|
||||
filtrer_ihh = st.checkbox(str(_("pages.analyse.filter_ihh")),
|
||||
key="analyse_filtrer_ihh")
|
||||
|
||||
ihh_type = "Pays"
|
||||
if filtrer_ihh:
|
||||
ihh_type = st.radio(str(_("pages.analyse.apply_ihh_filter", "Appliquer le filtre IHH sur :")),
|
||||
[str(_("pages.analyse.countries", "Pays")), str(_("pages.analyse.actors", "Acteurs"))],
|
||||
ihh_type = st.radio(str(_("pages.analyse.apply_ihh_filter")),
|
||||
[str(_("pages.analyse.countries")), str(_("pages.analyse.actors"))],
|
||||
horizontal=True,
|
||||
key="analyse_ihh_type")
|
||||
|
||||
filtrer_isg = st.checkbox(str(_("pages.analyse.filter_isg", "Filtrer les chemins contenant un pays instable (ISG ≥ 60)")),
|
||||
filtrer_isg = st.checkbox(str(_("pages.analyse.filter_isg")),
|
||||
key="analyse_filtrer_isg")
|
||||
logique_filtrage = st.radio(str(_("pages.analyse.filter_logic", "Logique de filtrage")),
|
||||
[str(_("pages.analyse.or", "OU")), str(_("pages.analyse.and", "ET"))],
|
||||
logique_filtrage = st.radio(str(_("pages.analyse.filter_logic")),
|
||||
[str(_("pages.analyse.or")), str(_("pages.analyse.and"))],
|
||||
horizontal=True,
|
||||
key="analyse_logique_filtrage")
|
||||
|
||||
@ -122,16 +122,9 @@ def configurer_filtres_vulnerabilite():
|
||||
|
||||
|
||||
def interface_analyse(G_temp):
|
||||
st.markdown(f"# {str(_('pages.analyse.title', 'Analyse du graphe'))}")
|
||||
with st.expander(str(_("pages.analyse.help", "Comment utiliser cet onglet ?")), expanded=False):
|
||||
st.markdown("\n".join(_("pages.analyse.help_content", [
|
||||
"1. Sélectionnez le niveau de départ (produit final, composant ou minerai)",
|
||||
"2. Choisissez le niveau d'arrivée souhaité",
|
||||
"3. Affinez votre sélection en spécifiant soit un ou des minerais à cibler spécifiquement ou des items précis à chaque niveau (optionnel)",
|
||||
"4. Définissez les critères d'analyse en sélectionnant les indices de vulnérabilité pertinents",
|
||||
"5. Choisissez le mode de combinaison des indices (ET/OU) selon votre besoin d'analyse",
|
||||
"6. Explorez le graphique généré en utilisant les contrôles de zoom et de déplacement ; vous pouvez basculer en mode plein écran pour le graphe"
|
||||
])))
|
||||
st.markdown(f"# {str(_('pages.analyse.title'))}")
|
||||
with st.expander(str(_("pages.analyse.help")), expanded=False):
|
||||
st.markdown("\n".join(_("pages.analyse.help_content")))
|
||||
st.markdown("---")
|
||||
|
||||
try:
|
||||
@ -155,7 +148,7 @@ def interface_analyse(G_temp):
|
||||
|
||||
# Lancement de l'analyse
|
||||
st.markdown("---")
|
||||
if st.button(str(_("pages.analyse.run_analysis", "Lancer l'analyse")), type="primary", key="analyse_lancer"):
|
||||
if st.button(str(_("pages.analyse.run_analysis")), type="primary", key="analyse_lancer"):
|
||||
afficher_sankey(
|
||||
G_temp,
|
||||
niveau_depart=niveau_depart,
|
||||
@ -172,4 +165,4 @@ def interface_analyse(G_temp):
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.graph_preview_error', 'Erreur de prévisualisation du graphe :'))} {e}")
|
||||
st.error(f"{str(_('errors.graph_preview_error'))} {e}")
|
||||
|
||||
@ -187,16 +187,22 @@ def couleur_criticite(p):
|
||||
|
||||
def edge_info(G, u, v):
|
||||
"""Génère l'info-bulle pour un lien"""
|
||||
# Liste d'attributs à exclure des infobulles des liens
|
||||
attributs_exclus = ["poids", "color", "fontcolor"]
|
||||
|
||||
data = G.get_edge_data(u, v)
|
||||
if not data:
|
||||
return f"{str(_('pages.analyse.sankey.relation', 'Relation'))} : {u} → {v}"
|
||||
return f"{str(_('pages.analyse.sankey.relation'))} : {u} → {v}"
|
||||
if isinstance(data, dict) and all(isinstance(k, int) for k in data):
|
||||
data = data[0]
|
||||
base = [f"{k}: {v}" for k, v in data.items()]
|
||||
return f"{str(_('pages.analyse.sankey.relation', 'Relation'))} : {u} → {v}<br>" + "<br>".join(base)
|
||||
base = [f"{k}: {v}" for k, v in data.items() if k not in attributs_exclus]
|
||||
return f"{str(_('pages.analyse.sankey.relation'))} : {u} → {v}<br>" + "<br>".join(base)
|
||||
|
||||
def preparer_donnees_sankey(G, liens_chemins, niveaux, chemins):
|
||||
"""Prépare les données pour le graphique Sankey"""
|
||||
# Liste d'attributs à exclure des infobulles des nœuds
|
||||
node_attributs_exclus = ["fillcolor", "niveau"]
|
||||
|
||||
df_liens = pd.DataFrame(list(liens_chemins), columns=["source", "target"])
|
||||
df_liens = df_liens.groupby(["source", "target"]).size().reset_index(name="value")
|
||||
|
||||
@ -215,7 +221,7 @@ def preparer_donnees_sankey(G, liens_chemins, niveaux, chemins):
|
||||
noeuds_utilises.add(n)
|
||||
|
||||
df_liens["color"] = df_liens.apply(
|
||||
lambda row: couleur_criticite(row["criticite"]) if row["criticite"] > 0 else "gray",
|
||||
lambda row: couleur_criticite(row["criticite"]) if row["criticite"] > 0 else "white",
|
||||
axis=1
|
||||
)
|
||||
|
||||
@ -226,7 +232,7 @@ def preparer_donnees_sankey(G, liens_chemins, niveaux, chemins):
|
||||
|
||||
customdata = []
|
||||
for n in sorted_nodes:
|
||||
info = [f"{k}: {v}" for k, v in G.nodes[n].items()]
|
||||
info = [f"{k}: {v}" for k, v in G.nodes[n].items() if k not in node_attributs_exclus]
|
||||
niveau = niveaux.get(n, 99)
|
||||
|
||||
# Ajout d'un ISG hérité si applicable
|
||||
@ -268,12 +274,17 @@ def creer_graphique_sankey(G, niveaux, df_liens, sorted_nodes, customdata, link_
|
||||
value=values,
|
||||
color=df_liens["color"].tolist(),
|
||||
customdata=link_customdata,
|
||||
hovertemplate="%{customdata}<extra></extra>"
|
||||
hovertemplate="%{customdata}<extra></extra>",
|
||||
line=dict(
|
||||
width=1, # Set fixed width to 3 pixels (or use 2 if preferred)
|
||||
color="grey"
|
||||
),
|
||||
arrowlen=10
|
||||
)
|
||||
))
|
||||
|
||||
fig.update_layout(
|
||||
title_text=str(_("pages.analyse.sankey.filtered_hierarchy", "Hiérarchie filtrée par niveaux et noeuds")),
|
||||
title_text=str(_("pages.analyse.sankey.filtered_hierarchy")),
|
||||
paper_bgcolor="white",
|
||||
plot_bgcolor="white"
|
||||
)
|
||||
@ -303,7 +314,7 @@ def exporter_graphe_filtre(G, liens_chemins):
|
||||
|
||||
with open(dot_path, encoding="utf-8") as f:
|
||||
st.download_button(
|
||||
label=str(_("pages.analyse.sankey.download_dot", "Télécharger le fichier DOT filtré")),
|
||||
label=str(_("pages.analyse.sankey.download_dot")),
|
||||
data=f.read(),
|
||||
file_name="graphe_filtré.dot",
|
||||
mime="text/plain"
|
||||
@ -325,7 +336,7 @@ def afficher_sankey(
|
||||
chemins = extraire_chemins_selon_criteres(G, niveaux, niveau_depart, noeuds_depart, noeuds_arrivee, minerais)
|
||||
|
||||
if not chemins:
|
||||
st.warning(str(_("pages.analyse.sankey.no_paths", "Aucun chemin trouvé pour les critères spécifiés.")))
|
||||
st.warning(str(_("pages.analyse.sankey.no_paths")))
|
||||
return
|
||||
|
||||
# Étape 3 : Filtrage des chemins selon les critères de vulnérabilité
|
||||
@ -335,12 +346,13 @@ def afficher_sankey(
|
||||
)
|
||||
|
||||
if not liens_chemins:
|
||||
st.warning(str(_("pages.analyse.sankey.no_matching_paths", "Aucun chemin ne correspond aux critères.")))
|
||||
st.warning(str(_("pages.analyse.sankey.no_matching_paths")))
|
||||
return
|
||||
|
||||
# Étape 4 : Préparation des données pour le graphique Sankey
|
||||
df_liens, sorted_nodes, customdata, link_customdata, node_indices = preparer_donnees_sankey(
|
||||
G, liens_chemins, niveaux, chemins_filtres if any([filtrer_ics, filtrer_ivc, filtrer_ihh, filtrer_isg]) else chemins
|
||||
G, liens_chemins, niveaux,
|
||||
chemins_filtres if any([filtrer_ics, filtrer_ivc, filtrer_ihh, filtrer_isg]) else chemins
|
||||
)
|
||||
|
||||
# Étape 5 : Création et affichage du graphique Sankey
|
||||
|
||||
@ -18,23 +18,9 @@ from .utils.fiche_utils import load_seuils, doit_regenerer_fiche
|
||||
from .generer import generer_fiche
|
||||
|
||||
def interface_fiches():
|
||||
st.markdown(f"# {str(_('pages.fiches.title', 'Découverte des fiches'))}")
|
||||
with st.expander(str(_("pages.fiches.help", "Comment utiliser cet onglet ?")), expanded=False):
|
||||
st.markdown("\n".join([
|
||||
" " + line for line in _("pages.fiches.help_content", [
|
||||
"1. Parcourez la liste des fiches disponibles par catégorie",
|
||||
"2. Sélectionnez une fiche pour afficher son contenu complet",
|
||||
"3. Consultez les données détaillées, graphiques et analyses supplémentaires",
|
||||
"4. Utilisez ces informations pour approfondir votre compréhension des vulnérabilités identifiées",
|
||||
"",
|
||||
"Les catégories sont les suivantes :",
|
||||
"* Assemblage : opération d'assemblage des produits finaux à partir des composants",
|
||||
"* Connexe : opérations diverses nécessaires pour fabriquer le numérique, mais n'entrant pas directement dans sa composition",
|
||||
"* Criticités : indices utilisés pour identifier et évaluer les vulnérabilités",
|
||||
"* Fabrication : opération de fabrication des composants à partir de minerais",
|
||||
"* Minerai : description et opérations d'extraction et de traitement des minerais"
|
||||
])
|
||||
]))
|
||||
st.markdown(f"# {str(_('pages.fiches.title'))}")
|
||||
with st.expander(str(_("pages.fiches.help")), expanded=False):
|
||||
st.markdown("\n".join(_("pages.fiches.help_content")))
|
||||
st.markdown("---")
|
||||
|
||||
if "fiches_arbo" not in st.session_state:
|
||||
@ -42,24 +28,24 @@ def interface_fiches():
|
||||
|
||||
arbo = st.session_state.get("fiches_arbo", {})
|
||||
if not arbo:
|
||||
st.warning(str(_("pages.fiches.no_files", "Aucune fiche disponible pour le moment.")))
|
||||
st.warning(str(_("pages.fiches.no_files")))
|
||||
return
|
||||
|
||||
dossiers = sorted(arbo.keys(), key=lambda x: x.lower())
|
||||
dossier_choisi = st.selectbox(
|
||||
str(_("pages.fiches.choose_category", "Choisissez une catégorie de fiches")),
|
||||
[str(_("pages.fiches.select_folder", "-- Sélectionner un dossier --"))] + dossiers
|
||||
str(_("pages.fiches.choose_category",)),
|
||||
[str(_("pages.fiches.select_folder"))] + dossiers
|
||||
)
|
||||
|
||||
if dossier_choisi and dossier_choisi != str(_("pages.fiches.select_folder", "-- Sélectionner un dossier --")):
|
||||
if dossier_choisi and dossier_choisi != str(_("pages.fiches.select_folder")):
|
||||
fiches = arbo.get(dossier_choisi, [])
|
||||
noms_fiches = [f['nom'] for f in fiches]
|
||||
fiche_choisie = st.selectbox(
|
||||
str(_("pages.fiches.choose_file", "Choisissez une fiche")),
|
||||
[str(_("pages.fiches.select_file", "-- Sélectionner une fiche --"))] + noms_fiches
|
||||
str(_("pages.fiches.choose_file")),
|
||||
[str(_("pages.fiches.select_file"))] + noms_fiches
|
||||
)
|
||||
|
||||
if fiche_choisie and fiche_choisie != str(_("pages.fiches.select_file", "-- Sélectionner une fiche --")):
|
||||
if fiche_choisie and fiche_choisie != str(_("pages.fiches.select_file")):
|
||||
fiche_info = next((f for f in fiches if f["nom"] == fiche_choisie), None)
|
||||
if fiche_info:
|
||||
try:
|
||||
@ -101,17 +87,17 @@ def interface_fiches():
|
||||
if os.path.exists(pdf_path):
|
||||
with open(pdf_path, "rb") as pdf_file:
|
||||
st.download_button(
|
||||
label=str(_("pages.fiches.download_pdf", "Télécharger cette fiche en PDF")),
|
||||
label=str(_("pages.fiches.download_pdf")),
|
||||
data=pdf_file,
|
||||
file_name=pdf_name,
|
||||
mime="application/pdf",
|
||||
help=str(_("pages.fiches.download_pdf", "Télécharger cette fiche en PDF")),
|
||||
help=str(_("pages.fiches.download_pdf")),
|
||||
key="telecharger_fiche_pdf"
|
||||
)
|
||||
else:
|
||||
st.warning(str(_("pages.fiches.pdf_unavailable", "Le fichier PDF de cette fiche n'est pas disponible.")))
|
||||
st.warning(str(_("pages.fiches.pdf_unavailable")))
|
||||
|
||||
st.markdown(f"## {str(_('pages.fiches.ticket_management', 'Gestion des tickets pour cette fiche'))}")
|
||||
st.markdown(f"## {str(_('pages.fiches.ticket_management'))}")
|
||||
afficher_tickets_par_fiche(rechercher_tickets_gitea(fiche_choisie))
|
||||
formulaire_creation_ticket_dynamique(fiche_choisie)
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ def gitea_request(method, url, **kwargs):
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except requests.RequestException as e:
|
||||
st.error(f"{str(_('errors.gitea_error', 'Erreur Gitea'))} ({method.upper()}): {e}")
|
||||
st.error(f"{str(_('errors.gitea_error'))} ({method.upper()}): {e}")
|
||||
return None
|
||||
|
||||
|
||||
@ -39,9 +39,9 @@ def charger_fiches_et_labels():
|
||||
"item": item.strip()
|
||||
}
|
||||
except FileNotFoundError:
|
||||
st.error(f"❌ {str(_('errors.file_not_found', 'Le fichier'))} {chemin_csv} {str(_('errors.is_missing', 'est introuvable.'))}")
|
||||
st.error(f"❌ {str(_('errors.file_not_found'))} {chemin_csv} {str(_('errors.is_missing'))}")
|
||||
except Exception as e:
|
||||
st.error(f"❌ {str(_('errors.file_loading', 'Erreur lors du chargement des fiches :'))} {str(e)}")
|
||||
st.error(f"❌ {str(_('errors.file_loading'))} {str(e)}")
|
||||
|
||||
return dictionnaire_fiches
|
||||
|
||||
@ -57,7 +57,7 @@ def rechercher_tickets_gitea(fiche_selectionnee):
|
||||
try:
|
||||
issues = reponse.json()
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.json_decode', 'Erreur de décodage JSON :'))} {e}")
|
||||
st.error(f"{str(_('errors.json_decode'))} {e}")
|
||||
return []
|
||||
|
||||
correspondances = charger_fiches_et_labels()
|
||||
@ -87,7 +87,7 @@ def get_labels_existants():
|
||||
try:
|
||||
return {label['name']: label['id'] for label in reponse.json()}
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.label_parsing', 'Erreur de parsing des labels :'))} {e}")
|
||||
st.error(f"{str(_('errors.label_parsing'))} {e}")
|
||||
return {}
|
||||
|
||||
|
||||
@ -114,6 +114,6 @@ def creer_ticket_gitea(titre, corps, labels):
|
||||
|
||||
issue_url = reponse.json().get("html_url", "")
|
||||
if issue_url:
|
||||
st.success(f"{str(_('pages.fiches.tickets.created_success', 'Ticket créé !'))} [Voir le ticket]({issue_url})")
|
||||
st.success(f"{str(_('pages.fiches.tickets.created_success'))} [Voir le ticket]({issue_url})")
|
||||
else:
|
||||
st.success(str(_('pages.fiches.tickets.created', 'Ticket créé avec succès.')))
|
||||
st.success(str(_('pages.fiches.tickets.created')))
|
||||
|
||||
@ -14,7 +14,7 @@ def parser_modele_ticket(contenu_modele):
|
||||
sections = {}
|
||||
lignes = contenu_modele.splitlines()
|
||||
titre_courant, contenu = None, []
|
||||
|
||||
|
||||
for ligne in lignes:
|
||||
if ligne.startswith("## "):
|
||||
if titre_courant:
|
||||
@ -22,10 +22,10 @@ def parser_modele_ticket(contenu_modele):
|
||||
titre_courant, contenu = ligne[3:].strip(), []
|
||||
elif titre_courant:
|
||||
contenu.append(ligne)
|
||||
|
||||
|
||||
if titre_courant:
|
||||
sections[titre_courant] = "\n".join(contenu).strip()
|
||||
|
||||
|
||||
return sections
|
||||
|
||||
|
||||
@ -34,47 +34,47 @@ def generer_labels(fiche_selectionnee):
|
||||
labels, selected_ops = [], []
|
||||
correspondances = charger_fiches_et_labels()
|
||||
cible = correspondances.get(fiche_selectionnee)
|
||||
|
||||
|
||||
if cible:
|
||||
if len(cible["operations"]) == 1:
|
||||
labels.append(cible["operations"][0])
|
||||
elif len(cible["operations"]) > 1:
|
||||
selected_ops = st.multiselect(str(_("pages.fiches.tickets.contribution_type", "Labels opération à associer")),
|
||||
cible["operations"],
|
||||
selected_ops = st.multiselect(str(_("pages.fiches.tickets.contribution_type")),
|
||||
cible["operations"],
|
||||
default=cible["operations"])
|
||||
|
||||
|
||||
return labels, selected_ops, cible
|
||||
|
||||
|
||||
def creer_champs_formulaire(sections, fiche_selectionnee):
|
||||
"""Crée les champs du formulaire basés sur les sections."""
|
||||
reponses = {}
|
||||
|
||||
|
||||
for section, aide in sections.items():
|
||||
if "Type de contribution" in section:
|
||||
options = sorted(set(re.findall(r"- \[.\] (.+)", aide)))
|
||||
if str(_("pages.fiches.tickets.other", "Autre")) not in options:
|
||||
options.append(str(_("pages.fiches.tickets.other", "Autre")))
|
||||
choix = st.radio(str(_("pages.fiches.tickets.contribution_type", "Type de contribution")), options)
|
||||
reponses[section] = st.text_input(str(_("pages.fiches.tickets.specify", "Précisez")), "") if choix == str(_("pages.fiches.tickets.other", "Autre")) else choix
|
||||
if str(_("pages.fiches.tickets.other")) not in options:
|
||||
options.append(str(_("pages.fiches.tickets.other")))
|
||||
choix = st.radio(str(_("pages.fiches.tickets.contribution_type")), options)
|
||||
reponses[section] = st.text_input(str(_("pages.fiches.tickets.specify")), "") if choix == str(_("pages.fiches.tickets.other")) else choix
|
||||
elif "Fiche concernée" in section:
|
||||
url_fiche = f"https://fabnum-git.peccini.fr/FabNum/Fiches/src/branch/{ENV}/Documents/{fiche_selectionnee.replace(' ', '%20')}"
|
||||
reponses[section] = url_fiche
|
||||
st.text_input(str(_("pages.fiches.tickets.concerned_card", "Fiche concernée")), value=url_fiche, disabled=True)
|
||||
st.text_input(str(_("pages.fiches.tickets.concerned_card")), value=url_fiche, disabled=True)
|
||||
elif "Sujet de la proposition" in section:
|
||||
reponses[section] = st.text_input(section, help=aide)
|
||||
else:
|
||||
reponses[section] = st.text_area(section, help=aide)
|
||||
|
||||
|
||||
return reponses
|
||||
|
||||
|
||||
def afficher_controles_formulaire():
|
||||
"""Affiche les boutons de contrôle du formulaire."""
|
||||
col1, col2 = st.columns(2)
|
||||
if col1.button(str(_("pages.fiches.tickets.preview", "Prévisualiser le ticket"))):
|
||||
if col1.button(str(_("pages.fiches.tickets.preview"))):
|
||||
st.session_state.previsualiser = True
|
||||
if col2.button(str(_("pages.fiches.tickets.cancel", "Annuler"))):
|
||||
if col2.button(str(_("pages.fiches.tickets.cancel"))):
|
||||
st.session_state.previsualiser = False
|
||||
st.rerun()
|
||||
|
||||
@ -83,8 +83,8 @@ def gerer_previsualisation_et_soumission(reponses, labels, selected_ops, cible):
|
||||
"""Gère la prévisualisation et la soumission du ticket."""
|
||||
if not st.session_state.get("previsualiser", False):
|
||||
return
|
||||
|
||||
st.subheader(str(_("pages.fiches.tickets.preview_title", "Prévisualisation du ticket")))
|
||||
|
||||
st.subheader(str(_("pages.fiches.tickets.preview_title")))
|
||||
for section, texte in reponses.items():
|
||||
st.markdown(f"#### {section}")
|
||||
st.code(texte, language="markdown")
|
||||
@ -92,9 +92,9 @@ def gerer_previsualisation_et_soumission(reponses, labels, selected_ops, cible):
|
||||
titre_ticket = reponses.get("Sujet de la proposition", "").strip() or "Ticket FabNum"
|
||||
final_labels = nettoyer_labels(labels + selected_ops + ([cible["item"]] if cible else []))
|
||||
|
||||
st.markdown(f"**{str(_('pages.fiches.tickets.summary', 'Résumé'))} :**\n- **{str(_('pages.fiches.tickets.title', 'Titre'))}** : `{titre_ticket}`\n- **{str(_('pages.fiches.tickets.labels', 'Labels'))}** : `{', '.join(final_labels)}`")
|
||||
st.markdown(f"**{str(_('pages.fiches.tickets.summary'))} :**\n- **{str(_('pages.fiches.tickets.title'))}** : `{titre_ticket}`\n- **{str(_('pages.fiches.tickets.labels'))}** : `{', '.join(final_labels)}`")
|
||||
|
||||
if st.button(str(_("pages.fiches.tickets.confirm", "Confirmer la création du ticket"))):
|
||||
if st.button(str(_("pages.fiches.tickets.confirm"))):
|
||||
labels_existants = get_labels_existants()
|
||||
labels_ids = [labels_existants[l] for l in final_labels if l in labels_existants]
|
||||
if "Backlog" in labels_existants:
|
||||
@ -104,23 +104,23 @@ def gerer_previsualisation_et_soumission(reponses, labels, selected_ops, cible):
|
||||
creer_ticket_gitea(titre_ticket, corps, labels_ids)
|
||||
|
||||
st.session_state.previsualiser = False
|
||||
st.success(str(_("pages.fiches.tickets.created", "Ticket créé et formulaire vidé.")))
|
||||
st.success(str(_("pages.fiches.tickets.created")))
|
||||
|
||||
|
||||
def formulaire_creation_ticket_dynamique(fiche_selectionnee):
|
||||
"""Fonction principale pour le formulaire de création de ticket."""
|
||||
with st.expander(str(_("pages.fiches.tickets.create_new", "Créer un nouveau ticket lié à cette fiche")), expanded=False):
|
||||
with st.expander(str(_("pages.fiches.tickets.create_new")), expanded=False):
|
||||
# Chargement et vérification du modèle
|
||||
contenu_modele = charger_modele_ticket()
|
||||
if not contenu_modele:
|
||||
st.error(str(_("pages.fiches.tickets.model_load_error", "Impossible de charger le modèle de ticket.")))
|
||||
st.error(str(_("pages.fiches.tickets.model_load_error")))
|
||||
return
|
||||
|
||||
|
||||
# Traitement du modèle et génération du formulaire
|
||||
sections = parser_modele_ticket(contenu_modele)
|
||||
labels, selected_ops, cible = generer_labels(fiche_selectionnee)
|
||||
reponses = creer_champs_formulaire(sections, fiche_selectionnee)
|
||||
|
||||
|
||||
# Gestion des contrôles et de la prévisualisation
|
||||
afficher_controles_formulaire()
|
||||
gerer_previsualisation_et_soumission(reponses, labels, selected_ops, cible)
|
||||
@ -136,5 +136,5 @@ def charger_modele_ticket():
|
||||
r.raise_for_status()
|
||||
return base64.b64decode(r.json().get("content", "")).decode("utf-8")
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('pages.fiches.tickets.model_error', 'Erreur chargement modèle :'))} {e}")
|
||||
st.error(f"{str(_('pages.fiches.tickets.model_error'))} {e}")
|
||||
return ""
|
||||
|
||||
@ -11,22 +11,22 @@ from .core import rechercher_tickets_gitea
|
||||
|
||||
def extraire_statut_par_label(ticket):
|
||||
labels = [label.get('name', '') for label in ticket.get('labels', [])]
|
||||
for statut in ["Backlog",
|
||||
str(_("pages.fiches.tickets.status.awaiting", "En attente de traitement")),
|
||||
str(_("pages.fiches.tickets.status.in_progress", "En cours")),
|
||||
str(_("pages.fiches.tickets.status.completed", "Terminés")),
|
||||
str(_("pages.fiches.tickets.status.rejected", "Non retenus"))]:
|
||||
for statut in ["Backlog",
|
||||
str(_("pages.fiches.tickets.status.awaiting")),
|
||||
str(_("pages.fiches.tickets.status.in_progress")),
|
||||
str(_("pages.fiches.tickets.status.completed")),
|
||||
str(_("pages.fiches.tickets.status.rejected"))]:
|
||||
if statut in labels:
|
||||
return statut
|
||||
return str(_("pages.fiches.tickets.status.others", "Autres"))
|
||||
return str(_("pages.fiches.tickets.status.others"))
|
||||
|
||||
|
||||
def afficher_tickets_par_fiche(tickets):
|
||||
if not tickets:
|
||||
st.info(str(_("pages.fiches.tickets.no_linked_tickets", "Aucun ticket lié à cette fiche.")))
|
||||
st.info(str(_("pages.fiches.tickets.no_linked_tickets")))
|
||||
return
|
||||
|
||||
st.markdown(str(_("pages.fiches.tickets.associated_tickets", "**Tickets associés à cette fiche**")))
|
||||
st.markdown(str(_("pages.fiches.tickets.associated_tickets")))
|
||||
tickets_groupes = defaultdict(list)
|
||||
for ticket in tickets:
|
||||
statut = extraire_statut_par_label(ticket)
|
||||
@ -34,14 +34,14 @@ def afficher_tickets_par_fiche(tickets):
|
||||
|
||||
nb_backlogs = len(tickets_groupes["Backlog"])
|
||||
if nb_backlogs:
|
||||
st.info(f"⤇ {nb_backlogs} {str(_('pages.fiches.tickets.moderation_notice', 'ticket(s) en attente de modération ne sont pas affichés.'))}")
|
||||
st.info(f"⤇ {nb_backlogs} {str(_('pages.fiches.tickets.moderation_notice'))}")
|
||||
|
||||
ordre_statuts = [
|
||||
str(_("pages.fiches.tickets.status.awaiting", "En attente de traitement")),
|
||||
str(_("pages.fiches.tickets.status.in_progress", "En cours")),
|
||||
str(_("pages.fiches.tickets.status.completed", "Terminés")),
|
||||
str(_("pages.fiches.tickets.status.rejected", "Non retenus")),
|
||||
str(_("pages.fiches.tickets.status.others", "Autres"))
|
||||
str(_("pages.fiches.tickets.status.awaiting")),
|
||||
str(_("pages.fiches.tickets.status.in_progress")),
|
||||
str(_("pages.fiches.tickets.status.completed")),
|
||||
str(_("pages.fiches.tickets.status.rejected")),
|
||||
str(_("pages.fiches.tickets.status.others"))
|
||||
]
|
||||
for statut in ordre_statuts:
|
||||
if tickets_groupes[statut]:
|
||||
@ -61,14 +61,14 @@ def recuperer_commentaires_ticket(issue_index):
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('pages.fiches.tickets.comment_error', 'Erreur lors de la récupération des commentaires :'))} {e}")
|
||||
st.error(f"{str(_('pages.fiches.tickets.comment_error'))} {e}")
|
||||
return []
|
||||
|
||||
|
||||
def afficher_carte_ticket(ticket):
|
||||
titre = ticket.get("title", str(_("pages.fiches.tickets.no_title", "Sans titre")))
|
||||
titre = ticket.get("title", str(_("pages.fiches.tickets.no_title")))
|
||||
url = ticket.get("html_url", "")
|
||||
user = ticket.get("user", {}).get("login", str(_("pages.fiches.tickets.unknown", "inconnu")))
|
||||
user = ticket.get("user", {}).get("login", str(_("pages.fiches.tickets.unknown")))
|
||||
created = ticket.get("created_at", "")
|
||||
updated = ticket.get("updated_at", "")
|
||||
body = ticket.get("body", "")
|
||||
@ -86,12 +86,12 @@ def afficher_carte_ticket(ticket):
|
||||
return "?"
|
||||
|
||||
date_created_str = format_date(created)
|
||||
maj_info = f"({str(_('pages.fiches.tickets.updated', 'MAJ'))} {format_date(updated)})" if updated and updated != created else ""
|
||||
maj_info = f"({str(_('pages.fiches.tickets.updated'))} {format_date(updated)})" if updated and updated != created else ""
|
||||
|
||||
commentaires = recuperer_commentaires_ticket(ticket.get("number"))
|
||||
commentaires_html = ""
|
||||
for commentaire in commentaires:
|
||||
auteur = html.escape(commentaire.get('user', {}).get('login', str(_("pages.fiches.tickets.unknown", "inconnu"))))
|
||||
auteur = html.escape(commentaire.get('user', {}).get('login', str(_("pages.fiches.tickets.unknown"))))
|
||||
contenu = html.escape(commentaire.get('body', ''))
|
||||
date = format_date(commentaire.get('created_at', ''))
|
||||
commentaires_html += f"""
|
||||
@ -105,12 +105,12 @@ def afficher_carte_ticket(ticket):
|
||||
st.markdown(f"""
|
||||
<div class=\"conteneur_ticket\">
|
||||
<h4><a href='{url}' target='_blank'>{titre}</a></h4>
|
||||
<p>{str(_("pages.fiches.tickets.opened_by", "Ouvert par"))} <strong>{html.escape(user)}</strong> {str(_("pages.fiches.tickets.on_date", "le"))} {date_created_str} {maj_info}</p>
|
||||
<p>{str(_("pages.fiches.tickets.subject_label", "Sujet"))} : <strong>{html.escape(sujet)}</strong></p>
|
||||
<p>Labels : {' • '.join(labels) if labels else str(_("pages.fiches.tickets.no_labels", "aucun"))}</p>
|
||||
<p>{str(_("pages.fiches.tickets.opened_by"))} <strong>{html.escape(user)}</strong> {str(_("pages.fiches.tickets.on_date"))} {date_created_str} {maj_info}</p>
|
||||
<p>{str(_("pages.fiches.tickets.subject_label"))} : <strong>{html.escape(sujet)}</strong></p>
|
||||
<p>Labels : {' • '.join(labels) if labels else str(_("pages.fiches.tickets.no_labels"))}</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
st.markdown(body, unsafe_allow_html=False)
|
||||
st.markdown("---")
|
||||
st.markdown(str(_("pages.fiches.tickets.comments", "**Commentaire(s) :**")))
|
||||
st.markdown(commentaires_html or str(_("pages.fiches.tickets.no_comments", "Aucun commentaire.")), unsafe_allow_html=True)
|
||||
st.markdown(str(_("pages.fiches.tickets.comments")))
|
||||
st.markdown(commentaires_html or str(_("pages.fiches.tickets.no_comments")), unsafe_allow_html=True)
|
||||
|
||||
@ -2,22 +2,22 @@ import streamlit as st
|
||||
from utils.translations import _
|
||||
|
||||
def ajouter_produit(G):
|
||||
st.markdown(f"## {str(_('pages.personnalisation.add_new_product', 'Ajouter un nouveau produit final'))}")
|
||||
new_prod = st.text_input(str(_("pages.personnalisation.new_product_name", "Nom du nouveau produit (unique)")), key="new_prod")
|
||||
st.markdown(f"## {str(_('pages.personnalisation.add_new_product'))}")
|
||||
new_prod = st.text_input(str(_("pages.personnalisation.new_product_name")), key="new_prod")
|
||||
if new_prod:
|
||||
ops_dispo = sorted([
|
||||
n for n, d in G.nodes(data=True)
|
||||
if d.get("niveau") == "10"
|
||||
and any(G.has_edge(p, n) and G.nodes[p].get("niveau") == "0" for p in G.predecessors(n))
|
||||
])
|
||||
sel_new_op = st.selectbox(str(_("pages.personnalisation.assembly_operation", "Opération d'assemblage (optionnelle)")), [str(_("pages.personnalisation.none", "-- Aucune --"))] + ops_dispo, index=0)
|
||||
sel_new_op = st.selectbox(str(_("pages.personnalisation.assembly_operation")), [str(_("pages.personnalisation.none"))] + ops_dispo, index=0)
|
||||
niveau1 = sorted([n for n, d in G.nodes(data=True) if d.get("niveau") == "1"])
|
||||
sel_comps = st.multiselect(str(_("pages.personnalisation.components_to_link", "Composants à lier")), options=niveau1)
|
||||
if st.button(str(_("pages.personnalisation.create_product", "Créer le produit"))):
|
||||
sel_comps = st.multiselect(str(_("pages.personnalisation.components_to_link")), options=niveau1)
|
||||
if st.button(str(_("pages.personnalisation.create_product"))):
|
||||
G.add_node(new_prod, niveau="0", personnalisation="oui", label=new_prod)
|
||||
if sel_new_op != str(_("pages.personnalisation.none", "-- Aucune --")):
|
||||
if sel_new_op != str(_("pages.personnalisation.none")):
|
||||
G.add_edge(new_prod, sel_new_op)
|
||||
for comp in sel_comps:
|
||||
G.add_edge(new_prod, comp)
|
||||
st.success(f"{new_prod} {str(_('pages.personnalisation.added', 'ajouté'))}")
|
||||
st.success(f"{new_prod} {str(_('pages.personnalisation.added'))}")
|
||||
return G
|
||||
|
||||
@ -1,24 +1,25 @@
|
||||
import streamlit as st
|
||||
import json
|
||||
from utils.translations import get_translation as _
|
||||
|
||||
def importer_exporter_graph(G):
|
||||
st.markdown("## Sauvegarder ou restaurer la configuration")
|
||||
if st.button("Exporter configuration"):
|
||||
st.markdown(f"## {_('pages.personnalisation.save_restore_config')}")
|
||||
if st.button(str(_("pages.personnalisation.export_config"))):
|
||||
nodes = [n for n, d in G.nodes(data=True) if d.get("personnalisation") == "oui"]
|
||||
edges = [(u, v) for u, v in G.edges() if u in nodes]
|
||||
conf = {"nodes": nodes, "edges": edges}
|
||||
json_str = json.dumps(conf, ensure_ascii=False)
|
||||
st.download_button(
|
||||
label="Télécharger (JSON)",
|
||||
label=str(_("pages.personnalisation.download_json")),
|
||||
data=json_str,
|
||||
file_name="config_personnalisation.json",
|
||||
mime="application/json"
|
||||
)
|
||||
|
||||
uploaded = st.file_uploader("Importer une configuration JSON (max 100 Ko)", type=["json"])
|
||||
uploaded = st.file_uploader(str(_("pages.personnalisation.import_config")), type=["json"])
|
||||
if uploaded:
|
||||
if uploaded.size > 100 * 1024:
|
||||
st.error("Fichier trop volumineux (max 100 Ko).")
|
||||
st.error(_("pages.personnalisation.file_too_large"))
|
||||
else:
|
||||
try:
|
||||
conf = json.loads(uploaded.read().decode("utf-8"))
|
||||
@ -26,17 +27,17 @@ def importer_exporter_graph(G):
|
||||
all_edges = conf.get("edges", [])
|
||||
|
||||
if not all_nodes:
|
||||
st.warning("Aucun produit trouvé dans le fichier.")
|
||||
st.warning(_("pages.personnalisation.no_products_found"))
|
||||
else:
|
||||
st.markdown("### Sélection des produits à restaurer")
|
||||
st.markdown(f"### {_('pages.personnalisation.select_products_to_restore')}")
|
||||
sel_nodes = st.multiselect(
|
||||
"Produits à restaurer",
|
||||
str(_("pages.personnalisation.products_to_restore")),
|
||||
options=all_nodes,
|
||||
default=all_nodes,
|
||||
key="restaurer_selection"
|
||||
)
|
||||
|
||||
if st.button("Restaurer les éléments sélectionnés", type="primary"):
|
||||
if st.button(str(_("pages.personnalisation.restore_selected")), type="primary"):
|
||||
for node in sel_nodes:
|
||||
if not G.has_node(node):
|
||||
G.add_node(node, niveau="0", personnalisation="oui", label=node)
|
||||
@ -45,8 +46,8 @@ def importer_exporter_graph(G):
|
||||
if u in sel_nodes and v in sel_nodes + list(G.nodes()) and not G.has_edge(u, v):
|
||||
G.add_edge(u, v)
|
||||
|
||||
st.success("Configuration partielle restaurée avec succès.")
|
||||
st.success(_("pages.personnalisation.config_restored"))
|
||||
except Exception as e:
|
||||
st.error(f"Erreur d'import : {e}")
|
||||
st.error(f"{_('errors.import_error')} {e}")
|
||||
|
||||
return G
|
||||
|
||||
@ -7,18 +7,9 @@ from .modification import modifier_produit
|
||||
from .import_export import importer_exporter_graph
|
||||
|
||||
def interface_personnalisation(G):
|
||||
st.markdown(f"# {str(_('pages.personnalisation.title', 'Personnalisation des produits finaux'))}")
|
||||
with st.expander(str(_("pages.personnalisation.help", "Comment utiliser cet onglet ?")), expanded=False):
|
||||
st.markdown("\n".join([
|
||||
" " + line for line in _("pages.personnalisation.help_content", [
|
||||
"1. Cliquez sur « Ajouter un produit final » pour créer un nouveau produit",
|
||||
"2. Donnez un nom à votre produit",
|
||||
"3. Sélectionnez une opération d'assemblage appropriée (si pertinent)",
|
||||
"4. Choisissez les composants qui constituent votre produit dans la liste proposée",
|
||||
"5. Sauvegardez votre configuration pour une réutilisation future",
|
||||
"6. Vous pourrez par la suite modifier ou supprimer vos produits personnalisés"
|
||||
])
|
||||
]))
|
||||
st.markdown(f"# {str(_('pages.personnalisation.title'))}")
|
||||
with st.expander(str(_("pages.personnalisation.help")), expanded=False):
|
||||
st.markdown("\n".join(_("pages.personnalisation.help_content")))
|
||||
st.markdown("---")
|
||||
|
||||
G = ajouter_produit(G)
|
||||
|
||||
@ -8,7 +8,7 @@ def get_produits_personnalises(G):
|
||||
def supprimer_produit(G, prod):
|
||||
"""Supprime un produit du graphe."""
|
||||
G.remove_node(prod)
|
||||
st.success(f"{prod} {str(_('pages.personnalisation.deleted', 'supprimé'))}")
|
||||
st.success(f"{prod} {str(_('pages.personnalisation.deleted'))}")
|
||||
st.session_state.pop("prod_sel", None)
|
||||
return G
|
||||
|
||||
@ -51,37 +51,37 @@ def mettre_a_jour_composants(G, prod, linked, nouveaux):
|
||||
return G
|
||||
|
||||
def modifier_produit(G):
|
||||
st.markdown(f"## {str(_('pages.personnalisation.modify_product', 'Modifier un produit final ajouté'))}")
|
||||
|
||||
st.markdown(f"## {str(_('pages.personnalisation.modify_product'))}")
|
||||
|
||||
# Sélection du produit à modifier
|
||||
produits0 = get_produits_personnalises(G)
|
||||
sel_display = st.multiselect(str(_("pages.personnalisation.products_to_modify", "Produits à modifier")), options=produits0)
|
||||
sel_display = st.multiselect(str(_("pages.personnalisation.products_to_modify")), options=produits0)
|
||||
|
||||
if not sel_display:
|
||||
return G
|
||||
|
||||
# Obtention du produit sélectionné
|
||||
prod = sel_display[0]
|
||||
|
||||
|
||||
# Suppression du produit si demandé
|
||||
if st.button(f"{str(_('pages.personnalisation.delete', 'Supprimer'))} {prod}"):
|
||||
if st.button(f"{str(_('pages.personnalisation.delete'))} {prod}"):
|
||||
return supprimer_produit(G, prod)
|
||||
|
||||
# Gestion des opérations d'assemblage
|
||||
ops_dispo = get_operations_disponibles(G)
|
||||
curr_ops = get_operations_actuelles(G, prod)
|
||||
default_idx = ops_dispo.index(curr_ops[0]) + 1 if curr_ops and curr_ops[0] in ops_dispo else 0
|
||||
sel_op = st.selectbox(str(_("pages.personnalisation.linked_assembly_operation", "Opération d'assemblage liée")), [str(_("pages.personnalisation.none", "-- Aucune --"))] + ops_dispo, index=default_idx)
|
||||
sel_op = st.selectbox(str(_("pages.personnalisation.linked_assembly_operation")), [str(_("pages.personnalisation.none"))] + ops_dispo, index=default_idx)
|
||||
|
||||
# Gestion des composants
|
||||
niveau1 = get_composants_niveau1(G)
|
||||
linked = get_composants_lies(G, prod)
|
||||
nouveaux = st.multiselect(f"{str(_('pages.personnalisation.components_linked_to', 'Composants liés à'))} {prod}", options=niveau1, default=linked)
|
||||
nouveaux = st.multiselect(f"{str(_('pages.personnalisation.components_linked_to'))} {prod}", options=niveau1, default=linked)
|
||||
|
||||
# Mise à jour des liens si demandé
|
||||
if st.button(f"{str(_('pages.personnalisation.update', 'Mettre à jour'))} {prod}"):
|
||||
if st.button(f"{str(_('pages.personnalisation.update'))} {prod}"):
|
||||
G = mettre_a_jour_operations(G, prod, curr_ops, sel_op)
|
||||
G = mettre_a_jour_composants(G, prod, linked, nouveaux)
|
||||
st.success(f"{prod} {str(_('pages.personnalisation.updated', 'mis à jour'))}")
|
||||
|
||||
st.success(f"{prod} {str(_('pages.personnalisation.updated'))}")
|
||||
|
||||
return G
|
||||
|
||||
@ -9,18 +9,18 @@ from utils.translations import _
|
||||
def afficher_graphique_altair(df):
|
||||
# Définir les catégories originales (en français) et leur ordre
|
||||
categories_fr = ["Assemblage", "Fabrication", "Traitement", "Extraction"]
|
||||
|
||||
|
||||
# Créer un dictionnaire de mappage entre les catégories originales et leurs traductions
|
||||
mappage_categories = {
|
||||
"Assemblage": str(_("pages.visualisations.categories.assembly", "Assemblage")),
|
||||
"Fabrication": str(_("pages.visualisations.categories.manufacturing", "Fabrication")),
|
||||
"Traitement": str(_("pages.visualisations.categories.processing", "Traitement")),
|
||||
"Extraction": str(_("pages.visualisations.categories.extraction", "Extraction"))
|
||||
"Assemblage": str(_("pages.visualisations.categories.assembly")),
|
||||
"Fabrication": str(_("pages.visualisations.categories.manufacturing")),
|
||||
"Traitement": str(_("pages.visualisations.categories.processing")),
|
||||
"Extraction": str(_("pages.visualisations.categories.extraction"))
|
||||
}
|
||||
|
||||
|
||||
# Filtrer les catégories qui existent dans les données
|
||||
categories_fr_filtrees = [cat for cat in categories_fr if cat in df['categorie'].unique()]
|
||||
|
||||
|
||||
# Parcourir les catégories dans l'ordre défini
|
||||
for cat_fr in categories_fr_filtrees:
|
||||
# Obtenir le nom traduit de la catégorie pour l'affichage
|
||||
@ -53,8 +53,8 @@ def afficher_graphique_altair(df):
|
||||
df_cat['ihh_acteurs_text'] = df_cat['ihh_acteurs'] + 0.5
|
||||
|
||||
base = alt.Chart(df_cat).encode(
|
||||
x=alt.X('ihh_pays:Q', title=str(_("pages.visualisations.axis_titles.ihh_countries", "IHH Pays (%)"))),
|
||||
y=alt.Y('ihh_acteurs:Q', title=str(_("pages.visualisations.axis_titles.ihh_actors", "IHH Acteurs (%)"))),
|
||||
x=alt.X('ihh_pays:Q', title=str(_("pages.visualisations.axis_titles.ihh_countries"))),
|
||||
y=alt.Y('ihh_acteurs:Q', title=str(_("pages.visualisations.axis_titles.ihh_actors"))),
|
||||
size=alt.Size('criticite_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
||||
color=alt.Color('criticite_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred']))
|
||||
)
|
||||
@ -83,7 +83,7 @@ def afficher_graphique_altair(df):
|
||||
chart = (points + lines + labels + hline_15 + hline_25 + hline_100 + vline_15 + vline_25 + vline_100).properties(
|
||||
width=500,
|
||||
height=400,
|
||||
title=str(_("pages.visualisations.chart_titles.concentration_criticality", "Concentration et criticité – {0}")).format(cat_traduit)
|
||||
title=str(_("pages.visualisations.chart_titles.concentration_criticality")).format(cat_traduit)
|
||||
).interactive()
|
||||
|
||||
st.altair_chart(chart, use_container_width=True)
|
||||
@ -91,7 +91,7 @@ def afficher_graphique_altair(df):
|
||||
|
||||
def creer_graphes(donnees):
|
||||
if not donnees:
|
||||
st.warning(str(_("pages.visualisations.no_data", "Aucune donnée à afficher.")))
|
||||
st.warning(str(_("pages.visualisations.no_data")))
|
||||
return
|
||||
|
||||
try:
|
||||
@ -122,8 +122,8 @@ def creer_graphes(donnees):
|
||||
df['ihh_reserves_text'] = df['ihh_reserves'] + 0.5
|
||||
|
||||
base = alt.Chart(df).encode(
|
||||
x=alt.X('ihh_extraction:Q', title=str(_("pages.visualisations.axis_titles.ihh_extraction", "IHH Extraction (%)"))),
|
||||
y=alt.Y('ihh_reserves:Q', title=str(_("pages.visualisations.axis_titles.ihh_reserves", "IHH Réserves (%)"))),
|
||||
x=alt.X('ihh_extraction:Q', title=str(_("pages.visualisations.axis_titles.ihh_extraction"))),
|
||||
y=alt.Y('ihh_reserves:Q', title=str(_("pages.visualisations.axis_titles.ihh_reserves"))),
|
||||
size=alt.Size('ivc_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
||||
color=alt.Color('ivc_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred'])),
|
||||
tooltip=['nom:N', 'ivc:Q', 'ihh_extraction:Q', 'ihh_reserves:Q']
|
||||
@ -153,13 +153,13 @@ def creer_graphes(donnees):
|
||||
chart = (points + lines + labels + hline_15 + hline_25 + hline_100 + vline_15 + vline_25 + vline_100).properties(
|
||||
width=600,
|
||||
height=500,
|
||||
title=str(_("pages.visualisations.chart_titles.concentration_resources", "Concentration des ressources critiques vs vulnérabilité IVC"))
|
||||
title=str(_("pages.visualisations.chart_titles.concentration_resources"))
|
||||
).interactive()
|
||||
|
||||
st.altair_chart(chart, use_container_width=True)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.graph_creation_error', 'Erreur lors de la création du graphique :'))} {e}")
|
||||
st.error(f"{str(_('errors.graph_creation_error'))} {e}")
|
||||
|
||||
|
||||
def lancer_visualisation_ihh_criticite(graph):
|
||||
@ -173,11 +173,11 @@ def lancer_visualisation_ihh_criticite(graph):
|
||||
|
||||
df = recuperer_donnees(graph, noeuds)
|
||||
if df.empty:
|
||||
st.warning(str(_("pages.visualisations.no_data", "Aucune donnée à visualiser.")))
|
||||
st.warning(str(_("pages.visualisations.no_data")))
|
||||
else:
|
||||
afficher_graphique_altair(df)
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.ihh_criticality_error', 'Erreur dans la visualisation IHH vs Criticité :'))} {e}")
|
||||
st.error(f"{str(_('errors.ihh_criticality_error'))} {e}")
|
||||
|
||||
|
||||
def lancer_visualisation_ihh_ivc(graph):
|
||||
@ -192,4 +192,4 @@ def lancer_visualisation_ihh_ivc(graph):
|
||||
data = recuperer_donnees_2(graph, noeuds_niveau_2)
|
||||
creer_graphes(data)
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.ihh_ivc_error', 'Erreur dans la visualisation IHH vs IVC :'))} {e}")
|
||||
st.error(f"{str(_('errors.ihh_ivc_error'))} {e}")
|
||||
|
||||
@ -8,39 +8,28 @@ from .graphes import (
|
||||
|
||||
|
||||
def interface_visualisations(G_temp, G_temp_ivc):
|
||||
st.markdown(f"# {str(_('pages.visualisations.title', 'Visualisations'))}")
|
||||
with st.expander(str(_("pages.visualisations.help", "Comment utiliser cet onglet ?")), expanded=False):
|
||||
st.markdown("\n".join(_("pages.visualisations.help_content", [
|
||||
"1. Explorez les graphiques présentant l'Indice de Herfindahl-Hirschmann (IHH)",
|
||||
"2. Analysez sa relation avec la criticité moyenne des minerais ou leur Indice de Vulnérabilité Concurrentielle (IVC)",
|
||||
"3. Zoomer dans les graphes pour mieux découvrir les informations",
|
||||
"",
|
||||
"Il est important de se rappeler que l'IHH a deux seuils :",
|
||||
"* en-dessous de 15, la concentration est considérée comme étant faible",
|
||||
"* au-dessus de 25, elle est considérée comme étant forte",
|
||||
"",
|
||||
"Ainsi plus le positionnement d'un point est en haut à droite des graphiques, plus les risques sont élevés.",
|
||||
"Les graphiques présentent 2 droites horizontales et vetrticales pour matérialiser ces seuils."
|
||||
])))
|
||||
st.markdown(f"# {str(_('pages.visualisations.title'))}")
|
||||
with st.expander(str(_("pages.visualisations.help")), expanded=False):
|
||||
st.markdown("\n".join(_("pages.visualisations.help_content")))
|
||||
st.markdown("---")
|
||||
|
||||
st.markdown(f"""## {str(_("pages.visualisations.ihh_criticality", "Indice de Herfindahl-Hirschmann - IHH vs Criticité"))}
|
||||
st.markdown(f"""## {str(_("pages.visualisations.ihh_criticality"))}
|
||||
|
||||
{str(_("pages.visualisations.ihh_criticality_desc", "La taille des points donne l'indication de la criticité de substituabilité du minerai."))}
|
||||
{str(_("pages.visualisations.ihh_criticality_desc"))}
|
||||
""")
|
||||
if st.button(str(_("buttons.run", "Lancer")), key="btn_ihh_criticite"):
|
||||
if st.button(str(_("buttons.run")), key="btn_ihh_criticite"):
|
||||
try:
|
||||
lancer_visualisation_ihh_criticite(G_temp)
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.ihh_criticality_error', 'Erreur dans la visualisation IHH vs Criticité :'))} {e}")
|
||||
st.error(f"{str(_('errors.ihh_criticality_error'))} {e}")
|
||||
|
||||
st.markdown(f"""## {str(_("pages.visualisations.ihh_ivc", "Indice de Herfindahl-Hirschmann - IHH vs IVC"))}
|
||||
st.markdown(f"""## {str(_("pages.visualisations.ihh_ivc"))}
|
||||
|
||||
{str(_("pages.visualisations.ihh_ivc_desc", "La taille des points donne l'indication de la criticité concurrentielle du minerai."))}
|
||||
{str(_("pages.visualisations.ihh_ivc_desc"))}
|
||||
""")
|
||||
|
||||
if st.button(str(_("buttons.run", "Lancer")), key="btn_ihh_ivc"):
|
||||
if st.button(str(_("buttons.run")), key="btn_ihh_ivc"):
|
||||
try:
|
||||
lancer_visualisation_ihh_ivc(G_temp_ivc)
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.ihh_ivc_error', 'Erreur dans la visualisation IHH vs IVC :'))} {e}")
|
||||
st.error(f"{str(_('errors.ihh_ivc_error'))} {e}")
|
||||
|
||||
276
assets/locales/en copy.json
Normal file
276
assets/locales/en copy.json
Normal file
@ -0,0 +1,276 @@
|
||||
{
|
||||
"app": {
|
||||
"title": "Fabnum – Chain Analysis",
|
||||
"description": "Ecosystem exploration and vulnerability identification.",
|
||||
"dev_mode": "You are in the development environment."
|
||||
},
|
||||
"header": {
|
||||
"title": "FabNum - Digital Manufacturing Chain",
|
||||
"subtitle": "Ecosystem exploration and vulnerability identification."
|
||||
},
|
||||
"footer": {
|
||||
"copyright": "Fabnum © 2025",
|
||||
"contact": "Contact",
|
||||
"license": "License",
|
||||
"license_text": "CC BY-NC-ND",
|
||||
"eco_note": "🌱 CO₂ calculations via",
|
||||
"eco_provider": "The Green Web Foundation",
|
||||
"powered_by": "🚀 Powered by",
|
||||
"powered_by_name": "Streamlit"
|
||||
},
|
||||
"sidebar": {
|
||||
"menu": "Main Menu",
|
||||
"navigation": "Main Navigation",
|
||||
"theme": "Theme",
|
||||
"theme_light": "Light",
|
||||
"theme_dark": "Dark",
|
||||
"theme_instructions_only": "Theme changes can only be made from the Instructions tab.",
|
||||
"impact": "Environmental Impact",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"auth": {
|
||||
"title": "Authentication",
|
||||
"username": "Username_token",
|
||||
"token": "Gitea Personal Access Token",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"logged_as": "Logged in as",
|
||||
"error": "❌ Access denied.",
|
||||
"gitea_error": "❌ Unable to verify user with Gitea.",
|
||||
"success": "Successfully logged out."
|
||||
},
|
||||
"navigation": {
|
||||
"instructions": "Instructions",
|
||||
"personnalisation": "Customization",
|
||||
"analyse": "Analysis",
|
||||
"visualisations": "Visualizations",
|
||||
"fiches": "Cards"
|
||||
},
|
||||
"pages": {
|
||||
"instructions": {
|
||||
"title": "Instructions"
|
||||
},
|
||||
"personnalisation": {
|
||||
"title": "Final Product Customization",
|
||||
"help": "How to use this tab?",
|
||||
"help_content": [
|
||||
"1. Click on \"Add a final product\" to create a new product",
|
||||
"2. Give your product a name",
|
||||
"3. Select an appropriate assembly operation (if relevant)",
|
||||
"4. Choose the components that make up your product from the list provided",
|
||||
"5. Save your configuration for future reuse",
|
||||
"6. You will be able to modify or delete your custom products later"
|
||||
],
|
||||
"add_new_product": "Add a new final product",
|
||||
"new_product_name": "New product name (unique)",
|
||||
"assembly_operation": "Assembly operation (optional)",
|
||||
"none": "-- None --",
|
||||
"components_to_link": "Components to link",
|
||||
"create_product": "Create product",
|
||||
"added": "added",
|
||||
"modify_product": "Modify an added final product",
|
||||
"products_to_modify": "Products to modify",
|
||||
"delete": "Delete",
|
||||
"linked_assembly_operation": "Linked assembly operation",
|
||||
"components_linked_to": "Components linked to",
|
||||
"update": "Update",
|
||||
"updated": "updated",
|
||||
"deleted": "deleted",
|
||||
"save_restore_config": "Save or restore configuration",
|
||||
"export_config": "Export configuration",
|
||||
"download_json": "Download (JSON)",
|
||||
"import_config": "Import a JSON configuration (max 100 KB)",
|
||||
"file_too_large": "File too large (max 100 KB).",
|
||||
"no_products_found": "No products found in the file.",
|
||||
"select_products_to_restore": "Select products to restore",
|
||||
"products_to_restore": "Products to restore",
|
||||
"restore_selected": "Restore selected items",
|
||||
"config_restored": "Partial configuration successfully restored.",
|
||||
"import_error": "Import error:"
|
||||
},
|
||||
"analyse": {
|
||||
"title": "Graph Analysis",
|
||||
"help": "How to use this tab?",
|
||||
"help_content": [
|
||||
"1. Select the starting level (final product, component, or mineral)",
|
||||
"2. Choose the desired destination level",
|
||||
"3. Refine your selection by specifying either one or more specific minerals to target or specific items at each level (optional)",
|
||||
"4. Define the analysis criteria by selecting the relevant vulnerability indices",
|
||||
"5. Choose the index combination mode (AND/OR) according to your analysis needs",
|
||||
"6. Explore the generated graph using zoom and panning controls; you can switch to full screen mode for the graph"
|
||||
],
|
||||
"selection_nodes": "Selection of start and end nodes",
|
||||
"select_level": "-- Select a level --",
|
||||
"start_level": "Start level",
|
||||
"end_level": "End level",
|
||||
"select_minerals": "Select one or more minerals",
|
||||
"filter_by_minerals": "Filter by minerals (optional)",
|
||||
"fine_selection": "Fine selection of items",
|
||||
"filter_start_nodes": "Filter by start nodes (optional)",
|
||||
"filter_end_nodes": "Filter by end nodes (optional)",
|
||||
"vulnerability_filters": "Selection of filters to identify vulnerabilities",
|
||||
"filter_ics": "Filter paths containing at least one critical mineral for a component (ICS > 66%)",
|
||||
"filter_ivc": "Filter paths containing at least one critical mineral in relation to sectoral competition (IVC > 30)",
|
||||
"filter_ihh": "Filter paths containing at least one critical operation in relation to geographical or industrial concentration (IHH countries or actors > 25)",
|
||||
"apply_ihh_filter": "Apply IHH filter on:",
|
||||
"countries": "Countries",
|
||||
"actors": "Actors",
|
||||
"filter_isg": "Filter paths containing an unstable country (ISG ≥ 60)",
|
||||
"filter_logic": "Filter logic",
|
||||
"or": "OR",
|
||||
"and": "AND",
|
||||
"run_analysis": "Run analysis",
|
||||
"sankey": {
|
||||
"no_paths": "No paths found for the specified criteria.",
|
||||
"no_matching_paths": "No paths match the criteria.",
|
||||
"filtered_hierarchy": "Hierarchy filtered by levels and nodes",
|
||||
"download_dot": "Download filtered DOT file",
|
||||
"relation": "Relation"
|
||||
}
|
||||
},
|
||||
"visualisations": {
|
||||
"title": "Visualizations",
|
||||
"help": "How to use this tab?",
|
||||
"help_content": [
|
||||
"1. Explore the graphs presenting the Herfindahl-Hirschmann Index (IHH)",
|
||||
"2. Analyze its relationship with the average criticality of minerals or their Competitive Vulnerability Index (IVC)",
|
||||
"3. Zoom in on the graphs to better discover the information",
|
||||
"",
|
||||
"It is important to remember that the IHH has two thresholds:",
|
||||
"* below 15, concentration is considered to be low",
|
||||
"* above 25, it is considered to be high",
|
||||
"",
|
||||
"Thus, the higher a point is positioned in the top right of the graphs, the higher the risks.",
|
||||
"The graphs present 2 horizontal and vertical lines to mark these thresholds."
|
||||
],
|
||||
"ihh_criticality": "Herfindahl-Hirschmann Index - IHH vs Criticality",
|
||||
"ihh_criticality_desc": "The size of the points indicates the substitutability criticality of the mineral.",
|
||||
"ihh_ivc": "Herfindahl-Hirschmann Index - IHH vs IVC",
|
||||
"ihh_ivc_desc": "The size of the points indicates the competitive criticality of the mineral.",
|
||||
"launch": "Launch",
|
||||
"no_data": "No data to display.",
|
||||
"categories": {
|
||||
"assembly": "Assembly",
|
||||
"manufacturing": "Manufacturing",
|
||||
"processing": "Processing",
|
||||
"extraction": "Extraction"
|
||||
},
|
||||
"axis_titles": {
|
||||
"ihh_countries": "IHH Countries (%)",
|
||||
"ihh_actors": "IHH Actors (%)",
|
||||
"ihh_extraction": "IHH Extraction (%)",
|
||||
"ihh_reserves": "IHH Reserves (%)"
|
||||
},
|
||||
"chart_titles": {
|
||||
"concentration_criticality": "Concentration and Criticality – {0}",
|
||||
"concentration_resources": "Concentration of Critical Resources vs IVC Vulnerability"
|
||||
}
|
||||
},
|
||||
"fiches": {
|
||||
"title": "Card Discovery",
|
||||
"help": "How to use this tab?",
|
||||
"help_content": [
|
||||
"1. Browse the list of available cards by category",
|
||||
"2. Select a card to display its full content",
|
||||
"3. Consult detailed data, graphs, and additional analyses",
|
||||
"4. Use this information to deepen your understanding of the identified vulnerabilities",
|
||||
"",
|
||||
"The categories are as follows:",
|
||||
"* Assembly: operation of assembling final products from components",
|
||||
"* Related: various operations necessary to manufacture digital technology, but not directly entering its composition",
|
||||
"* Criticalities: indices used to identify and evaluate vulnerabilities",
|
||||
"* Manufacturing: operation of manufacturing components from minerals",
|
||||
"* Mineral: description and operations of extraction and processing of minerals"
|
||||
],
|
||||
"no_files": "No cards available at the moment.",
|
||||
"choose_category": "Choose a card category",
|
||||
"select_folder": "-- Select a folder --",
|
||||
"choose_file": "Choose a card",
|
||||
"select_file": "-- Select a card --",
|
||||
"loading_error": "Error loading the card:",
|
||||
"download_pdf": "Download this card as PDF",
|
||||
"pdf_unavailable": "The PDF file for this card is not available.",
|
||||
"ticket_management": "Ticket management for this card",
|
||||
"tickets": {
|
||||
"create_new": "Create a new ticket linked to this card",
|
||||
"model_load_error": "Unable to load the ticket template.",
|
||||
"contribution_type": "Contribution type",
|
||||
"specify": "Specify",
|
||||
"other": "Other",
|
||||
"concerned_card": "Concerned card",
|
||||
"subject": "Subject of the proposal",
|
||||
"preview": "Preview ticket",
|
||||
"cancel": "Cancel",
|
||||
"preview_title": "Ticket preview",
|
||||
"summary": "Summary",
|
||||
"title": "Title",
|
||||
"labels": "Labels",
|
||||
"confirm": "Confirm ticket creation",
|
||||
"created": "Ticket created and form cleared.",
|
||||
"model_error": "Template loading error:",
|
||||
"no_linked_tickets": "No tickets linked to this card.",
|
||||
"associated_tickets": "Tickets associated with this card",
|
||||
"moderation_notice": "ticket(s) awaiting moderation are not displayed.",
|
||||
"status": {
|
||||
"awaiting": "Awaiting processing",
|
||||
"in_progress": "In progress",
|
||||
"completed": "Completed",
|
||||
"rejected": "Rejected",
|
||||
"others": "Others"
|
||||
},
|
||||
"no_title": "No title",
|
||||
"unknown": "unknown",
|
||||
"subject_label": "Subject",
|
||||
"no_labels": "none",
|
||||
"comments": "Comment(s):",
|
||||
"no_comments": "No comments.",
|
||||
"comment_error": "Error retrieving comments:",
|
||||
"opened_by": "Opened by",
|
||||
"on_date": "on",
|
||||
"updated": "UPDATED"
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_levels": {
|
||||
"0": "Final product",
|
||||
"1": "Component",
|
||||
"2": "Mineral",
|
||||
"10": "Operation",
|
||||
"11": "Operation country",
|
||||
"12": "Operation actor",
|
||||
"99": "Geographic country"
|
||||
},
|
||||
"errors": {
|
||||
"log_read_error": "Log reading error:",
|
||||
"graph_preview_error": "Graph preview error:",
|
||||
"graph_creation_error": "Error creating the graph:",
|
||||
"ihh_criticality_error": "Error in IHH vs Criticality visualization:",
|
||||
"ihh_ivc_error": "Error in IHH vs IVC visualization:",
|
||||
"comment_fetch_error": "Error retrieving comments:",
|
||||
"template_load_error": "Template loading error:",
|
||||
"import_error": "Import error:"
|
||||
},
|
||||
"buttons": {
|
||||
"download": "Download",
|
||||
"run": "Run",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"filter": "Filter",
|
||||
"search": "Search",
|
||||
"create": "Create",
|
||||
"update": "Update",
|
||||
"delete": "Delete",
|
||||
"preview": "Preview",
|
||||
"export": "Export",
|
||||
"import": "Import",
|
||||
"restore": "Restore",
|
||||
"browse_files": "Browse files"
|
||||
},
|
||||
"ui": {
|
||||
"file_uploader": {
|
||||
"drag_drop_here": "Drag and drop file here",
|
||||
"size_limit": "100 KB limit per file • JSON"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@ def initialiser_logger():
|
||||
|
||||
def connexion():
|
||||
if "logged_in" not in st.session_state or not st.session_state.logged_in:
|
||||
auth_title = str(_("auth.title", "Authentification"))
|
||||
auth_title = str(_("auth.title"))
|
||||
st.html(f"""
|
||||
<section role="region" aria-label="region-authentification">
|
||||
<div role="region" aria-labelledby="Authentification">
|
||||
@ -42,9 +42,9 @@ def connexion():
|
||||
with st.form("auth_form"):
|
||||
# Ajout d'un champ identifiant fictif pour activer l'autocomplétion navigateur
|
||||
# et permettre de stocker le token comme un mot de passe par le navigateur
|
||||
identifiant = st.text_input(str(_("auth.username", "Identifiant_token")), value="fabnum-connexion", key="nom_utilisateur")
|
||||
token = st.text_input(str(_("auth.token", "Token d'accès personnel Gitea")), type="password")
|
||||
submitted = st.form_submit_button(str(_("auth.login", "Se connecter")))
|
||||
identifiant = st.text_input(str(_("auth.username")), value="fabnum-connexion", key="nom_utilisateur")
|
||||
token = st.text_input(str(_("auth.token")), type="password")
|
||||
submitted = st.form_submit_button(str(_("auth.login")))
|
||||
|
||||
if submitted and token:
|
||||
erreur = True
|
||||
@ -78,11 +78,11 @@ def connexion():
|
||||
st.rerun()
|
||||
|
||||
except requests.RequestException:
|
||||
st.error(str(_("auth.gitea_error", "❌ Impossible de vérifier l'utilisateur auprès de Gitea.")))
|
||||
st.error(str(_("auth.gitea_error")))
|
||||
|
||||
if erreur:
|
||||
logger.warning(f"Accès refusé pour tentative avec token depuis IP {ip}")
|
||||
st.error(str(_("auth.error", "❌ Accès refusé.")))
|
||||
st.error(str(_("auth.error")))
|
||||
|
||||
st.html("""
|
||||
</div>
|
||||
@ -92,22 +92,22 @@ def connexion():
|
||||
|
||||
def bouton_deconnexion():
|
||||
if st.session_state.get("logged_in", False):
|
||||
auth_title = str(_("auth.title", "Authentification"))
|
||||
auth_title = str(_("auth.title"))
|
||||
st.html(f"""
|
||||
<section role="region" aria-label="region-authentification">
|
||||
<div role="region" aria-labelledby="Authentification">
|
||||
<p id="Authentification" class="decorative-heading">{auth_title}</p>
|
||||
""")
|
||||
|
||||
st.sidebar.markdown(f"{str(_('auth.logged_as', 'Connecté en tant que'))} `{st.session_state.username}`")
|
||||
if st.sidebar.button(str(_("auth.logout", "Se déconnecter"))):
|
||||
st.sidebar.markdown(f"{str(_('auth.logged_as'))} `{st.session_state.username}`")
|
||||
if st.sidebar.button(str(_("auth.logout"))):
|
||||
st.session_state.logged_in = False
|
||||
st.session_state.username = ""
|
||||
st.session_state.token = ""
|
||||
st.success(str(_("auth.success", "Déconnecté avec succès.")))
|
||||
st.success(str(_("auth.success")))
|
||||
st.rerun()
|
||||
|
||||
st.html("""
|
||||
</div>
|
||||
</section>
|
||||
""")
|
||||
""")
|
||||
|
||||
@ -11,11 +11,11 @@ def afficher_pied_de_page():
|
||||
<div role='contentinfo' aria-labelledby='footer-appli' class='wide-footer'>
|
||||
<div class='info-footer'>
|
||||
<p id='footer-appli' class='info-footer'>
|
||||
{_("footer.copyright", "Fabnum © 2025")} – <a href='mailto:stephan-pro@peccini.fr'>{_("footer.contact", "Contact")}</a> – {_("footer.license", "Licence")} <a href='https://creativecommons.org/licenses/by-nc-nd/4.0/deed.fr' target='_blank'>{_("footer.license_text", "CC BY-NC-ND")}</a>
|
||||
{_("footer.copyright")} – <a href='mailto:stephan-pro@peccini.fr'>{_("footer.contact")}</a> – {_("footer.license")} <a href='https://creativecommons.org/licenses/by-nc-nd/4.0/deed.fr' target='_blank'>{_("footer.license_text")}</a>
|
||||
</p>
|
||||
<p class='footer-note'>
|
||||
{_("footer.eco_note", "🌱 Calculs eqCO₂ via")} <a href='https://www.thegreenwebfoundation.org/' target='_blank'>{_("footer.eco_provider", "The Green Web Foundation")}</a><br>
|
||||
{_("footer.powered_by", "🚀 Propulsé par")} <a href='https://streamlit.io/' target='_blank'>{_("footer.powered_by_name", "Streamlit")}</a>
|
||||
{_("footer.eco_note")} <a href='https://www.thegreenwebfoundation.org/' target='_blank'>{_("footer.eco_provider")}</a><br>
|
||||
{_("footer.powered_by")} <a href='https://streamlit.io/' target='_blank'>{_("footer.powered_by_name")}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -7,13 +7,13 @@ def afficher_entete():
|
||||
header = f"""
|
||||
<header role="banner" aria-labelledby="entete-header">
|
||||
<div class='wide-header'>
|
||||
<p id='entete-header' class='titre-header'>{_("header.title", "FabNum - Chaîne de fabrication du numérique")}</p>
|
||||
<p id='entete-header' class='titre-header'>{_("header.title")}</p>
|
||||
"""
|
||||
|
||||
if ENV == "dev":
|
||||
header += f"<p>🔧 {_("app.dev_mode", "Vous êtes dans l'environnement de développement.")}</p>"
|
||||
header += f"<p>🔧 {_("app.dev_mode")}</p>"
|
||||
else:
|
||||
header += f"<p>{_("header.subtitle", "Parcours de l'écosystème et identification des vulnérabilités.")}</p>"
|
||||
header += f"<p>{_("header.subtitle")}</p>"
|
||||
|
||||
header += """
|
||||
</div>
|
||||
|
||||
@ -7,22 +7,22 @@ from utils.translations import _
|
||||
def afficher_menu():
|
||||
with st.sidebar:
|
||||
st.markdown(f"""
|
||||
<nav role="navigation" aria-label="{str(_('sidebar.menu', 'Menu principal'))}">
|
||||
<div role="region" aria-label="{str(_('sidebar.navigation', 'Navigation principale'))}" class="onglets-accessibles">
|
||||
<nav role="navigation" aria-label="{str(_('sidebar.menu'))}">
|
||||
<div role="region" aria-label="{str(_('sidebar.navigation'))}" class="onglets-accessibles">
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Définir la variable instructions_text une seule fois en haut de la fonction
|
||||
instructions_text = str(_("navigation.instructions", "Instructions"))
|
||||
instructions_text = str(_("navigation.instructions"))
|
||||
if "onglet" not in st.session_state:
|
||||
st.session_state.onglet = instructions_text
|
||||
|
||||
onglet_choisi = None
|
||||
onglets = [
|
||||
str(_("navigation.instructions", "Instructions")),
|
||||
str(_("navigation.personnalisation", "Personnalisation")),
|
||||
str(_("navigation.analyse", "Analyse")),
|
||||
str(_("navigation.visualisations", "Visualisations")),
|
||||
str(_("navigation.fiches", "Fiches"))
|
||||
str(_("navigation.instructions")),
|
||||
str(_("navigation.personnalisation")),
|
||||
str(_("navigation.analyse")),
|
||||
str(_("navigation.visualisations")),
|
||||
str(_("navigation.fiches"))
|
||||
]
|
||||
|
||||
for nom in onglets:
|
||||
@ -45,9 +45,9 @@ def afficher_menu():
|
||||
#
|
||||
if st.session_state.onglet == instructions_text:
|
||||
if "theme_mode" not in st.session_state:
|
||||
st.session_state.theme_mode = str(_("sidebar.theme_light", "Clair"))
|
||||
st.session_state.theme_mode = str(_("sidebar.theme_light"))
|
||||
|
||||
theme_title = str(_("sidebar.theme", "Thème"))
|
||||
theme_title = str(_("sidebar.theme"))
|
||||
st.markdown(f"""
|
||||
<section role="region" aria-label="region-theme">
|
||||
<div role="region" aria-labelledby="Theme">
|
||||
@ -55,11 +55,11 @@ def afficher_menu():
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
theme_options = [
|
||||
str(_("sidebar.theme_light", "Clair")),
|
||||
str(_("sidebar.theme_dark", "Sombre"))
|
||||
str(_("sidebar.theme_light")),
|
||||
str(_("sidebar.theme_dark"))
|
||||
]
|
||||
theme = st.radio(
|
||||
str(_("sidebar.theme", "Thème")),
|
||||
str(_("sidebar.theme")),
|
||||
theme_options,
|
||||
index=theme_options.index(st.session_state.theme_mode),
|
||||
horizontal=True,
|
||||
@ -71,14 +71,14 @@ def afficher_menu():
|
||||
</div>
|
||||
</nav>""", unsafe_allow_html=True)
|
||||
else :
|
||||
theme_title = str(_("sidebar.theme", "Thème"))
|
||||
theme_title = str(_("sidebar.theme"))
|
||||
st.markdown(f"""
|
||||
<section role="region" aria-label="region-theme">
|
||||
<div role="region" aria-labelledby="Theme">
|
||||
<p id="Theme" class="decorative-heading">{theme_title}</p>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.info(str(_("sidebar.theme_instructions_only", "Le changement de thème ne peut se faire que depuis l'onglet Instructions.")))
|
||||
st.info(str(_("sidebar.theme_instructions_only")))
|
||||
|
||||
st.markdown("""
|
||||
<hr />
|
||||
@ -101,8 +101,8 @@ def afficher_menu():
|
||||
|
||||
|
||||
def afficher_impact(total_bytes):
|
||||
impact_label = str(_("sidebar.impact", "Impact environnemental"))
|
||||
loading_text = str(_("sidebar.loading", "Chargement en cours…"))
|
||||
impact_label = str(_("sidebar.impact"))
|
||||
loading_text = str(_("sidebar.loading"))
|
||||
|
||||
with st.sidebar:
|
||||
components.html(f"""
|
||||
|
||||
10
fabnum.py
10
fabnum.py
@ -154,11 +154,11 @@ ouvrir_page()
|
||||
dot_file_path = None
|
||||
|
||||
# Obtenir les noms traduits des onglets
|
||||
instructions_tab = _("navigation.instructions", "Instructions")
|
||||
fiches_tab = _("navigation.fiches", "Fiches")
|
||||
personnalisation_tab = _("navigation.personnalisation", "Personnalisation")
|
||||
analyse_tab = _("navigation.analyse", "Analyse")
|
||||
visualisations_tab = _("navigation.visualisations", "Visualisations")
|
||||
instructions_tab = _("navigation.instructions")
|
||||
fiches_tab = _("navigation.fiches")
|
||||
personnalisation_tab = _("navigation.personnalisation")
|
||||
analyse_tab = _("navigation.analyse")
|
||||
visualisations_tab = _("navigation.visualisations")
|
||||
|
||||
if st.session_state.onglet == instructions_tab:
|
||||
markdown_content = charger_instructions_depuis_gitea(INSTRUCTIONS)
|
||||
|
||||
@ -31,7 +31,7 @@ def load_translations(lang="fr"):
|
||||
logger.error(f"Erreur lors du chargement des traductions: {e}")
|
||||
return {}
|
||||
|
||||
def get_translation(key, default=None):
|
||||
def get_translation(key):
|
||||
"""
|
||||
Récupère une traduction par sa clé.
|
||||
Les clés peuvent être hiérarchiques, séparées par des points.
|
||||
@ -51,7 +51,7 @@ def get_translation(key, default=None):
|
||||
|
||||
# Si aucune traduction n'est chargée, retourner la valeur par défaut
|
||||
if not st.session_state.get("translations"):
|
||||
return default if default is not None else key
|
||||
return f"⊗⤇ {key} ⤆⊗"
|
||||
|
||||
# Parcourir la hiérarchie des clés
|
||||
keys = key.split(".")
|
||||
@ -59,7 +59,7 @@ def get_translation(key, default=None):
|
||||
|
||||
for k in keys:
|
||||
if not isinstance(current, dict) or k not in current:
|
||||
return default if default is not None else key
|
||||
return f"⊗⤇ {key} ⤆⊗"
|
||||
current = current[k]
|
||||
|
||||
return current
|
||||
|
||||
@ -8,10 +8,10 @@ from utils.translations import _
|
||||
|
||||
def afficher_graphique_altair(df):
|
||||
ordre_personnalise = [
|
||||
str(_("pages.visualisations.categories.assembly", "Assemblage")),
|
||||
str(_("pages.visualisations.categories.manufacturing", "Fabrication")),
|
||||
str(_("pages.visualisations.categories.processing", "Traitement")),
|
||||
str(_("pages.visualisations.categories.extraction", "Extraction"))
|
||||
str(_("pages.visualisations.categories.assembly")),
|
||||
str(_("pages.visualisations.categories.manufacturing")),
|
||||
str(_("pages.visualisations.categories.processing")),
|
||||
str(_("pages.visualisations.categories.extraction"))
|
||||
]
|
||||
categories = [cat for cat in ordre_personnalise if cat in df['categorie'].unique()]
|
||||
for cat in categories:
|
||||
@ -42,8 +42,8 @@ def afficher_graphique_altair(df):
|
||||
df_cat['ihh_acteurs_text'] = df_cat['ihh_acteurs'] + 0.5
|
||||
|
||||
base = alt.Chart(df_cat).encode(
|
||||
x=alt.X('ihh_pays:Q', title=str(_("pages.visualisations.axis_titles.ihh_countries", "IHH Pays (%)"))),
|
||||
y=alt.Y('ihh_acteurs:Q', title=str(_("pages.visualisations.axis_titles.ihh_actors", "IHH Acteurs (%)"))),
|
||||
x=alt.X('ihh_pays:Q', title=str(_("pages.visualisations.axis_titles.ihh_countries"))),
|
||||
y=alt.Y('ihh_acteurs:Q', title=str(_("pages.visualisations.axis_titles.ihh_actors"))),
|
||||
size=alt.Size('criticite_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
||||
color=alt.Color('criticite_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred']))
|
||||
)
|
||||
@ -70,7 +70,7 @@ def afficher_graphique_altair(df):
|
||||
chart = (points + lines + labels + hline_15 + hline_25 + vline_15 + vline_25).properties(
|
||||
width=500,
|
||||
height=400,
|
||||
title=str(_("pages.visualisations.chart_titles.concentration_criticality", "Concentration et criticité – {0}")).format(str(cat))
|
||||
title=str(_("pages.visualisations.chart_titles.concentration_criticality")).format(str(cat))
|
||||
).interactive()
|
||||
|
||||
st.altair_chart(chart, use_container_width=True)
|
||||
@ -78,7 +78,7 @@ def afficher_graphique_altair(df):
|
||||
|
||||
def creer_graphes(donnees):
|
||||
if not donnees:
|
||||
st.warning(str(_("pages.visualisations.no_data", "Aucune donnée à afficher.")))
|
||||
st.warning(str(_("pages.visualisations.no_data")))
|
||||
return
|
||||
|
||||
try:
|
||||
@ -109,8 +109,8 @@ def creer_graphes(donnees):
|
||||
df['ihh_reserves_text'] = df['ihh_reserves'] + 0.5
|
||||
|
||||
base = alt.Chart(df).encode(
|
||||
x=alt.X('ihh_extraction:Q', title=str(_("pages.visualisations.axis_titles.ihh_extraction", "IHH Extraction (%)"))),
|
||||
y=alt.Y('ihh_reserves:Q', title=str(_("pages.visualisations.axis_titles.ihh_reserves", "IHH Réserves (%)"))),
|
||||
x=alt.X('ihh_extraction:Q', title=str(_("pages.visualisations.axis_titles.ihh_extraction"))),
|
||||
y=alt.Y('ihh_reserves:Q', title=str(_("pages.visualisations.axis_titles.ihh_reserves"))),
|
||||
size=alt.Size('ivc_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
||||
color=alt.Color('ivc_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred'])),
|
||||
tooltip=['nom:N', 'ivc:Q', 'ihh_extraction:Q', 'ihh_reserves:Q']
|
||||
@ -138,13 +138,13 @@ def creer_graphes(donnees):
|
||||
chart = (points + lines + labels + hline_15 + hline_25 + vline_15 + vline_25).properties(
|
||||
width=600,
|
||||
height=500,
|
||||
title=str(_("pages.visualisations.chart_titles.concentration_resources", "Concentration des ressources critiques vs vulnérabilité IVC"))
|
||||
title=str(_("pages.visualisations.chart_titles.concentration_resources"))
|
||||
).interactive()
|
||||
|
||||
st.altair_chart(chart, use_container_width=True)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.graph_creation_error', 'Erreur lors de la création du graphique :'))} {e}")
|
||||
st.error(f"{str(_('errors.graph_creation_error'))} {e}")
|
||||
|
||||
|
||||
def lancer_visualisation_ihh_criticite(graph):
|
||||
@ -158,11 +158,11 @@ def lancer_visualisation_ihh_criticite(graph):
|
||||
|
||||
df = recuperer_donnees(graph, noeuds)
|
||||
if df.empty:
|
||||
st.warning(str(_("pages.visualisations.no_data", "Aucune donnée à visualiser.")))
|
||||
st.warning(str(_("pages.visualisations.no_data")))
|
||||
else:
|
||||
afficher_graphique_altair(df)
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.ihh_criticality_error', 'Erreur dans la visualisation IHH vs Criticité :'))} {e}")
|
||||
st.error(f"{str(_('errors.ihh_criticality_error'))} {e}")
|
||||
|
||||
|
||||
def lancer_visualisation_ihh_ivc(graph):
|
||||
@ -177,4 +177,4 @@ def lancer_visualisation_ihh_ivc(graph):
|
||||
data = recuperer_donnees_2(graph, noeuds_niveau_2)
|
||||
creer_graphes(data)
|
||||
except Exception as e:
|
||||
st.error(f"{str(_('errors.ihh_ivc_error', 'Erreur dans la visualisation IHH vs IVC :'))} {e}")
|
||||
st.error(f"{str(_('errors.ihh_ivc_error'))} {e}")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user