solidstats 0.0.4 → 1.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/CHANGELOG.md +61 -5
- data/README.md +52 -5
- data/app/controllers/solidstats/dashboard_controller.rb +43 -123
- data/app/services/solidstats/audit_service.rb +56 -0
- data/app/services/solidstats/data_collector_service.rb +83 -0
- data/app/services/solidstats/todo_service.rb +114 -0
- data/app/views/solidstats/dashboard/_todos.html.erb +6 -27
- data/app/views/solidstats/dashboard/audit/_additional_styles.css +22 -0
- data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +47 -58
- data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +1 -1
- data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +0 -23
- data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +1092 -38
- data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +4 -3
- data/app/views/solidstats/dashboard/index.html.erb +1152 -162
- data/config/routes.rb +1 -0
- data/lib/solidstats/version.rb +1 -1
- data/lib/tasks/solidstats_release.rake +69 -0
- metadata +10 -5
- data/app/views/solidstats/dashboard/audit/_audit_filters.html.erb +0 -5
| @@ -1,66 +1,1120 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            <% results = @audit_output.dig("results") || [] %>
         | 
| 2 | 
            +
            <div class="vulnerabilities-controls">
         | 
| 3 | 
            +
              <div class="vulnerability-filters">
         | 
| 4 | 
            +
                <div class="filter-group">
         | 
| 5 | 
            +
                  <label>Severity:</label>
         | 
| 6 | 
            +
                  <button class="filter-btn active" data-filter="all">All</button>
         | 
| 7 | 
            +
                  <button class="filter-btn critical-filter" data-filter="critical">Critical</button>
         | 
| 8 | 
            +
                  <button class="filter-btn high-filter" data-filter="high">High</button>
         | 
| 9 | 
            +
                  <button class="filter-btn" data-filter="medium">Medium</button>
         | 
| 10 | 
            +
                  <button class="filter-btn" data-filter="low">Low</button>
         | 
| 11 | 
            +
                  <button class="filter-btn" data-filter="unknown">Unknown</button>
         | 
| 12 | 
            +
                </div>
         | 
| 13 | 
            +
                <div class="search-container">
         | 
| 14 | 
            +
                  <input type="text" id="vulnerability-search" placeholder="Search vulnerabilities..." class="search-input">
         | 
| 15 | 
            +
                  <div class="search-icon">🔍</div>
         | 
| 16 | 
            +
                </div>
         | 
| 17 | 
            +
              </div>
         | 
| 18 | 
            +
              <div class="vulnerability-actions">
         | 
| 19 | 
            +
                <button class="action-button bulk-action-button" disabled>
         | 
| 20 | 
            +
                  <span class="action-icon">✓</span> Bulk Update
         | 
| 21 | 
            +
                </button>
         | 
| 22 | 
            +
                <button class="action-button export-csv-button" disabled>
         | 
| 23 | 
            +
                  <span class="action-icon">↓</span> Export CSV
         | 
| 24 | 
            +
                </button>
         | 
| 25 | 
            +
              </div>
         | 
| 26 | 
            +
            </div>
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            <div class="vulnerability-summary">
         | 
| 29 | 
            +
              <div class="donut-chart">
         | 
| 30 | 
            +
                <div class="chart-container" id="severity-donut-chart">
         | 
| 31 | 
            +
                  <!-- CSS-based donut chart -->
         | 
| 32 | 
            +
                  <% 
         | 
| 33 | 
            +
                    # Calculate counts for each severity
         | 
| 34 | 
            +
                    critical_count = results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "critical" }
         | 
| 35 | 
            +
                    high_count = results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "high" }
         | 
| 36 | 
            +
                    medium_count = results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "medium" }
         | 
| 37 | 
            +
                    low_count = results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "low" }
         | 
| 38 | 
            +
                    unknown_count = results.count { |r| ["", nil].include?(r.dig("advisory", "criticality")) || 
         | 
| 39 | 
            +
                                                        r.dig("advisory", "criticality").to_s.downcase == "unknown" }
         | 
| 40 | 
            +
                    
         | 
| 41 | 
            +
                    # Calculate total for percentages
         | 
| 42 | 
            +
                    total = critical_count + high_count + medium_count + low_count + unknown_count
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    # Calculate percentages (avoid division by zero)
         | 
| 45 | 
            +
                    critical_pct = total > 0 ? critical_count.to_f / total : 0
         | 
| 46 | 
            +
                    high_pct = total > 0 ? high_count.to_f / total : 0
         | 
| 47 | 
            +
                    medium_pct = total > 0 ? medium_count.to_f / total : 0
         | 
| 48 | 
            +
                    low_pct = total > 0 ? low_count.to_f / total : 0
         | 
| 49 | 
            +
                    unknown_pct = total > 0 ? unknown_count.to_f / total : 0
         | 
| 50 | 
            +
                  %>
         | 
| 51 | 
            +
                  <div class="donut" 
         | 
| 52 | 
            +
                       style="--critical: <%= critical_pct %>; 
         | 
| 53 | 
            +
                              --high: <%= high_pct %>; 
         | 
| 54 | 
            +
                              --medium: <%= medium_pct %>; 
         | 
| 55 | 
            +
                              --low: <%= low_pct %>; 
         | 
| 56 | 
            +
                              --unknown: <%= unknown_pct %>;"
         | 
| 57 | 
            +
                       data-count="<%= total %>"
         | 
| 58 | 
            +
                       data-critical="<%= critical_count %>"
         | 
| 59 | 
            +
                       data-high="<%= high_count %>"
         | 
| 60 | 
            +
                       data-medium="<%= medium_count %>"
         | 
| 61 | 
            +
                       data-low="<%= low_count %>"
         | 
| 62 | 
            +
                       data-unknown="<%= unknown_count %>">
         | 
| 63 | 
            +
                  </div>
         | 
| 64 | 
            +
                </div>
         | 
| 65 | 
            +
                <div class="chart-legend">
         | 
| 66 | 
            +
                  <div class="legend-item">
         | 
| 67 | 
            +
                    <span class="legend-color critical"></span>
         | 
| 68 | 
            +
                    <span class="legend-label">Critical</span>
         | 
| 69 | 
            +
                  </div>
         | 
| 70 | 
            +
                  <div class="legend-item">
         | 
| 71 | 
            +
                    <span class="legend-color high"></span>
         | 
| 72 | 
            +
                    <span class="legend-label">High</span>
         | 
| 73 | 
            +
                  </div>
         | 
| 74 | 
            +
                  <div class="legend-item">
         | 
| 75 | 
            +
                    <span class="legend-color medium"></span>
         | 
| 76 | 
            +
                    <span class="legend-label">Medium</span>
         | 
| 77 | 
            +
                  </div>
         | 
| 78 | 
            +
                  <div class="legend-item">
         | 
| 79 | 
            +
                    <span class="legend-color low"></span>
         | 
| 80 | 
            +
                    <span class="legend-label">Low</span>
         | 
| 81 | 
            +
                  </div>
         | 
| 82 | 
            +
                  <div class="legend-item">
         | 
| 83 | 
            +
                    <span class="legend-color unknown"></span>
         | 
| 84 | 
            +
                    <span class="legend-label">Unknown</span>
         | 
| 85 | 
            +
                  </div>
         | 
| 86 | 
            +
                </div>
         | 
| 87 | 
            +
              </div>
         | 
| 88 | 
            +
              
         | 
| 89 | 
            +
              <div class="vulnerability-stats">
         | 
| 90 | 
            +
                <div class="stat-item">
         | 
| 91 | 
            +
                  <div class="stat-value"><%= results.count { |r| %w[critical high].include?(r.dig("advisory", "criticality").to_s.downcase) } %></div>
         | 
| 92 | 
            +
                  <div class="stat-label">Critical/High</div>
         | 
| 93 | 
            +
                </div>
         | 
| 94 | 
            +
                <div class="stat-item">
         | 
| 95 | 
            +
                  <div class="stat-value"><%= results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "medium" } %></div>
         | 
| 96 | 
            +
                  <div class="stat-label">Medium</div>
         | 
| 97 | 
            +
                </div>
         | 
| 98 | 
            +
                <div class="stat-item">
         | 
| 99 | 
            +
                  <div class="stat-value"><%= results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "low" } %></div>
         | 
| 100 | 
            +
                  <div class="stat-label">Low</div>
         | 
| 101 | 
            +
                </div>
         | 
| 102 | 
            +
              </div>
         | 
| 103 | 
            +
            </div>
         | 
| 104 | 
            +
             | 
| 2 105 | 
             
            <div class="vulnerabilities-table">
         | 
| 3 106 | 
             
              <table class="table">
         | 
| 4 107 | 
             
                <thead>
         | 
| 5 108 | 
             
                  <tr>
         | 
| 6 | 
            -
                    <th> | 
| 7 | 
            -
                    <th> | 
| 8 | 
            -
                    <th> | 
| 9 | 
            -
                    <th>CVE</th>
         | 
| 10 | 
            -
                    <th> | 
| 11 | 
            -
                    <th> | 
| 109 | 
            +
                    <th class="severity-column">Severity</th>
         | 
| 110 | 
            +
                    <th class="gem-column">Gem</th>
         | 
| 111 | 
            +
                    <th class="version-column">Version</th>
         | 
| 112 | 
            +
                    <th class="cve-column">CVE</th>
         | 
| 113 | 
            +
                    <th class="description-column">Description</th>
         | 
| 114 | 
            +
                    <th class="remediation-column">Remediation</th>
         | 
| 12 115 | 
             
                  </tr>
         | 
| 13 116 | 
             
                </thead>
         | 
| 14 117 | 
             
                <tbody>
         | 
| 15 | 
            -
                  <%  | 
| 118 | 
            +
                  <% # We already defined results at the top of the file %>
         | 
| 16 119 | 
             
                  <% results.each_with_index do |result, index| %>
         | 
| 17 120 | 
             
                    <% 
         | 
| 18 121 | 
             
                      gem = result.dig("gem") || {}
         | 
| 19 122 | 
             
                      advisory = result.dig("advisory") || {}
         | 
| 20 | 
            -
                      criticality = advisory["criticality"] || " | 
| 21 | 
            -
                       | 
| 123 | 
            +
                      criticality = advisory["criticality"] || "unknown"
         | 
| 124 | 
            +
                      criticality = criticality.to_s.downcase # Ensure consistent lowercase
         | 
| 125 | 
            +
                      is_high = %w[high critical].include?(criticality)
         | 
| 126 | 
            +
                      
         | 
| 127 | 
            +
                      # Extract CVE or GHSA ID
         | 
| 128 | 
            +
                      vuln_id = advisory["cve"].present? ? "CVE-#{advisory["cve"]}" : 
         | 
| 129 | 
            +
                               (advisory["ghsa"].present? ? "GHSA-#{advisory["ghsa"]}" : "N/A")
         | 
| 130 | 
            +
                      
         | 
| 131 | 
            +
                      # Format the link
         | 
| 132 | 
            +
                      vuln_link = advisory["cve"].present? ? "https://nvd.nist.gov/vuln/detail/CVE-#{advisory["cve"]}" :
         | 
| 133 | 
            +
                                 (advisory["ghsa"].present? ? "https://github.com/advisories/#{advisory["ghsa"]}" : "#")
         | 
| 134 | 
            +
                                 
         | 
| 135 | 
            +
                      # Extract published date and format it
         | 
| 136 | 
            +
                      published_date = advisory["date"] || "Unknown"
         | 
| 137 | 
            +
                      formatted_date = published_date != "Unknown" ? Date.parse(published_date).strftime("%b %d, %Y") : "Unknown"
         | 
| 22 138 | 
             
                    %>
         | 
| 23 | 
            -
                    <tr class="vulnerability-row <%=  | 
| 24 | 
            -
                      <td | 
| 25 | 
            -
             | 
| 26 | 
            -
                      <td>
         | 
| 27 | 
            -
                        <span class="severity <%= criticality.to_s.downcase %>">
         | 
| 139 | 
            +
                    <tr class="vulnerability-row <%= criticality.to_s.downcase %>" data-severity="<%= criticality.to_s.downcase %>" data-gem="<%= gem["name"] %>" data-index="<%= index %>">
         | 
| 140 | 
            +
                      <td class="severity-column">
         | 
| 141 | 
            +
                        <div class="severity-badge <%= criticality.to_s.downcase %>">
         | 
| 28 142 | 
             
                          <%= criticality %>
         | 
| 29 | 
            -
                        </ | 
| 143 | 
            +
                        </div>
         | 
| 30 144 | 
             
                      </td>
         | 
| 31 | 
            -
                      <td>
         | 
| 32 | 
            -
                         | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 145 | 
            +
                      <td class="gem-column">
         | 
| 146 | 
            +
                        <div class="gem-name"><%= gem["name"] %></div>
         | 
| 147 | 
            +
                        <% if gem["source_url"].present? %>
         | 
| 148 | 
            +
                          <a href="<%= gem["source_url"] %>" target="_blank" class="gem-source-link">
         | 
| 149 | 
            +
                            <small>Repository <span class="external-link-icon">↗</span></small>
         | 
| 35 150 | 
             
                          </a>
         | 
| 36 | 
            -
                        <%  | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 151 | 
            +
                        <% end %>
         | 
| 152 | 
            +
                      </td>
         | 
| 153 | 
            +
                      <td class="version-column">
         | 
| 154 | 
            +
                        <div class="version-info">
         | 
| 155 | 
            +
                          <span class="current-version"><%= gem["version"] %></span>
         | 
| 156 | 
            +
                        </div>
         | 
| 157 | 
            +
                        <div class="vulnerability-meta">
         | 
| 158 | 
            +
                          <small>Published: <%= formatted_date %></small>
         | 
| 159 | 
            +
                        </div>
         | 
| 160 | 
            +
                      </td>
         | 
| 161 | 
            +
                      <td class="cve-column">
         | 
| 162 | 
            +
                        <% if vuln_id != "N/A" %>
         | 
| 163 | 
            +
                          <a href="<%= vuln_link %>" target="_blank" class="vuln-link">
         | 
| 164 | 
            +
                            <%= vuln_id %>
         | 
| 165 | 
            +
                            <span class="external-link-icon">↗</span>
         | 
| 39 166 | 
             
                          </a>
         | 
| 40 167 | 
             
                        <% else %>
         | 
| 41 168 | 
             
                          N/A
         | 
| 42 169 | 
             
                        <% end %>
         | 
| 43 170 | 
             
                      </td>
         | 
| 44 | 
            -
                      <td>
         | 
| 45 | 
            -
                         | 
| 171 | 
            +
                      <td class="description-column">
         | 
| 172 | 
            +
                        <div class="vulnerability-title"><%= advisory["title"] %></div>
         | 
| 46 173 | 
             
                        <% if advisory["description"].present? %>
         | 
| 47 | 
            -
                          <a href="#vulnerability-<%= index %>" class=" | 
| 174 | 
            +
                          <a href="#vulnerability-<%= index %>" class="details-link-btn scroll-to-details">
         | 
| 175 | 
            +
                            <span class="details-icon">⤵</span> More details
         | 
| 176 | 
            +
                          </a>
         | 
| 48 177 | 
             
                        <% end %>
         | 
| 49 178 | 
             
                      </td>
         | 
| 50 | 
            -
                      <td>
         | 
| 51 | 
            -
                         | 
| 52 | 
            -
                           | 
| 53 | 
            -
                            < | 
| 54 | 
            -
                              < | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 179 | 
            +
                      <td class="remediation-column">
         | 
| 180 | 
            +
                        <% if advisory["patched_versions"].present? %>
         | 
| 181 | 
            +
                          <div class="remediation-action">
         | 
| 182 | 
            +
                            <div class="patched-versions-pill">
         | 
| 183 | 
            +
                              <div class="versions-container">
         | 
| 184 | 
            +
                                <% advisory["patched_versions"].each_with_index do |version_text, index| %>
         | 
| 185 | 
            +
                                  <% version_display = version_text.include?(">=") ? version_text : ">= #{version_text}" %>
         | 
| 186 | 
            +
                                  <span class="solution-version<%= index == advisory["patched_versions"].size - 1 ? ' last-version' : '' %>"><%= version_display %></span>
         | 
| 187 | 
            +
                                <% end %>
         | 
| 188 | 
            +
                              </div>
         | 
| 189 | 
            +
                              <button class="copy-btn" data-solution="<%= "#{gem["name"]}, #{advisory["patched_versions"].join(", ")}" %>" title="Copy gem name and patched versions">
         | 
| 190 | 
            +
                                <span class="copy-icon">📋</span>
         | 
| 191 | 
            +
                              </button>
         | 
| 192 | 
            +
                            </div>
         | 
| 193 | 
            +
                          </div>
         | 
| 194 | 
            +
                        <% else %>
         | 
| 195 | 
            +
                          <span class="no-patch">No patch available</span>
         | 
| 196 | 
            +
                        <% end %>
         | 
| 61 197 | 
             
                      </td>
         | 
| 62 198 | 
             
                    </tr>
         | 
| 63 199 | 
             
                  <% end %>
         | 
| 64 200 | 
             
                </tbody>
         | 
| 65 201 | 
             
              </table>
         | 
| 66 | 
            -
            </div>
         | 
| 202 | 
            +
            </div>
         | 
| 203 | 
            +
             | 
| 204 | 
            +
            <style>
         | 
| 205 | 
            +
              .vulnerabilities-controls {
         | 
| 206 | 
            +
                display: flex;
         | 
| 207 | 
            +
                justify-content: space-between;
         | 
| 208 | 
            +
                align-items: center;
         | 
| 209 | 
            +
                margin-bottom: 1.5rem;
         | 
| 210 | 
            +
                flex-wrap: wrap;
         | 
| 211 | 
            +
                gap: 1rem;
         | 
| 212 | 
            +
              }
         | 
| 213 | 
            +
              
         | 
| 214 | 
            +
              .vulnerability-filters {
         | 
| 215 | 
            +
                display: flex;
         | 
| 216 | 
            +
                align-items: center;
         | 
| 217 | 
            +
                gap: 1.5rem;
         | 
| 218 | 
            +
                flex-wrap: wrap;
         | 
| 219 | 
            +
              }
         | 
| 220 | 
            +
              
         | 
| 221 | 
            +
              .filter-group {
         | 
| 222 | 
            +
                display: flex;
         | 
| 223 | 
            +
                align-items: center;
         | 
| 224 | 
            +
                gap: 0.5rem;
         | 
| 225 | 
            +
              }
         | 
| 226 | 
            +
              
         | 
| 227 | 
            +
              .filter-group label {
         | 
| 228 | 
            +
                font-size: 0.9rem;
         | 
| 229 | 
            +
                color: #555;
         | 
| 230 | 
            +
              }
         | 
| 231 | 
            +
              
         | 
| 232 | 
            +
              .filter-btn {
         | 
| 233 | 
            +
                background: #f8f9fa;
         | 
| 234 | 
            +
                border: 1px solid #dee2e6;
         | 
| 235 | 
            +
                padding: 0.35rem 0.75rem;
         | 
| 236 | 
            +
                border-radius: 4px;
         | 
| 237 | 
            +
                font-size: 0.85rem;
         | 
| 238 | 
            +
                cursor: pointer;
         | 
| 239 | 
            +
                transition: all 0.2s;
         | 
| 240 | 
            +
              }
         | 
| 241 | 
            +
              
         | 
| 242 | 
            +
              .filter-btn:hover {
         | 
| 243 | 
            +
                background-color: #e9ecef;
         | 
| 244 | 
            +
              }
         | 
| 245 | 
            +
              
         | 
| 246 | 
            +
              .filter-btn.active {
         | 
| 247 | 
            +
                background-color: #e9f5ff;
         | 
| 248 | 
            +
                color: #0366d6;
         | 
| 249 | 
            +
                border-color: #0366d6;
         | 
| 250 | 
            +
              }
         | 
| 251 | 
            +
              
         | 
| 252 | 
            +
              .critical-filter {
         | 
| 253 | 
            +
                border-left: 3px solid #dc3545;
         | 
| 254 | 
            +
              }
         | 
| 255 | 
            +
              
         | 
| 256 | 
            +
              .high-filter {
         | 
| 257 | 
            +
                border-left: 3px solid #fd7e14;
         | 
| 258 | 
            +
              }
         | 
| 259 | 
            +
              
         | 
| 260 | 
            +
              .search-container {
         | 
| 261 | 
            +
                position: relative;
         | 
| 262 | 
            +
              }
         | 
| 263 | 
            +
              
         | 
| 264 | 
            +
              .search-input {
         | 
| 265 | 
            +
                padding: 0.5rem 0.75rem 0.5rem 2rem;
         | 
| 266 | 
            +
                border: 1px solid #dee2e6;
         | 
| 267 | 
            +
                border-radius: 4px;
         | 
| 268 | 
            +
                font-size: 0.9rem;
         | 
| 269 | 
            +
                width: 250px;
         | 
| 270 | 
            +
              }
         | 
| 271 | 
            +
              
         | 
| 272 | 
            +
              .search-icon {
         | 
| 273 | 
            +
                position: absolute;
         | 
| 274 | 
            +
                left: 0.75rem;
         | 
| 275 | 
            +
                top: 50%;
         | 
| 276 | 
            +
                transform: translateY(-50%);
         | 
| 277 | 
            +
                color: #6c757d;
         | 
| 278 | 
            +
                font-size: 0.9rem;
         | 
| 279 | 
            +
              }
         | 
| 280 | 
            +
              
         | 
| 281 | 
            +
              .vulnerability-summary {
         | 
| 282 | 
            +
                display: flex;
         | 
| 283 | 
            +
                gap: 1.5rem;
         | 
| 284 | 
            +
                margin-bottom: 1.5rem;
         | 
| 285 | 
            +
                background-color: #f9fafb;
         | 
| 286 | 
            +
                border-radius: 8px;
         | 
| 287 | 
            +
                padding: 1.5rem;
         | 
| 288 | 
            +
                align-items: center;
         | 
| 289 | 
            +
              }
         | 
| 290 | 
            +
              
         | 
| 291 | 
            +
              .donut-chart {
         | 
| 292 | 
            +
                display: flex;
         | 
| 293 | 
            +
                align-items: center;
         | 
| 294 | 
            +
                gap: 1.5rem;
         | 
| 295 | 
            +
              }
         | 
| 296 | 
            +
              
         | 
| 297 | 
            +
              .chart-container {
         | 
| 298 | 
            +
                width: 100px;
         | 
| 299 | 
            +
                height: 100px;
         | 
| 300 | 
            +
                position: relative;
         | 
| 301 | 
            +
              }
         | 
| 302 | 
            +
              
         | 
| 303 | 
            +
              .donut {
         | 
| 304 | 
            +
                width: 100%;
         | 
| 305 | 
            +
                height: 100%;
         | 
| 306 | 
            +
                border-radius: 50%;
         | 
| 307 | 
            +
                background: conic-gradient(
         | 
| 308 | 
            +
                  #dc3545 0% calc(var(--critical) * 100%),
         | 
| 309 | 
            +
                  #fd7e14 calc(var(--critical) * 100%) calc((var(--critical) + var(--high)) * 100%), 
         | 
| 310 | 
            +
                  #ffc107 calc((var(--critical) + var(--high)) * 100%) calc((var(--critical) + var(--high) + var(--medium)) * 100%),
         | 
| 311 | 
            +
                  #28a745 calc((var(--critical) + var(--high) + var(--medium)) * 100%) calc((var(--critical) + var(--high) + var(--medium) + var(--low)) * 100%),
         | 
| 312 | 
            +
                  #adb5bd calc((var(--critical) + var(--high) + var(--medium) + var(--low)) * 100%) 100%
         | 
| 313 | 
            +
                );
         | 
| 314 | 
            +
                display: flex;
         | 
| 315 | 
            +
                align-items: center;
         | 
| 316 | 
            +
                justify-content: center;
         | 
| 317 | 
            +
                position: relative;
         | 
| 318 | 
            +
              }
         | 
| 319 | 
            +
              
         | 
| 320 | 
            +
              .donut::after {
         | 
| 321 | 
            +
                content: attr(data-count);
         | 
| 322 | 
            +
                width: 60px;
         | 
| 323 | 
            +
                height: 60px;
         | 
| 324 | 
            +
                background: white;
         | 
| 325 | 
            +
                border-radius: 50%;
         | 
| 326 | 
            +
                position: absolute;
         | 
| 327 | 
            +
                display: flex;
         | 
| 328 | 
            +
                align-items: center;
         | 
| 329 | 
            +
                justify-content: center;
         | 
| 330 | 
            +
                font-weight: bold;
         | 
| 331 | 
            +
                font-size: 16px;
         | 
| 332 | 
            +
                color: #333;
         | 
| 333 | 
            +
              }
         | 
| 334 | 
            +
              
         | 
| 335 | 
            +
              .chart-legend {
         | 
| 336 | 
            +
                display: flex;
         | 
| 337 | 
            +
                flex-direction: column;
         | 
| 338 | 
            +
                gap: 0.5rem;
         | 
| 339 | 
            +
              }
         | 
| 340 | 
            +
              
         | 
| 341 | 
            +
              .legend-item {
         | 
| 342 | 
            +
                display: flex;
         | 
| 343 | 
            +
                align-items: center;
         | 
| 344 | 
            +
                gap: 0.5rem;
         | 
| 345 | 
            +
                font-size: 0.85rem;
         | 
| 346 | 
            +
              }
         | 
| 347 | 
            +
              
         | 
| 348 | 
            +
              .legend-color {
         | 
| 349 | 
            +
                width: 12px;
         | 
| 350 | 
            +
                height: 12px;
         | 
| 351 | 
            +
                border-radius: 2px;
         | 
| 352 | 
            +
              }
         | 
| 353 | 
            +
              
         | 
| 354 | 
            +
              .legend-color.critical {
         | 
| 355 | 
            +
                background-color: #dc3545;
         | 
| 356 | 
            +
              }
         | 
| 357 | 
            +
              
         | 
| 358 | 
            +
              .legend-color.high {
         | 
| 359 | 
            +
                background-color: #fd7e14;
         | 
| 360 | 
            +
              }
         | 
| 361 | 
            +
              
         | 
| 362 | 
            +
              .legend-color.medium {
         | 
| 363 | 
            +
                background-color: #ffc107;
         | 
| 364 | 
            +
              }
         | 
| 365 | 
            +
              
         | 
| 366 | 
            +
              .legend-color.low {
         | 
| 367 | 
            +
                background-color: #28a745;
         | 
| 368 | 
            +
              }
         | 
| 369 | 
            +
              
         | 
| 370 | 
            +
              .legend-color.unknown {
         | 
| 371 | 
            +
                background-color: #adb5bd;
         | 
| 372 | 
            +
              }
         | 
| 373 | 
            +
              
         | 
| 374 | 
            +
              .vulnerability-stats {
         | 
| 375 | 
            +
                display: flex;
         | 
| 376 | 
            +
                gap: 1.5rem;
         | 
| 377 | 
            +
                margin-left: auto;
         | 
| 378 | 
            +
              }
         | 
| 379 | 
            +
              
         | 
| 380 | 
            +
              .stat-item {
         | 
| 381 | 
            +
                text-align: center;
         | 
| 382 | 
            +
                min-width: 80px;
         | 
| 383 | 
            +
              }
         | 
| 384 | 
            +
              
         | 
| 385 | 
            +
              .stat-value {
         | 
| 386 | 
            +
                font-size: 2rem;
         | 
| 387 | 
            +
                font-weight: 700;
         | 
| 388 | 
            +
                line-height: 1;
         | 
| 389 | 
            +
              }
         | 
| 390 | 
            +
              
         | 
| 391 | 
            +
              .stat-label {
         | 
| 392 | 
            +
                font-size: 0.85rem;
         | 
| 393 | 
            +
                color: #6c757d;
         | 
| 394 | 
            +
                margin-top: 0.5rem;
         | 
| 395 | 
            +
              }
         | 
| 396 | 
            +
              
         | 
| 397 | 
            +
              .vulnerabilities-table {
         | 
| 398 | 
            +
                margin-bottom: 1.5rem;
         | 
| 399 | 
            +
                width: 100%;
         | 
| 400 | 
            +
              }
         | 
| 401 | 
            +
              
         | 
| 402 | 
            +
              .table {
         | 
| 403 | 
            +
                width: 100%;
         | 
| 404 | 
            +
                border-collapse: separate;
         | 
| 405 | 
            +
                border-spacing: 0;
         | 
| 406 | 
            +
                font-size: 0.9rem;
         | 
| 407 | 
            +
                table-layout: fixed;
         | 
| 408 | 
            +
              }
         | 
| 409 | 
            +
              
         | 
| 410 | 
            +
              .table th {
         | 
| 411 | 
            +
                background-color: #f8f9fa;
         | 
| 412 | 
            +
                padding: 0.75rem;
         | 
| 413 | 
            +
                text-align: left;
         | 
| 414 | 
            +
                font-weight: 600;
         | 
| 415 | 
            +
                border-bottom: 2px solid #dee2e6;
         | 
| 416 | 
            +
              }
         | 
| 417 | 
            +
              
         | 
| 418 | 
            +
              .table td {
         | 
| 419 | 
            +
                padding: 0.65rem 0.75rem;
         | 
| 420 | 
            +
                border-bottom: 1px solid #dee2e6;
         | 
| 421 | 
            +
                vertical-align: middle;
         | 
| 422 | 
            +
                word-break: break-word;
         | 
| 423 | 
            +
                overflow-wrap: break-word;
         | 
| 424 | 
            +
              }
         | 
| 425 | 
            +
              
         | 
| 426 | 
            +
              .severity-column {
         | 
| 427 | 
            +
                width: 8%;
         | 
| 428 | 
            +
                min-width: 90px;
         | 
| 429 | 
            +
              }
         | 
| 430 | 
            +
              
         | 
| 431 | 
            +
              .gem-column {
         | 
| 432 | 
            +
                width: 15%;
         | 
| 433 | 
            +
                min-width: 120px;
         | 
| 434 | 
            +
              }
         | 
| 435 | 
            +
              
         | 
| 436 | 
            +
              .version-column {
         | 
| 437 | 
            +
                width: 10%;
         | 
| 438 | 
            +
                min-width: 100px;
         | 
| 439 | 
            +
              }
         | 
| 440 | 
            +
              
         | 
| 441 | 
            +
              .cve-column {
         | 
| 442 | 
            +
                width: 12%;
         | 
| 443 | 
            +
                min-width: 110px;
         | 
| 444 | 
            +
              }
         | 
| 445 | 
            +
              
         | 
| 446 | 
            +
              .description-column {
         | 
| 447 | 
            +
                width: 30%;
         | 
| 448 | 
            +
                min-width: 180px;
         | 
| 449 | 
            +
              }
         | 
| 450 | 
            +
              
         | 
| 451 | 
            +
              .remediation-column {
         | 
| 452 | 
            +
                width: 20%;
         | 
| 453 | 
            +
                min-width: 160px;
         | 
| 454 | 
            +
              }
         | 
| 455 | 
            +
              
         | 
| 456 | 
            +
              .vulnerability-row {
         | 
| 457 | 
            +
                transition: background-color 0.2s;
         | 
| 458 | 
            +
              }
         | 
| 459 | 
            +
              
         | 
| 460 | 
            +
              .vulnerability-row:hover {
         | 
| 461 | 
            +
                background-color: #f8f9fa;
         | 
| 462 | 
            +
              }
         | 
| 463 | 
            +
              
         | 
| 464 | 
            +
              .vulnerability-row.critical {
         | 
| 465 | 
            +
                border-left: 4px solid #dc3545;
         | 
| 466 | 
            +
              }
         | 
| 467 | 
            +
              
         | 
| 468 | 
            +
              .vulnerability-row.high {
         | 
| 469 | 
            +
                border-left: 4px solid #fd7e14;
         | 
| 470 | 
            +
              }
         | 
| 471 | 
            +
              
         | 
| 472 | 
            +
              .vulnerability-row.medium {
         | 
| 473 | 
            +
                border-left: 4px solid #ffc107;
         | 
| 474 | 
            +
              }
         | 
| 475 | 
            +
              
         | 
| 476 | 
            +
              .vulnerability-row.low {
         | 
| 477 | 
            +
                border-left: 4px solid #28a745;
         | 
| 478 | 
            +
              }
         | 
| 479 | 
            +
              
         | 
| 480 | 
            +
              .vulnerability-row.unknown {
         | 
| 481 | 
            +
                border-left: 4px solid #adb5bd;
         | 
| 482 | 
            +
              }
         | 
| 483 | 
            +
              
         | 
| 484 | 
            +
              .severity-badge {
         | 
| 485 | 
            +
                display: inline-block;
         | 
| 486 | 
            +
                padding: 0.3rem 0.6rem;
         | 
| 487 | 
            +
                border-radius: 50px;
         | 
| 488 | 
            +
                font-weight: 600;
         | 
| 489 | 
            +
                font-size: 0.8rem;
         | 
| 490 | 
            +
                text-align: center;
         | 
| 491 | 
            +
                white-space: nowrap;
         | 
| 492 | 
            +
                text-transform: uppercase;
         | 
| 493 | 
            +
                letter-spacing: 0.03em;
         | 
| 494 | 
            +
                min-width: 70px;
         | 
| 495 | 
            +
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
         | 
| 496 | 
            +
              }
         | 
| 497 | 
            +
              
         | 
| 498 | 
            +
              .severity-badge.critical {
         | 
| 499 | 
            +
                background-color: #dc3545;
         | 
| 500 | 
            +
                color: white;
         | 
| 501 | 
            +
              }
         | 
| 502 | 
            +
              
         | 
| 503 | 
            +
              .severity-badge.high {
         | 
| 504 | 
            +
                background-color: #fd7e14;
         | 
| 505 | 
            +
                color: white;
         | 
| 506 | 
            +
              }
         | 
| 507 | 
            +
              
         | 
| 508 | 
            +
              .severity-badge.medium {
         | 
| 509 | 
            +
                background-color: #ffc107;
         | 
| 510 | 
            +
                color: #212529;
         | 
| 511 | 
            +
              }
         | 
| 512 | 
            +
              
         | 
| 513 | 
            +
              .severity-badge.low {
         | 
| 514 | 
            +
                background-color: #28a745;
         | 
| 515 | 
            +
                color: white;
         | 
| 516 | 
            +
              }
         | 
| 517 | 
            +
              
         | 
| 518 | 
            +
              .severity-badge.unknown {
         | 
| 519 | 
            +
                background-color: #adb5bd;
         | 
| 520 | 
            +
                color: white;
         | 
| 521 | 
            +
              }
         | 
| 522 | 
            +
              
         | 
| 523 | 
            +
              .details-icon {
         | 
| 524 | 
            +
                font-size: 0.9rem;
         | 
| 525 | 
            +
                font-weight: bold;
         | 
| 526 | 
            +
              }
         | 
| 527 | 
            +
              
         | 
| 528 | 
            +
              .gem-name {
         | 
| 529 | 
            +
                font-weight: 500;
         | 
| 530 | 
            +
                overflow-wrap: break-word;
         | 
| 531 | 
            +
                word-wrap: break-word;
         | 
| 532 | 
            +
                word-break: break-word;
         | 
| 533 | 
            +
                hyphens: auto;
         | 
| 534 | 
            +
              }
         | 
| 535 | 
            +
              
         | 
| 536 | 
            +
              .version-info {
         | 
| 537 | 
            +
                display: flex;
         | 
| 538 | 
            +
                align-items: center;
         | 
| 539 | 
            +
                gap: 0.35rem;
         | 
| 540 | 
            +
                flex-wrap: wrap;
         | 
| 541 | 
            +
              }
         | 
| 542 | 
            +
              
         | 
| 543 | 
            +
              .current-version {
         | 
| 544 | 
            +
                font-family: monospace;
         | 
| 545 | 
            +
                font-size: 0.85rem;
         | 
| 546 | 
            +
                word-break: break-all;
         | 
| 547 | 
            +
              }
         | 
| 548 | 
            +
              
         | 
| 549 | 
            +
              .solution-version {
         | 
| 550 | 
            +
                font-family: monospace;
         | 
| 551 | 
            +
                font-size: 0.85rem;
         | 
| 552 | 
            +
                color: #28a745;
         | 
| 553 | 
            +
                word-break: break-all;
         | 
| 554 | 
            +
                font-weight: 500;
         | 
| 555 | 
            +
                padding: 0.1rem 0.25rem;
         | 
| 556 | 
            +
                display: inline-block;
         | 
| 557 | 
            +
                background-color: rgba(40, 167, 69, 0.08);
         | 
| 558 | 
            +
                border-radius: 3px;
         | 
| 559 | 
            +
                margin-right: 0.25rem;
         | 
| 560 | 
            +
              }
         | 
| 561 | 
            +
              
         | 
| 562 | 
            +
              .solution-version.last-version {
         | 
| 563 | 
            +
                margin-bottom: 0;
         | 
| 564 | 
            +
              }
         | 
| 565 | 
            +
              
         | 
| 566 | 
            +
              .solution-version.single-version {
         | 
| 567 | 
            +
                margin-right: 0;
         | 
| 568 | 
            +
              }
         | 
| 569 | 
            +
              
         | 
| 570 | 
            +
              .vuln-link {
         | 
| 571 | 
            +
                color: #0366d6;
         | 
| 572 | 
            +
                text-decoration: none;
         | 
| 573 | 
            +
                display: flex;
         | 
| 574 | 
            +
                align-items: center;
         | 
| 575 | 
            +
                gap: 0.35rem;
         | 
| 576 | 
            +
                word-break: break-all;
         | 
| 577 | 
            +
              }
         | 
| 578 | 
            +
              
         | 
| 579 | 
            +
              .external-link-icon {
         | 
| 580 | 
            +
                font-size: 0.75rem;
         | 
| 581 | 
            +
                opacity: 0.75;
         | 
| 582 | 
            +
              }
         | 
| 583 | 
            +
              
         | 
| 584 | 
            +
              .vulnerability-title {
         | 
| 585 | 
            +
                font-weight: 500;
         | 
| 586 | 
            +
                margin-bottom: 0.5rem;
         | 
| 587 | 
            +
                overflow-wrap: break-word;
         | 
| 588 | 
            +
                word-wrap: break-word;
         | 
| 589 | 
            +
                hyphens: auto;
         | 
| 590 | 
            +
              }
         | 
| 591 | 
            +
              
         | 
| 592 | 
            +
              .details-toggle-btn {
         | 
| 593 | 
            +
                background: none;
         | 
| 594 | 
            +
                border: none;
         | 
| 595 | 
            +
                color: #0366d6;
         | 
| 596 | 
            +
                font-size: 0.85rem;
         | 
| 597 | 
            +
                padding: 0;
         | 
| 598 | 
            +
                cursor: pointer;
         | 
| 599 | 
            +
                display: flex;
         | 
| 600 | 
            +
                align-items: center;
         | 
| 601 | 
            +
                gap: 0.25rem;
         | 
| 602 | 
            +
              }
         | 
| 603 | 
            +
              
         | 
| 604 | 
            +
              .toggle-icon {
         | 
| 605 | 
            +
                transition: transform 0.2s;
         | 
| 606 | 
            +
              }
         | 
| 607 | 
            +
              
         | 
| 608 | 
            +
              .vulnerability-details-container {
         | 
| 609 | 
            +
                position: relative;
         | 
| 610 | 
            +
                z-index: 5;
         | 
| 611 | 
            +
              }
         | 
| 612 | 
            +
              
         | 
| 613 | 
            +
              .vulnerability-details {
         | 
| 614 | 
            +
                max-height: 0;
         | 
| 615 | 
            +
                overflow: hidden;
         | 
| 616 | 
            +
                transition: max-height 0.3s ease, opacity 0.3s ease;
         | 
| 617 | 
            +
                margin-top: 0.75rem;
         | 
| 618 | 
            +
                font-size: 0.85rem;
         | 
| 619 | 
            +
                opacity: 0;
         | 
| 620 | 
            +
              }
         | 
| 621 | 
            +
              
         | 
| 622 | 
            +
              .vulnerability-details.expanded {
         | 
| 623 | 
            +
                max-height: 300px;
         | 
| 624 | 
            +
                overflow-y: auto;
         | 
| 625 | 
            +
                width: 100%;
         | 
| 626 | 
            +
                opacity: 1;
         | 
| 627 | 
            +
                position: relative;
         | 
| 628 | 
            +
                z-index: 10;
         | 
| 629 | 
            +
              }
         | 
| 630 | 
            +
              
         | 
| 631 | 
            +
              .details-content {
         | 
| 632 | 
            +
                background-color: #f8f9fa;
         | 
| 633 | 
            +
                padding: 0.75rem;
         | 
| 634 | 
            +
                border-radius: 4px;
         | 
| 635 | 
            +
                white-space: normal;
         | 
| 636 | 
            +
                color: #495057;
         | 
| 637 | 
            +
                overflow-wrap: break-word;
         | 
| 638 | 
            +
                word-wrap: break-word;
         | 
| 639 | 
            +
                border: 1px solid #e9ecef;
         | 
| 640 | 
            +
                box-shadow: 0 2px 5px rgba(0,0,0,0.05);
         | 
| 641 | 
            +
              }
         | 
| 642 | 
            +
              
         | 
| 643 | 
            +
              .remediation-action {
         | 
| 644 | 
            +
                display: flex;
         | 
| 645 | 
            +
                align-items: center;
         | 
| 646 | 
            +
                gap: 0.5rem;
         | 
| 647 | 
            +
                flex-wrap: wrap;
         | 
| 648 | 
            +
              }
         | 
| 649 | 
            +
              
         | 
| 650 | 
            +
              .patched-versions-pill {
         | 
| 651 | 
            +
                display: flex;
         | 
| 652 | 
            +
                align-items: center;
         | 
| 653 | 
            +
                background-color: rgba(40, 167, 69, 0.08);
         | 
| 654 | 
            +
                border-radius: 4px;
         | 
| 655 | 
            +
                padding: 0.35rem 0.5rem;
         | 
| 656 | 
            +
                max-width: 100%;
         | 
| 657 | 
            +
                overflow-wrap: break-word;
         | 
| 658 | 
            +
                border-left: 3px solid #28a745;
         | 
| 659 | 
            +
              }
         | 
| 660 | 
            +
              
         | 
| 661 | 
            +
              .versions-container {
         | 
| 662 | 
            +
                display: flex;
         | 
| 663 | 
            +
                flex-direction: column;
         | 
| 664 | 
            +
                gap: 0.25rem;
         | 
| 665 | 
            +
                flex-grow: 1;
         | 
| 666 | 
            +
              }
         | 
| 667 | 
            +
              
         | 
| 668 | 
            +
              .more-versions {
         | 
| 669 | 
            +
                font-size: 0.75rem;
         | 
| 670 | 
            +
                background-color: rgba(40, 167, 69, 0.2);
         | 
| 671 | 
            +
                color: #28a745;
         | 
| 672 | 
            +
                font-weight: 500;
         | 
| 673 | 
            +
                border-radius: 3px;
         | 
| 674 | 
            +
                padding: 0.15rem 0.35rem;
         | 
| 675 | 
            +
                cursor: help;
         | 
| 676 | 
            +
                margin-left: 0.25rem;
         | 
| 677 | 
            +
                display: inline-block;
         | 
| 678 | 
            +
                min-width: 1.5rem;
         | 
| 679 | 
            +
                text-align: center;
         | 
| 680 | 
            +
              }
         | 
| 681 | 
            +
              
         | 
| 682 | 
            +
              .copy-btn {
         | 
| 683 | 
            +
                background-color: transparent;
         | 
| 684 | 
            +
                color: #495057;
         | 
| 685 | 
            +
                border: none;
         | 
| 686 | 
            +
                padding: 0.25rem;
         | 
| 687 | 
            +
                margin-left: 0.25rem;
         | 
| 688 | 
            +
                border-radius: 4px;
         | 
| 689 | 
            +
                font-size: 0.85rem;
         | 
| 690 | 
            +
                cursor: pointer;
         | 
| 691 | 
            +
                transition: all 0.2s;
         | 
| 692 | 
            +
              }
         | 
| 693 | 
            +
              
         | 
| 694 | 
            +
              .copy-btn:hover {
         | 
| 695 | 
            +
                background-color: #e9ecef;
         | 
| 696 | 
            +
              }
         | 
| 697 | 
            +
              
         | 
| 698 | 
            +
              .action-button:disabled {
         | 
| 699 | 
            +
                opacity: 0.6;
         | 
| 700 | 
            +
                cursor: not-allowed;
         | 
| 701 | 
            +
                position: relative;
         | 
| 702 | 
            +
              }
         | 
| 703 | 
            +
              
         | 
| 704 | 
            +
              .action-button:disabled:hover::after {
         | 
| 705 | 
            +
                content: "Currently unavailable";
         | 
| 706 | 
            +
                position: absolute;
         | 
| 707 | 
            +
                up: -150px; /* Position tooltip to the right of the button */
         | 
| 708 | 
            +
                top: 50%;
         | 
| 709 | 
            +
                transform: translateY(-50%);
         | 
| 710 | 
            +
                background-color: #333;
         | 
| 711 | 
            +
                color: white;
         | 
| 712 | 
            +
                padding: 4px 8px;
         | 
| 713 | 
            +
                border-radius: 4px;
         | 
| 714 | 
            +
                font-size: 12px;
         | 
| 715 | 
            +
                white-space: nowrap;
         | 
| 716 | 
            +
                z-index: 100;
         | 
| 717 | 
            +
              }
         | 
| 718 | 
            +
              
         | 
| 719 | 
            +
              .no-patch {
         | 
| 720 | 
            +
                color: #6c757d;
         | 
| 721 | 
            +
                font-style: italic;
         | 
| 722 | 
            +
                font-size: 0.85rem;
         | 
| 723 | 
            +
              }
         | 
| 724 | 
            +
              
         | 
| 725 | 
            +
              .vulnerabilities-pagination {
         | 
| 726 | 
            +
                display: flex;
         | 
| 727 | 
            +
                justify-content: space-between;
         | 
| 728 | 
            +
                align-items: center;
         | 
| 729 | 
            +
                margin-top: 1.5rem;
         | 
| 730 | 
            +
                padding-top: 1rem;
         | 
| 731 | 
            +
                border-top: 1px solid #dee2e6;
         | 
| 732 | 
            +
              }
         | 
| 733 | 
            +
              
         | 
| 734 | 
            +
              .pagination-text {
         | 
| 735 | 
            +
                color: #6c757d;
         | 
| 736 | 
            +
                font-size: 0.85rem;
         | 
| 737 | 
            +
              }
         | 
| 738 | 
            +
              
         | 
| 739 | 
            +
              .pagination-controls {
         | 
| 740 | 
            +
                display: flex;
         | 
| 741 | 
            +
                align-items: center;
         | 
| 742 | 
            +
                gap: 1rem;
         | 
| 743 | 
            +
              }
         | 
| 744 | 
            +
              
         | 
| 745 | 
            +
              .pagination-pages {
         | 
| 746 | 
            +
                font-size: 0.9rem;
         | 
| 747 | 
            +
              }
         | 
| 748 | 
            +
              
         | 
| 749 | 
            +
              .pagination-btn {
         | 
| 750 | 
            +
                background-color: #f8f9fa;
         | 
| 751 | 
            +
                border: 1px solid #dee2e6;
         | 
| 752 | 
            +
                padding: 0.35rem 0.75rem;
         | 
| 753 | 
            +
                border-radius: 4px;
         | 
| 754 | 
            +
                font-size: 0.85rem;
         | 
| 755 | 
            +
                cursor: pointer;
         | 
| 756 | 
            +
                transition: all 0.2s;
         | 
| 757 | 
            +
              }
         | 
| 758 | 
            +
              
         | 
| 759 | 
            +
              .pagination-btn:hover:not(:disabled) {
         | 
| 760 | 
            +
                background-color: #e9ecef;
         | 
| 761 | 
            +
              }
         | 
| 762 | 
            +
              
         | 
| 763 | 
            +
              .pagination-btn:disabled {
         | 
| 764 | 
            +
                opacity: 0.5;
         | 
| 765 | 
            +
                cursor: not-allowed;
         | 
| 766 | 
            +
              }
         | 
| 767 | 
            +
              
         | 
| 768 | 
            +
              .details-link-btn {
         | 
| 769 | 
            +
                color: #0366d6;
         | 
| 770 | 
            +
                font-size: 0.85rem;
         | 
| 771 | 
            +
                text-decoration: none;
         | 
| 772 | 
            +
                display: inline-flex;
         | 
| 773 | 
            +
                align-items: center;
         | 
| 774 | 
            +
                gap: 0.4rem;
         | 
| 775 | 
            +
                padding: 0.2rem 0.5rem;
         | 
| 776 | 
            +
                border-radius: 4px;
         | 
| 777 | 
            +
                transition: background-color 0.2s;
         | 
| 778 | 
            +
              }
         | 
| 779 | 
            +
              
         | 
| 780 | 
            +
              .details-link-btn:hover {
         | 
| 781 | 
            +
                background-color: rgba(3, 102, 214, 0.1);
         | 
| 782 | 
            +
                text-decoration: none;
         | 
| 783 | 
            +
              }
         | 
| 784 | 
            +
              
         | 
| 785 | 
            +
              .scroll-to-details {
         | 
| 786 | 
            +
                cursor: pointer;
         | 
| 787 | 
            +
              }
         | 
| 788 | 
            +
              
         | 
| 789 | 
            +
              /* Responsive styles for smaller screens */
         | 
| 790 | 
            +
              @media (max-width: 992px) {
         | 
| 791 | 
            +
                .vulnerability-summary {
         | 
| 792 | 
            +
                  flex-direction: column;
         | 
| 793 | 
            +
                  align-items: flex-start;
         | 
| 794 | 
            +
                }
         | 
| 795 | 
            +
                
         | 
| 796 | 
            +
                .vulnerability-stats {
         | 
| 797 | 
            +
                  margin-left: 0;
         | 
| 798 | 
            +
                  margin-top: 1rem;
         | 
| 799 | 
            +
                  width: 100%;
         | 
| 800 | 
            +
                  justify-content: space-around;
         | 
| 801 | 
            +
                }
         | 
| 802 | 
            +
              }
         | 
| 803 | 
            +
              
         | 
| 804 | 
            +
              @media (max-width: 768px) {
         | 
| 805 | 
            +
                .severity-column {
         | 
| 806 | 
            +
                  width: 12%;
         | 
| 807 | 
            +
                  text-align: center;
         | 
| 808 | 
            +
                }
         | 
| 809 | 
            +
                
         | 
| 810 | 
            +
                .severity-badge {
         | 
| 811 | 
            +
                  min-width: 60px;
         | 
| 812 | 
            +
                  padding: 0.25rem 0.4rem;
         | 
| 813 | 
            +
                  font-size: 0.75rem;
         | 
| 814 | 
            +
                }
         | 
| 815 | 
            +
                
         | 
| 816 | 
            +
                .gem-column {
         | 
| 817 | 
            +
                  width: 18%;
         | 
| 818 | 
            +
                }
         | 
| 819 | 
            +
                
         | 
| 820 | 
            +
                .version-column {
         | 
| 821 | 
            +
                  width: 12%;
         | 
| 822 | 
            +
                }
         | 
| 823 | 
            +
                
         | 
| 824 | 
            +
                .cve-column {
         | 
| 825 | 
            +
                  width: 15%;
         | 
| 826 | 
            +
                }
         | 
| 827 | 
            +
                
         | 
| 828 | 
            +
                .description-column {
         | 
| 829 | 
            +
                  width: 25%;
         | 
| 830 | 
            +
                }
         | 
| 831 | 
            +
                
         | 
| 832 | 
            +
                .remediation-column {
         | 
| 833 | 
            +
                  width: 15%;
         | 
| 834 | 
            +
                }
         | 
| 835 | 
            +
                
         | 
| 836 | 
            +
                .vulnerability-filters {
         | 
| 837 | 
            +
                  flex-direction: column;
         | 
| 838 | 
            +
                  align-items: flex-start;
         | 
| 839 | 
            +
                  gap: 0.75rem;
         | 
| 840 | 
            +
                }
         | 
| 841 | 
            +
                
         | 
| 842 | 
            +
                .search-input {
         | 
| 843 | 
            +
                  width: 100%;
         | 
| 844 | 
            +
                }
         | 
| 845 | 
            +
              }
         | 
| 846 | 
            +
              
         | 
| 847 | 
            +
              /* Extra small devices */
         | 
| 848 | 
            +
              @media (max-width: 576px) {
         | 
| 849 | 
            +
                .table {
         | 
| 850 | 
            +
                  table-layout: auto;
         | 
| 851 | 
            +
                }
         | 
| 852 | 
            +
                
         | 
| 853 | 
            +
                .severity-column {
         | 
| 854 | 
            +
                  width: 15%;
         | 
| 855 | 
            +
                }
         | 
| 856 | 
            +
                
         | 
| 857 | 
            +
                .vulnerability-stats {
         | 
| 858 | 
            +
                  justify-content: space-between;
         | 
| 859 | 
            +
                  width: 100%;
         | 
| 860 | 
            +
                }
         | 
| 861 | 
            +
                
         | 
| 862 | 
            +
                .stat-item {
         | 
| 863 | 
            +
                  min-width: auto;
         | 
| 864 | 
            +
                }
         | 
| 865 | 
            +
                
         | 
| 866 | 
            +
                /* Ensure text doesn't overflow in vulnerability details */
         | 
| 867 | 
            +
                .vulnerability-details .details-content {
         | 
| 868 | 
            +
                  max-width: 100%;
         | 
| 869 | 
            +
                  white-space: normal;
         | 
| 870 | 
            +
                }
         | 
| 871 | 
            +
                
         | 
| 872 | 
            +
                /* Make the action buttons more touchable on mobile */
         | 
| 873 | 
            +
                .update-btn, .copy-btn {
         | 
| 874 | 
            +
                  padding: 0.5rem;
         | 
| 875 | 
            +
                  min-height: 44px; /* Better touch target */
         | 
| 876 | 
            +
                }
         | 
| 877 | 
            +
                
         | 
| 878 | 
            +
                /* Enhance remediation column display on small screens */
         | 
| 879 | 
            +
                .remediation-column .patched-versions-pill {
         | 
| 880 | 
            +
                  flex-direction: column;
         | 
| 881 | 
            +
                  align-items: flex-start;
         | 
| 882 | 
            +
                }
         | 
| 883 | 
            +
                
         | 
| 884 | 
            +
                .remediation-column .versions-container {
         | 
| 885 | 
            +
                  width: 100%;
         | 
| 886 | 
            +
                  margin-bottom: 0.25rem;
         | 
| 887 | 
            +
                }
         | 
| 888 | 
            +
                
         | 
| 889 | 
            +
                .copy-btn {
         | 
| 890 | 
            +
                  align-self: flex-end;
         | 
| 891 | 
            +
                }
         | 
| 892 | 
            +
              }
         | 
| 893 | 
            +
            </style>
         | 
| 894 | 
            +
             | 
| 895 | 
            +
            <script>
         | 
| 896 | 
            +
              document.addEventListener('DOMContentLoaded', function() {
         | 
| 897 | 
            +
                // Filter buttons functionality
         | 
| 898 | 
            +
                document.querySelectorAll('.filter-btn').forEach(function(button) {
         | 
| 899 | 
            +
                  button.addEventListener('click', function() {
         | 
| 900 | 
            +
                    // Update active state
         | 
| 901 | 
            +
                    document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active'));
         | 
| 902 | 
            +
                    this.classList.add('active');
         | 
| 903 | 
            +
                    
         | 
| 904 | 
            +
                    const filter = this.getAttribute('data-filter');
         | 
| 905 | 
            +
                    const rows = document.querySelectorAll('.vulnerability-row');
         | 
| 906 | 
            +
                    
         | 
| 907 | 
            +
                    // Counts for updating donut chart
         | 
| 908 | 
            +
                    let visibleCritical = 0;
         | 
| 909 | 
            +
                    let visibleHigh = 0;
         | 
| 910 | 
            +
                    let visibleMedium = 0;
         | 
| 911 | 
            +
                    let visibleLow = 0;
         | 
| 912 | 
            +
                    let visibleUnknown = 0;
         | 
| 913 | 
            +
                    let visibleTotal = 0;
         | 
| 914 | 
            +
                    
         | 
| 915 | 
            +
                    rows.forEach(function(row) {
         | 
| 916 | 
            +
                      const severity = row.getAttribute('data-severity');
         | 
| 917 | 
            +
                      
         | 
| 918 | 
            +
                      if (filter === 'all') {
         | 
| 919 | 
            +
                        row.style.display = '';
         | 
| 920 | 
            +
                        
         | 
| 921 | 
            +
                        // Count by severity for all visible rows
         | 
| 922 | 
            +
                        if (severity === 'critical') visibleCritical++;
         | 
| 923 | 
            +
                        else if (severity === 'high') visibleHigh++;
         | 
| 924 | 
            +
                        else if (severity === 'medium') visibleMedium++;
         | 
| 925 | 
            +
                        else if (severity === 'low') visibleLow++;
         | 
| 926 | 
            +
                        else visibleUnknown++;
         | 
| 927 | 
            +
                        
         | 
| 928 | 
            +
                        visibleTotal++;
         | 
| 929 | 
            +
                        
         | 
| 930 | 
            +
                      } else {
         | 
| 931 | 
            +
                        if (severity === filter) {
         | 
| 932 | 
            +
                          row.style.display = '';
         | 
| 933 | 
            +
                          
         | 
| 934 | 
            +
                          // For filtered view, only count matching severity
         | 
| 935 | 
            +
                          if (severity === 'critical') visibleCritical++;
         | 
| 936 | 
            +
                          else if (severity === 'high') visibleHigh++;
         | 
| 937 | 
            +
                          else if (severity === 'medium') visibleMedium++;
         | 
| 938 | 
            +
                          else if (severity === 'low') visibleLow++;
         | 
| 939 | 
            +
                          else visibleUnknown++;
         | 
| 940 | 
            +
                          
         | 
| 941 | 
            +
                          visibleTotal++;
         | 
| 942 | 
            +
                        } else {
         | 
| 943 | 
            +
                          row.style.display = 'none';
         | 
| 944 | 
            +
                        }
         | 
| 945 | 
            +
                      }
         | 
| 946 | 
            +
                    });
         | 
| 947 | 
            +
                    
         | 
| 948 | 
            +
                    // Update donut chart percentages based on visible rows
         | 
| 949 | 
            +
                    const donut = document.querySelector('.donut');
         | 
| 950 | 
            +
                    if (donut) {
         | 
| 951 | 
            +
                      // Calculate percentages
         | 
| 952 | 
            +
                      const criticalPct = visibleTotal > 0 ? visibleCritical / visibleTotal : 0;
         | 
| 953 | 
            +
                      const highPct = visibleTotal > 0 ? visibleHigh / visibleTotal : 0;
         | 
| 954 | 
            +
                      const mediumPct = visibleTotal > 0 ? visibleMedium / visibleTotal : 0;
         | 
| 955 | 
            +
                      const lowPct = visibleTotal > 0 ? visibleLow / visibleTotal : 0;
         | 
| 956 | 
            +
                      const unknownPct = visibleTotal > 0 ? visibleUnknown / visibleTotal : 0;
         | 
| 957 | 
            +
                      
         | 
| 958 | 
            +
                      // Update CSS variables
         | 
| 959 | 
            +
                      donut.style.setProperty('--critical', criticalPct);
         | 
| 960 | 
            +
                      donut.style.setProperty('--high', highPct);
         | 
| 961 | 
            +
                      donut.style.setProperty('--medium', mediumPct);
         | 
| 962 | 
            +
                      donut.style.setProperty('--low', lowPct);
         | 
| 963 | 
            +
                      donut.style.setProperty('--unknown', unknownPct);
         | 
| 964 | 
            +
                      
         | 
| 965 | 
            +
                      // Update data attributes
         | 
| 966 | 
            +
                      donut.setAttribute('data-count', visibleTotal);
         | 
| 967 | 
            +
                      donut.setAttribute('data-critical', visibleCritical);
         | 
| 968 | 
            +
                      donut.setAttribute('data-high', visibleHigh);
         | 
| 969 | 
            +
                      donut.setAttribute('data-medium', visibleMedium);
         | 
| 970 | 
            +
                      donut.setAttribute('data-low', visibleLow);
         | 
| 971 | 
            +
                      donut.setAttribute('data-unknown', visibleUnknown);
         | 
| 972 | 
            +
                    }
         | 
| 973 | 
            +
                    
         | 
| 974 | 
            +
                    // Update stats counters
         | 
| 975 | 
            +
                    const critHighCounter = document.querySelector('.vulnerability-stats .stat-value:nth-child(1)');
         | 
| 976 | 
            +
                    const mediumCounter = document.querySelector('.vulnerability-stats .stat-item:nth-child(2) .stat-value');
         | 
| 977 | 
            +
                    const lowCounter = document.querySelector('.vulnerability-stats .stat-item:nth-child(3) .stat-value');
         | 
| 978 | 
            +
                    
         | 
| 979 | 
            +
                    if (critHighCounter) {
         | 
| 980 | 
            +
                      critHighCounter.textContent = visibleCritical + visibleHigh;
         | 
| 981 | 
            +
                    }
         | 
| 982 | 
            +
                    if (mediumCounter) {
         | 
| 983 | 
            +
                      mediumCounter.textContent = visibleMedium;
         | 
| 984 | 
            +
                    }
         | 
| 985 | 
            +
                    if (lowCounter) {
         | 
| 986 | 
            +
                      lowCounter.textContent = visibleLow;
         | 
| 987 | 
            +
                    }
         | 
| 988 | 
            +
                  });
         | 
| 989 | 
            +
                });
         | 
| 990 | 
            +
                
         | 
| 991 | 
            +
                // Search functionality
         | 
| 992 | 
            +
                const searchInput = document.getElementById('vulnerability-search');
         | 
| 993 | 
            +
                if (searchInput) {
         | 
| 994 | 
            +
                  searchInput.addEventListener('input', function() {
         | 
| 995 | 
            +
                    const searchTerm = this.value.toLowerCase();
         | 
| 996 | 
            +
                    const rows = document.querySelectorAll('.vulnerability-row');
         | 
| 997 | 
            +
                    
         | 
| 998 | 
            +
                    rows.forEach(function(row) {
         | 
| 999 | 
            +
                      const text = row.textContent.toLowerCase();
         | 
| 1000 | 
            +
                      const gemName = row.getAttribute('data-gem').toLowerCase();
         | 
| 1001 | 
            +
                      
         | 
| 1002 | 
            +
                      if (text.includes(searchTerm) || gemName.includes(searchTerm)) {
         | 
| 1003 | 
            +
                        row.style.display = '';
         | 
| 1004 | 
            +
                      } else {
         | 
| 1005 | 
            +
                        row.style.display = 'none';
         | 
| 1006 | 
            +
                      }
         | 
| 1007 | 
            +
                    });
         | 
| 1008 | 
            +
                  });
         | 
| 1009 | 
            +
                }
         | 
| 1010 | 
            +
                
         | 
| 1011 | 
            +
                // Scroll to vulnerability details when clicking "More details"
         | 
| 1012 | 
            +
                document.querySelectorAll('.scroll-to-details').forEach(function(link) {
         | 
| 1013 | 
            +
                  link.addEventListener('click', function(e) {
         | 
| 1014 | 
            +
                    e.preventDefault();
         | 
| 1015 | 
            +
                    const targetId = this.getAttribute('href');
         | 
| 1016 | 
            +
                    const detailsSection = document.querySelector('.vulnerabilities-details-section');
         | 
| 1017 | 
            +
                    
         | 
| 1018 | 
            +
                    // Ensure the details section is visible
         | 
| 1019 | 
            +
                    if (detailsSection.classList.contains('hidden')) {
         | 
| 1020 | 
            +
                      detailsSection.classList.remove('hidden');
         | 
| 1021 | 
            +
                      const toggleBtn = document.querySelector('.toggle-details-btn');
         | 
| 1022 | 
            +
                      if (toggleBtn) {
         | 
| 1023 | 
            +
                        toggleBtn.textContent = 'Hide Details';
         | 
| 1024 | 
            +
                        toggleBtn.setAttribute('aria-expanded', 'true');
         | 
| 1025 | 
            +
                      }
         | 
| 1026 | 
            +
                    }
         | 
| 1027 | 
            +
                    
         | 
| 1028 | 
            +
                    // Scroll to the specific vulnerability detail
         | 
| 1029 | 
            +
                    setTimeout(() => {
         | 
| 1030 | 
            +
                      const targetElement = document.querySelector(targetId);
         | 
| 1031 | 
            +
                      if (targetElement) {
         | 
| 1032 | 
            +
                        const headerHeight = 60; // Approximate height of sticky header
         | 
| 1033 | 
            +
                        const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - headerHeight - 20;
         | 
| 1034 | 
            +
                        window.scrollTo({
         | 
| 1035 | 
            +
                          top: targetPosition,
         | 
| 1036 | 
            +
                          behavior: 'smooth'
         | 
| 1037 | 
            +
                        });
         | 
| 1038 | 
            +
                        
         | 
| 1039 | 
            +
                        // Highlight the target element briefly
         | 
| 1040 | 
            +
                        targetElement.classList.add('highlight-detail');
         | 
| 1041 | 
            +
                        setTimeout(() => {
         | 
| 1042 | 
            +
                          targetElement.classList.remove('highlight-detail');
         | 
| 1043 | 
            +
                        }, 1500);
         | 
| 1044 | 
            +
                      }
         | 
| 1045 | 
            +
                    }, 100);
         | 
| 1046 | 
            +
                  });
         | 
| 1047 | 
            +
                });
         | 
| 1048 | 
            +
                
         | 
| 1049 | 
            +
                // Tooltip functionality for patched versions
         | 
| 1050 | 
            +
                document.querySelectorAll('.more-versions').forEach(function(element) {
         | 
| 1051 | 
            +
                  element.addEventListener('mouseenter', function() {
         | 
| 1052 | 
            +
                    // Could implement custom tooltip here if needed
         | 
| 1053 | 
            +
                  });
         | 
| 1054 | 
            +
                });
         | 
| 1055 | 
            +
                
         | 
| 1056 | 
            +
                // Copy button functionality
         | 
| 1057 | 
            +
                document.querySelectorAll('.copy-btn').forEach(function(button) {
         | 
| 1058 | 
            +
                  button.addEventListener('click', function() {
         | 
| 1059 | 
            +
                    const solution = this.getAttribute('data-solution');
         | 
| 1060 | 
            +
                    
         | 
| 1061 | 
            +
                    // Create a temporary input element
         | 
| 1062 | 
            +
                    const tempInput = document.createElement('input');
         | 
| 1063 | 
            +
                    tempInput.value = solution;
         | 
| 1064 | 
            +
                    document.body.appendChild(tempInput);
         | 
| 1065 | 
            +
                    tempInput.select();
         | 
| 1066 | 
            +
                    document.execCommand('copy');
         | 
| 1067 | 
            +
                    document.body.removeChild(tempInput);
         | 
| 1068 | 
            +
                    
         | 
| 1069 | 
            +
                    // Show feedback
         | 
| 1070 | 
            +
                    const originalIcon = this.innerHTML;
         | 
| 1071 | 
            +
                    this.innerHTML = '<span>✓</span>';
         | 
| 1072 | 
            +
                    setTimeout(() => {
         | 
| 1073 | 
            +
                      this.innerHTML = originalIcon;
         | 
| 1074 | 
            +
                      
         | 
| 1075 | 
            +
                      // Show toast notification
         | 
| 1076 | 
            +
                      if (typeof showNotification === 'function') {
         | 
| 1077 | 
            +
                        showNotification('Copied to clipboard!', 'success');
         | 
| 1078 | 
            +
                      }
         | 
| 1079 | 
            +
                    }, 1500);
         | 
| 1080 | 
            +
                  });
         | 
| 1081 | 
            +
                });
         | 
| 1082 | 
            +
                
         | 
| 1083 | 
            +
                // No select-all checkbox or update buttons anymore
         | 
| 1084 | 
            +
                
         | 
| 1085 | 
            +
                // Ensure table fits within viewport
         | 
| 1086 | 
            +
                function adjustTableResponsiveness() {
         | 
| 1087 | 
            +
                  const table = document.querySelector('.vulnerabilities-table table');
         | 
| 1088 | 
            +
                  const viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
         | 
| 1089 | 
            +
                  
         | 
| 1090 | 
            +
                  // If viewport is very small, change display approach
         | 
| 1091 | 
            +
                  if (viewportWidth < 576) {
         | 
| 1092 | 
            +
                    document.querySelectorAll('.vulnerability-row').forEach(row => {
         | 
| 1093 | 
            +
                      // Add more bottom padding for mobile view
         | 
| 1094 | 
            +
                      row.style.paddingBottom = '10px';
         | 
| 1095 | 
            +
                    });
         | 
| 1096 | 
            +
                    
         | 
| 1097 | 
            +
                    // Ensure description column takes necessary space
         | 
| 1098 | 
            +
                    const descriptionCells = document.querySelectorAll('.description-column');
         | 
| 1099 | 
            +
                    descriptionCells.forEach(cell => {
         | 
| 1100 | 
            +
                      cell.style.maxWidth = '100%';
         | 
| 1101 | 
            +
                      cell.style.width = 'auto';
         | 
| 1102 | 
            +
                    });
         | 
| 1103 | 
            +
                    
         | 
| 1104 | 
            +
                    // Make severity column more compact
         | 
| 1105 | 
            +
                    const severityCells = document.querySelectorAll('.severity-column');
         | 
| 1106 | 
            +
                    severityCells.forEach(cell => {
         | 
| 1107 | 
            +
                      const badge = cell.querySelector('.severity-badge');
         | 
| 1108 | 
            +
                      if (badge) {
         | 
| 1109 | 
            +
                        badge.style.padding = '0.2rem 0.4rem';
         | 
| 1110 | 
            +
                        badge.style.minWidth = 'auto';
         | 
| 1111 | 
            +
                      }
         | 
| 1112 | 
            +
                    });
         | 
| 1113 | 
            +
                  }
         | 
| 1114 | 
            +
                }
         | 
| 1115 | 
            +
                
         | 
| 1116 | 
            +
                // Run on page load and on resize
         | 
| 1117 | 
            +
                adjustTableResponsiveness();
         | 
| 1118 | 
            +
                window.addEventListener('resize', adjustTableResponsiveness);
         | 
| 1119 | 
            +
              });
         | 
| 1120 | 
            +
            </script>
         |