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