hlsv 1.0.0 → 2.0.0

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -3
  3. data/README.md +36 -29
  4. data/lib/hlsv/analysis_registry.rb +47 -0
  5. data/lib/hlsv/analysis_runner.rb +193 -0
  6. data/lib/hlsv/config_manager.rb +125 -0
  7. data/lib/hlsv/html2word.rb +18 -18
  8. data/lib/hlsv/path_guard.rb +32 -0
  9. data/lib/hlsv/sdtm_validation/dataset.rb +169 -0
  10. data/lib/hlsv/sdtm_validation/define.rb +142 -0
  11. data/lib/hlsv/sdtm_validation/report.rb +357 -0
  12. data/lib/hlsv/sdtm_validation.rb +390 -0
  13. data/lib/hlsv/url_helper.rb +28 -0
  14. data/lib/hlsv/version.rb +1 -1
  15. data/lib/hlsv/web_app.rb +264 -418
  16. data/lib/hlsv.rb +12 -5
  17. data/public/css/accessibility/accessibility.css +33 -0
  18. data/public/css/base/layout.css +33 -0
  19. data/public/css/base/reset.css +23 -0
  20. data/public/css/base/typography.css +31 -0
  21. data/public/css/components/buttons.css +142 -0
  22. data/public/css/components/file-tree.css +107 -0
  23. data/public/css/components/footer.css +43 -0
  24. data/public/css/components/forms.css +56 -0
  25. data/public/css/components/header.css +52 -0
  26. data/public/css/components/status.css +56 -0
  27. data/public/css/features/csv-table.css +204 -0
  28. data/public/css/features/file-browser.css +208 -0
  29. data/public/css/responsive/responsive.css +133 -0
  30. data/public/css/styles.css +25 -0
  31. data/public/css/styles_csv.css +23 -0
  32. data/public/favicon.ico +0 -0
  33. data/public/js/analysis.js +201 -0
  34. data/public/js/app.js +63 -0
  35. data/public/js/browser.js +172 -0
  36. data/public/js/config.js +214 -0
  37. data/public/js/results.js +240 -0
  38. data/public/js/utils.js +57 -0
  39. data/views/csv_view.erb +11 -12
  40. data/views/index.erb +70 -19
  41. data/views/{report_template.erb → report.erb} +203 -188
  42. metadata +39 -41
  43. data/lib/hlsv/find_keys.rb +0 -979
  44. data/lib/hlsv/mon_script.rb +0 -169
  45. data/public/app.js +0 -569
  46. data/public/styles.css +0 -586
  47. data/public/styles_csv.css +0 -448
@@ -0,0 +1,204 @@
1
+ /*
2
+ Copyright (c) 2026 AdClin
3
+ Licensed under the GNU Affero General Public License v3.0 or later.
4
+ See the LICENSE file for details.
5
+ */
6
+
7
+ /* ===========================
8
+ PAGE OVERRIDE
9
+ This page needs the full viewport width for wide tables,
10
+ unlike the constrained 1200px layout of the main app (base/reset.css)
11
+ =========================== */
12
+ body {
13
+ max-width: none;
14
+ margin: 0;
15
+ }
16
+
17
+ /* ===========================
18
+ INFO SECTION - CSV color variant
19
+ (structure/padding/border-radius come from base/layout.css .info;
20
+ only the color scheme is overridden here for this page)
21
+ =========================== */
22
+ .info {
23
+ background: linear-gradient(135deg, #e8f4f8 0%, #d1ecf1 100%);
24
+ border-left-color: #3498db;
25
+ box-shadow: 0 2px 4px rgba(52, 152, 219, 0.1);
26
+ }
27
+
28
+ .info p {
29
+ font-weight: normal;
30
+ margin: 10px 0;
31
+ line-height: 1.6;
32
+ }
33
+
34
+ .info strong {
35
+ color: #2980b9;
36
+ }
37
+
38
+ /* ===========================
39
+ EXPORT BUTTON
40
+ =========================== */
41
+ .btn-export {
42
+ display: inline-flex;
43
+ align-items: center;
44
+ gap: 8px;
45
+ padding: 12px 24px;
46
+ border: none;
47
+ border-radius: 6px;
48
+ background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
49
+ color: white;
50
+ font-size: 14px;
51
+ font-weight: 600;
52
+ text-decoration: none;
53
+ box-shadow: 0 4px 8px rgba(0, 123, 255, 0.2);
54
+ transition: all 0.3s ease;
55
+ cursor: pointer;
56
+ }
57
+
58
+ .btn-export:hover {
59
+ background: linear-gradient(135deg, #0056b3 0%, #004085 100%);
60
+ transform: translateY(-2px);
61
+ box-shadow: 0 6px 12px rgba(0, 123, 255, 0.3);
62
+ }
63
+
64
+ .btn-export:active {
65
+ transform: translateY(0);
66
+ }
67
+
68
+ /* ===========================
69
+ TABLE CONTAINER
70
+ =========================== */
71
+ .table-container {
72
+ background: white;
73
+ border-radius: 8px;
74
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
75
+ overflow: auto;
76
+ max-height: calc(100vh - 450px);
77
+ }
78
+
79
+ /* ===========================
80
+ TABLE
81
+ =========================== */
82
+ table {
83
+ border-collapse: collapse;
84
+ width: max-content;
85
+ min-width: 100%;
86
+ font-size: 13px;
87
+ }
88
+
89
+ th, td {
90
+ border: 1px solid #dee2e6;
91
+ padding: 10px 12px;
92
+ white-space: nowrap;
93
+ text-align: left;
94
+ }
95
+
96
+ thead th {
97
+ background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%);
98
+ color: #2c3e50;
99
+ font-weight: 600;
100
+ position: sticky;
101
+ top: 0;
102
+ z-index: 10;
103
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
104
+ }
105
+
106
+ thead th:first-child {
107
+ border-top-left-radius: 8px;
108
+ }
109
+
110
+ thead th:last-child {
111
+ border-top-right-radius: 8px;
112
+ }
113
+
114
+ /* ===========================
115
+ TABLE BODY - ALTERNATING GROUPS
116
+ =========================== */
117
+ tbody tr {
118
+ transition: all 0.2s ease;
119
+ }
120
+
121
+ tbody tr:hover {
122
+ background-color: #f8f9fa !important;
123
+ transform: scale(1.01);
124
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
125
+ }
126
+
127
+ tr.even-group {
128
+ background-color: #ffffff;
129
+ }
130
+
131
+ tr.odd-group {
132
+ background-color: #e3f2fd;
133
+ }
134
+
135
+ tbody td {
136
+ color: #495057;
137
+ }
138
+
139
+ tbody td:first-child {
140
+ font-weight: 600;
141
+ color: #2c3e50;
142
+ background-color: rgba(52, 152, 219, 0.05);
143
+ }
144
+
145
+ /* ===========================
146
+ RESPONSIVE - table-specific
147
+ (layout-level responsive rules for header/footer/etc. already
148
+ come from responsive/responsive.css via the shared components)
149
+ =========================== */
150
+ @media (max-width: 768px) {
151
+ .info {
152
+ padding: 15px;
153
+ }
154
+
155
+ table {
156
+ font-size: 11px;
157
+ }
158
+
159
+ th, td {
160
+ padding: 8px 10px;
161
+ }
162
+
163
+ .btn-export {
164
+ width: 100%;
165
+ justify-content: center;
166
+ }
167
+
168
+ .table-container {
169
+ max-height: calc(100vh - 500px);
170
+ }
171
+ }
172
+
173
+ @media (max-width: 480px) {
174
+ table {
175
+ font-size: 10px;
176
+ }
177
+
178
+ th, td {
179
+ padding: 6px 8px;
180
+ }
181
+ }
182
+
183
+ /* ===========================
184
+ ACCESSIBILITY - table-specific
185
+ =========================== */
186
+ @media (prefers-contrast: high) {
187
+ table {
188
+ border: 2px solid #000;
189
+ }
190
+
191
+ th, td {
192
+ border: 1px solid #000;
193
+ }
194
+
195
+ .btn-export {
196
+ border: 2px solid #000;
197
+ }
198
+ }
199
+
200
+ .table-container:focus-visible,
201
+ .btn-export:focus-visible {
202
+ outline: 2px solid #007bff;
203
+ outline-offset: 2px;
204
+ }
@@ -0,0 +1,208 @@
1
+ /* ===========================
2
+ FILE/DIRECTORY BROWSER MODAL
3
+ (used by the "Browse" buttons next to Datasets Directory
4
+ and Path to define.xml)
5
+ =========================== */
6
+ .config-header-row {
7
+ display: flex;
8
+ justify-content: space-between;
9
+ align-items: center;
10
+ flex-wrap: wrap;
11
+ gap: 10px;
12
+ border-bottom: 3px solid #007bff;
13
+ padding-bottom: 15px;
14
+ margin-bottom: 20px;
15
+ }
16
+
17
+ .config-header-row h2 {
18
+ border-bottom: none;
19
+ padding-bottom: 0;
20
+ margin-bottom: 0;
21
+ }
22
+
23
+ .section-header-row {
24
+ display: flex;
25
+ justify-content: space-between;
26
+ align-items: center;
27
+ flex-wrap: wrap;
28
+ gap: 10px;
29
+ margin-top: 20px;
30
+ margin-bottom: 10px;
31
+ }
32
+
33
+ .section-header-row h3 {
34
+ margin: 0;
35
+ }
36
+
37
+ .quick-actions {
38
+ display: flex;
39
+ gap: 8px;
40
+ flex-wrap: wrap;
41
+ }
42
+
43
+ .form-label-row {
44
+ display: flex;
45
+ justify-content: space-between;
46
+ align-items: center;
47
+ gap: 10px;
48
+ margin-bottom: 8px;
49
+ }
50
+
51
+ .form-label-row label {
52
+ margin-bottom: 0;
53
+ }
54
+
55
+ button.btn-browse-link {
56
+ background: none;
57
+ border: none;
58
+ box-shadow: none;
59
+ color: #007bff;
60
+ font-size: 0.85em;
61
+ font-weight: 600;
62
+ padding: 4px 8px;
63
+ margin: 0;
64
+ border-radius: 4px;
65
+ cursor: pointer;
66
+ white-space: nowrap;
67
+ flex-shrink: 0;
68
+ }
69
+
70
+ button.btn-browse-link:hover {
71
+ background: #eef4ff;
72
+ color: #0056b3;
73
+ text-decoration: underline;
74
+ transform: none;
75
+ box-shadow: none;
76
+ }
77
+
78
+ .modal-overlay {
79
+ position: fixed;
80
+ inset: 0;
81
+ background: rgba(0, 0, 0, 0.5);
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ z-index: 1000;
86
+ }
87
+
88
+ .modal-box {
89
+ background: white;
90
+ border-radius: 8px;
91
+ width: 90%;
92
+ max-width: 600px;
93
+ max-height: 80vh;
94
+ display: flex;
95
+ flex-direction: column;
96
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
97
+ }
98
+
99
+ .modal-header {
100
+ display: flex;
101
+ justify-content: space-between;
102
+ align-items: center;
103
+ padding: 15px 20px;
104
+ border-bottom: 2px solid #e9ecef;
105
+ }
106
+
107
+ .modal-header h3 {
108
+ margin: 0;
109
+ color: #333;
110
+ font-size: 1.2em;
111
+ }
112
+
113
+ .modal-close {
114
+ background: none;
115
+ border: none;
116
+ box-shadow: none;
117
+ font-size: 1.1em;
118
+ cursor: pointer;
119
+ color: #6c757d;
120
+ padding: 4px 8px;
121
+ margin: 0;
122
+ }
123
+
124
+ .modal-close:hover {
125
+ color: #333;
126
+ transform: none;
127
+ box-shadow: none;
128
+ }
129
+
130
+ .modal-path-bar {
131
+ display: flex;
132
+ gap: 10px;
133
+ align-items: center;
134
+ padding: 12px 20px;
135
+ background: #f8f9fa;
136
+ border-bottom: 1px solid #dee2e6;
137
+ }
138
+
139
+ .modal-path-bar #browse-up {
140
+ cursor: pointer;
141
+ background: linear-gradient(135deg, #6c757d 0%, #545b62 100%);
142
+ color: white;
143
+ border: none;
144
+ border-radius: 6px;
145
+ padding: 6px 12px;
146
+ margin: 0;
147
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
148
+ }
149
+
150
+ .modal-path-bar #browse-up:hover:not(:disabled) {
151
+ background: linear-gradient(135deg, #545b62 0%, #3d4246 100%);
152
+ transform: none;
153
+ }
154
+
155
+ .modal-path-bar input {
156
+ flex: 1;
157
+ padding: 8px 12px;
158
+ border: 2px solid #ddd;
159
+ border-radius: 6px;
160
+ font-family: 'Courier New', monospace;
161
+ font-size: 0.85em;
162
+ background: white;
163
+ color: #6c757d;
164
+ }
165
+
166
+ .modal-list {
167
+ overflow-y: auto;
168
+ flex: 1;
169
+ padding: 8px 0;
170
+ min-height: 200px;
171
+ }
172
+
173
+ .browse-entry {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 10px;
177
+ padding: 10px 20px;
178
+ cursor: pointer;
179
+ transition: background 0.2s ease;
180
+ }
181
+
182
+ .browse-entry:hover {
183
+ background: #f0f4ff;
184
+ }
185
+
186
+ .browse-entry:focus-visible {
187
+ outline: 2px solid #007bff;
188
+ outline-offset: -2px;
189
+ }
190
+
191
+ .modal-footer {
192
+ display: flex;
193
+ justify-content: flex-end;
194
+ gap: 10px;
195
+ padding: 15px 20px;
196
+ border-top: 2px solid #e9ecef;
197
+ }
198
+
199
+ .modal-footer button {
200
+ margin: 0;
201
+ }
202
+
203
+ .browse-empty {
204
+ padding: 30px 20px;
205
+ color: #6c757d;
206
+ text-align: center;
207
+ }
208
+
@@ -0,0 +1,133 @@
1
+
2
+ @media (max-width: 768px) {
3
+ body {
4
+ padding: 10px;
5
+ }
6
+
7
+ .app-header {
8
+ flex-direction: column;
9
+ gap: 15px;
10
+ text-align: center;
11
+ }
12
+
13
+ .app-header h1 {
14
+ font-size: 1.5em;
15
+ }
16
+
17
+ .company-logo {
18
+ height: 40px;
19
+ }
20
+
21
+ .company-name {
22
+ font-size: 1em;
23
+ }
24
+
25
+ .container {
26
+ padding: 20px;
27
+ }
28
+
29
+ .config-grid {
30
+ grid-template-columns: 1fr;
31
+ }
32
+
33
+ h2 {
34
+ font-size: 1.3em;
35
+ }
36
+
37
+ .button-group {
38
+ flex-direction: column;
39
+ }
40
+
41
+ .button-group button {
42
+ width: 100%;
43
+ margin-right: 0;
44
+ }
45
+
46
+ .file-item {
47
+ flex-direction: column;
48
+ align-items: flex-start;
49
+ gap: 10px;
50
+ }
51
+
52
+ .file-item > div:last-child {
53
+ width: 100%;
54
+ }
55
+
56
+ .file-item button {
57
+ width: 100%;
58
+ }
59
+
60
+ .footer-content {
61
+ flex-direction: column;
62
+ gap: 8px;
63
+ }
64
+
65
+ .footer-content .separator {
66
+ display: none;
67
+ }
68
+
69
+ .modal-box {
70
+ width: 95%;
71
+ max-height: 85vh;
72
+ }
73
+ }
74
+
75
+ @media (max-width: 480px) {
76
+ .app-header h1 {
77
+ font-size: 1.3em;
78
+ }
79
+
80
+ button {
81
+ padding: 10px 16px;
82
+ font-size: 13px;
83
+ }
84
+
85
+ .company-logo {
86
+ height: 35px;
87
+ }
88
+ }
89
+
90
+ @media print {
91
+ body {
92
+ background: white;
93
+ padding: 0;
94
+ }
95
+
96
+ .actions {
97
+ display: none;
98
+ }
99
+
100
+ .table-container {
101
+ max-height: none;
102
+ box-shadow: none;
103
+ overflow: visible;
104
+ }
105
+
106
+ thead th {
107
+ position: static;
108
+ box-shadow: none;
109
+ }
110
+
111
+ tbody tr:hover {
112
+ transform: none;
113
+ box-shadow: none;
114
+ }
115
+
116
+ .info {
117
+ page-break-inside: avoid;
118
+ }
119
+
120
+ .app-header,
121
+ .app-footer {
122
+ box-shadow: none;
123
+ }
124
+
125
+ .company-link {
126
+ pointer-events: none;
127
+ }
128
+
129
+ a {
130
+ color: #000;
131
+ text-decoration: underline;
132
+ }
133
+ }
@@ -0,0 +1,25 @@
1
+ /*
2
+ * Copyright (c) 2026 AdClin
3
+ * Licensed under the GNU Affero General Public License v3.0 or later.
4
+ * See the LICENSE file for details.
5
+ *
6
+ * Main stylesheet entry point.
7
+ */
8
+
9
+ @import url("base/reset.css");
10
+
11
+ @import url("components/header.css");
12
+ @import url("components/footer.css");
13
+
14
+ @import url("base/typography.css");
15
+ @import url("base/layout.css");
16
+
17
+ @import url("components/forms.css");
18
+ @import url("components/buttons.css");
19
+ @import url("components/status.css");
20
+ @import url("components/file-tree.css");
21
+
22
+ @import url("features/file-browser.css");
23
+
24
+ @import url("responsive/responsive.css");
25
+ @import url("accessibility/accessibility.css");
@@ -0,0 +1,23 @@
1
+ /*
2
+ * Copyright (c) 2026 AdClin
3
+ * Licensed under the GNU Affero General Public License v3.0 or later.
4
+ * See the LICENSE file for details.
5
+ *
6
+ * Main stylesheet entry point.
7
+ */
8
+
9
+ @import url("base/reset.css");
10
+
11
+ @import url("components/header.css");
12
+ @import url("components/footer.css");
13
+
14
+ @import url("base/typography.css");
15
+ @import url("base/layout.css");
16
+
17
+ @import url("components/buttons.css");
18
+ @import url("components/status.css");
19
+
20
+ @import url("features/csv-table.css");
21
+
22
+ @import url("responsive/responsive.css");
23
+ @import url("accessibility/accessibility.css");
Binary file