solidstats 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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -0
  3. data/README.md +35 -0
  4. data/app/assets/javascripts/solidstats/application.js +257 -0
  5. data/app/assets/javascripts/solidstats/dashboard.js +225 -0
  6. data/app/assets/javascripts/solidstats/gem_metadata.js +554 -0
  7. data/app/assets/stylesheets/solidstats/application.css +6 -1
  8. data/app/assets/stylesheets/solidstats/components/action_button.css +99 -0
  9. data/app/assets/stylesheets/solidstats/components/dashboard.css +151 -0
  10. data/app/assets/stylesheets/solidstats/components/dashboard_header.css +93 -0
  11. data/app/assets/stylesheets/solidstats/components/dashboard_layout.css +97 -0
  12. data/app/assets/stylesheets/solidstats/components/gem_metadata.css +1403 -0
  13. data/app/assets/stylesheets/solidstats/components/navigation.css +80 -0
  14. data/app/assets/stylesheets/solidstats/components/quick_navigation.css +54 -0
  15. data/app/assets/stylesheets/solidstats/components/security.css +332 -0
  16. data/app/assets/stylesheets/solidstats/components/status_badge.css +58 -0
  17. data/app/assets/stylesheets/solidstats/components/summary_card.css +66 -0
  18. data/app/assets/stylesheets/solidstats/components/tab_navigation.css +95 -0
  19. data/app/components/solidstats/base_component.rb +88 -0
  20. data/app/components/solidstats/code_quality/code_quality_section_component.html.erb +0 -0
  21. data/app/components/solidstats/code_quality/code_quality_section_component.rb +0 -0
  22. data/app/components/solidstats/code_quality/section_component.html.erb +45 -0
  23. data/app/components/solidstats/code_quality/section_component.rb +34 -0
  24. data/app/components/solidstats/dashboard_header_component.html.erb +39 -0
  25. data/app/components/solidstats/dashboard_header_component.rb +33 -0
  26. data/app/components/solidstats/previews/action_button_component_preview/button_vs_link.html.erb +6 -0
  27. data/app/components/solidstats/previews/action_button_component_preview/sizes.html.erb +6 -0
  28. data/app/components/solidstats/previews/action_button_component_preview/variants.html.erb +6 -0
  29. data/app/components/solidstats/previews/action_button_component_preview/with_icons.html.erb +6 -0
  30. data/app/components/solidstats/previews/action_button_component_preview.rb +64 -0
  31. data/app/components/solidstats/previews/navigation_component_preview.rb +74 -0
  32. data/app/components/solidstats/previews/stats_overview_component_preview.rb +100 -0
  33. data/app/components/solidstats/previews/status_badge_component_preview/sizes.html.erb +6 -0
  34. data/app/components/solidstats/previews/status_badge_component_preview/statuses.html.erb +6 -0
  35. data/app/components/solidstats/previews/status_badge_component_preview/with_icons.html.erb +6 -0
  36. data/app/components/solidstats/previews/status_badge_component_preview.rb +49 -0
  37. data/app/components/solidstats/previews/summary_card_component_preview/clickable.html.erb +9 -0
  38. data/app/components/solidstats/previews/summary_card_component_preview/dashboard_layout.html.erb +9 -0
  39. data/app/components/solidstats/previews/summary_card_component_preview/statuses.html.erb +6 -0
  40. data/app/components/solidstats/previews/summary_card_component_preview/value_formats.html.erb +6 -0
  41. data/app/components/solidstats/previews/summary_card_component_preview.rb +67 -0
  42. data/app/components/solidstats/quick_navigation_component.html.erb +8 -0
  43. data/app/components/solidstats/quick_navigation_component.rb +21 -0
  44. data/app/components/solidstats/security/gem_impact_analysis_component.html.erb +44 -0
  45. data/app/components/solidstats/security/gem_impact_analysis_component.rb +45 -0
  46. data/app/components/solidstats/security/overview_component.html.erb +21 -0
  47. data/app/components/solidstats/security/overview_component.rb +104 -0
  48. data/app/components/solidstats/security/section_component.html.erb +26 -0
  49. data/app/components/solidstats/security/section_component.rb +52 -0
  50. data/app/components/solidstats/security/timeline_component.html.erb +39 -0
  51. data/app/components/solidstats/security/timeline_component.rb +43 -0
  52. data/app/components/solidstats/tasks_section_component.html.erb +17 -0
  53. data/app/components/solidstats/tasks_section_component.rb +22 -0
  54. data/app/components/solidstats/ui/action_button_component.html.erb +6 -0
  55. data/app/components/solidstats/ui/action_button_component.rb +71 -0
  56. data/app/components/solidstats/ui/dashboard_layout_component.html.erb +19 -0
  57. data/app/components/solidstats/ui/dashboard_layout_component.rb +85 -0
  58. data/app/components/solidstats/ui/navigation_component.html.erb +34 -0
  59. data/app/components/solidstats/ui/navigation_component.rb +72 -0
  60. data/app/components/solidstats/ui/stats_overview_component.html.erb +14 -0
  61. data/app/components/solidstats/ui/stats_overview_component.rb +78 -0
  62. data/app/components/solidstats/ui/status_badge_component.html.erb +6 -0
  63. data/app/components/solidstats/ui/status_badge_component.rb +42 -0
  64. data/app/components/solidstats/ui/summary_card_component.html.erb +12 -0
  65. data/app/components/solidstats/ui/summary_card_component.rb +63 -0
  66. data/app/components/solidstats/ui/tab_navigation_component.html.erb +22 -0
  67. data/app/components/solidstats/ui/tab_navigation_component.rb +79 -0
  68. data/app/controllers/solidstats/dashboard_controller.rb +22 -0
  69. data/app/controllers/solidstats/gem_metadata_controller.rb +12 -0
  70. data/app/helpers/solidstats/application_helper.rb +42 -0
  71. data/app/services/solidstats/gem_metadata/fetcher_service.rb +136 -0
  72. data/app/services/solidstats/log_size_monitor_service.rb +94 -0
  73. data/app/views/layouts/solidstats/application.html.erb +2 -1
  74. data/app/views/solidstats/dashboard/_log_monitor.html.erb +759 -0
  75. data/app/views/solidstats/dashboard/index.html.erb +67 -1323
  76. data/app/views/solidstats/gem_metadata/_panel.html.erb +419 -0
  77. data/config/routes.rb +7 -0
  78. data/lib/generators/solidstats/feature/feature_generator.rb +170 -0
  79. data/lib/generators/solidstats/feature/templates/component.html.erb +84 -0
  80. data/lib/generators/solidstats/feature/templates/component.rb.erb +103 -0
  81. data/lib/generators/solidstats/feature/templates/component.scss +243 -0
  82. data/lib/generators/solidstats/feature/templates/component_test.rb.erb +183 -0
  83. data/lib/generators/solidstats/feature/templates/controller.rb.erb +44 -0
  84. data/lib/generators/solidstats/feature/templates/controller_test.rb.erb +111 -0
  85. data/lib/generators/solidstats/feature/templates/detail_view.html.erb +755 -0
  86. data/lib/generators/solidstats/feature/templates/preview.rb.erb +107 -0
  87. data/lib/generators/solidstats/feature/templates/service.rb.erb +132 -0
  88. data/lib/generators/solidstats/feature/templates/service_test.rb.erb +109 -0
  89. data/lib/generators/solidstats/install_generator.rb +109 -0
  90. data/lib/generators/solidstats/templates/initializer.rb +112 -0
  91. data/lib/solidstats/asset_compatibility.rb +238 -0
  92. data/lib/solidstats/asset_manifest.rb +205 -0
  93. data/lib/solidstats/engine.rb +114 -9
  94. data/lib/solidstats/version.rb +1 -1
  95. data/lib/solidstats.rb +299 -2
  96. data/lib/tasks/solidstats_install.rake +122 -2
  97. metadata +99 -2
@@ -1,1337 +1,81 @@
1
+ <%# Modern Component-Based Dashboard %>
2
+ <%# Refactored from 1,368-line monolithic file to clean ViewComponent architecture %>
3
+
1
4
  <div class="solidstats-dashboard">
2
- <header class="dashboard-header">
3
- <div class="header-main">
4
- <h1><span class="icon">🚥</span> Solidstats Dashboard</h1>
5
- <% created_at = @audit_output.dig("created_at") %>
6
- <span class="audit-date">Last updated: <%= created_at ? DateTime.parse(created_at).strftime("%B %d, %Y at %H:%M") : Time.now.strftime("%B %d, %Y at %H:%M") %></span>
7
- </div>
8
-
9
- <nav class="dashboard-nav">
10
- <ul>
11
- <li><a href="#overview" class="nav-item active" data-section="overview">Overview</a></li>
12
- <li><a href="#security" class="nav-item" data-section="security">Security</a></li>
13
- <li><a href="#code-quality" class="nav-item" data-section="code-quality">Code Quality</a></li>
14
- <li><a href="#tasks" class="nav-item" data-section="tasks">Tasks</a></li>
15
- </ul>
16
-
17
- <div class="dashboard-actions">
18
- <a href="#" class="action-button" onclick="refreshAudit(); return false;">
19
- <span class="action-icon">↻</span> Refresh
20
- </a>
21
- <button class="action-button" disabled title="Export is currently disabled" style="cursor: not-allowed;">
22
- <span class="action-icon">↓</span> Export
23
- </button>
24
- </div>
25
- </nav>
26
- </header>
5
+ <%# Dashboard Header with Navigation and Actions %>
6
+ <%= render Solidstats::DashboardHeaderComponent.new(audit_output: @audit_output) %>
27
7
 
28
- <!-- Overview Section with Key Metrics -->
8
+ <%# Overview Section with Key Metrics Summary Cards %>
29
9
  <section id="overview" class="dashboard-section active">
30
10
  <h2 class="section-title">Overview</h2>
31
11
 
32
12
  <div class="stats-summary">
33
- <div class="summary-card <%= @audit_output.dig('results').present? ? 'status-warning' : 'status-ok' %>" data-section="security" data-tab="security-overview">
34
- <div class="summary-icon">🔒</div>
35
- <div class="summary-data">
36
- <div class="summary-value"><%= @audit_output.dig('results')&.size || 0 %></div>
37
- <div class="summary-label">Security Issues</div>
38
- </div>
39
- </div>
40
-
41
- <div class="summary-card <%= @todo_items&.present? ? 'status-warning' : 'status-ok' %>" data-section="tasks" data-tab="todos">
42
- <div class="summary-icon">📝</div>
43
- <div class="summary-data">
44
- <div class="summary-value"><%= @todo_items&.count || 0 %></div>
45
- <div class="summary-label">TODO Items</div>
46
- </div>
47
- </div>
48
-
49
- <div class="summary-card <%= @coverage.to_f > 80 ? 'status-ok' : (@coverage.to_f > 60 ? 'status-warning' : 'status-danger') %>" data-section="code-quality" data-tab="test-coverage">
50
- <div class="summary-icon">🧪</div>
51
- <div class="summary-data">
52
- <div class="summary-value"><%= @coverage %>%</div>
53
- <div class="summary-label">Test Coverage</div>
54
- </div>
55
- </div>
56
- </div>
57
- </section>
13
+ <%# Security Issues Summary Card %>
14
+ <%= render Solidstats::Ui::SummaryCardComponent.new(
15
+ title: "Security Issues",
16
+ value: @audit_output.dig('results')&.size || 0,
17
+ status: :warning,
18
+ icon: "🔒",
19
+ section: "security",
20
+ tab: "security-overview"
21
+ ) %>
58
22
 
59
- <!-- Security Section -->
60
- <section id="security" class="dashboard-section">
61
- <h2 class="section-title">Security</h2>
62
-
63
- <div class="security-overview">
64
- <% results = @audit_output.dig("results") || [] %>
65
- <% vulnerabilities_count = results.size %>
66
- <% high_severity = results.count { |r| %w[high critical].include?(r.dig("advisory", "criticality").to_s.downcase) } %>
67
- <% affected_gems = results.map { |r| r.dig("gem", "name") }.uniq.size %>
68
-
69
- <div class="security-score-container">
70
- <div class="security-score <%= vulnerabilities_count == 0 ? 'score-excellent' : (high_severity > 0 ? 'score-critical' : 'score-warning') %>">
71
- <div class="score-value"><%= vulnerabilities_count == 0 ? 'A+' : (high_severity > 0 ? 'C' : 'B') %></div>
72
- <div class="score-label">Security<br>Rating</div>
73
- </div>
74
-
75
- <div class="security-metrics">
76
- <div class="metric-item <%= high_severity > 0 ? 'metric-critical' : '' %>">
77
- <div class="metric-icon">⚠️</div>
78
- <div class="metric-data">
79
- <div class="metric-value"><%= high_severity %></div>
80
- <div class="metric-label">Critical Issues</div>
81
- </div>
82
- </div>
83
-
84
- <div class="metric-item <%= vulnerabilities_count > 0 ? 'metric-warning' : '' %>">
85
- <div class="metric-icon">🔍</div>
86
- <div class="metric-data">
87
- <div class="metric-value"><%= vulnerabilities_count %></div>
88
- <div class="metric-label">Total Vulnerabilities</div>
89
- </div>
90
- </div>
91
-
92
- <div class="metric-item <%= affected_gems > 0 ? 'metric-warning' : '' %>">
93
- <div class="metric-icon">💎</div>
94
- <div class="metric-data">
95
- <div class="metric-value"><%= affected_gems %></div>
96
- <div class="metric-label">Affected Gems</div>
97
- </div>
98
- </div>
99
- </div>
100
- </div>
101
- </div>
102
-
103
- <div class="tabs-container">
104
- <div class="tabs-header security-tabs">
105
- <button class="tab-button active" data-tab="security-overview">
106
- <span class="tab-icon">📊</span> Overview
107
- </button>
108
- <button class="tab-button" data-tab="security-gems">
109
- <span class="tab-icon">💎</span> Affected Gems
110
- </button>
111
- <button class="tab-button" data-tab="security-timeline">
112
- <span class="tab-icon">📈</span> Timeline
113
- </button>
114
- </div>
115
-
116
- <div class="tabs-content">
117
- <div class="tab-content active" id="security-overview">
118
- <%= render partial: 'solidstats/dashboard/audit/security_audit', locals: { results: results } %>
119
- </div>
120
- <div class="tab-content" id="security-gems">
121
- <div class="gem-impact-analysis">
122
- <h3>Gem Impact Analysis</h3>
123
- <% results = @audit_output.dig("results") || [] %>
124
- <% if results.any? %>
125
- <div class="gems-container">
126
- <% results.map { |r| r.dig("gem", "name") }.uniq.each do |gem_name| %>
127
- <% gem_vulns = results.select { |r| r.dig("gem", "name") == gem_name } %>
128
- <% highest_severity = gem_vulns.map { |v| v.dig("advisory", "criticality").to_s.downcase }.select { |c| %w[critical high medium low].include?(c) }.min_by { |s| %w[critical high medium low].index(s) || 999 } || "unknown" %>
129
-
130
- <div class="gem-card severity-<%= highest_severity %>">
131
- <div class="gem-header">
132
- <div class="gem-name"><%= gem_name %></div>
133
- <div class="gem-severity <%= highest_severity %>"><%= highest_severity.capitalize %></div>
134
- </div>
135
- <div class="gem-details">
136
- <div class="gem-versions">
137
- <div class="current-version">
138
- <span class="version-label">Current:</span>
139
- <span class="version-value"><%= gem_vulns.first.dig("gem", "version") rescue "Unknown" %></span>
140
- </div>
141
- <div class="target-version">
142
- <span class="version-label">Target:</span>
143
- <span class="version-value"><%= gem_vulns.first.dig("advisory", "patched_versions")&.first || "N/A" %></span>
144
- </div>
145
- </div>
146
- <div class="gem-vulnerabilities-count">
147
- <%= gem_vulns.size %> <%= "vulnerability".pluralize(gem_vulns.size) %> found
148
- </div>
149
- </div>
150
- <div class="gem-actions">
151
- <button class="action-button gem-update-button">
152
- <span class="action-icon">↑</span> Update Gem
153
- </button>
154
- </div>
155
- </div>
156
- <% end %>
157
- </div>
158
- <% else %>
159
- <div class="empty-state">
160
- <div class="empty-icon">✅</div>
161
- <div class="empty-message">No vulnerable gems found</div>
162
- <div class="empty-description">Your application is secure. Keep up with regular security audits!</div>
163
- </div>
164
- <% end %>
165
- </div>
166
- </div>
167
- <div class="tab-content" id="security-timeline">
168
- <div class="security-timeline-container">
169
- <h3>Security Timeline</h3>
170
- <div class="timeline-chart-placeholder">
171
- <div class="chart-header">
172
- <div class="chart-title">Vulnerability History</div>
173
- <div class="chart-legend">
174
- <div class="legend-item">
175
- <span class="legend-color" style="background-color: #dc3545;"></span>
176
- <span class="legend-label">Critical</span>
177
- </div>
178
- <div class="legend-item">
179
- <span class="legend-color" style="background-color: #ffc107;"></span>
180
- <span class="legend-label">Medium</span>
181
- </div>
182
- <div class="legend-item">
183
- <span class="legend-color" style="background-color: #28a745;"></span>
184
- <span class="legend-label">Low</span>
185
- </div>
186
- </div>
187
- </div>
188
- <div class="chart-visualization">
189
- <!-- Placeholder for the actual chart -->
190
- <div class="chart-timeline">
191
- <div class="timeline-point" style="left: 10%;">
192
- <div class="timeline-marker critical"></div>
193
- <div class="timeline-date">Jan 2025</div>
194
- </div>
195
- <div class="timeline-point" style="left: 30%;">
196
- <div class="timeline-marker medium"></div>
197
- <div class="timeline-date">Feb 2025</div>
198
- </div>
199
- <div class="timeline-point" style="left: 65%;">
200
- <div class="timeline-marker low"></div>
201
- <div class="timeline-date">Apr 2025</div>
202
- </div>
203
- <div class="timeline-point" style="left: 85%;">
204
- <div class="timeline-marker critical"></div>
205
- <div class="timeline-date">May 2025</div>
206
- </div>
207
- </div>
208
- </div>
209
- </div>
210
- <div class="timeline-insights">
211
- <div class="insight-card">
212
- <div class="insight-header">Key Insights</div>
213
- <div class="insight-content">
214
- <div class="insight-item">
215
- <div class="insight-title">Notable Trend</div>
216
- <div class="insight-description">4 vulnerabilities discovered in the last 3 months.</div>
217
- </div>
218
- <div class="insight-item">
219
- <div class="insight-title">Recent Activity</div>
220
- <div class="insight-description">Last security scan: <%= Time.now.strftime("%B %d, %Y") %></div>
221
- </div>
222
- </div>
223
- </div>
224
- </div>
225
- </div>
226
- </div>
227
- </div>
228
- </div>
229
- </section>
23
+ <%# Gem Count Summary Card %>
24
+ <%= render Solidstats::Ui::SummaryCardComponent.new(
25
+ title: "Total Gems",
26
+ value: (Solidstats::GemMetadata::FetcherService.call.size rescue 0),
27
+ status: :info,
28
+ icon: "💎",
29
+ section: "gem-metadata"
30
+ ) %>
230
31
 
231
- <!-- Code Quality Section -->
232
- <section id="code-quality" class="dashboard-section">
233
- <h2 class="section-title">Code Quality</h2>
234
-
235
- <div class="tabs-container">
236
- <div class="tabs-header">
237
- <button class="tab-button active" data-tab="quality-metrics">Metrics</button>
238
- <button class="tab-button" data-tab="test-coverage">Test Coverage</button>
239
- <button class="tab-button" data-tab="code-health">Code Health</button>
240
- </div>
241
-
242
- <div class="tabs-content">
243
- <div class="tab-content active" id="quality-metrics">
244
- <div class="stat-card">
245
- <h2><span class="icon">🧹</span> Code Quality</h2>
246
- <div class="card-content">
247
- <div class="metric">
248
- <!-- Your code quality metrics -->
249
- </div>
250
- </div>
251
- </div>
252
- </div>
253
- <div class="tab-content" id="test-coverage">
254
- <div class="stat-card <%= @coverage.to_f > 80 ? 'status-ok' : (@coverage.to_f > 60 ? 'status-warning' : 'status-danger') %>">
255
- <h2><span class="icon">🧪</span> Test Coverage</h2>
256
- <div class="card-content">
257
- <div class="progress-container">
258
- <div class="progress-bar" style="width: <%= @coverage %>%"></div>
259
- </div>
260
- <div class="metric">
261
- <span class="metric-value"><%= @coverage %>%</span>
262
- </div>
263
- </div>
264
- </div>
265
- </div>
266
- <div class="tab-content" id="code-health">
267
- <div class="stat-card">
268
- <h2><span class="icon">📝</span> Code Health</h2>
269
- <div class="card-content">
270
- <!-- Your code health metrics -->
271
- </div>
272
- </div>
273
- </div>
274
- </div>
275
- </div>
276
- </section>
32
+ <%# TODO Items Summary Card %>
33
+ <%= render Solidstats::Ui::SummaryCardComponent.new(
34
+ title: "TODO Items",
35
+ value: @todo_items&.count || 0,
36
+ status: ((@todo_items&.count || 0) > 10 ? :warning : :ok),
37
+ icon: "📝",
38
+ section: "tasks",
39
+ tab: "todos"
40
+ ) %>
277
41
 
278
- <!-- Tasks Section -->
279
- <section id="tasks" class="dashboard-section">
280
- <h2 class="section-title">Tasks</h2>
281
-
282
- <div class="tabs-container">
283
- <div class="tabs-header">
284
- <button class="tab-button active" data-tab="todos">TODO Items</button>
285
- <button class="tab-button" data-tab="fixmes">FIXMEs</button>
286
- <button class="tab-button" data-tab="hacks">HACKs</button>
287
- </div>
288
-
289
- <div class="tabs-content">
290
- <div class="tab-content active" id="todos">
291
- <%= render partial: 'todos' %>
292
- </div>
293
- <div class="tab-content" id="fixmes">
294
- <!-- FIXME specific content -->
295
- </div>
296
- <div class="tab-content" id="hacks">
297
- <!-- HACK specific content -->
298
- </div>
299
- </div>
42
+ <%# Test Coverage Summary Card %>
43
+ <%= render Solidstats::Ui::SummaryCardComponent.new(
44
+ title: "Test Coverage",
45
+ value: "#{@coverage.to_f.round(1)}%",
46
+ status: (@coverage.to_f >= 80 ? :ok : :warning),
47
+ icon: "📊",
48
+ section: "code-quality",
49
+ tab: "test-coverage"
50
+ ) %>
51
+
52
+ <%# Log Size Summary Card %>
53
+ <%= render Solidstats::Ui::SummaryCardComponent.new(
54
+ title: "Log Size",
55
+ value: "#{@log_data[:total_size_mb]} MB",
56
+ status: @log_data[:status]&.to_sym || :ok,
57
+ icon: "📋",
58
+ section: "code-quality",
59
+ tab: "log-monitor"
60
+ ) %>
300
61
  </div>
301
62
  </section>
302
-
303
- <!-- Floating quick navigation -->
304
- <div class="quick-nav">
305
- <button class="quick-nav-toggle">↑</button>
306
- <div class="quick-nav-menu">
307
- <a href="#overview" class="quick-nav-item">Overview</a>
308
- <a href="#security" class="quick-nav-item">Security</a>
309
- <a href="#code-quality" class="quick-nav-item">Code Quality</a>
310
- <a href="#tasks" class="quick-nav-item">Tasks</a>
311
- </div>
312
- </div>
313
- </div>
314
63
 
315
- <style>
316
- /* Base styles for dashboard */
317
- .solidstats-dashboard {
318
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
319
- color: #333;
320
- max-width: 1200px;
321
- margin: 0 auto;
322
- padding: 20px;
323
- }
324
-
325
- /* Header and Navigation styles */
326
- .dashboard-header {
327
- margin-bottom: 2rem;
328
- position: sticky;
329
- top: 0;
330
- z-index: 100;
331
- background-color: #fff;
332
- padding: 15px 0;
333
- border-bottom: 1px solid #eaeaea;
334
- }
335
-
336
- .header-main {
337
- display: flex;
338
- justify-content: space-between;
339
- align-items: baseline;
340
- margin-bottom: 1rem;
341
- }
342
-
343
- .dashboard-header h1 {
344
- font-size: 1.8rem;
345
- font-weight: 600;
346
- margin: 0;
347
- }
348
-
349
- .dashboard-last-updated {
350
- color: #666;
351
- margin: 0;
352
- font-size: 0.9rem;
353
- }
354
-
355
- .dashboard-nav {
356
- display: flex;
357
- justify-content: space-between;
358
- align-items: center;
359
- }
360
-
361
- .dashboard-nav ul {
362
- display: flex;
363
- list-style-type: none;
364
- margin: 0;
365
- padding: 0;
366
- gap: 1rem;
367
- }
368
-
369
- .nav-item {
370
- padding: 0.5rem 1rem;
371
- text-decoration: none;
372
- color: #555;
373
- border-radius: 4px;
374
- font-weight: 500;
375
- transition: all 0.2s;
376
- }
377
-
378
- .nav-item:hover {
379
- background-color: #f5f5f5;
380
- color: #000;
381
- }
382
-
383
- .nav-item.active {
384
- background-color: #e9f5ff;
385
- color: #0366d6;
386
- }
387
-
388
- /* Section styling */
389
- .dashboard-section {
390
- margin-bottom: 3rem;
391
- display: none;
392
- }
393
-
394
- .dashboard-section.active {
395
- display: block;
396
- }
397
-
398
- .section-title {
399
- font-size: 1.5rem;
400
- margin-bottom: 1.5rem;
401
- padding-bottom: 0.5rem;
402
- border-bottom: 1px solid #eee;
403
- }
404
-
405
- /* Overview summary cards */
406
- .stats-summary {
407
- display: grid;
408
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
409
- gap: 1rem;
410
- margin-bottom: 2rem;
411
- }
412
-
413
- .summary-card {
414
- display: flex;
415
- align-items: center;
416
- padding: 1.5rem;
417
- background: #fff;
418
- border-radius: 8px;
419
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
420
- transition: transform 0.2s;
421
- cursor: pointer;
422
- }
423
-
424
- .summary-card:hover {
425
- transform: translateY(-2px);
426
- }
427
-
428
- .summary-icon {
429
- font-size: 2rem;
430
- margin-right: 1rem;
431
- }
432
-
433
- .summary-data {
434
- flex-grow: 1;
435
- }
436
-
437
- .summary-value {
438
- font-size: 1.8rem;
439
- font-weight: 700;
440
- line-height: 1;
441
- margin-bottom: 0.25rem;
442
- }
443
-
444
- .summary-label {
445
- color: #666;
446
- font-size: 0.9rem;
447
- }
448
-
449
- /* Tabs container */
450
- .tabs-container {
451
- background: #fff;
452
- border-radius: 8px;
453
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
454
- overflow: hidden;
455
- }
456
-
457
- .tabs-header {
458
- display: flex;
459
- background-color: #f8f9fa;
460
- border-bottom: 1px solid #dee2e6;
461
- overflow-x: auto;
462
- }
463
-
464
- .tab-button {
465
- padding: 0.75rem 1.25rem;
466
- background: none;
467
- border: none;
468
- font-size: 0.9rem;
469
- font-weight: 500;
470
- color: #555;
471
- cursor: pointer;
472
- white-space: nowrap;
473
- }
474
-
475
- .tab-button:hover {
476
- color: #000;
477
- background-color: #f1f1f1;
478
- }
479
-
480
- .tab-button.active {
481
- color: #0366d6;
482
- border-bottom: 2px solid #0366d6;
483
- background-color: white;
484
- }
485
-
486
- .tabs-content {
487
- padding: 1.5rem;
488
- }
489
-
490
- .tab-content {
491
- display: none;
492
- }
493
-
494
- .tab-content.active {
495
- display: block;
496
- }
497
-
498
- /* Card grid layout - modified for tabs */
499
- .stats-cards {
500
- display: grid;
501
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
502
- gap: 1.5rem;
503
- margin-bottom: 2rem;
504
- }
505
-
506
- /* Quick navigation floating button */
507
- .quick-nav {
508
- position: fixed;
509
- bottom: 2rem;
510
- right: 2rem;
511
- z-index: 99;
512
- }
513
-
514
- .quick-nav-toggle {
515
- width: 50px;
516
- height: 50px;
517
- border-radius: 50%;
518
- background-color: #0366d6;
519
- color: white;
520
- border: none;
521
- font-size: 1.5rem;
522
- cursor: pointer;
523
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
524
- transition: all 0.3s;
525
- }
526
-
527
- .quick-nav-toggle:hover {
528
- transform: scale(1.05);
529
- }
530
-
531
- .quick-nav-menu {
532
- position: absolute;
533
- bottom: 60px;
534
- right: 0;
535
- background-color: white;
536
- border-radius: 8px;
537
- width: 150px;
538
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
539
- display: none;
540
- flex-direction: column;
541
- }
542
-
543
- .quick-nav-item {
544
- padding: 0.75rem 1rem;
545
- text-decoration: none;
546
- color: #333;
547
- transition: background-color 0.2s;
548
- }
549
-
550
- .quick-nav-item:hover {
551
- background-color: #f5f5f5;
552
- }
553
-
554
- .quick-nav:hover .quick-nav-menu {
555
- display: flex;
556
- }
557
-
558
- /* Action buttons */
559
- .dashboard-actions {
560
- display: flex;
561
- gap: 0.5rem;
562
- }
563
-
564
- .action-button {
565
- display: inline-flex;
566
- align-items: center;
567
- padding: 0.5rem 1rem;
568
- background-color: #f8f9fa;
569
- border: 1px solid #dee2e6;
570
- color: #333;
571
- text-decoration: none;
572
- border-radius: 4px;
573
- font-size: 0.9rem;
574
- cursor: pointer;
575
- transition: all 0.2s;
576
- }
577
-
578
- .action-button:hover {
579
- background-color: #e9ecef;
580
- }
581
-
582
- .action-icon {
583
- margin-right: 0.25rem;
584
- }
585
-
586
- /* Fix for audit card to span full width when details are shown */
587
- .audit-card {
588
- position: relative;
589
- transition: all 0.3s ease;
590
- }
591
-
592
- /* Card styles - simplified for tabs */
593
- .stat-card {
594
- background: transparent;
595
- padding: 0;
596
- box-shadow: none;
597
- overflow: visible;
598
- }
599
-
600
- .stat-card h2 {
601
- font-size: 1.2rem;
602
- font-weight: 600;
603
- margin: 0 0 1rem 0;
604
- display: flex;
605
- align-items: center;
606
- gap: 0.5rem;
607
- }
608
-
609
- /* Security section specific styles */
610
- .security-overview {
611
- margin-bottom: 1.5rem;
612
- background-color: #fff;
613
- border-radius: 8px;
614
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
615
- padding: 1.5rem;
616
- }
617
-
618
- .security-score-container {
619
- display: flex;
620
- align-items: center;
621
- gap: 2rem;
622
- }
623
-
624
- .security-score {
625
- width: 120px;
626
- height: 120px;
627
- border-radius: 50%;
628
- display: flex;
629
- flex-direction: column;
630
- align-items: center;
631
- justify-content: center;
632
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
633
- }
634
-
635
- .score-value {
636
- font-size: 3rem;
637
- font-weight: 700;
638
- line-height: 1;
639
- }
640
-
641
- .score-label {
642
- font-size: 0.85rem;
643
- text-align: center;
644
- margin-top: 0.25rem;
645
- }
646
-
647
- .score-excellent {
648
- background-color: #e9f7ef;
649
- color: #27ae60;
650
- border: 3px solid #27ae60;
651
- }
652
-
653
- .score-warning {
654
- background-color: #fcf3cf;
655
- color: #f39c12;
656
- border: 3px solid #f39c12;
657
- }
658
-
659
- .score-critical {
660
- background-color: #fdedeb;
661
- color: #e74c3c;
662
- border: 3px solid #e74c3c;
663
- }
664
-
665
- .security-metrics {
666
- display: flex;
667
- flex-grow: 1;
668
- gap: 2rem;
669
- }
670
-
671
- .metric-item {
672
- flex: 1;
673
- display: flex;
674
- align-items: center;
675
- gap: 1rem;
676
- padding: 1rem;
677
- background-color: #f9fafb;
678
- border-radius: 8px;
679
- transition: transform 0.2s;
680
- }
681
-
682
- .metric-item:hover {
683
- transform: translateY(-2px);
684
- }
685
-
686
- .metric-critical {
687
- border-left: 4px solid #e74c3c;
688
- }
689
-
690
- .metric-warning {
691
- border-left: 4px solid #f39c12;
692
- }
693
-
694
- .metric-icon {
695
- font-size: 1.8rem;
696
- opacity: 0.8;
697
- }
698
-
699
- .metric-data {
700
- flex-grow: 1;
701
- }
702
-
703
- .metric-value {
704
- font-size: 1.8rem;
705
- font-weight: 700;
706
- line-height: 1;
707
- }
708
-
709
- .metric-label {
710
- font-size: 0.85rem;
711
- color: #666;
712
- margin-top: 0.25rem;
713
- }
714
-
715
- .security-tabs .tab-button {
716
- position: relative;
717
- }
718
-
719
- .security-tabs .tab-button .tab-icon {
720
- margin-right: 0.5rem;
721
- }
722
-
723
- .gem-impact-analysis {
724
- padding: 1rem 0;
725
- }
726
-
727
- .gems-container {
728
- display: grid;
729
- grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
730
- gap: 1rem;
731
- margin-top: 1.5rem;
732
- }
733
-
734
- .gem-card {
735
- background-color: #fff;
736
- border-radius: 8px;
737
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
738
- overflow: hidden;
739
- transition: transform 0.2s;
740
- }
741
-
742
- .gem-card:hover {
743
- transform: translateY(-2px);
744
- }
745
-
746
- .gem-card.severity-critical {
747
- border-top: 4px solid #e74c3c;
748
- }
749
-
750
- .gem-card.severity-high {
751
- border-top: 4px solid #fd7e14;
752
- }
753
-
754
- .gem-card.severity-medium {
755
- border-top: 4px solid #f39c12;
756
- }
757
-
758
- .gem-card.severity-low {
759
- border-top: 4px solid #27ae60;
760
- }
761
-
762
- .gem-header {
763
- padding: 1rem;
764
- display: flex;
765
- justify-content: space-between;
766
- align-items: center;
767
- border-bottom: 1px solid #eeeeee;
768
- }
769
-
770
- .gem-name {
771
- font-weight: 600;
772
- color: #333;
773
- }
774
-
775
- .gem-severity {
776
- font-size: 0.85rem;
777
- padding: 0.2rem 0.75rem;
778
- border-radius: 12px;
779
- }
780
-
781
- .gem-severity.critical {
782
- background-color: rgba(231, 76, 60, 0.15);
783
- color: #e74c3c;
784
- }
785
-
786
- .gem-severity.high {
787
- background-color: rgba(253, 126, 20, 0.15);
788
- color: #fd7e14;
789
- }
790
-
791
- .gem-severity.medium {
792
- background-color: rgba(243, 156, 18, 0.15);
793
- color: #f39c12;
794
- }
795
-
796
- .gem-severity.low {
797
- background-color: rgba(39, 174, 96, 0.15);
798
- color: #27ae60;
799
- }
800
-
801
- .gem-severity.unknown {
802
- background-color: rgba(173, 181, 189, 0.15);
803
- color: #6c757d;
804
- }
805
-
806
- .gem-details {
807
- padding: 1rem;
808
- background-color: #f9fafb;
809
- }
810
-
811
- .gem-versions {
812
- display: flex;
813
- justify-content: space-between;
814
- margin-bottom: 0.75rem;
815
- font-size: 0.9rem;
816
- }
817
-
818
- .version-label {
819
- color: #666;
820
- }
821
-
822
- .version-value {
823
- font-family: monospace;
824
- }
825
-
826
- .gem-vulnerabilities-count {
827
- font-size: 0.85rem;
828
- color: #666;
829
- }
830
-
831
- .gem-actions {
832
- padding: 1rem;
833
- display: flex;
834
- justify-content: center;
835
- }
836
-
837
- .empty-state {
838
- text-align: center;
839
- padding: 3rem 1rem;
840
- }
841
-
842
- .empty-icon {
843
- font-size: 3rem;
844
- margin-bottom: 1rem;
845
- color: #27ae60;
846
- }
847
-
848
- .empty-message {
849
- font-size: 1.2rem;
850
- font-weight: 600;
851
- margin-bottom: 0.5rem;
852
- }
853
-
854
- .empty-description {
855
- color: #666;
856
- }
857
-
858
- .security-timeline-container {
859
- padding: 1rem 0;
860
- }
861
-
862
- .timeline-chart-placeholder {
863
- background-color: #fff;
864
- border-radius: 8px;
865
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
866
- padding: 1.5rem;
867
- margin-top: 1.5rem;
868
- }
869
-
870
- .chart-header {
871
- display: flex;
872
- justify-content: space-between;
873
- align-items: center;
874
- margin-bottom: 1.5rem;
875
- }
876
-
877
- .chart-title {
878
- font-weight: 600;
879
- font-size: 1.1rem;
880
- }
881
-
882
- .chart-legend {
883
- display: flex;
884
- gap: 1rem;
885
- }
886
-
887
- .legend-item {
888
- display: flex;
889
- align-items: center;
890
- gap: 0.5rem;
891
- font-size: 0.85rem;
892
- }
893
-
894
- .legend-color {
895
- width: 12px;
896
- height: 12px;
897
- border-radius: 2px;
898
- }
899
-
900
- .chart-visualization {
901
- height: 200px;
902
- position: relative;
903
- margin-bottom: 1.5rem;
904
- }
905
-
906
- .chart-timeline {
907
- height: 80px;
908
- background-color: #f8f9fa;
909
- border-radius: 4px;
910
- position: relative;
911
- margin-top: 40px;
912
- }
913
-
914
- .timeline-point {
915
- position: absolute;
916
- bottom: 0;
917
- transform: translateX(-50%);
918
- }
919
-
920
- .timeline-marker {
921
- width: 12px;
922
- height: 12px;
923
- border-radius: 50%;
924
- position: relative;
925
- }
926
-
927
- .timeline-marker::before {
928
- content: '';
929
- position: absolute;
930
- bottom: 100%;
931
- left: 50%;
932
- transform: translateX(-50%);
933
- width: 1px;
934
- height: 30px;
935
- background-color: rgba(0,0,0,0.1);
936
- }
937
-
938
- .timeline-marker.critical {
939
- background-color: #dc3545;
940
- }
941
-
942
- .timeline-marker.medium {
943
- background-color: #ffc107;
944
- }
945
-
946
- .timeline-marker.low {
947
- background-color: #28a745;
948
- }
949
-
950
- .timeline-date {
951
- position: absolute;
952
- bottom: 100%;
953
- left: 50%;
954
- transform: translateX(-50%);
955
- white-space: nowrap;
956
- font-size: 0.85rem;
957
- color: #666;
958
- margin-bottom: 0.5rem;
959
- }
960
-
961
- .timeline-insights {
962
- margin-top: 1.5rem;
963
- }
964
-
965
- .insight-card {
966
- background-color: #fff;
967
- border-radius: 8px;
968
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
969
- overflow: hidden;
970
- }
971
-
972
- .insight-header {
973
- background-color: #f8f9fa;
974
- padding: 1rem;
975
- font-weight: 600;
976
- border-bottom: 1px solid #eeeeee;
977
- }
978
-
979
- .insight-content {
980
- padding: 1rem;
981
- }
982
-
983
- .insight-item {
984
- margin-bottom: 1rem;
985
- }
986
-
987
- .insight-item:last-child {
988
- margin-bottom: 0;
989
- }
990
-
991
- .insight-title {
992
- font-weight: 600;
993
- font-size: 0.95rem;
994
- margin-bottom: 0.5rem;
995
- }
996
-
997
- .insight-description {
998
- color: #555;
999
- font-size: 0.9rem;
1000
- }
1001
-
1002
- /* Responsive adjustments */
1003
- @media (max-width: 768px) {
1004
- .dashboard-nav {
1005
- flex-direction: column;
1006
- gap: 1rem;
1007
- }
1008
-
1009
- .dashboard-nav ul {
1010
- width: 100%;
1011
- overflow-x: auto;
1012
- padding-bottom: 0.5rem;
1013
- }
1014
-
1015
- .dashboard-actions {
1016
- width: 100%;
1017
- }
1018
-
1019
- .action-button {
1020
- flex: 1;
1021
- justify-content: center;
1022
- }
1023
-
1024
- .tabs-header {
1025
- padding: 0;
1026
- }
1027
-
1028
- .tab-button {
1029
- padding: 0.75rem 1rem;
1030
- }
1031
- }
1032
-
1033
- /* Keep compatibility with existing styles */
1034
- /* Details panel styling */
1035
- .details-panel {
1036
- background: #f9f9f9;
1037
- border-top: 1px solid #eaeaea;
1038
- padding: 1.5rem;
1039
- margin-top: 1.5rem;
1040
- border-radius: 0 0 8px 8px;
1041
- overflow: visible;
1042
- }
1043
-
1044
- .details-panel.hidden {
1045
- display: none;
1046
- }
1047
-
1048
- .details-panel:not(.hidden) {
1049
- display: block;
1050
- }
1051
-
1052
- /* Toggle details button */
1053
- .toggle-details {
1054
- display: inline-block;
1055
- color: #007bff;
1056
- cursor: pointer;
1057
- font-size: 0.9rem;
1058
- text-decoration: none;
1059
- }
1060
-
1061
- .toggle-details:hover {
1062
- text-decoration: underline;
1063
- }
1064
-
1065
- /* Status colors */
1066
- .status-ok {
1067
- border-left: 4px solid #28a745;
1068
- }
1069
-
1070
- .status-warning {
1071
- border-left: 4px solid #ffc107;
1072
- }
1073
-
1074
- .status-danger {
1075
- border-left: 4px solid #dc3545;
1076
- }
1077
-
1078
- /* Toast notifications styling */
1079
- .toast-notification {
1080
- position: fixed;
1081
- bottom: 20px;
1082
- left: 50%;
1083
- transform: translateX(-50%) translateY(100%);
1084
- background-color: white;
1085
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
1086
- border-radius: 4px;
1087
- padding: 0.75rem 1.5rem;
1088
- font-size: 0.9rem;
1089
- z-index: 1000;
1090
- transition: transform 0.3s ease;
1091
- opacity: 0;
1092
- }
1093
-
1094
- .toast-notification.visible {
1095
- transform: translateX(-50%) translateY(0);
1096
- opacity: 1;
1097
- }
1098
-
1099
- .toast-notification.success {
1100
- border-left: 4px solid #28a745;
1101
- }
1102
-
1103
- .toast-notification.info {
1104
- border-left: 4px solid #17a2b8;
1105
- }
1106
-
1107
- .toast-notification.warning {
1108
- border-left: 4px solid #ffc107;
1109
- }
1110
-
1111
- .toast-notification.error {
1112
- border-left: 4px solid #dc3545;
1113
- }
1114
- </style>
64
+ <%# Security Section - Component-Based Architecture %>
65
+ <%= render Solidstats::Security::SectionComponent.new(audit_output: @audit_output) %>
1115
66
 
1116
- <script>
1117
- document.addEventListener('DOMContentLoaded', function() {
1118
- // Check for hash in URL and navigate accordingly
1119
- function handleHashNavigation() {
1120
- if (location.hash) {
1121
- const hashParts = location.hash.substring(1).split('/');
1122
- const section = hashParts[0];
1123
- const tab = hashParts[1] || null;
1124
-
1125
- if (section && document.getElementById(section)) {
1126
- // On page load, don't scroll - this prevents jumping around
1127
- // For manual hash changes, do scroll
1128
- const isPageLoad = !window.hasPageLoaded;
1129
- navigateToSection(section, tab, !isPageLoad);
1130
- return true;
1131
- }
1132
- }
1133
- return false;
1134
- }
1135
-
1136
- // Track if page has loaded fully
1137
- window.hasPageLoaded = false;
1138
-
1139
- // Try to handle hash navigation, if it fails (no hash or invalid section),
1140
- // the default 'overview' section will remain active
1141
- const hashNavigated = handleHashNavigation();
1142
- if (!hashNavigated) {
1143
- // If no hash navigation happened, set default hash to #overview
1144
- updateUrlHash('overview');
1145
- }
1146
-
1147
- // Listen for hash changes
1148
- window.addEventListener('hashchange', handleHashNavigation);
1149
-
1150
- // Mark page as fully loaded after a short delay
1151
- setTimeout(() => {
1152
- window.hasPageLoaded = true;
1153
- }, 500);
1154
-
1155
- // Toggle details panels
1156
- document.querySelectorAll('.toggle-details').forEach(function(toggle) {
1157
- toggle.addEventListener('click', function(e) {
1158
- e.preventDefault();
1159
- const targetId = this.getAttribute('data-target');
1160
- const panel = document.getElementById(targetId);
1161
- const card = this.closest('.stat-card');
1162
-
1163
- panel.classList.toggle('hidden');
1164
- card.classList.toggle('expanded');
1165
-
1166
- this.textContent = panel.classList.contains('hidden') ? 'Show Details' : 'Hide Details';
1167
-
1168
- // If showing details, scroll to it for better visibility
1169
- if (!panel.classList.contains('hidden')) {
1170
- setTimeout(() => {
1171
- panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
1172
- }, 100);
1173
- }
1174
- });
1175
- });
1176
-
1177
- // Function to update URL hash with current state
1178
- function updateUrlHash(section, tab = null) {
1179
- let hash = '#' + section;
1180
- if (tab) {
1181
- hash += '/' + tab;
1182
- }
1183
- history.replaceState(null, null, hash);
1184
- }
1185
-
1186
- // Function to navigate to section and tab
1187
- function navigateToSection(section, tab = null, shouldScroll = false) {
1188
- // Remove active class from all nav items and sections
1189
- document.querySelectorAll('.nav-item').forEach(item => item.classList.remove('active'));
1190
- document.querySelectorAll('.dashboard-section').forEach(section => section.classList.remove('active'));
1191
-
1192
- // Add active class to matching nav item
1193
- const navItem = document.querySelector(`.nav-item[data-section="${section}"]`);
1194
- if (navItem) {
1195
- navItem.classList.add('active');
1196
- }
1197
-
1198
- // Show corresponding section
1199
- const sectionElement = document.getElementById(section);
1200
- if (sectionElement) {
1201
- sectionElement.classList.add('active');
1202
-
1203
- // If tab is specified, activate that tab
1204
- if (tab) {
1205
- const tabsContainer = sectionElement.querySelector('.tabs-container');
1206
- if (tabsContainer) {
1207
- // Deactivate all tabs first
1208
- tabsContainer.querySelectorAll('.tab-button').forEach(button => button.classList.remove('active'));
1209
- tabsContainer.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
1210
-
1211
- // Activate the target tab
1212
- const targetTabButton = tabsContainer.querySelector(`.tab-button[data-tab="${tab}"]`);
1213
- const targetTabContent = tabsContainer.querySelector(`#${tab}`);
1214
-
1215
- if (targetTabButton) targetTabButton.classList.add('active');
1216
- if (targetTabContent) targetTabContent.classList.add('active');
1217
- }
1218
- }
1219
-
1220
- // Scroll to section if requested (with a small delay to ensure rendering)
1221
- if (shouldScroll) {
1222
- setTimeout(() => {
1223
- sectionElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
1224
- }, 100);
1225
- }
1226
-
1227
- // Update URL hash
1228
- updateUrlHash(section, tab);
1229
- }
1230
- }
1231
-
1232
- // Main navigation
1233
- document.querySelectorAll('.nav-item').forEach(function(navItem) {
1234
- navItem.addEventListener('click', function(e) {
1235
- e.preventDefault();
1236
-
1237
- // Get section ID from data attribute
1238
- const sectionId = this.getAttribute('data-section');
1239
-
1240
- // Navigate to the section
1241
- navigateToSection(sectionId);
1242
- });
1243
- });
1244
-
1245
- // Tab navigation
1246
- document.querySelectorAll('.tab-button').forEach(function(tabButton) {
1247
- tabButton.addEventListener('click', function() {
1248
- const tabContainer = this.closest('.tabs-container');
1249
- const tabId = this.getAttribute('data-tab');
1250
-
1251
- // Find the current active section
1252
- const currentSection = document.querySelector('.dashboard-section.active').id;
1253
-
1254
- // Remove active class from all buttons and tabs within this container
1255
- tabContainer.querySelectorAll('.tab-button').forEach(button => button.classList.remove('active'));
1256
- tabContainer.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
1257
-
1258
- // Add active class to clicked button and corresponding tab
1259
- this.classList.add('active');
1260
- tabContainer.querySelector(`#${tabId}`).classList.add('active');
1261
-
1262
- // Update URL hash with current section and tab
1263
- updateUrlHash(currentSection, tabId);
1264
- });
1265
- });
1266
-
1267
- // Quick navigation
1268
- document.querySelectorAll('.quick-nav-item').forEach(function(navItem) {
1269
- navItem.addEventListener('click', function(e) {
1270
- e.preventDefault();
1271
- const targetId = this.getAttribute('href').substring(1);
1272
-
1273
- // Navigate to the specified section
1274
- navigateToSection(targetId);
1275
-
1276
- // Close the quick nav menu
1277
- document.querySelector('.quick-nav-menu').style.display = 'none';
1278
- });
1279
- });
1280
-
1281
- // Summary card navigation
1282
- document.querySelectorAll('.summary-card').forEach(function(card) {
1283
- card.addEventListener('click', function() {
1284
- const section = this.getAttribute('data-section');
1285
- const tab = this.getAttribute('data-tab');
1286
-
1287
- // Navigate to the specified section and tab, with scrolling
1288
- navigateToSection(section, tab, true);
1289
- });
1290
- });
1291
- });
67
+ <%# Code Quality Section - Component-Based Architecture %>
68
+ <%= render Solidstats::CodeQuality::SectionComponent.new(coverage: @coverage, log_data: @log_data) %>
1292
69
 
1293
- function refreshAudit() {
1294
- // Show loading indicator
1295
- const refreshButton = document.querySelector('.action-button');
1296
- const originalText = refreshButton.innerHTML;
1297
- refreshButton.innerHTML = '<span class="action-icon">⟳</span> Refreshing...';
1298
- refreshButton.disabled = true;
1299
-
1300
- // Make AJAX call to refresh endpoint
1301
- fetch('/solidstats/refresh', {
1302
- method: 'GET',
1303
- headers: {
1304
- 'Accept': 'application/json',
1305
- 'X-Requested-With': 'XMLHttpRequest'
1306
- },
1307
- credentials: 'same-origin'
1308
- })
1309
- .then(response => {
1310
- if (!response.ok) {
1311
- throw new Error('Network response was not ok');
1312
- }
1313
- return response.json();
1314
- })
1315
- .then(data => {
1316
- // Update the dashboard with fresh data
1317
- location.reload();
1318
-
1319
- // Show success notification
1320
- showNotification('Dashboard data refreshed successfully', 'success');
1321
-
1322
- // Reset button state
1323
- refreshButton.innerHTML = originalText;
1324
- refreshButton.disabled = false;
1325
- })
1326
- .catch(error => {
1327
- console.error('Error refreshing data:', error);
1328
-
1329
- // Show error notification
1330
- showNotification('Failed to refresh data. Please try again.', 'error');
1331
-
1332
- // Reset button state
1333
- refreshButton.innerHTML = originalText;
1334
- refreshButton.disabled = false;
1335
- });
1336
- }
1337
- </script>
70
+ <%# Gem Metadata Section %>
71
+ <section id="gem-metadata" class="dashboard-section">
72
+ <h2 class="section-title">Gem Dependencies</h2>
73
+ <%= render partial: 'solidstats/gem_metadata/panel' %>
74
+ </section>
75
+
76
+ <%# Tasks Section - Component-Based Architecture %>
77
+ <%= render Solidstats::TasksSectionComponent.new(todo_items: @todo_items) %>
78
+
79
+ <%# Quick Navigation - Component-Based Architecture %>
80
+ <%= render Solidstats::QuickNavigationComponent.new %>
81
+ </div>