@@ -60,20 +60,65 @@ export const renderers = {
6060 bb . generate ( bbConfig ) ;
6161 } ,
6262
63- colored_bar ( id , config ) {
64- const monoColor = config . color || getCssVar ( "--bulma-link" ) ;
65- bb . generate ( {
66- bindto : `#chart-${ id } ` ,
67- data : { x : "x" , columns : config . columns , type : "bar" , color : ( ) => monoColor } ,
68- axis : {
69- rotated : true ,
70- x : { type : "category" , label : { text : config . x_label || "CWE" , position : "outer-middle" } } ,
71- y : { label : { text : config . y_label || "Advisories" , position : "outer-center" } , tick : { format : formatWholeNumbersOnly } }
72- } ,
73- tooltip : { format : { title : x => config . full_labels ?. [ x ] || config . columns [ 0 ] [ x + 1 ] , value : val => val . toLocaleString ( ) } } ,
74- legend : { show : false }
63+ colored_bar ( id , config ) {
64+ const chartContainer = document . getElementById ( `chart-${ id } ` ) ;
65+ if ( ! chartContainer ) return ;
66+
67+ const labels = config . columns [ 0 ] . slice ( 1 ) ;
68+ const values = config . columns [ 1 ] . slice ( 1 ) ;
69+ const monoColor = config . color || getCssVar ( "--bulma-link" ) ;
70+
71+ chartContainer . innerHTML = "" ;
72+
73+ labels . forEach ( ( label , index ) => {
74+ const row = document . createElement ( "div" ) ;
75+ row . style . marginBottom = "12px" ;
76+ row . style . cursor = "pointer" ;
77+
78+ const title = document . createElement ( "div" ) ;
79+ title . textContent =
80+ config . full_labels ?. [ index ] || label ;
81+
82+ const bar = document . createElement ( "div" ) ;
83+ bar . style . height = "28px" ;
84+ bar . style . width = `${ Math . max ( values [ index ] * 10 , 20 ) } px` ;
85+ bar . style . maxWidth = "100%" ;
86+ bar . style . backgroundColor = monoColor ;
87+ bar . style . borderRadius = "4px" ;
88+ bar . style . marginTop = "4px" ;
89+ bar . textContent = values [ index ] . toLocaleString ( ) ;
90+ bar . style . color = "white" ;
91+ bar . style . paddingLeft = "8px" ;
92+ bar . style . lineHeight = "28px" ;
93+
94+ row . appendChild ( title ) ;
95+ row . appendChild ( bar ) ;
96+
97+ row . addEventListener ( "click" , async ( ) => {
98+ const cweId = label . replace ( "CWE-" , "" ) ;
99+
100+ try {
101+ const response = await fetch (
102+ `/insights/cwe/${ cweId } /advisories/`
103+ ) ;
104+
105+ if ( ! response . ok ) {
106+ throw new Error ( "Failed to fetch advisories" ) ;
107+ }
108+
109+ const data = await response . json ( ) ;
110+
111+ showCweAdvisories ( label , data . advisories ) ;
112+ } catch ( error ) {
113+ console . error ( "Error loading CWE advisories:" , error ) ;
114+ showCweAdvisories ( label , [ ] ) ;
115+ }
75116 } ) ;
76- } ,
117+
118+ chartContainer . appendChild ( row ) ;
119+ } ) ;
120+ } ,
121+
77122
78123 scatter ( id , config ) {
79124 const [ , ...buckets ] = config . columns [ 0 ] ;
@@ -146,3 +191,76 @@ export const renderers = {
146191 } ) ;
147192 }
148193} ;
194+
195+ function showCweAdvisories ( cwe , advisories ) {
196+ let modal = document . getElementById ( "cwe-advisories-modal" ) ;
197+
198+ if ( ! modal ) {
199+ modal = document . createElement ( "div" ) ;
200+ modal . id = "cwe-advisories-modal" ;
201+ modal . className = "modal" ;
202+
203+ modal . innerHTML = `
204+ <div class="modal-background"></div>
205+ <div class="modal-card">
206+ <header class="modal-card-head">
207+ <p class="modal-card-title" id="cwe-advisories-title"></p>
208+ <button class="delete" aria-label="close"></button>
209+ </header>
210+
211+ <section class="modal-card-body" id="cwe-advisories-body">
212+ </section>
213+ </div>
214+ ` ;
215+
216+ document . body . appendChild ( modal ) ;
217+
218+ modal . querySelector ( ".modal-background" ) . addEventListener (
219+ "click" ,
220+ ( ) => modal . classList . remove ( "is-active" )
221+ ) ;
222+
223+ modal . querySelector ( ".delete" ) . addEventListener (
224+ "click" ,
225+ ( ) => modal . classList . remove ( "is-active" )
226+ ) ;
227+ }
228+
229+ document . getElementById ( "cwe-advisories-title" ) . textContent =
230+ `${ cwe } Advisories` ;
231+
232+ const body = document . getElementById ( "cwe-advisories-body" ) ;
233+
234+ if ( ! advisories . length ) {
235+ body . innerHTML = `
236+ <p class="has-text-grey">
237+ No advisories found for ${ cwe } .
238+ </p>
239+ ` ;
240+ } else {
241+ body . innerHTML = `
242+ <div class="content">
243+ <p>
244+ <strong>${ advisories . length } </strong>
245+ advisories associated with ${ cwe }
246+ </p>
247+ <ul>
248+ ${ advisories . map ( advisory => `
249+ <li>
250+ <a href="${ advisory . url } " target="_blank">
251+ ${ advisory . avid }
252+ </a>
253+ ${
254+ advisory . summary
255+ ? `<p class="has-text-grey">${ advisory . summary } </p>`
256+ : ""
257+ }
258+ </li>
259+ ` ) . join ( "" ) }
260+ </ul>
261+ </div>
262+ ` ;
263+ }
264+
265+ modal . classList . add ( "is-active" ) ;
266+ }
0 commit comments