|
| 1 | +{% extends "base.html" %} |
| 2 | +{% load static %} |
| 3 | +{% load utils %} |
| 4 | + |
| 5 | +{% block extrahead %} |
| 6 | +<style> |
| 7 | + .table { |
| 8 | + table-layout: fixed !important; |
| 9 | + width: 100% !important; |
| 10 | + } |
| 11 | + |
| 12 | + .table th { |
| 13 | + border-width: 0 1px 1px 1px !important; |
| 14 | + border-color: #dbdbdb !important; |
| 15 | + background-color: #fff !important; |
| 16 | + } |
| 17 | + |
| 18 | + #table-header th { |
| 19 | + height: 100px; |
| 20 | + padding: 0.75rem 0.5rem !important; |
| 21 | + } |
| 22 | + |
| 23 | + #table-header th>div { |
| 24 | + display: inline-flex !important; |
| 25 | + flex-direction: column !important; |
| 26 | + justify-content: space-between !important; |
| 27 | + height: 100% !important; |
| 28 | + width: 100%; |
| 29 | + } |
| 30 | + |
| 31 | + #table-header th:first-child { |
| 32 | + width: 120px !important; |
| 33 | + } |
| 34 | + |
| 35 | + #table-header th:nth-child(2) { |
| 36 | + width: 150px !important; |
| 37 | + } |
| 38 | + |
| 39 | + #table-header th:nth-child(n+3) { |
| 40 | + width: 150px !important; |
| 41 | + } |
| 42 | + |
| 43 | + .table td { |
| 44 | + vertical-align: middle !important; |
| 45 | + } |
| 46 | + |
| 47 | + #summaries-container p strong { |
| 48 | + display: block; |
| 49 | + word-break: break-all; |
| 50 | + overflow-wrap: break-word; |
| 51 | + line-height: 1.3; |
| 52 | + margin-bottom: 4px; |
| 53 | + } |
| 54 | + |
| 55 | + .summary-text { |
| 56 | + line-height: 1.4; |
| 57 | + white-space: pre-wrap; |
| 58 | + word-break: break-word; |
| 59 | + } |
| 60 | + |
| 61 | + div.summary-text a:link, |
| 62 | + div.summary-text a { |
| 63 | + color: #3273dc !important; |
| 64 | + } |
| 65 | + |
| 66 | + div.summary-text a:visited { |
| 67 | + color: #8a4de8 !important; |
| 68 | + } |
| 69 | + |
| 70 | + div.summary-text a:hover { |
| 71 | + color: #0a0a0a !important; |
| 72 | + } |
| 73 | + |
| 74 | + .advisory-link { |
| 75 | + display: inline-flex; |
| 76 | + align-items: center; |
| 77 | + gap: 4px; |
| 78 | + text-align: left; |
| 79 | + text-decoration: underline; |
| 80 | + white-space: normal; |
| 81 | + flex-wrap: wrap; |
| 82 | + } |
| 83 | + |
| 84 | + .table-container { |
| 85 | + overflow-x: auto; |
| 86 | + overflow-y: auto; |
| 87 | + max-height: 800px; |
| 88 | + border: 1px solid #dbdbdb; |
| 89 | + border-radius: 6px; |
| 90 | + } |
| 91 | + |
| 92 | + .has-tooltip-top::before { |
| 93 | + transform: translate(-5% , -100%) !important; |
| 94 | + } |
| 95 | +</style> |
| 96 | +{% endblock %} |
| 97 | + |
| 98 | +{% block content %} |
| 99 | +<section class="section my-0 pt-3 px-0 mx-0"> |
| 100 | + <div class="container is-fluid"> |
| 101 | + <div class="columns is-vcentered mb-5"> |
| 102 | + <div class="column"> |
| 103 | + <h1 class="title is-3">Extra Mitigation Curation</h1> |
| 104 | + <h2 class="subtitle is-5 has-text-grey"> |
| 105 | + <a href={% url 'advisory_details' avid %} target="_blank" rel="noopener noreferrer" class="has-text-link"> |
| 106 | + <span>{{ avid }}</span> |
| 107 | + <span class="icon is-small"><i class="fa fa-external-link"></i></span> |
| 108 | + </a> |
| 109 | + </h2> |
| 110 | + </div> |
| 111 | + |
| 112 | + <div class="column is-narrow"> |
| 113 | + <div class="box p-4 has-background-white shadow-soft" style="min-width: 260px; border-radius: 5px;"> |
| 114 | + <div class="is-flex is-justify-content-space-between is-align-items-center mb-2"> |
| 115 | + <span class="is-size-6 has-text-grey-dark uppercase has-text-weight-bold mr-2"> |
| 116 | + Progress |
| 117 | + </span> |
| 118 | + <span class="tag is-info is-light has-text-weight-bold" id="progress-text">0 / 0</span> |
| 119 | + </div> |
| 120 | + <progress class="progress is-info is-small mb-0" id="progress" value="0" max="100"></progress> |
| 121 | + </div> |
| 122 | + </div> |
| 123 | + |
| 124 | + </div> |
| 125 | + |
| 126 | + <div class="columns"> |
| 127 | + <div class="column is-3"> |
| 128 | + <h3 class="title is-5">Advisory Summary</h3> |
| 129 | + <div id="summaries-container" style="max-height: 950px; overflow-y: auto;"> |
| 130 | + {% for avid, text in advisory_summaries.items %} |
| 131 | + <div class="notification is-info is-light p-3 mb-3"> |
| 132 | + <p><strong>{{ avid }}</strong></p> |
| 133 | + <div class="text summary-text is-2">{{ text|normalize_links|urlize }}</div> |
| 134 | + </div> |
| 135 | + {% empty %} |
| 136 | + <div class="notification is-light p-3 mb-3 has-text-grey"> |
| 137 | + No summaries available. |
| 138 | + </div> |
| 139 | + {% endfor %} |
| 140 | + </div> |
| 141 | + </div> |
| 142 | + |
| 143 | + <div class="column is-9"> |
| 144 | + <div class="box"> |
| 145 | + <div class="mb-4"> |
| 146 | + <h4 class="title is-4 mb-1" id="current-purl"></h4> |
| 147 | + <a id="toggle-conflict" class="is-link is-5" style="display:none;">Show more</a> |
| 148 | + </div> |
| 149 | + |
| 150 | + <div class="table-container"> |
| 151 | + <table class="table is-bordered is-narrow is-fullwidth is-hoverable"> |
| 152 | + <thead style="position: sticky; top: 0; background: white; z-index: 10;"> |
| 153 | + <tr id="table-header"> |
| 154 | + <th class="has-text-weight-bold has-text-centered pt-4 is-size-5">Mitigation Type</th> |
| 155 | + <th class="has-text-weight-bold has-text-centered pt-4 is-size-5">Mitigation Data</th> |
| 156 | + <th class="has-text-weight-bold has-text-centered pt-4 is-size-5">Mitigation Note</th> |
| 157 | + </tr> |
| 158 | + </thead> |
| 159 | + <tbody id="curation-body"> |
| 160 | + |
| 161 | + |
| 162 | + </tbody> |
| 163 | + </table> |
| 164 | + </div> |
| 165 | + |
| 166 | + <div class="level mt-5"> |
| 167 | + <div class="level-left"> |
| 168 | + <button class="button is-light" id="prev-btn" onclick="app.navigate(-1)"> |
| 169 | + <span class="icon"><i class="fa fa-chevron-left"></i></span> |
| 170 | + <span>Previous Item</span> |
| 171 | + </button> |
| 172 | + </div> |
| 173 | + <div class="level-right"> |
| 174 | + <button class="button is-info" id="next-btn" onclick="app.navigate(1)"> |
| 175 | + <span>Next Item</span> |
| 176 | + <span class="icon"><i class="fa fa-chevron-right"></i></span> |
| 177 | + </button> |
| 178 | + <button class="button is-info is-hidden" id="finish-btn" disabled> |
| 179 | + <span class="icon"><i class="fa fa-check"></i></span> |
| 180 | + <span>Submit</span> |
| 181 | + </button> |
| 182 | + </div> |
| 183 | + </div> |
| 184 | + </div> |
| 185 | + </div> |
| 186 | + </div> |
| 187 | + </div> |
| 188 | +</section> |
| 189 | +{% endblock %} |
| 190 | + |
| 191 | + |
| 192 | +{% block scripts %} |
| 193 | +<script> |
| 194 | + const curationItems = {{ curation_items|safe }}; |
| 195 | + const mitigation_items = [ |
| 196 | + "upgrade_version", |
| 197 | + "upgrade_note", |
| 198 | + "downgrade_version", |
| 199 | + "downgrade_note", |
| 200 | + "patch", |
| 201 | + "patch_note", |
| 202 | + "config", |
| 203 | + "config_note", |
| 204 | + "filter_ips_ports", |
| 205 | + "filter_ips_ports_note", |
| 206 | + "replace_package", |
| 207 | + "replace_note", |
| 208 | + ]; |
| 209 | + |
| 210 | +const app = { |
| 211 | + currentIndex: 0, |
| 212 | + userStates: {}, |
| 213 | + |
| 214 | + init() { |
| 215 | + this.renderMitigationCuration(); |
| 216 | + |
| 217 | + document.querySelector('.summary-text')?.addEventListener('click', (e) => { |
| 218 | + if (e.target.closest('a')) { |
| 219 | + e.target.target = '_blank'; |
| 220 | + } |
| 221 | + }); |
| 222 | + }, |
| 223 | + |
| 224 | + renderMitigationCuration() { |
| 225 | + const total = curationItems.length; |
| 226 | + const purl = curationItems[this.currentIndex]; |
| 227 | + const progPercentage = ((this.currentIndex + 1) / total) * 100; |
| 228 | + document.getElementById('progress').value = progPercentage; |
| 229 | + document.getElementById('progress-text').innerText = `${this.currentIndex + 1} / ${total}`; |
| 230 | + document.getElementById('current-purl').innerText = `No reported fix version for ${purl}`; |
| 231 | + |
| 232 | + if (!this.userStates[this.currentIndex]) { |
| 233 | + this.userStates[this.currentIndex] = Object.fromEntries( |
| 234 | + mitigation_items.map((key) => [key, ""] |
| 235 | + ) |
| 236 | + ); |
| 237 | + this.userStates[this.currentIndex]["purl"] = purl; |
| 238 | + } |
| 239 | + |
| 240 | + this.renderBody(); |
| 241 | + this.updateNavButtons(); |
| 242 | + }, |
| 243 | + |
| 244 | + renderBody() { |
| 245 | + state = this.userStates[this.currentIndex] |
| 246 | + const body = document.getElementById('curation-body'); |
| 247 | + body.innerHTML = ` |
| 248 | +<tr> |
| 249 | + <td class="has-text-weight-bold"style="word-break: break-all;"> |
| 250 | + Upgrade to Versions |
| 251 | + <span class="icon has-tooltip-multiline has-tooltip-bottom has-tooltip-arrow has-text-weight-normal" |
| 252 | + data-tooltip="Specify the version(s) users should upgrade to in order to remediate the vulnerability. Enter one or more fixed versions separated by commas (e.g., 1.0.1, 2.3.4). Use the note field for any additional upgrade guidance or constraints."> |
| 253 | + <i class="fa fa-info-circle"></i> |
| 254 | + </span> |
| 255 | + </td> |
| 256 | + <td> |
| 257 | + <input class="input" type="text" id="upgrade_version" value="${state.upgrade_version}" placeholder="1.0.1, 3.0.0, ..."> |
| 258 | + <td> |
| 259 | + <textarea class="textarea" id="upgrade_note" rows="1" placeholder="Note..">${state.upgrade_note}</textarea></td> |
| 260 | + </td> |
| 261 | +</tr> |
| 262 | +
|
| 263 | +<tr> |
| 264 | + <td class="has-text-weight-bold"style="word-break: break-all;"> |
| 265 | + Downgrade to Versions |
| 266 | + <span class="icon has-tooltip-multiline has-tooltip-top has-tooltip-arrow has-text-weight-normal" data-tooltip="Specify the version(s) users should downgrade to if no fixed upgrade is available. Enter one or more non-vulnerable versions separated by commas (e.g., 1.0.1, 2.3.4). Use the note field for any additional guidance or cautions related to the downgrade."> |
| 267 | + <i class="fa fa-info-circle"></i> |
| 268 | + </span> |
| 269 | + </td> |
| 270 | + <td> |
| 271 | + <input class="input" type="text" id="downgrade_version" value="${state.downgrade_version}" placeholder="1.0.1, 3.0.0, ..."> |
| 272 | + <td> |
| 273 | + <textarea class="textarea" id="downgrade_note" rows="1" placeholder="Note..">${state.downgrade_note}</textarea></td> |
| 274 | + </td> |
| 275 | +</tr> |
| 276 | +
|
| 277 | +<tr> |
| 278 | + <td class="has-text-weight-bold"style="word-break: break-all;"> |
| 279 | + Apply Patch |
| 280 | + <span class="icon has-tooltip-multiline has-tooltip-top has-tooltip-arrow has-text-weight-normal" data-tooltip="Provide a URL to the official patch, hotfix that remediates the vulnerability. Use the note field to include any additional instructions, prerequisites, or known limitations."> |
| 281 | + <i class="fa fa-info-circle"></i> |
| 282 | + </span> |
| 283 | + </td> |
| 284 | + <td> |
| 285 | + <input class="input" type="text" id="patch" value="${state.patch}" placeholder="Patch URL"> |
| 286 | + <td> |
| 287 | + <textarea class="textarea" id="patch_note" rows="1" placeholder="Note..">${state.patch_note}</textarea></td> |
| 288 | + </td> |
| 289 | +</tr> |
| 290 | +
|
| 291 | +
|
| 292 | +<tr> |
| 293 | + <td class="has-text-weight-bold"style="word-break: break-all;"> |
| 294 | + Change Config |
| 295 | + <span class="icon has-tooltip-multiline has-tooltip-top has-tooltip-arrow has-text-weight-normal" data-tooltip="Provide the configuration changes that mitigate the vulnerabilities, such as modifying settings, disabling features, or enabling security controls. Use the note field to include any potential side effects, or additional implementation guidance."> |
| 296 | + <i class="fa fa-info-circle"></i> |
| 297 | + </span> |
| 298 | + </td> |
| 299 | + <td> |
| 300 | +<textarea class="textarea" id="config" rows="1" placeholder="Configuration details">${state.config}</textarea> |
| 301 | + <td> |
| 302 | + <textarea class="textarea" id="config_note" rows="1" placeholder="Note..">${state.config_note}</textarea></td> |
| 303 | + </td> |
| 304 | +</tr> |
| 305 | +
|
| 306 | +<tr> |
| 307 | + <td class="has-text-weight-bold"style="word-break: break-all;"> |
| 308 | + Apply Network Restrictions |
| 309 | + <span class="icon has-tooltip-multiline has-tooltip-top has-tooltip-arrow has-text-weight-normal" data-tooltip="Specify the network restrictions required to mitigate the vulnerability, such as blocking or limiting access to specific ports, IP addresses, or network ranges. Use the note field to provide firewall rules, scope details, or additional implementation guidance."> |
| 310 | + <i class="fa fa-info-circle"></i> |
| 311 | + </span> |
| 312 | + </td> |
| 313 | + <td> |
| 314 | +<textarea class="textarea" id="filter_ips_ports" rows="1" placeholder="Details of Ports/IPs to filter">${state.filter_ips_ports}</textarea> |
| 315 | + <td> |
| 316 | + <textarea class="textarea" id="filter_ips_ports_note" rows="1" placeholder="Note..">${state.filter_ips_ports_note}</textarea></td> |
| 317 | + </td> |
| 318 | +</tr> |
| 319 | +
|
| 320 | +<tr> |
| 321 | + <td class="has-text-weight-bold"style="word-break: break-all;"> |
| 322 | + Replace Package |
| 323 | + <span class="icon has-tooltip-multiline has-tooltip-top has-tooltip-arrow has-text-weight-normal" data-tooltip="Specify the Package-URL of the replacement package to use instead of the vulnerable package. Use the note field for compatibility details, or additional guidance."> |
| 324 | + <i class="fa fa-info-circle"></i> |
| 325 | + </span> |
| 326 | + </td> |
| 327 | + <td> |
| 328 | + <input class="input" type="text" id="replace_package" value="${state.replace_package}" placeholder="pkg:npm/foobar@3.1.0"> |
| 329 | + <td> |
| 330 | + <textarea class="textarea" id="replace_note" rows="1" placeholder="Note..">${state.replace_note}</textarea></td> |
| 331 | + </td> |
| 332 | +</tr>`; |
| 333 | + }, |
| 334 | + |
| 335 | + saveChanges() { |
| 336 | + this.userStates[this.currentIndex] = { |
| 337 | + ...this.userStates[this.currentIndex], |
| 338 | + ...Object.fromEntries( |
| 339 | + mitigation_items.map((key) => [ |
| 340 | + key, |
| 341 | + document.getElementById(key)?.value ?? "" |
| 342 | + ]) |
| 343 | + ), |
| 344 | + }; |
| 345 | + }, |
| 346 | + |
| 347 | + navigate(dir) { |
| 348 | + this.saveChanges(); |
| 349 | + console.log(this.userStates[this.currentIndex]); |
| 350 | + this.currentIndex += dir; |
| 351 | + this.renderMitigationCuration(); |
| 352 | + }, |
| 353 | + |
| 354 | + updateNavButtons() { |
| 355 | + document.getElementById('prev-btn').disabled = this.currentIndex === 0; |
| 356 | + const isLast = this.currentIndex === curationItems.length - 1; |
| 357 | + document.getElementById('next-btn').classList.toggle('is-hidden', isLast); |
| 358 | + document.getElementById('finish-btn').classList.toggle('is-hidden', !isLast); |
| 359 | + }, |
| 360 | + |
| 361 | +}; |
| 362 | +document.addEventListener('DOMContentLoaded', () => app.init()); |
| 363 | + |
| 364 | +</script> |
| 365 | +{% endblock %} |
0 commit comments