solidstats 3.0.0.beta.1 → 3.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.
- checksums.yaml +4 -4
- data/README.md +33 -0
- data/app/assets/javascripts/solidstats/application.js +257 -0
- data/app/assets/javascripts/solidstats/dashboard.js +179 -0
- data/app/assets/stylesheets/solidstats/application.css +6 -1
- data/app/controllers/solidstats/dashboard_controller.rb +28 -35
- data/app/controllers/solidstats/gem_metadata_controller.rb +12 -0
- data/app/controllers/solidstats/logs_controller.rb +12 -12
- data/app/controllers/solidstats/performance_controller.rb +2 -2
- data/app/controllers/solidstats/productivity_controller.rb +10 -10
- data/app/controllers/solidstats/quality_controller.rb +32 -32
- data/app/controllers/solidstats/securities_controller.rb +7 -7
- data/app/helpers/solidstats/application_helper.rb +10 -10
- data/app/helpers/solidstats/performance_helper.rb +32 -32
- data/app/helpers/solidstats/productivity_helper.rb +20 -20
- data/app/services/solidstats/bundler_audit_service.rb +13 -13
- data/app/services/solidstats/coverage_compass_service.rb +59 -59
- data/app/services/solidstats/load_lens_service.rb +90 -70
- data/app/services/solidstats/log_size_monitor_service.rb +59 -59
- data/app/services/solidstats/my_todo_service.rb +68 -68
- data/app/services/solidstats/style_patrol_service.rb +44 -44
- data/app/views/layouts/solidstats/application.html.erb +1 -1
- data/app/views/solidstats/shared/_quick_actions.html.erb +1 -1
- data/config/routes.rb +4 -4
- data/lib/generators/solidstats/clean/clean_generator.rb +24 -0
- data/lib/generators/solidstats/clean/templates/README +8 -0
- data/lib/generators/solidstats/install/install_generator.rb +32 -17
- data/lib/generators/solidstats/templates/initializer.rb +112 -0
- data/lib/solidstats/asset_compatibility.rb +238 -0
- data/lib/solidstats/asset_manifest.rb +205 -0
- data/lib/solidstats/engine.rb +49 -9
- data/lib/solidstats/version.rb +1 -1
- data/lib/solidstats.rb +24 -11
- data/lib/tasks/solidstats.rake +67 -0
- data/lib/tasks/solidstats_performance.rake +6 -29
- data/lib/tasks/solidstats_tasks.rake +16 -0
- metadata +14 -5
- data/lib/tasks/solidstats_install.rake +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81043c13f33e9684bcc3ea3d99f47f63d9bd17873c1f6792988e9bf752a7e572
|
4
|
+
data.tar.gz: dc4c6a7edd2f8720197e0beba69752deb847bc72422a4c6351670d6f448a04bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae9f6ca74cfe86e05cec7f9b0be6ce1b4621b3f672def7a08a1f94790fd81a8715da2c6927825ebf7c684ce3dbd84081a825a63268963af985ad0dcebbaddbc2
|
7
|
+
data.tar.gz: f856e1776b287e7ca34752b3a6ad27b2cdf842ac7b955fbf95f2e265a4bb45a9820b05bc2403f7c3d6f03301e35dca78890192f14344cfe176a952ecdcadac16
|
data/README.md
CHANGED
@@ -1,12 +1,27 @@
|
|
1
1
|
Solidstats is a local-only Rails engine that shows your project's health at `/solidstats`. The dashboard provides real-time insights into your application's security, code quality, and development tasks.
|
2
2
|
|
3
3
|
## Features
|
4
|
+
|
5
|
+
### Core Dashboard
|
4
6
|
- Interactive security dashboard with real-time refresh capability
|
5
7
|
- Comprehensive gem vulnerability analysis with severity breakdown
|
6
8
|
- Visual security score rating (A+, B, C) and metrics
|
7
9
|
- Bundler Audit scan with detailed remediation suggestions
|
8
10
|
- Interactive vulnerability details with patched version information
|
9
11
|
- Gem impact analysis showing affected gems by severity
|
12
|
+
|
13
|
+
### Gem Metadata System
|
14
|
+
- **Complete Gem Analysis Platform**: Comprehensive gem metadata dashboard with detailed information about all gems in your project
|
15
|
+
- **Dual-View System**: Switch between table and grid layouts for optimal data presentation
|
16
|
+
- **Table View**: Sortable columns with gem name, version, description, dependencies, and status
|
17
|
+
- **Grid View**: Card-based layout with 3 cards per row (responsive: 3 on desktop, 2 on tablet, 1 on mobile)
|
18
|
+
- **Advanced Filtering**: Real-time search and filtering across all gem attributes
|
19
|
+
- **Dependency Analysis**: View gem dependencies with version compatibility information
|
20
|
+
- **Status Monitoring**: Real-time health indicators and gem status tracking
|
21
|
+
- **Download Statistics**: Popularity metrics and download statistics for gems
|
22
|
+
- **License Information**: Security compliance and license tracking
|
23
|
+
|
24
|
+
### System Monitoring
|
10
25
|
- Log Size Monitor for tracking and managing application log files
|
11
26
|
- Log file truncation tool for individual or all log files
|
12
27
|
- LoadLens - Development performance monitoring with request tracking
|
@@ -14,6 +29,12 @@ Solidstats is a local-only Rails engine that shows your project's health at `/so
|
|
14
29
|
- TODO/FIXME tracker with file hotspots
|
15
30
|
- Test coverage summary
|
16
31
|
|
32
|
+
### Architecture
|
33
|
+
- **View Component Architecture**: Modern, maintainable component-based UI system
|
34
|
+
- **Feature Generator System**: Automated scaffolding for rapid development
|
35
|
+
- **CSS Component Architecture**: Organized, conflict-free styling with responsive design
|
36
|
+
- **Cross-Browser Compatibility**: Enhanced support across modern browsers
|
37
|
+
|
17
38
|
## Compatibility
|
18
39
|
|
19
40
|
- Ruby 2.7+: Compatible with Rails 6.1 through Rails 7.0
|
@@ -75,6 +96,18 @@ You can refresh the dashboard data at any time by clicking the "Refresh" button
|
|
75
96
|
3. Show real-time feedback during the refresh process
|
76
97
|
4. Update the "Last Updated" timestamp
|
77
98
|
|
99
|
+
### Gem Metadata
|
100
|
+
Comprehensive gem analysis and management platform featuring:
|
101
|
+
- **Complete Gem Information**: Detailed metadata for all gems in your project including versions, descriptions, dependencies, and status
|
102
|
+
- **Flexible Views**:
|
103
|
+
- **Table View**: Sortable table with comprehensive gem information, perfect for detailed analysis
|
104
|
+
- **Grid View**: Card-based layout showing 3 gems per row with visual appeal and responsive design
|
105
|
+
- **Advanced Search & Filtering**: Real-time filtering across gem names, descriptions, and dependencies
|
106
|
+
- **Dependency Analysis**: View and analyze gem dependencies with version information
|
107
|
+
- **Status Monitoring**: Health indicators and status tracking for all gems
|
108
|
+
- **Download Metrics**: Popularity statistics and download information
|
109
|
+
- **License Tracking**: Security compliance and license information for each gem
|
110
|
+
|
78
111
|
### Code Quality
|
79
112
|
Displays code quality metrics, test coverage, and code health indicators.
|
80
113
|
|
@@ -0,0 +1,257 @@
|
|
1
|
+
// Solidstats JavaScript Application Manifest
|
2
|
+
// This file includes all JavaScript modules for the Solidstats dashboard
|
3
|
+
//
|
4
|
+
//= require dashboard
|
5
|
+
//= require_tree .
|
6
|
+
|
7
|
+
// Initialize dashboard when DOM is ready
|
8
|
+
document.addEventListener('DOMContentLoaded', function() {
|
9
|
+
console.log('🚀 Solidstats Dashboard JavaScript loaded');
|
10
|
+
|
11
|
+
// Ensure dashboard module is available
|
12
|
+
if (typeof window.SolidstatsDashboard !== 'undefined') {
|
13
|
+
console.log('✅ Dashboard module loaded successfully');
|
14
|
+
|
15
|
+
// Verify key functions are available
|
16
|
+
const requiredFunctions = ['init', 'navigateToSection', 'setupMainNavigation'];
|
17
|
+
const missingFunctions = requiredFunctions.filter(func =>
|
18
|
+
typeof window.SolidstatsDashboard[func] !== 'function'
|
19
|
+
);
|
20
|
+
|
21
|
+
if (missingFunctions.length === 0) {
|
22
|
+
console.log('✅ All dashboard functions available');
|
23
|
+
} else {
|
24
|
+
console.warn('⚠️ Missing functions:', missingFunctions);
|
25
|
+
}
|
26
|
+
} else {
|
27
|
+
console.warn('⚠️ Dashboard module not found');
|
28
|
+
console.log('Available globals:', Object.keys(window).filter(k => k.includes('Solid')));
|
29
|
+
}
|
30
|
+
});
|
31
|
+
|
32
|
+
|
33
|
+
// Dashboard JavaScript functionality
|
34
|
+
|
35
|
+
// Create the Solidstats Dashboard namespace
|
36
|
+
window.SolidstatsDashboard = window.SolidstatsDashboard || {};
|
37
|
+
|
38
|
+
// Main dashboard initialization
|
39
|
+
window.SolidstatsDashboard.init = function() {
|
40
|
+
handleUrlNavigation();
|
41
|
+
setupEventListeners();
|
42
|
+
};
|
43
|
+
|
44
|
+
document.addEventListener('DOMContentLoaded', function() {
|
45
|
+
window.SolidstatsDashboard.init();
|
46
|
+
});
|
47
|
+
|
48
|
+
function handleUrlNavigation() {
|
49
|
+
// Parse URL hash for initial navigation
|
50
|
+
const hash = window.location.hash.substring(1);
|
51
|
+
if (hash) {
|
52
|
+
const [section, tab] = hash.split('/');
|
53
|
+
if (section) {
|
54
|
+
setTimeout(() => {
|
55
|
+
window.SolidstatsDashboard.navigateToSection(section, tab, true);
|
56
|
+
}, 100);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
function setupEventListeners() {
|
62
|
+
window.SolidstatsDashboard.setupMainNavigation();
|
63
|
+
window.SolidstatsDashboard.setupTabNavigation();
|
64
|
+
window.SolidstatsDashboard.setupQuickNavigation();
|
65
|
+
window.SolidstatsDashboard.setupSummaryCardNavigation();
|
66
|
+
}
|
67
|
+
|
68
|
+
// Function to update URL hash with current state
|
69
|
+
window.SolidstatsDashboard.updateUrlHash = function(section, tab = null) {
|
70
|
+
let hash = '#' + section;
|
71
|
+
if (tab) {
|
72
|
+
hash += '/' + tab;
|
73
|
+
}
|
74
|
+
history.replaceState(null, null, hash);
|
75
|
+
};
|
76
|
+
|
77
|
+
// Function to navigate to section and tab
|
78
|
+
window.SolidstatsDashboard.navigateToSection = function(section, tab = null, shouldScroll = false) {
|
79
|
+
// Remove active class from all nav items and sections
|
80
|
+
document.querySelectorAll('.nav-item').forEach(item => item.classList.remove('active'));
|
81
|
+
document.querySelectorAll('.dashboard-section').forEach(section => section.classList.remove('active'));
|
82
|
+
|
83
|
+
// Add active class to matching nav item
|
84
|
+
const navItem = document.querySelector(`.nav-item[data-section="${section}"]`);
|
85
|
+
if (navItem) {
|
86
|
+
navItem.classList.add('active');
|
87
|
+
}
|
88
|
+
|
89
|
+
// Show corresponding section
|
90
|
+
const sectionElement = document.getElementById(section);
|
91
|
+
if (sectionElement) {
|
92
|
+
sectionElement.classList.add('active');
|
93
|
+
|
94
|
+
// If tab is specified, activate that tab
|
95
|
+
if (tab) {
|
96
|
+
const sectionEl = sectionElement;
|
97
|
+
if (sectionEl) {
|
98
|
+
// Deactivate all tabs first
|
99
|
+
sectionEl.querySelectorAll('.tab-item').forEach(item => item.classList.remove('active'));
|
100
|
+
sectionEl.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
101
|
+
|
102
|
+
// Activate the target tab
|
103
|
+
const targetTabItem = sectionEl.querySelector(`.tab-item[data-tab="${tab}"]`);
|
104
|
+
const targetTabContent = sectionEl.querySelector(`#${tab}`);
|
105
|
+
|
106
|
+
if (targetTabItem) targetTabItem.classList.add('active');
|
107
|
+
if (targetTabContent) targetTabContent.classList.add('active');
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
// Scroll to section if requested (with a small delay to ensure rendering)
|
112
|
+
if (shouldScroll) {
|
113
|
+
setTimeout(() => {
|
114
|
+
sectionElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
115
|
+
}, 100);
|
116
|
+
}
|
117
|
+
|
118
|
+
// Update URL hash
|
119
|
+
window.SolidstatsDashboard.updateUrlHash(section, tab);
|
120
|
+
}
|
121
|
+
};
|
122
|
+
|
123
|
+
// Main navigation
|
124
|
+
window.SolidstatsDashboard.setupMainNavigation = function() {
|
125
|
+
document.querySelectorAll('.nav-item').forEach(function(navItem) {
|
126
|
+
navItem.addEventListener('click', function(e) {
|
127
|
+
e.preventDefault();
|
128
|
+
|
129
|
+
// Get section ID from data attribute
|
130
|
+
const sectionId = this.getAttribute('data-section');
|
131
|
+
|
132
|
+
// Navigate to the section
|
133
|
+
window.SolidstatsDashboard.navigateToSection(sectionId);
|
134
|
+
});
|
135
|
+
});
|
136
|
+
};
|
137
|
+
|
138
|
+
// Tab navigation
|
139
|
+
window.SolidstatsDashboard.setupTabNavigation = function() {
|
140
|
+
document.querySelectorAll('.tab-item').forEach(function(tabItem) {
|
141
|
+
if (!tabItem.hasAttribute('disabled')) {
|
142
|
+
tabItem.addEventListener('click', function(e) {
|
143
|
+
e.preventDefault();
|
144
|
+
const tabContainer = this.closest('.dashboard-section');
|
145
|
+
const tabId = this.getAttribute('data-tab');
|
146
|
+
|
147
|
+
// Find the current active section
|
148
|
+
const currentSection = tabContainer.id;
|
149
|
+
|
150
|
+
// Remove active class from all tab items and tabs within this container
|
151
|
+
tabContainer.querySelectorAll('.tab-item').forEach(item => item.classList.remove('active'));
|
152
|
+
tabContainer.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
153
|
+
|
154
|
+
// Add active class to clicked item and corresponding tab
|
155
|
+
this.classList.add('active');
|
156
|
+
const targetContent = tabContainer.querySelector(`#${tabId}`);
|
157
|
+
if (targetContent) {
|
158
|
+
targetContent.classList.add('active');
|
159
|
+
}
|
160
|
+
|
161
|
+
// Update URL hash with current section and tab
|
162
|
+
window.SolidstatsDashboard.updateUrlHash(currentSection, tabId);
|
163
|
+
});
|
164
|
+
}
|
165
|
+
});
|
166
|
+
};
|
167
|
+
|
168
|
+
// Quick navigation
|
169
|
+
window.SolidstatsDashboard.setupQuickNavigation = function() {
|
170
|
+
document.querySelectorAll('.quick-nav-item').forEach(function(navItem) {
|
171
|
+
navItem.addEventListener('click', function(e) {
|
172
|
+
e.preventDefault();
|
173
|
+
const targetId = this.getAttribute('href').substring(1);
|
174
|
+
|
175
|
+
// Navigate to the specified section
|
176
|
+
window.SolidstatsDashboard.navigateToSection(targetId);
|
177
|
+
|
178
|
+
// Close the quick nav menu
|
179
|
+
document.querySelector('.quick-nav-menu').style.display = 'none';
|
180
|
+
});
|
181
|
+
});
|
182
|
+
};
|
183
|
+
|
184
|
+
// Summary card navigation
|
185
|
+
window.SolidstatsDashboard.setupSummaryCardNavigation = function() {
|
186
|
+
document.querySelectorAll('.summary-card').forEach(function(card) {
|
187
|
+
card.addEventListener('click', function() {
|
188
|
+
const section = this.getAttribute('data-section');
|
189
|
+
const tab = this.getAttribute('data-tab');
|
190
|
+
|
191
|
+
// Navigate to the specified section and tab, with scrolling
|
192
|
+
window.SolidstatsDashboard.navigateToSection(section, tab, true);
|
193
|
+
});
|
194
|
+
});
|
195
|
+
};
|
196
|
+
|
197
|
+
// Refresh functionality
|
198
|
+
window.SolidstatsDashboard.refreshAudit = function() {
|
199
|
+
// Show loading indicator
|
200
|
+
const refreshButton = document.querySelector('.action-button');
|
201
|
+
const originalText = refreshButton.innerHTML;
|
202
|
+
refreshButton.innerHTML = '<span class="action-icon">⟳</span> Refreshing...';
|
203
|
+
refreshButton.disabled = true;
|
204
|
+
|
205
|
+
// Make AJAX call to refresh endpoint
|
206
|
+
fetch('/solidstats/refresh', {
|
207
|
+
method: 'GET',
|
208
|
+
headers: {
|
209
|
+
'Accept': 'application/json',
|
210
|
+
'X-Requested-With': 'XMLHttpRequest'
|
211
|
+
},
|
212
|
+
credentials: 'same-origin'
|
213
|
+
})
|
214
|
+
.then(response => {
|
215
|
+
if (!response.ok) {
|
216
|
+
throw new Error('Network response was not ok');
|
217
|
+
}
|
218
|
+
return response.json();
|
219
|
+
})
|
220
|
+
.then(data => {
|
221
|
+
// Update the dashboard with fresh data
|
222
|
+
location.reload();
|
223
|
+
|
224
|
+
// Show success notification
|
225
|
+
window.SolidstatsDashboard.showNotification('Dashboard data refreshed successfully', 'success');
|
226
|
+
|
227
|
+
// Reset button state
|
228
|
+
refreshButton.innerHTML = originalText;
|
229
|
+
refreshButton.disabled = false;
|
230
|
+
})
|
231
|
+
.catch(error => {
|
232
|
+
console.error('Error refreshing data:', error);
|
233
|
+
|
234
|
+
// Show error notification
|
235
|
+
window.SolidstatsDashboard.showNotification('Failed to refresh data. Please try again.', 'error');
|
236
|
+
|
237
|
+
// Reset button state
|
238
|
+
refreshButton.innerHTML = originalText;
|
239
|
+
refreshButton.disabled = false;
|
240
|
+
});
|
241
|
+
};
|
242
|
+
|
243
|
+
// Notification system
|
244
|
+
window.SolidstatsDashboard.showNotification = function(message, type) {
|
245
|
+
// Simple notification system
|
246
|
+
const notification = document.createElement('div');
|
247
|
+
notification.className = `notification notification-${type}`;
|
248
|
+
notification.textContent = message;
|
249
|
+
|
250
|
+
// Add to body
|
251
|
+
document.body.appendChild(notification);
|
252
|
+
|
253
|
+
// Remove after 3 seconds
|
254
|
+
setTimeout(() => {
|
255
|
+
notification.remove();
|
256
|
+
}, 3000);
|
257
|
+
};
|
@@ -0,0 +1,179 @@
|
|
1
|
+
// Dashboard JavaScript functionality
|
2
|
+
|
3
|
+
// Create the Solidstats Dashboard namespace
|
4
|
+
window.SolidstatsDashboard = window.SolidstatsDashboard || {};
|
5
|
+
|
6
|
+
// Main dashboard initialization
|
7
|
+
window.SolidstatsDashboard.init = function() {
|
8
|
+
handleUrlNavigation();
|
9
|
+
setupEventListeners();
|
10
|
+
};
|
11
|
+
|
12
|
+
document.addEventListener('DOMContentLoaded', function() {
|
13
|
+
window.SolidstatsDashboard.init();
|
14
|
+
});
|
15
|
+
|
16
|
+
function handleUrlNavigation() {
|
17
|
+
// Parse URL hash for initial navigation
|
18
|
+
const hash = window.location.hash.substring(1);
|
19
|
+
if (hash) {
|
20
|
+
const [section, tab] = hash.split('/');
|
21
|
+
if (section) {
|
22
|
+
setTimeout(() => {
|
23
|
+
window.SolidstatsDashboard.navigateToSection(section, tab, true);
|
24
|
+
}, 100);
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
function setupEventListeners() {
|
30
|
+
window.SolidstatsDashboard.setupMainNavigation();
|
31
|
+
window.SolidstatsDashboard.setupTabNavigation();
|
32
|
+
window.SolidstatsDashboard.setupQuickNavigation();
|
33
|
+
window.SolidstatsDashboard.setupSummaryCardNavigation();
|
34
|
+
}
|
35
|
+
|
36
|
+
// Function to update URL hash with current state
|
37
|
+
window.SolidstatsDashboard.updateUrlHash = function(section, tab = null) {
|
38
|
+
let hash = '#' + section;
|
39
|
+
if (tab) {
|
40
|
+
hash += '/' + tab;
|
41
|
+
}
|
42
|
+
history.replaceState(null, null, hash);
|
43
|
+
};
|
44
|
+
|
45
|
+
// Function to navigate to section and tab
|
46
|
+
window.SolidstatsDashboard.navigateToSection = function(section, tab = null, shouldScroll = false) {
|
47
|
+
// Remove active class from all nav items and sections
|
48
|
+
document.querySelectorAll('.nav-item').forEach(item => item.classList.remove('active'));
|
49
|
+
document.querySelectorAll('.dashboard-section').forEach(section => section.classList.remove('active'));
|
50
|
+
|
51
|
+
// Add active class to matching nav item
|
52
|
+
const navItem = document.querySelector(`.nav-item[data-section="${section}"]`);
|
53
|
+
if (navItem) {
|
54
|
+
navItem.classList.add('active');
|
55
|
+
}
|
56
|
+
|
57
|
+
// Show corresponding section
|
58
|
+
const sectionElement = document.getElementById(section);
|
59
|
+
if (sectionElement) {
|
60
|
+
sectionElement.classList.add('active');
|
61
|
+
|
62
|
+
// If tab is specified, activate that tab
|
63
|
+
if (tab) {
|
64
|
+
const sectionEl = sectionElement;
|
65
|
+
if (sectionEl) {
|
66
|
+
// Deactivate all tabs first
|
67
|
+
sectionEl.querySelectorAll('.tab-item').forEach(item => item.classList.remove('active'));
|
68
|
+
sectionEl.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
69
|
+
|
70
|
+
// Activate the target tab
|
71
|
+
const targetTabItem = sectionEl.querySelector(`.tab-item[data-tab="${tab}"]`);
|
72
|
+
const targetTabContent = sectionEl.querySelector(`#${tab}`);
|
73
|
+
|
74
|
+
if (targetTabItem) targetTabItem.classList.add('active');
|
75
|
+
if (targetTabContent) targetTabContent.classList.add('active');
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
// Scroll to section if requested (with a small delay to ensure rendering)
|
80
|
+
if (shouldScroll) {
|
81
|
+
setTimeout(() => {
|
82
|
+
sectionElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
83
|
+
}, 100);
|
84
|
+
}
|
85
|
+
|
86
|
+
// Update URL hash
|
87
|
+
window.SolidstatsDashboard.updateUrlHash(section, tab);
|
88
|
+
}
|
89
|
+
};
|
90
|
+
|
91
|
+
// Main navigation
|
92
|
+
window.SolidstatsDashboard.setupMainNavigation = function() {
|
93
|
+
document.querySelectorAll('.nav-item').forEach(function(navItem) {
|
94
|
+
navItem.addEventListener('click', function(e) {
|
95
|
+
e.preventDefault();
|
96
|
+
|
97
|
+
// Get section ID from data attribute
|
98
|
+
const sectionId = this.getAttribute('data-section');
|
99
|
+
|
100
|
+
// Navigate to the section
|
101
|
+
window.SolidstatsDashboard.navigateToSection(sectionId);
|
102
|
+
});
|
103
|
+
});
|
104
|
+
};
|
105
|
+
|
106
|
+
// Tab navigation
|
107
|
+
window.SolidstatsDashboard.setupTabNavigation = function() {
|
108
|
+
document.querySelectorAll('.tab-item').forEach(function(tabItem) {
|
109
|
+
if (!tabItem.hasAttribute('disabled')) {
|
110
|
+
tabItem.addEventListener('click', function(e) {
|
111
|
+
e.preventDefault();
|
112
|
+
const tabContainer = this.closest('.dashboard-section');
|
113
|
+
const tabId = this.getAttribute('data-tab');
|
114
|
+
|
115
|
+
// Find the current active section
|
116
|
+
const currentSection = tabContainer.id;
|
117
|
+
|
118
|
+
// Remove active class from all tab items and tabs within this container
|
119
|
+
tabContainer.querySelectorAll('.tab-item').forEach(item => item.classList.remove('active'));
|
120
|
+
tabContainer.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
121
|
+
|
122
|
+
// Add active class to clicked item and corresponding tab
|
123
|
+
this.classList.add('active');
|
124
|
+
const targetContent = tabContainer.querySelector(`#${tabId}`);
|
125
|
+
if (targetContent) {
|
126
|
+
targetContent.classList.add('active');
|
127
|
+
}
|
128
|
+
|
129
|
+
// Update URL hash with current section and tab
|
130
|
+
window.SolidstatsDashboard.updateUrlHash(currentSection, tabId);
|
131
|
+
});
|
132
|
+
}
|
133
|
+
});
|
134
|
+
};
|
135
|
+
|
136
|
+
// Quick navigation
|
137
|
+
window.SolidstatsDashboard.setupQuickNavigation = function() {
|
138
|
+
document.querySelectorAll('.quick-nav-item').forEach(function(navItem) {
|
139
|
+
navItem.addEventListener('click', function(e) {
|
140
|
+
e.preventDefault();
|
141
|
+
const targetId = this.getAttribute('href').substring(1);
|
142
|
+
|
143
|
+
// Navigate to the specified section
|
144
|
+
window.SolidstatsDashboard.navigateToSection(targetId);
|
145
|
+
|
146
|
+
// Close the quick nav menu
|
147
|
+
document.querySelector('.quick-nav-menu').style.display = 'none';
|
148
|
+
});
|
149
|
+
});
|
150
|
+
};
|
151
|
+
|
152
|
+
// Summary card navigation
|
153
|
+
window.SolidstatsDashboard.setupSummaryCardNavigation = function() {
|
154
|
+
document.querySelectorAll('.summary-card').forEach(function(card) {
|
155
|
+
card.addEventListener('click', function() {
|
156
|
+
const section = this.getAttribute('data-section');
|
157
|
+
const tab = this.getAttribute('data-tab');
|
158
|
+
|
159
|
+
// Navigate to the specified section and tab, with scrolling
|
160
|
+
window.SolidstatsDashboard.navigateToSection(section, tab, true);
|
161
|
+
});
|
162
|
+
});
|
163
|
+
};
|
164
|
+
|
165
|
+
// Notification system
|
166
|
+
window.SolidstatsDashboard.showNotification = function(message, type) {
|
167
|
+
// Simple notification system
|
168
|
+
const notification = document.createElement('div');
|
169
|
+
notification.className = `notification notification-${type}`;
|
170
|
+
notification.textContent = message;
|
171
|
+
|
172
|
+
// Add to body
|
173
|
+
document.body.appendChild(notification);
|
174
|
+
|
175
|
+
// Remove after 3 seconds
|
176
|
+
setTimeout(() => {
|
177
|
+
notification.remove();
|
178
|
+
}, 3000);
|
179
|
+
};
|
@@ -10,6 +10,11 @@
|
|
10
10
|
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
11
|
* It is generally better to create a new file per style scope.
|
12
12
|
*
|
13
|
-
*= require_tree
|
13
|
+
*= require_tree ./components
|
14
14
|
*= require_self
|
15
15
|
*/
|
16
|
+
|
17
|
+
/* Base Solidstats styles */
|
18
|
+
.solidstats-dashboard {
|
19
|
+
font-family: system-ui, -apple-system, sans-serif;
|
20
|
+
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Solidstats
|
2
2
|
class DashboardController < ApplicationController
|
3
|
-
layout
|
4
|
-
|
3
|
+
layout "solidstats/dashboard"
|
4
|
+
|
5
5
|
TODO_CACHE_FILE = Rails.root.join("tmp", "solidstats_todos.json")
|
6
6
|
AUDIT_CACHE_HOURS = 12 # Configure how many hours before refreshing
|
7
7
|
|
@@ -10,7 +10,7 @@ module Solidstats
|
|
10
10
|
# Load dashboard cards from JSON file
|
11
11
|
@dashboard_cards = load_dashboard_cards
|
12
12
|
@quick_actions = quick_actions_data
|
13
|
-
render
|
13
|
+
render "dashboard"
|
14
14
|
end
|
15
15
|
|
16
16
|
def refresh
|
@@ -21,8 +21,11 @@ module Solidstats
|
|
21
21
|
Solidstats::StylePatrolService.refresh_cache
|
22
22
|
Solidstats::CoverageCompassService.refresh_cache
|
23
23
|
Solidstats::LoadLensService.scan_and_cache
|
24
|
-
|
25
|
-
|
24
|
+
|
25
|
+
respond_to do |format|
|
26
|
+
format.html { redirect_to solidstats_dashboard_path, notice: "Dashboard data refreshed successfully!" }
|
27
|
+
format.json { render json: { status: "success", message: "Dashboard data refreshed successfully!" } }
|
28
|
+
end
|
26
29
|
end
|
27
30
|
|
28
31
|
private
|
@@ -30,39 +33,39 @@ module Solidstats
|
|
30
33
|
def quick_actions_data
|
31
34
|
[
|
32
35
|
{
|
33
|
-
icon:
|
34
|
-
label:
|
35
|
-
color:
|
36
|
-
action:
|
36
|
+
icon: "refresh-cw",
|
37
|
+
label: "Refresh Data",
|
38
|
+
color: "blue",
|
39
|
+
action: "refresh_path"
|
37
40
|
},
|
38
41
|
{
|
39
|
-
icon:
|
40
|
-
label:
|
41
|
-
color:
|
42
|
-
action:
|
42
|
+
icon: "settings",
|
43
|
+
label: "Configure",
|
44
|
+
color: "purple",
|
45
|
+
action: "#"
|
43
46
|
},
|
44
47
|
{
|
45
|
-
icon:
|
46
|
-
label:
|
47
|
-
color:
|
48
|
-
action:
|
48
|
+
icon: "download",
|
49
|
+
label: "Export",
|
50
|
+
color: "green",
|
51
|
+
action: "#"
|
49
52
|
},
|
50
53
|
{
|
51
|
-
icon:
|
52
|
-
label:
|
53
|
-
color:
|
54
|
-
action:
|
54
|
+
icon: "bell",
|
55
|
+
label: "Alerts",
|
56
|
+
color: "orange",
|
57
|
+
action: "#"
|
55
58
|
}
|
56
59
|
]
|
57
60
|
end
|
58
61
|
|
59
62
|
def load_dashboard_cards
|
60
63
|
json_file_path = Rails.root.join("solidstats", "summary.json")
|
61
|
-
|
64
|
+
|
62
65
|
begin
|
63
66
|
# Read and parse the JSON file
|
64
67
|
json_data = JSON.parse(File.read(json_file_path))
|
65
|
-
|
68
|
+
|
66
69
|
# Transform the JSON data into the format expected by the view
|
67
70
|
json_data.map do |name, data|
|
68
71
|
{
|
@@ -77,23 +80,13 @@ module Solidstats
|
|
77
80
|
end
|
78
81
|
rescue Errno::ENOENT
|
79
82
|
Rails.logger.warn("Summary JSON file not found, generating initial data...")
|
80
|
-
|
81
|
-
|
83
|
+
# Fallback to empty array if JSON is invalid
|
84
|
+
[]
|
82
85
|
rescue JSON::ParserError => e
|
83
86
|
Rails.logger.error("Error parsing summary JSON: #{e.message}")
|
84
87
|
# Fallback to empty array if JSON is invalid
|
85
88
|
[]
|
86
89
|
end
|
87
90
|
end
|
88
|
-
|
89
|
-
def generate_initial_data
|
90
|
-
# Force a scan to create initial data if missing
|
91
|
-
Solidstats::LogSizeMonitorService.scan_and_cache
|
92
|
-
Solidstats::BundlerAuditService.scan_and_cache
|
93
|
-
Solidstats::MyTodoService.collect_todos
|
94
|
-
Solidstats::StylePatrolService.refresh_cache
|
95
|
-
Solidstats::CoverageCompassService.refresh_cache
|
96
|
-
Solidstats::DevLogParserService.scan_and_cache
|
97
|
-
end
|
98
91
|
end
|
99
92
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Solidstats
|
2
|
+
class GemMetadataController < ApplicationController
|
3
|
+
def refresh
|
4
|
+
@gems = Solidstats::GemMetadata::FetcherService.call(nil, true)
|
5
|
+
|
6
|
+
respond_to do |format|
|
7
|
+
format.html { redirect_to "/solidstats#gem-metadata", notice: "Gem metadata refreshed successfully." }
|
8
|
+
format.json { render json: { gems: @gems } }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|