genelastic 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. genelastic/api/.env +4 -0
  2. genelastic/api/cli_start_api.py +18 -0
  3. genelastic/api/errors.py +52 -0
  4. genelastic/api/extends/example.py +0 -6
  5. genelastic/api/extends/example.yml +0 -0
  6. genelastic/api/routes.py +313 -181
  7. genelastic/api/server.py +34 -26
  8. genelastic/api/settings.py +5 -9
  9. genelastic/api/specification.yml +512 -0
  10. genelastic/common/__init__.py +0 -39
  11. genelastic/common/cli.py +100 -0
  12. genelastic/common/elastic.py +374 -46
  13. genelastic/common/exceptions.py +34 -2
  14. genelastic/common/server.py +59 -0
  15. genelastic/common/types.py +1 -14
  16. genelastic/import_data/__init__.py +0 -27
  17. genelastic/import_data/checker.py +99 -0
  18. genelastic/import_data/checker_observer.py +13 -0
  19. genelastic/import_data/cli/__init__.py +0 -0
  20. genelastic/import_data/cli/cli_check.py +136 -0
  21. genelastic/import_data/cli/gen_data.py +143 -0
  22. genelastic/import_data/cli/import_data.py +346 -0
  23. genelastic/import_data/cli/info.py +247 -0
  24. genelastic/import_data/{cli_integrity.py → cli/integrity.py} +29 -7
  25. genelastic/import_data/cli/validate.py +146 -0
  26. genelastic/import_data/collect.py +185 -0
  27. genelastic/import_data/constants.py +136 -11
  28. genelastic/import_data/import_bundle.py +102 -59
  29. genelastic/import_data/import_bundle_factory.py +70 -149
  30. genelastic/import_data/importers/__init__.py +0 -0
  31. genelastic/import_data/importers/importer_base.py +131 -0
  32. genelastic/import_data/importers/importer_factory.py +85 -0
  33. genelastic/import_data/importers/importer_types.py +223 -0
  34. genelastic/import_data/logger.py +2 -1
  35. genelastic/import_data/models/__init__.py +0 -0
  36. genelastic/import_data/models/analyses.py +178 -0
  37. genelastic/import_data/models/analysis.py +144 -0
  38. genelastic/import_data/models/data_file.py +110 -0
  39. genelastic/import_data/models/process.py +45 -0
  40. genelastic/import_data/models/processes.py +84 -0
  41. genelastic/import_data/models/tags.py +170 -0
  42. genelastic/import_data/models/unique_list.py +109 -0
  43. genelastic/import_data/models/validate.py +26 -0
  44. genelastic/import_data/patterns.py +90 -0
  45. genelastic/import_data/random_bundle.py +79 -54
  46. genelastic/import_data/resolve.py +157 -0
  47. genelastic/ui/.env +1 -0
  48. genelastic/ui/cli_start_ui.py +20 -0
  49. genelastic/ui/routes.py +333 -0
  50. genelastic/ui/server.py +9 -82
  51. genelastic/ui/settings.py +2 -6
  52. genelastic/ui/static/cea-cnrgh.ico +0 -0
  53. genelastic/ui/static/cea.ico +0 -0
  54. genelastic/ui/static/layout.ico +0 -0
  55. genelastic/ui/static/novaseq6000.png +0 -0
  56. genelastic/ui/static/style.css +430 -0
  57. genelastic/ui/static/ui.js +458 -0
  58. genelastic/ui/templates/analyses.html +98 -0
  59. genelastic/ui/templates/analysis_detail.html +44 -0
  60. genelastic/ui/templates/bi_process_detail.html +129 -0
  61. genelastic/ui/templates/bi_processes.html +116 -0
  62. genelastic/ui/templates/explorer.html +356 -0
  63. genelastic/ui/templates/home.html +207 -0
  64. genelastic/ui/templates/layout.html +153 -0
  65. genelastic/ui/templates/version.html +21 -0
  66. genelastic/ui/templates/wet_process_detail.html +131 -0
  67. genelastic/ui/templates/wet_processes.html +116 -0
  68. genelastic-0.9.0.dist-info/METADATA +686 -0
  69. genelastic-0.9.0.dist-info/RECORD +76 -0
  70. genelastic-0.9.0.dist-info/WHEEL +4 -0
  71. genelastic-0.9.0.dist-info/entry_points.txt +10 -0
  72. genelastic-0.9.0.dist-info/licenses/LICENSE +519 -0
  73. genelastic/import_data/analyses.py +0 -69
  74. genelastic/import_data/analysis.py +0 -205
  75. genelastic/import_data/bi_process.py +0 -27
  76. genelastic/import_data/bi_processes.py +0 -49
  77. genelastic/import_data/cli_gen_data.py +0 -116
  78. genelastic/import_data/cli_import.py +0 -379
  79. genelastic/import_data/cli_info.py +0 -256
  80. genelastic/import_data/cli_validate.py +0 -54
  81. genelastic/import_data/data_file.py +0 -87
  82. genelastic/import_data/filename_pattern.py +0 -57
  83. genelastic/import_data/tags.py +0 -123
  84. genelastic/import_data/wet_process.py +0 -28
  85. genelastic/import_data/wet_processes.py +0 -53
  86. genelastic-0.7.0.dist-info/METADATA +0 -105
  87. genelastic-0.7.0.dist-info/RECORD +0 -40
  88. genelastic-0.7.0.dist-info/WHEEL +0 -5
  89. genelastic-0.7.0.dist-info/entry_points.txt +0 -6
  90. genelastic-0.7.0.dist-info/top_level.txt +0 -1
@@ -0,0 +1,207 @@
1
+ {% extends "layout.html" %}
2
+ {% block title %}
3
+ Accueil - BEx
4
+ {% endblock title %}
5
+ {% block content %}
6
+ <div class="container mt-5">
7
+ <div class="text-center mb-5">
8
+ <h1 class="display-4 fw-bold text-primary">Base Expérimentale du CNRGH</h1>
9
+ </div>
10
+ <!-- Mode d'affichage -->
11
+ <div class="mb-4 d-flex justify-content-end align-items-center gap-3">
12
+ <label class="fw-semibold mb-0">Mode d'affichage :</label>
13
+ <div class="btn-group" role="group" aria-label="Mode d'affichage">
14
+ <button id="viewTableBtn"
15
+ class="btn btn-outline-primary btn-sm active"
16
+ title="Mode Tableau"
17
+ aria-pressed="true">
18
+ <i class="bi bi-table"></i> Tableau
19
+ </button>
20
+ <button id="viewListBtn"
21
+ class="btn btn-outline-primary btn-sm"
22
+ title="Mode Liste"
23
+ aria-pressed="false">
24
+ <i class="bi bi-list"></i> Liste
25
+ </button>
26
+ </div>
27
+ </div>
28
+ {% macro render_section_header(title) %}
29
+ <div class="d-flex justify-content-between align-items-center mt-5 mb-4 pb-2 border-bottom">
30
+ <h2 class="fw-semibold text-secondary">{{ title }}</h2>
31
+ </div>
32
+ {% endmacro %}
33
+ {% macro render_table_section(data_dict, prefix, title) %}
34
+ {{ render_section_header(title) }}
35
+ <div class="row g-4 justify-content-center">
36
+ {% for field, values in data_dict.items() %}
37
+ {% set clean_name = field.replace('metadata.bi_process.', '')
38
+ .replace('metadata.wet_process.', '')
39
+ .replace('metadata.', '')
40
+ .replace('_', ' ')
41
+ .replace('.', ' ')
42
+ .title() %}
43
+ {% set html_id = prefix + clean_name.replace(' ', '') %}
44
+ {% set canvas_id = 'chart_' + html_id %}
45
+ {% set is_empty = values|length == 0 %}
46
+ <div class="col-12 col-md-6 col-lg-4 metadata-table"
47
+ id="{{ html_id }}"
48
+ data-label="{{ clean_name }}">
49
+ <div class="card shadow-sm h-100 {% if is_empty %}opacity-50{% endif %}">
50
+ <div class="card-header text-white fw-semibold text-center bg-primary d-flex justify-content-between align-items-center">
51
+ <span>{{ clean_name }}</span>
52
+ </div>
53
+ <div class="card-body p-3 d-flex flex-column justify-content-center align-items-center">
54
+ {% if not is_empty %}
55
+ {% if values|length > 1 %}
56
+ <canvas id="{{ canvas_id }}"
57
+ width="300"
58
+ height="300"
59
+ data-label="{{ clean_name }}"
60
+ data-labels='{{ values.keys() | list | tojson }}'
61
+ data-values='{{ values.values() | list | tojson }}'></canvas>
62
+ {% else %}
63
+ {% for value, count_val in values.items() %}
64
+ <div class="text-center">
65
+ <p class="mb-1 fw-semibold">{{ value or "—" }}</p>
66
+ <span class="badge bg-primary rounded-pill fs-5">{{ count_val }}</span>
67
+ </div>
68
+ {% endfor %}
69
+ {% endif %}
70
+ {% else %}
71
+ <p class="text-muted text-center my-3">Aucune donnée disponible</p>
72
+ {% endif %}
73
+ </div>
74
+ </div>
75
+ </div>
76
+ {% endfor %}
77
+ </div>
78
+ {% endmacro %}
79
+ {% macro render_list_section(data_dict, prefix, title) %}
80
+ {{ render_section_header(title) }}
81
+ <div class="d-flex justify-content-end align-items-center gap-2 mb-3">
82
+ <button class="btn btn-outline-secondary btn-sm expand-all-btn"
83
+ data-target="{{ prefix }}">Dérouler tout</button>
84
+ <button class="btn btn-outline-secondary btn-sm collapse-all-btn"
85
+ data-target="{{ prefix }}">Replier tout</button>
86
+ </div>
87
+ <div class="accordion mb-5" id="accordion_{{ prefix }}">
88
+ {% for field, values in data_dict.items() %}
89
+ {% set clean_name = field.replace('metadata.', '').replace('_', ' ').replace('.', ' ').title() %}
90
+ {% set collapse_id = prefix + clean_name.replace(' ', '') %}
91
+ {% set is_empty = values|length == 0 %}
92
+ <div class="accordion-item {% if is_empty %}opacity-50{% endif %}">
93
+ <h2 class="accordion-header" id="heading_{{ collapse_id }}">
94
+ <button class="accordion-button collapsed"
95
+ type="button"
96
+ data-bs-toggle="collapse"
97
+ data-bs-target="#collapse_{{ collapse_id }}"
98
+ aria-expanded="false"
99
+ aria-controls="collapse_{{ collapse_id }}">{{ clean_name }}</button>
100
+ </h2>
101
+ <div id="collapse_{{ collapse_id }}"
102
+ class="accordion-collapse collapse"
103
+ aria-labelledby="heading_{{ collapse_id }}"
104
+ data-bs-parent="#accordion_{{ prefix }}">
105
+ <div class="accordion-body">
106
+ {% if not is_empty %}
107
+ <ul class="list-group">
108
+ {% for value, count_val in values.items() %}
109
+ <li class="list-group-item d-flex justify-content-between align-items-center">
110
+ {% if 'wet_process' in field %}
111
+ <a href="{{ url_for('routes.wet_process_detail', wet_process_id=value) }}">{{ value or "—" }}</a>
112
+ {% elif 'bi_process' in field %}
113
+ <a href="{{ url_for('routes.bi_process_detail', bi_process_id=value) }}">{{ value or "—" }}</a>
114
+ {% else %}
115
+ {{ value or "—" }}
116
+ {% endif %}
117
+ <span class="badge bg-primary rounded-pill">{{ count_val }}</span>
118
+ </li>
119
+ {% endfor %}
120
+ </ul>
121
+ {% else %}
122
+ <p class="text-muted text-center">Aucune donnée disponible</p>
123
+ {% endif %}
124
+ </div>
125
+ </div>
126
+ </div>
127
+ {% endfor %}
128
+ </div>
129
+ {% endmacro %}
130
+ {% if wet_count or bi_count %}
131
+ <!-- Boutons toggle groupes -->
132
+ <div class="d-flex flex-wrap gap-2 mb-3">
133
+ <button id="toggle-all-filters"
134
+ class="btn btn-outline-primary btn-sm toggle-group-btn">Tout masquer</button>
135
+ <button id="toggle-wet"
136
+ class="btn btn-outline-secondary btn-sm toggle-group-btn"
137
+ data-label="Index Wet-Processes">Masquer Index Wet-Processes</button>
138
+ <button id="toggle-bi"
139
+ class="btn btn-outline-secondary btn-sm toggle-group-btn"
140
+ data-label="Index Bi-Processes">Masquer Index Bi-Processes</button>
141
+ </div>
142
+ <!-- Switchs pour métadonnées -->
143
+ <div class="row gy-3">
144
+ {% for data_dict, prefix in [(wet_count, 'table_wet_'), (bi_count, 'table_bi_')] %}
145
+ {% for field, values in data_dict.items() %}
146
+ {% set clean_name = field.replace('metadata.bi_process.', '')
147
+ .replace('metadata.wet_process.', '')
148
+ .replace('metadata.', '')
149
+ .replace('_', ' ')
150
+ .replace('.', ' ')
151
+ .title() %}
152
+ {% set html_id = prefix + clean_name.replace(' ', '') %}
153
+ {% set is_empty = values|length == 0 %}
154
+ <div class="col-6 col-md-4 col-lg-3">
155
+ <div class="form-check form-switch {% if is_empty %}opacity-50{% endif %}">
156
+ <input class="form-check-input meta-toggle"
157
+ type="checkbox"
158
+ id="toggle_{{ html_id }}"
159
+ data-target="{{ html_id }}"
160
+ {% if not is_empty %}checked{% endif %}>
161
+ <label class="form-check-label" for="toggle_{{ html_id }}">{{ clean_name }}</label>
162
+ </div>
163
+ </div>
164
+ {% endfor %}
165
+ {% endfor %}
166
+ </div>
167
+ <!-- Vue tableau -->
168
+ <div id="tableView">
169
+ {{ render_table_section(wet_count, 'table_wet_', 'Index Wet-Processes') }}
170
+ {{ render_table_section(bi_count, 'table_bi_', 'Index Bi-Processes') }}
171
+ </div>
172
+ <!-- Vue liste (initialement masquée) -->
173
+ <div id="listView" style="display:none;">
174
+ {{ render_list_section(wet_count, 'list_wet_', 'Index Wet-Processes') }}
175
+ {{ render_list_section(bi_count, 'list_bi_', 'Index Bi-Processes') }}
176
+ </div>
177
+ {% else %}
178
+ <p class="text-center text-muted fs-5">Aucune donnée de métadonnées disponible pour le moment.</p>
179
+ {% endif %}
180
+ <!-- Modal graphique occurrences -->
181
+ <div id="chartModal" class="modal-overlay" style="display:none;">
182
+ <div class="modal-content"
183
+ role="dialog"
184
+ aria-modal="true"
185
+ aria-labelledby="modalTitle">
186
+ <button id="closeModalBtn" class="btn-close" aria-label="Fermer">&times;</button>
187
+ <h4 id="modalTitle" class="mb-3 text-center">Répartition des occurrences</h4>
188
+ <div class="d-flex gap-3 align-items-start">
189
+ <canvas id="occurrencesChart" width="300" height="300"></canvas>
190
+ <div>
191
+ <div class="mb-3 text-center">
192
+ <button id="toggleChartTypeBtn" class="btn btn-outline-secondary btn-sm">Changer le type de diagramme</button>
193
+ </div>
194
+ <div id="chartLegend" class="chart-legend"></div>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ <!-- Injection des données en JS -->
201
+ <script>
202
+ const metadataCountsWet = {{ wet_count | tojson }};
203
+ const metadataCountsBi = {{ bi_count | tojson }};
204
+ </script>
205
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
206
+ <script src="{{ url_for('static', filename='js/ui.js') }}"></script>
207
+ {% endblock content %}
@@ -0,0 +1,153 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" xmlns="http://www.w3.org/1999/html">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="description" content="BEx : Base Expérimentale" />
7
+ <meta name="keywords"
8
+ content="bex, base expérimentale, wet processes, bi processes, analyses" />
9
+ <title>
10
+ {% block title %}
11
+ Genelastic
12
+ {% endblock title %}
13
+ </title>
14
+ <link rel="icon"
15
+ href="{{ url_for('static', filename='cea.ico') }}"
16
+ type="image/x-icon" />
17
+ <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap"
18
+ rel="stylesheet" />
19
+ <link href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.3/dist/flatly/bootstrap.min.css"
20
+ rel="stylesheet" />
21
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"
22
+ rel="stylesheet" />
23
+ <link rel="stylesheet"
24
+ type="text/css"
25
+ href="{{ url_for('static', filename='style.css') }}" />
26
+ </head>
27
+ <body class="bg-light">
28
+ <!-- Navbar -->
29
+ <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
30
+ <div class="container-fluid d-flex justify-content-between align-items-center">
31
+ <a class="navbar-brand d-flex align-items-center"
32
+ href="{{ url_for('routes.home') }}">
33
+ <img src="{{ url_for('static', filename='cea-cnrgh.ico') }}"
34
+ alt="Logo"
35
+ class="logo-navbar me-2" />
36
+ </a>
37
+ <!-- Onglets normaux -->
38
+ <div class="collapse navbar-collapse">
39
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
40
+ <li class="nav-item">
41
+ <a class="nav-link" href="{{ url_for('routes.show_analyses') }}">Analyses</a>
42
+ </li>
43
+ <li class="nav-item">
44
+ <a class="nav-link" href="{{ url_for('routes.show_wet_processes') }}">Conditions expérimentales</a>
45
+ </li>
46
+ <li class="nav-item">
47
+ <a class="nav-link" href="{{ url_for('routes.show_bi_processes') }}">Conditions
48
+ bio-informatiques</a>
49
+ </li>
50
+ <li class="nav-item">
51
+ <a class="nav-link" href="{{ url_for('routes.explorer') }}">Explorer</a>
52
+ </li>
53
+ <li class="nav-item">
54
+ <a class="nav-link" href="{{ url_for('routes.version') }}">Version</a>
55
+ </li>
56
+ </ul>
57
+ </div>
58
+ <!-- Bouton burger toujours visible -->
59
+ <button id="burgerBtn"
60
+ class="btn btn-dark"
61
+ type="button"
62
+ aria-expanded="false"
63
+ aria-label="Toggle navigation">
64
+ <i class="bi bi-list" style="font-size: 1.5rem;"></i>
65
+ </button>
66
+ </div>
67
+ </nav>
68
+ <!-- Menu burger (panel fixe à droite, caché par défaut) -->
69
+ <div id="burgerMenu"
70
+ class="collapse bg-dark text-white shadow-lg position-fixed top-0 end-0 vh-100"
71
+ style="width: 250px;
72
+ z-index: 1050;
73
+ overflow-y: auto;
74
+ display:none">
75
+ <div class="p-3">
76
+ <button id="closeBurgerMenu" class="btn btn-outline-light mb-3 w-100">Fermer &times;</button>
77
+ <button onclick="location.href='{{ url_for('routes.show_analyses') }}'"
78
+ class="btn btn-link text-white border-bottom w-100 text-start">Analyses</button>
79
+ <button onclick="location.href='{{ url_for('routes.show_wet_processes') }}'"
80
+ class="btn btn-link text-white border-bottom w-100 text-start">Conditions expérimentales</button>
81
+ <button onclick="location.href='{{ url_for('routes.show_bi_processes') }}'"
82
+ class="btn btn-link text-white border-bottom w-100 text-start">Conditions bio-informatiques</button>
83
+ <button onclick="location.href='{{ url_for('routes.explorer') }}'"
84
+ class="btn btn-link text-white border-bottom w-100 text-start">Explorer</button>
85
+ <button onclick="location.href='{{ url_for('routes.version') }}'"
86
+ class="btn btn-link text-white w-100 text-start">Version</button>
87
+ </div>
88
+ </div>
89
+ <div class="container py-4">
90
+ {% block content %}
91
+ {% endblock content %}
92
+ </div>
93
+ <a href="#"
94
+ id="backToTop"
95
+ class="btn btn-primary position-fixed arrow-back-to-top">
96
+ <i class="bi bi-arrow-up"></i>
97
+ </a>
98
+ <script type="text/javascript" src="/static/ui.js"></script>
99
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
100
+ <script>
101
+ const backToTopBtn = document.getElementById('backToTop');
102
+
103
+ window.addEventListener('scroll', () => {
104
+ if (window.scrollY > 50) {
105
+ backToTopBtn.style.display = 'inline-block';
106
+ } else {
107
+ backToTopBtn.style.display = 'none';
108
+ }
109
+ });
110
+
111
+ backToTopBtn.addEventListener('click', (e) => {
112
+ e.preventDefault();
113
+ window.scrollTo({
114
+ top: 0,
115
+ behavior: 'smooth'
116
+ });
117
+ });
118
+
119
+ // Menu burger
120
+ const burgerBtn = document.getElementById('burgerBtn');
121
+ const burgerMenu = document.getElementById('burgerMenu');
122
+ const closeBtn = document.getElementById('closeBurgerMenu');
123
+
124
+ burgerBtn.addEventListener('click', () => {
125
+ if (burgerMenu.style.display === 'block') {
126
+ burgerMenu.style.display = 'none';
127
+ burgerBtn.setAttribute('aria-expanded', 'false');
128
+ } else {
129
+ burgerMenu.style.display = 'block';
130
+ burgerBtn.setAttribute('aria-expanded', 'true');
131
+ }
132
+ });
133
+
134
+ closeBtn.addEventListener('click', () => {
135
+ burgerMenu.style.display = 'none';
136
+ burgerBtn.setAttribute('aria-expanded', 'false');
137
+ });
138
+
139
+ // Fermer le menu burger si clic en dehors
140
+ window.addEventListener('click', (e) => {
141
+ if (!burgerMenu.contains(e.target) && !burgerBtn.contains(e.target)) {
142
+ burgerMenu.style.display = 'none';
143
+ burgerBtn.setAttribute('aria-expanded', 'false');
144
+ }
145
+ });
146
+ </script>
147
+ <footer class="footer mt-auto py-3 text-white">
148
+ <div class="container text-center">
149
+ <span>&copy; 2025 CEA/CNRGH · Base Expérimentale · Tous droits réservés</span>
150
+ </div>
151
+ </footer>
152
+ </body>
153
+ </html>
@@ -0,0 +1,21 @@
1
+ {% extends "layout.html" %}
2
+ {% block title %}
3
+ Version - BEx
4
+ {% endblock title %}
5
+ {% block content %}
6
+ <div class="container mt-5">
7
+ <div class="card shadow-sm text-center">
8
+ <div class="card-body">
9
+ <h2 class="mb-3">Version de Genelastic</h2>
10
+ <p class="lead mb-4">
11
+ Version actuelle:
12
+ <strong>{{ version }}</strong>
13
+ </p>
14
+ <a href="https://pypi.org/project/genelastic/"
15
+ class="btn btn-primary btn-lg"
16
+ target="_blank">Télécharger
17
+ Genelastic sur PyPI</a>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ {% endblock content %}
@@ -0,0 +1,131 @@
1
+ {% extends "layout.html" %}
2
+ {% block title %}
3
+ Détails - {{ proc_id }} - BEx
4
+ {% endblock title %}
5
+ {% block content %}
6
+ <div class="container mt-5">
7
+ <h2 class="mb-4 text-center text-primary">
8
+ Détails de la condition
9
+ <span class="condition-details">{{ proc_id }}</span>
10
+ </h2>
11
+ {% if not error %}
12
+ <!-- Formulaire de sélection pour comparaison -->
13
+ <form method="get" class="mb-4 d-flex align-items-center gap-2">
14
+ <label for="compare_with" class="form-label m-0">
15
+ Comparer la sélection avec une condition bio-expérimentale
16
+ :
17
+ </label>
18
+ <select name="compare_with" id="compare_with" class="form-select w-auto ">
19
+ <option value="">----------</option>
20
+ {% for bp in bi_processes %}
21
+ <option value="{{ bp }}" {% if selected_bi== bp %}selected{% endif %}>{{ bp }}</option>
22
+ {% endfor %}
23
+ </select>
24
+ <button type="submit" class="btn btn-primary">Comparer</button>
25
+ </form>
26
+ <div class="row">
27
+ <!-- Wet Process -->
28
+ <div class="col-md-6 mb-4">
29
+ <div class="card shadow-lg border-0 h-100">
30
+ <div class="card-body">
31
+ <h5 class="card-title text-center">Wet Process</h5>
32
+ <div class="table-responsive">
33
+ <table class="details-table">
34
+ <thead>
35
+ <tr class="text-center">
36
+ <th scope="col">Champ</th>
37
+ <th scope="col">Valeur</th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ {% for key, value in wet_process.items() %}
42
+ <tr>
43
+ <td class="fw-bold text-capitalize">{{ key.replace('_', ' ') }}</td>
44
+ <td>{{ value }}</td>
45
+ </tr>
46
+ {% endfor %}
47
+ </tbody>
48
+ </table>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ <!-- Bi Process (s’il y en a un sélectionné) -->
54
+ {% if bi_process_data %}
55
+ <div class="col-md-6 mb-4">
56
+ <div class="card shadow-lg border-0 h-100">
57
+ <div class="card-body">
58
+ <h5 class="card-title text-center">
59
+ Bi Process : <span class="select-compared-process">{{ selected_bi }}</span>
60
+ </h5>
61
+ {% if bi_process_data.error %}
62
+ <div class="alert alert-danger text-center">{{ bi_process_data.error }}</div>
63
+ {% else %}
64
+ <div class="table-responsive">
65
+ <table class="details-table">
66
+ <thead>
67
+ <tr class="text-center">
68
+ <th scope="col">Champ</th>
69
+ <th scope="col">Valeur</th>
70
+ </tr>
71
+ </thead>
72
+ <tbody>
73
+ {% for key, value in bi_process_data.items() %}
74
+ {% if key == 'steps' %}
75
+ <tr>
76
+ <td class="fw-bold text-capitalize">Steps</td>
77
+ <td>
78
+ <button class="btn btn-sm btn-primary"
79
+ type="button"
80
+ data-bs-toggle="collapse"
81
+ data-bs-target="#biStepsCollapse-{{ proc_id }}"
82
+ aria-expanded="false"
83
+ aria-controls="biStepsCollapse-{{ proc_id }}">Afficher les étapes</button>
84
+ <div class="collapse mt-2" id="biStepsCollapse-{{ proc_id }}">
85
+ <div class="table-responsive">
86
+ <table class="details-table table-sm mt-2 text-center"
87
+ style="min-width: 600px">
88
+ <thead>
89
+ <tr>
90
+ <th>Commande</th>
91
+ <th>Nom</th>
92
+ <th>Version</th>
93
+ <th>Sortie</th>
94
+ </tr>
95
+ </thead>
96
+ <tbody>
97
+ {% for step in value %}
98
+ <tr>
99
+ <td>{{ step.cmd }}</td>
100
+ <td>{{ step.name }}</td>
101
+ <td>{{ step.version }}</td>
102
+ <td>{{ step.output if 'output' in step else '-' }}</td>
103
+ </tr>
104
+ {% endfor %}
105
+ </tbody>
106
+ </table>
107
+ </div>
108
+ </div>
109
+ </td>
110
+ </tr>
111
+ {% else %}
112
+ <tr>
113
+ <td class="fw-bold text-capitalize">{{ key.replace('_', ' ') }}</td>
114
+ <td>{{ value }}</td>
115
+ </tr>
116
+ {% endif %}
117
+ {% endfor %}
118
+ </tbody>
119
+ </table>
120
+ </div>
121
+ {% endif %}
122
+ </div>
123
+ </div>
124
+ </div>
125
+ {% endif %}
126
+ </div>
127
+ {% else %}
128
+ <div class="p-3 mb-2 bg-danger text-white text-center" role="alert">{{ error.message }}</div>
129
+ {% endif %}
130
+ </div>
131
+ {% endblock content %}
@@ -0,0 +1,116 @@
1
+ {% extends "layout.html" %}
2
+ {% block title %}
3
+ Wet Processes - BEx
4
+ {% endblock title %}
5
+ {% block content %}
6
+ <div class="container mt-5">
7
+ <div class="text-center mb-4">
8
+ <h1 class="display-5 title">Liste des conditions expérimentales</h1>
9
+ <p class="lead">Sélectionner les conditions pour afficher les analyses associées.</p>
10
+ </div>
11
+ <div class="card shadow-sm">
12
+ <div class="card-body">
13
+ <h3 class="mb-4">Conditions disponibles</h3>
14
+ <form id="wetForm" method="get" action="{{ request.path }}">
15
+ <input type="text"
16
+ id="wetSearch"
17
+ class="form-control mb-3"
18
+ placeholder="Rechercher un wet process..." />
19
+ <div class="row">
20
+ <div class="col-md-6">
21
+ <div class="list-group" id="wetProcessList">
22
+ {% for wet_process in wet_processes %}
23
+ <div class="list-group-item wet-process-item">
24
+ <div class="form-check">
25
+ <input class="form-check-input"
26
+ type="checkbox"
27
+ id="wet_{{ loop.index }}"
28
+ name="wet_processes"
29
+ value="{{ wet_process }}"
30
+ {% if wet_process in selected_wet_processes %}checked{% endif %} />
31
+ <label class="form-check-label" for="wet_{{ loop.index }}">{{ wet_process }}</label>
32
+ </div>
33
+ </div>
34
+ {% endfor %}
35
+ </div>
36
+ <div class="d-flex gap-2 mt-3">
37
+ <button type="button" class="reset-error-btn btn" id="resetWetSelection">Réinitialiser la sélection</button>
38
+ <button type="button" class="btn btn-success" id="selectAllWet">Tout sélectionner</button>
39
+ </div>
40
+ </div>
41
+ {% if selected_wet_processes %}
42
+ <div class="col-md-6">
43
+ {% if analyses %}
44
+ <h4 class="mt-4">Analyses associées :</h4>
45
+ <ul class="list-group mt-3">
46
+ {% for analysis in analyses %}
47
+ <li class="list-group-item">
48
+ <a href="{{ url_for('routes.show_analysis_detail', analysis_id=analysis, source='wet') }}"
49
+ class="text-decoration-none">
50
+ <div class="d-flex justify-content-between align-items-center">
51
+ <span>{{ analysis }}</span>
52
+ <i class="bi bi-chevron-right"></i>
53
+ </div>
54
+ </a>
55
+ </li>
56
+ {% endfor %}
57
+ </ul>
58
+ {% else %}
59
+ <div class="alert alert-danger mt-4" role="alert">
60
+ <strong>Aucune analyse correspondante !</strong>
61
+ Aucun processus associé à ce Wet Process.
62
+ </div>
63
+ {% endif %}
64
+ </div>
65
+ {% endif %}
66
+ </div>
67
+ </form>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ <script>
72
+ document.getElementById('wetSearch').addEventListener('input', function() {
73
+ const filter = this.value.toLowerCase();
74
+ const items = document.querySelectorAll('.wet-process-item');
75
+ items.forEach((item) => {
76
+ const label = item.querySelector('label').innerText.toLowerCase();
77
+ if (label.startsWith(filter)) {
78
+ item.style.display = '';
79
+ } else {
80
+ item.style.display = 'none';
81
+ }
82
+ });
83
+ });
84
+
85
+ document
86
+ .getElementById('resetWetSelection')
87
+ .addEventListener('click', function() {
88
+ const checkboxes = document.querySelectorAll(
89
+ '#wetForm input[type="checkbox"]',
90
+ );
91
+ checkboxes.forEach((cb) => (cb.checked = false));
92
+ document.getElementById('wetSearch').value = '';
93
+ document.getElementById('wetForm').submit();
94
+ });
95
+
96
+ document
97
+ .getElementById('selectAllWet')
98
+ .addEventListener('click', function() {
99
+ document
100
+ .querySelectorAll('#wetProcessList .form-check-input')
101
+ .forEach((cb) => (cb.checked = true));
102
+ document.getElementById('wetForm').submit();
103
+ });
104
+
105
+ const wetCheckboxes = document.querySelectorAll('#wetForm input[type="checkbox"]');
106
+ const resetWetBtn = document.getElementById('resetWetSelection');
107
+
108
+ function updateWetResetButton() {
109
+ const anyChecked = Array.from(wetCheckboxes).some(cb => cb.checked);
110
+ resetWetBtn.disabled = !anyChecked;
111
+ }
112
+
113
+ wetCheckboxes.forEach(cb => cb.addEventListener('change', updateWetResetButton));
114
+ updateWetResetButton();
115
+ </script>
116
+ {% endblock content %}