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
@@ -0,0 +1,419 @@
1
+ <%= stylesheet_link_tag 'solidstats/components/gem_metadata' %>
2
+ <%= javascript_include_tag 'solidstats/gem_metadata' %>
3
+
4
+ <%# Enhanced Gem Metadata Index with Modern UI/UX %>
5
+ <div class="gem-metadata-index">
6
+ <!-- Page Header -->
7
+ <div class="page-header">
8
+ <div class="header-content">
9
+ <div class="header-title-section">
10
+ <h1 class="page-title">
11
+ <span class="page-icon">💎</span>
12
+ Gem Dependencies
13
+ </h1>
14
+ <div class="page-subtitle">
15
+ Complete overview of your project's gem dependencies
16
+ </div>
17
+ </div>
18
+ <div class="header-actions">
19
+ <%= button_to refresh_gem_metadata_path,
20
+ method: :post,
21
+ class: "action-btn refresh-btn",
22
+ title: "Refresh gem metadata",
23
+ form: { style: "display: inline-block;" },
24
+ data: { confirm: 'This will fetch the latest gem information. Continue?' } do %>
25
+ <i class="fas fa-sync-alt"></i>
26
+ <span class="btn-text">Refresh All</span>
27
+ <% end %>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <!-- Stats Overview -->
33
+ <div class="stats-overview">
34
+ <% total_gems = @gems.size %>
35
+ <% outdated_gems = @gems.count { |gem| gem.current_version && gem.version && gem.version != gem.current_version } %>
36
+ <% up_to_date_gems = @gems.count { |gem| gem.current_version && gem.version && gem.version == gem.current_version } %>
37
+ <% unavailable_gems = @gems.count { |gem| gem.info == '(API unavailable)' } %>
38
+
39
+ <div class="stat-card total">
40
+ <div class="stat-icon">📦</div>
41
+ <div class="stat-content">
42
+ <div class="stat-number"><%= total_gems %></div>
43
+ <div class="stat-label">Total Gems</div>
44
+ </div>
45
+ </div>
46
+
47
+ <div class="stat-card outdated">
48
+ <div class="stat-icon">⚠️</div>
49
+ <div class="stat-content">
50
+ <div class="stat-number"><%= outdated_gems %></div>
51
+ <div class="stat-label">Need Updates</div>
52
+ </div>
53
+ </div>
54
+
55
+ <div class="stat-card up-to-date">
56
+ <div class="stat-icon">✅</div>
57
+ <div class="stat-content">
58
+ <div class="stat-number"><%= up_to_date_gems %></div>
59
+ <div class="stat-label">Up to Date</div>
60
+ </div>
61
+ </div>
62
+
63
+ <div class="stat-card unavailable">
64
+ <div class="stat-icon">❓</div>
65
+ <div class="stat-content">
66
+ <div class="stat-number"><%= unavailable_gems %></div>
67
+ <div class="stat-label">Unavailable</div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Search and Filters -->
73
+ <div class="search-filters">
74
+ <div class="search-section">
75
+ <div class="search-input-wrapper">
76
+ <i class="fas fa-search search-icon"></i>
77
+ <input type="search"
78
+ class="search-input"
79
+ id="gem-search"
80
+ placeholder="Search gems by name..."
81
+ aria-label="Search gems">
82
+ <button class="clear-search" id="clear-search" style="display: none;">
83
+ <i class="fas fa-times"></i>
84
+ </button>
85
+ </div>
86
+ </div>
87
+
88
+ <div class="filters-section">
89
+ <div class="filter-group">
90
+ <label class="filter-label">Status:</label>
91
+ <select class="filter-select" id="status-filter">
92
+ <option value="">All</option>
93
+ <option value="outdated">Need Updates</option>
94
+ <option value="up-to-date">Up to Date</option>
95
+ <option value="unavailable">Unavailable</option>
96
+ </select>
97
+ </div>
98
+
99
+ <div class="filter-group">
100
+ <label class="filter-label">Sort by:</label>
101
+ <select class="filter-select" id="sort-filter">
102
+ <option value="name-asc">Name (A-Z)</option>
103
+ <option value="name-desc">Name (Z-A)</option>
104
+ <option value="status-desc">Status (Updates First)</option>
105
+ <option value="release-desc">Release Date (Newest)</option>
106
+ <option value="release-asc">Release Date (Oldest)</option>
107
+ </select>
108
+ </div>
109
+
110
+ <div class="filter-group view-toggle-group">
111
+ <label class="filter-label">View:</label>
112
+ <div class="view-toggle">
113
+ <button class="view-toggle-btn active" id="grid-view-btn" title="Grid View" aria-label="Switch to grid view">
114
+ <i class="fas fa-th"></i>
115
+ </button>
116
+ <button class="view-toggle-btn" id="table-view-btn" title="Table View" aria-label="Switch to table view">
117
+ <i class="fas fa-table"></i>
118
+ </button>
119
+ </div>
120
+ </div>
121
+
122
+ <div class="filter-group export-group" id="export-group" style="display: none;">
123
+ <button class="export-btn" id="export-table-btn" title="Export table data to CSV">
124
+ <i class="fas fa-download"></i>
125
+ Export CSV
126
+ </button>
127
+ </div>
128
+
129
+ <button class="reset-filters" id="reset-filters">
130
+ <i class="fas fa-undo"></i>
131
+ Reset
132
+ </button>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- Loading State -->
137
+ <div class="loading-overlay" id="loading-overlay" style="display: none;">
138
+ <div class="loading-content">
139
+ <div class="loading-spinner"></div>
140
+ <div class="loading-text">Loading gem metadata...</div>
141
+ </div>
142
+ </div>
143
+
144
+ <!-- Gems Grid -->
145
+ <div class="gems-container">
146
+ <% if @gems.any? %>
147
+ <div class="gems-grid" id="gems-grid">
148
+ <% @gems.each_with_index do |gem, index| %>
149
+ <div class="gem-card-full <%= 'unavailable' if gem.info == '(API unavailable)' %>"
150
+ data-gem-name="<%= gem.name.downcase %>"
151
+ data-status="<%= gem.current_version && gem.version && gem.version != gem.current_version ? 'outdated' : (gem.current_version && gem.version && gem.version == gem.current_version ? 'up-to-date' : 'unavailable') %>"
152
+ data-release-date="<%= gem.released&.to_i || 0 %>"
153
+ style="animation-delay: <%= (index * 0.05).round(2) %>s">
154
+
155
+ <div class="gem-card-header">
156
+ <div class="gem-title-section">
157
+ <h3 class="gem-name"><%= gem.name %></h3>
158
+ <div class="gem-status-badges">
159
+ <% if gem.current_version && gem.version && gem.version != gem.current_version %>
160
+ <span class="status-badge upgrade-available" title="Upgrade available">
161
+ <i class="fas fa-arrow-up"></i>
162
+ Update Available
163
+ </span>
164
+ <% elsif gem.current_version && gem.version && gem.version == gem.current_version %>
165
+ <span class="status-badge up-to-date" title="Up to date">
166
+ <i class="fas fa-check"></i>
167
+ Latest
168
+ </span>
169
+ <% elsif gem.info == '(API unavailable)' %>
170
+ <span class="status-badge unavailable" title="API unavailable">
171
+ <i class="fas fa-exclamation-triangle"></i>
172
+ Unavailable
173
+ </span>
174
+ <% end %>
175
+ </div>
176
+ </div>
177
+ </div>
178
+
179
+ <div class="gem-card-body">
180
+ <div class="versions-section">
181
+ <div class="version-group">
182
+ <div class="version-item">
183
+ <span class="version-label">Current</span>
184
+ <% if gem.current_version %>
185
+ <span class="version-badge current" title="Current version">
186
+ <%= gem.current_version %>
187
+ </span>
188
+ <% else %>
189
+ <span class="version-badge unknown">N/A</span>
190
+ <% end %>
191
+ </div>
192
+
193
+ <div class="version-item">
194
+ <span class="version-label">Latest</span>
195
+ <% if gem.version %>
196
+ <span class="version-badge latest" title="Latest version">
197
+ <%= gem.version %>
198
+ </span>
199
+ <% else %>
200
+ <span class="version-badge unknown">N/A</span>
201
+ <% end %>
202
+ </div>
203
+ </div>
204
+ </div>
205
+
206
+ <div class="gem-info-section">
207
+ <div class="release-info">
208
+ <i class="fas fa-calendar-alt"></i>
209
+ <% if gem.released %>
210
+ <span title="<%= gem.released.strftime('%Y-%m-%d %H:%M') %>">
211
+ <%= time_ago_in_words(gem.released) %> ago
212
+ </span>
213
+ <% else %>
214
+ <span>Unknown release date</span>
215
+ <% end %>
216
+ </div>
217
+
218
+ <% if gem.info && gem.info != '(API unavailable)' %>
219
+ <div class="gem-description">
220
+ <i class="fas fa-info-circle"></i>
221
+ <span title="<%= gem.info %>"><%= truncate(gem.info, length: 120) %></span>
222
+ </div>
223
+ <% end %>
224
+ </div>
225
+
226
+ <% if gem.runtime&.any? %>
227
+ <div class="dependencies-section">
228
+ <div class="deps-header">
229
+ <i class="fas fa-cubes"></i>
230
+ <span>Dependencies (<%= gem.runtime.size %>)</span>
231
+ </div>
232
+ <div class="deps-grid">
233
+ <% gem.runtime.first(6).each do |dep| %>
234
+ <span class="dep-badge <%= 'rails-dep' if dep['name'] == 'rails' %>"
235
+ title="<%= dep['name'] %> <%= dep['requirements'] %>">
236
+ <%= dep['name'] %>
237
+ <span class="dep-version"><%= dep['requirements'] %></span>
238
+ </span>
239
+ <% end %>
240
+ <% if gem.runtime.size > 6 %>
241
+ <span class="dep-more"
242
+ title="<%= gem.runtime.drop(6).map { |d| "#{d['name']} #{d['requirements']}" }.join(', ') %>">
243
+ +<%= gem.runtime.size - 6 %> more
244
+ </span>
245
+ <% end %>
246
+ </div>
247
+ </div>
248
+ <% end %>
249
+ </div>
250
+ </div>
251
+ <% end %>
252
+ </div>
253
+
254
+ <!-- Table View (Hidden by default) -->
255
+ <div class="gems-table-container" id="gems-table-container" style="display: none;">
256
+ <div class="table-wrapper">
257
+ <table class="gems-table" id="gems-table">
258
+ <thead>
259
+ <tr>
260
+ <th class="sortable" data-sort="name">
261
+ <span>Gem Name</span>
262
+ <i class="fas fa-sort sort-icon"></i>
263
+ </th>
264
+ <th class="sortable" data-sort="status">
265
+ <span>Status</span>
266
+ <i class="fas fa-sort sort-icon"></i>
267
+ </th>
268
+ <th class="sortable" data-sort="current">
269
+ <span>Current</span>
270
+ <i class="fas fa-sort sort-icon"></i>
271
+ </th>
272
+ <th class="sortable" data-sort="latest">
273
+ <span>Latest</span>
274
+ <i class="fas fa-sort sort-icon"></i>
275
+ </th>
276
+ <th class="sortable" data-sort="released">
277
+ <span>Released</span>
278
+ <i class="fas fa-sort sort-icon"></i>
279
+ </th>
280
+ <th>Description</th>
281
+ <th>Dependencies</th>
282
+ </tr>
283
+ </thead>
284
+ <tbody id="gems-table-body">
285
+ <% @gems.each do |gem| %>
286
+ <tr class="gem-table-row <%= 'unavailable' if gem.info == '(API unavailable)' %>"
287
+ data-gem-name="<%= gem.name.downcase %>"
288
+ data-status="<%= gem.current_version && gem.version && gem.version != gem.current_version ? 'outdated' : (gem.current_version && gem.version && gem.version == gem.current_version ? 'up-to-date' : 'unavailable') %>"
289
+ data-release-date="<%= gem.released&.to_i || 0 %>">
290
+
291
+ <td class="gem-name-cell">
292
+ <strong class="gem-name-table"><%= gem.name %></strong>
293
+ </td>
294
+
295
+ <td class="status-cell">
296
+ <% if gem.current_version && gem.version && gem.version != gem.current_version %>
297
+ <span class="status-badge-table upgrade-available" title="Upgrade available">
298
+ <i class="fas fa-arrow-up"></i>
299
+ Update Available
300
+ </span>
301
+ <% elsif gem.current_version && gem.version && gem.version == gem.current_version %>
302
+ <span class="status-badge-table up-to-date" title="Up to date">
303
+ <i class="fas fa-check"></i>
304
+ Latest
305
+ </span>
306
+ <% elsif gem.info == '(API unavailable)' %>
307
+ <span class="status-badge-table unavailable" title="API unavailable">
308
+ <i class="fas fa-exclamation-triangle"></i>
309
+ Unavailable
310
+ </span>
311
+ <% else %>
312
+ <span class="status-badge-table unknown">
313
+ Unknown
314
+ </span>
315
+ <% end %>
316
+ </td>
317
+
318
+ <td class="version-cell">
319
+ <% if gem.current_version %>
320
+ <span class="version-badge-table current" title="Current version">
321
+ <%= gem.current_version %>
322
+ </span>
323
+ <% else %>
324
+ <span class="version-badge-table unknown">N/A</span>
325
+ <% end %>
326
+ </td>
327
+
328
+ <td class="version-cell">
329
+ <% if gem.version %>
330
+ <span class="version-badge-table latest" title="Latest version">
331
+ <%= gem.version %>
332
+ </span>
333
+ <% else %>
334
+ <span class="version-badge-table unknown">N/A</span>
335
+ <% end %>
336
+ </td>
337
+
338
+ <td class="release-cell">
339
+ <% if gem.released %>
340
+ <span title="<%= gem.released.strftime('%Y-%m-%d %H:%M') %>">
341
+ <%= time_ago_in_words(gem.released) %> ago
342
+ </span>
343
+ <% else %>
344
+ <span class="text-muted">Unknown</span>
345
+ <% end %>
346
+ </td>
347
+
348
+ <td class="description-cell">
349
+ <% if gem.info && gem.info != '(API unavailable)' %>
350
+ <span title="<%= gem.info %>"><%= truncate(gem.info, length: 80) %></span>
351
+ <% else %>
352
+ <span class="text-muted">No description available</span>
353
+ <% end %>
354
+ </td>
355
+
356
+ <td class="dependencies-cell">
357
+ <% if gem.runtime&.any? %>
358
+ <div class="table-deps">
359
+ <% gem.runtime.first(3).each do |dep| %>
360
+ <span class="dep-badge-table <%= 'rails-dep' if dep['name'] == 'rails' %>"
361
+ title="<%= dep['name'] %> <%= dep['requirements'] %>">
362
+ <%= dep['name'] %>
363
+ </span>
364
+ <% end %>
365
+ <% if gem.runtime.size > 3 %>
366
+ <span class="dep-more-table"
367
+ title="<%= gem.runtime.drop(3).map { |d| "#{d['name']} #{d['requirements']}" }.join(', ') %>">
368
+ +<%= gem.runtime.size - 3 %>
369
+ </span>
370
+ <% end %>
371
+ </div>
372
+ <% else %>
373
+ <span class="text-muted">None</span>
374
+ <% end %>
375
+ </td>
376
+ </tr>
377
+ <% end %>
378
+ </tbody>
379
+ </table>
380
+ </div>
381
+ </div>
382
+ <% else %>
383
+ <div class="empty-state">
384
+ <div class="empty-icon">🔍</div>
385
+ <div class="empty-title">No Gem Data Found</div>
386
+ <div class="empty-description">
387
+ It looks like we couldn't find any gem metadata. This could happen if your Gemfile.lock is missing or the API is unavailable.
388
+ </div>
389
+ <div class="empty-actions">
390
+ <%= button_to refresh_gem_metadata_path,
391
+ method: :post,
392
+ class: "action-btn refresh-btn primary",
393
+ form: { style: "display: inline-block;" } do %>
394
+ <i class="fas fa-sync-alt"></i>
395
+ Try Refreshing
396
+ <% end %>
397
+ </div>
398
+ </div>
399
+ <% end %>
400
+ </div>
401
+
402
+ <!-- Footer -->
403
+ <div class="page-footer">
404
+ <div class="footer-content">
405
+ <div class="footer-links">
406
+ <%= link_to "https://railsbump.org",
407
+ class: "footer-link external",
408
+ target: "_blank",
409
+ rel: "noopener" do %>
410
+ <i class="fas fa-external-link-alt"></i>
411
+ Run full Rails compatibility scan on RailsBump
412
+ <% end %>
413
+ </div>
414
+ <div class="footer-stats">
415
+ Last updated: <span id="last-updated">just now</span>
416
+ </div>
417
+ </div>
418
+ </div>
419
+ </div>
data/config/routes.rb CHANGED
@@ -1,4 +1,11 @@
1
1
  Solidstats::Engine.routes.draw do
2
2
  root to: "dashboard#index"
3
3
  get "refresh", to: "dashboard#refresh", as: :refresh
4
+
5
+ # Route for truncating logs - accepts filename without extension
6
+ post "truncate-log(/:filename)", to: "dashboard#truncate_log", as: :truncate_log,
7
+ constraints: { filename: /[^\.]+/ }
8
+
9
+ post "gem_metadata/refresh", to: "gem_metadata#refresh", as: :refresh_gem_metadata
10
+ get "gem_metadata/refresh", to: "gem_metadata#refresh" # Fallback for direct URL access
4
11
  end
@@ -0,0 +1,170 @@
1
+ require "rails/generators/named_base"
2
+
3
+ module Solidstats
4
+ module Generators
5
+ class FeatureGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Generate a new Solidstats feature with service, component, and views"
9
+
10
+ class_option :icon, type: :string, default: "📊", desc: "Icon for the feature"
11
+ class_option :section, type: :string, default: "metrics", desc: "Dashboard section (overview, security, code-quality, metrics)"
12
+ class_option :status_colors, type: :boolean, default: true, desc: "Include status-based coloring"
13
+ class_option :cache_hours, type: :numeric, default: 1, desc: "Cache duration in hours"
14
+
15
+ def create_service
16
+ template "service.rb.erb", "app/services/solidstats/#{file_name}_service.rb"
17
+ end
18
+
19
+ def create_component_helper
20
+ template "component.rb.erb", "app/helpers/solidstats/#{file_name}_helper.rb"
21
+ end
22
+
23
+ def create_component_partial
24
+ template "component.html.erb", "app/views/solidstats/dashboard/_#{file_name}_summary.html.erb"
25
+ end
26
+
27
+ def create_component_styles
28
+ template "component.scss", "app/assets/stylesheets/solidstats/#{file_name}_component.scss"
29
+ end
30
+
31
+ def create_preview
32
+ template "preview.rb.erb", "app/views/solidstats/#{file_name}/preview.html.erb"
33
+ end
34
+
35
+ def create_detail_view
36
+ template "detail_view.html.erb", "app/views/solidstats/#{file_name}/index.html.erb"
37
+ end
38
+
39
+ def create_controller
40
+ template "controller.rb.erb", "app/controllers/solidstats/#{file_name}_controller.rb"
41
+ end
42
+
43
+ def create_service_test
44
+ template "service_test.rb.erb", "test/services/solidstats/#{file_name}_service_test.rb"
45
+ end
46
+
47
+ def create_component_test
48
+ template "component_test.rb.erb", "test/helpers/solidstats/#{file_name}_helper_test.rb"
49
+ end
50
+
51
+ def create_controller_test
52
+ template "controller_test.rb.erb", "test/controllers/solidstats/#{file_name}_controller_test.rb"
53
+ end
54
+
55
+ def add_route
56
+ route_content = " get \"#{file_name}\", to: \"#{file_name}#index\", as: :#{file_name}"
57
+ inject_into_file "config/routes.rb", after: "Solidstats::Engine.routes.draw do\n" do
58
+ "#{route_content}\n"
59
+ end
60
+ end
61
+
62
+ def integrate_with_dashboard
63
+ # Add instance variable to dashboard controller
64
+ inject_into_file "app/controllers/solidstats/dashboard_controller.rb",
65
+ after: "def index\n" do
66
+ " @#{file_name}_data = #{class_name}Service.new.summary\n"
67
+ end
68
+
69
+ # Add to refresh method
70
+ inject_into_file "app/controllers/solidstats/dashboard_controller.rb",
71
+ after: "def refresh\n" do
72
+ " #{file_name}_data = #{class_name}Service.new.fetch(true)\n"
73
+ end
74
+
75
+ # Add to refresh JSON response
76
+ inject_into_file "app/controllers/solidstats/dashboard_controller.rb",
77
+ after: "render json: {\n" do
78
+ " #{file_name}_data: #{file_name}_data,\n"
79
+ end
80
+ end
81
+
82
+ def update_dashboard_view
83
+ # Add summary card to overview section
84
+ card_partial = <<~ERB
85
+
86
+ <div class="summary-card <%= @#{file_name}_data&.dig(:status) || 'status-ok' %>" data-section="#{options[:section]}" data-tab="#{file_name.dasherize}">
87
+ <div class="summary-icon">#{options[:icon]}</div>
88
+ <div class="summary-data">
89
+ <div class="summary-value"><%= @#{file_name}_data&.dig(:value) || 0 %></div>
90
+ <div class="summary-label">#{human_name}</div>
91
+ </div>
92
+ </div>
93
+ ERB
94
+
95
+ inject_into_file "app/views/solidstats/dashboard/index.html.erb",
96
+ after: '<div class="stats-summary">' do
97
+ card_partial
98
+ end
99
+
100
+ # Add section if it doesn't exist for metrics
101
+ if options[:section] == "metrics"
102
+ section_content = <<~ERB
103
+
104
+ <!-- #{human_name} Section -->
105
+ <section id="#{file_name.dasherize}" class="dashboard-section">
106
+ <h2 class="section-title">#{human_name}</h2>
107
+ <%= render Solidstats::#{class_name}Component.new(data: @#{file_name}_data) %>
108
+ </section>
109
+ ERB
110
+
111
+ inject_into_file "app/views/solidstats/dashboard/index.html.erb",
112
+ before: " <!-- Floating quick navigation -->" do
113
+ section_content
114
+ end
115
+
116
+ # Add to navigation
117
+ nav_item = <<~ERB
118
+
119
+ <li><a href="##{file_name.dasherize}" class="nav-item" data-section="#{file_name.dasherize}">#{human_name}</a></li>
120
+ ERB
121
+
122
+ inject_into_file "app/views/solidstats/dashboard/index.html.erb",
123
+ after: '<nav class="dashboard-nav">' do
124
+ " <ul>#{nav_item}"
125
+ end
126
+
127
+ # Add to quick nav
128
+ quick_nav_item = <<~ERB
129
+
130
+ <a href="##{file_name.dasherize}" class="quick-nav-item">#{human_name}</a>
131
+ ERB
132
+
133
+ inject_into_file "app/views/solidstats/dashboard/index.html.erb",
134
+ after: '<div class="quick-nav-menu">' do
135
+ quick_nav_item
136
+ end
137
+ end
138
+ end
139
+
140
+ def create_directory_structure
141
+ empty_directory "app/views/solidstats/#{file_name}"
142
+ end
143
+
144
+ def show_next_steps
145
+ say "\n" + set_color("#{human_name} feature generated successfully!", :green, :bold)
146
+ say "\nNext steps:"
147
+ say " 1. Implement data collection logic in #{set_color("app/services/solidstats/#{file_name}_service.rb", :yellow)}"
148
+ say " 2. Customize the component template in #{set_color("app/components/solidstats/#{file_name}_component.html.erb", :yellow)}"
149
+ say " 3. Add any required styling in #{set_color("app/components/solidstats/#{file_name}_component.scss", :yellow)}"
150
+ say " 4. Run tests: #{set_color("rails test test/services/solidstats/#{file_name}_service_test.rb", :cyan)}"
151
+ say " 5. Visit #{set_color("http://localhost:3000/rails/view_components", :cyan)} to preview the component"
152
+ say "\nThe feature will appear in the #{set_color(options[:section], :blue)} section of the dashboard."
153
+ end
154
+
155
+ private
156
+
157
+ def human_name
158
+ file_name.humanize
159
+ end
160
+
161
+ def class_name
162
+ file_name.camelize
163
+ end
164
+
165
+ def section_human_name
166
+ options[:section].humanize
167
+ end
168
+ end
169
+ end
170
+ end