1import{c as me,aR as C,r as a,j as e,B as m,aP as X,bi as ue,M as Q,ce as ie,a7 as De,aB as J,aU as Z,G as $,ad as Ge,P as He,L as V,Q as ne,av as ee,S as he,aN as B,a3 as Ke,H as qe,E as Ve,f as de,g as Be,ay as Ye}from"./index-_c_gQtrU.js";import{B as R}from"./badge-BATMd08-.js";import{I as se}from"./input-dSWOBfhi.js";import{L as te}from"./label-weGxv0wL.js";import{T as xe,a as ge,b as fe,c as Y,d as S,e as ye,f as v}from"./table-BN_HZGFe.js";import{u as Xe}from"./usePageTitle-uaPxC3Ql.js";import{T as Fe,a as ze,b as Me,c as Je}from"./tabs-Cw8gYaU1.js";import{S as je,a as be,b as Ne,c as _e,d as w}from"./select-l0S6bcUn.js";import{D as We,a as Qe,b as Ze,c as es,d as ss,e as ts}from"./dialog-D8vfWcAJ.js";import{f as as,d as Se}from"./download-TncpEQfg.js";import{b as is}from"./severity-BXmG7dTa.js";import{C as U}from"./copy-lcCfD5Td.js";import{f as pe}from"./datetime-BmGcoJdI.js";import{s as ns}from"./statusLabel-DdNXTiVS.js";import{T as ve}from"./terminal-DfEDOOYT.js";import{D as ae}from"./download-BozcklbR.js";import{E as rs}from"./eye-off-B0mw1_cf.js";import{E as Ae}from"./eye-CiWYF5Wj.js";import{K as O}from"./key-BcXbpXxF.js";import{Z as cs}from"./zap-Dw83I8-V.js";import{C as os}from"./chevron-down-Boidayeg.js";import{S as ls}from"./sliders-horizontal-bPk87vFA.js";import{E as ds}from"./external-link-BOGsznTd.js";import"./index-BhLdGwe9.js";import"./index-DJe-iqC9.js";/** 2 * @license lucide-react v0.294.0 - ISC 3 * 4 * This source code is licensed under the ISC license. 5 * See the LICENSE file in the root directory of this source tree. 6 */const Pe=me("FileCode2",[["path",{d:"M4 22h14a2 2 0 0 0 2-2V7.5L14.5 2H6a2 2 0 0 0-2 2v4",key:"702lig"}],["polyline",{points:"14 2 14 8 20 8",key:"1ew0cm"}],["path",{d:"m9 18 3-3-3-3",key:"112psh"}],["path",{d:"m5 12-3 3 3 3",key:"oke12k"}]]);/** 7 * @license lucide-react v0.294.0 - ISC 8 * 9 * This source code is licensed under the ISC license. 10 * See the LICENSE file in the root directory of this source tree. 11 */const ps=me("Image",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2",key:"1m3agn"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}]]),ms={keyword:"text-purple-400",string:"text-emerald-500",comment:"text-slate-400 italic",variable:"text-amber-500",number:"text-blue-400",boolean:"text-rose-400",property:"text-sky-500",punctuation:"text-slate-400",function:"text-indigo-400",operator:"text-pink-400",plain:""};function us(c,l){if(!c)return[{type:"plain",value:""}];const n=[];(l==="yaml"||l==="bash")&&n.push({re:/#.*$/gm,type:"comment"}),l==="groovy"&&n.push({re:/\/\/.*$/gm,type:"comment"}),n.push({re:/\$\{\{[^}]*\}\}/g,type:"variable"}),n.push({re:/\$\{[^}]*\}/g,type:"variable"}),n.push({re:/\$\w+/g,type:"variable"}),n.push({re:/"(?:[^"\\]|\\.)*"/g,type:"string"}),n.push({re:/'(?:[^'\\]|\\.)*'/g,type:"string"}),l==="yaml"&&n.push({re:/^(\s*)([\w][\w.-]*?)(?=:)/gm,type:"property"}),n.push({re:/\b(true|false|null|yes|no|none)\b/gi,type:"boolean"}),n.push({re:/\b\d+\b/g,type:"number"}),l==="bash"&&n.push({re:/\b(if|then|else|elif|fi|for|do|done|while|until|case|esac|echo|exit|export|set|curl|jq|apk|apt-get|pip|npm|chmod)\b/g,type:"keyword"}),l==="groovy"&&n.push({re:/\b(def|if|else|return|new|import|class|stage|stages|steps|step|script|pipeline|agent|any|environment|post|always|success|failure|when|expression|sh|echo|error|env|readJSON|writeJSON|junit|archiveArtifacts|node|checkout|scm|withCredentials|string|credentialsId|usernamePassword)\b/g,type:"keyword"}),l==="yaml"&&n.push({re:/\b(name|on|push|pull_request|jobs|runs-on|steps|uses|with|run|id|if|env|secrets|needs|strategy|matrix|timeout-minutes|continue-on-error|stage|image|script|before_script|after_script|rules|only|except|artifacts|paths|reports|cache|services|variables|trigger|include|extends|allow_failure)\b/g,type:"keyword"}),n.push({re:/[|>]{1,2}|&&|\|\||[=!<>]=?|[-+]{1,2}/g,type:"operator"});const t=[];for(const{re:o,type:h}of n){o.lastIndex=0;let x;for(;(x=o.exec(c))!==null;)t.push({start:x.index,end:x.index+x[0].length,type:h,value:x[0]})}t.sort((o,h)=>o.start-h.start||o.end-h.end);const p=[];let i=0;for(const o of t)o.start<i||(o.start>i&&p.push({type:"plain",value:c.slice(i,o.start)}),p.push({type:o.type,value:o.value}),i=o.end);return i<c.length&&p.push({type:"plain",value:c.slice(i)}),p.length===0&&p.push({type:"plain",value:c}),p}function hs(c,l){if(!l)return c;let n=c;for(const[t,p]of Object.entries(l))n=n.split(t).join(p);return n}function F({code:c,language:l,filename:n,showLineNumbers:t=!0,highlightLines:p=[],maxHeight:i="500px",variables:o,onCopy:h}){const{toast:x}=C(),[g,y]=a.useState(!1),r=a.useRef(null),b=a.useMemo(()=>hs(c,o),[c,o]),N=a.useMemo(()=>b.split(` 12`),[b]),u=a.useMemo(()=>N.map(T=>us(T,l)),[N,l]),A=async()=>{try{await navigator.clipboard.writeText(b),y(!0),x({variant:"success",title:"Copied to clipboard"}),setTimeout(()=>y(!1),2e3),h==null||h()}catch{x({title:"Failed to copy",variant:"destructive"})}},E=String(N.length).length;return e.jsxs("div",{className:"group relative rounded-lg border border-border overflow-hidden bg-[#0d1117]",children:[e.jsxs("div",{className:"flex items-center justify-between px-4 py-2 bg-[#161b22] border-b border-[#30363d]",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("div",{className:"flex gap-1.5",children:[e.jsx("span",{className:"w-3 h-3 rounded-full bg-[#ff5f57]"}),e.jsx("span",{className:"w-3 h-3 rounded-full bg-[#febc2e]"}),e.jsx("span",{className:"w-3 h-3 rounded-full bg-[#28c840]"})]}),n&&e.jsxs("div",{className:"flex items-center gap-1.5 ml-3 text-xs text-slate-400",children:[e.jsx(Pe,{className:"w-3.5 h-3.5"}),e.jsx("span",{className:"font-mono",children:n})]}),!n&&e.jsx("span",{className:"ml-3 text-xs font-mono text-slate-400 uppercase tracking-wider",children:l})]}),e.jsx(m,{size:"sm",variant:"ghost",onClick:A,className:"h-7 px-2.5 text-slate-400 hover:text-white hover:bg-[#30363d] transition-colors",children:g?e.jsxs(e.Fragment,{children:[e.jsx(X,{className:"w-3.5 h-3.5 mr-1 text-emerald-400"}),e.jsx("span",{className:"text-xs text-emerald-400",children:"Copied"})]}):e.jsxs(e.Fragment,{children:[e.jsx(U,{className:"w-3.5 h-3.5 mr-1"}),e.jsx("span",{className:"text-xs",children:"Copy"})]})})]}),e.jsx("pre",{ref:r,className:"overflow-auto text-sm leading-[1.6] font-mono",style:{maxHeight:i},children:e.jsx("code",{className:"block py-3",children:u.map((T,s)=>{const j=s+1,P=p.includes(j);return e.jsxs("div",{className:`flex hover:bg-[#1c2128] transition-colors ${P?"bg-amber-500/5 border-l-2 border-amber-500":""}`,children:[t&&e.jsx("span",{className:"select-none text-right text-slate-400 pr-4 pl-4 flex-shrink-0",style:{minWidth:`${E+3}ch`},children:j}),e.jsx("span",{className:`flex-1 pr-4 ${t?"":"pl-4"} text-slate-300`,children:T.map((_,I)=>_.type==="plain"?e.jsx("span",{children:_.value},I):e.jsx("span",{className:ms[_.type],children:_.value},I))})]},s)})})})]})}const xs=`# .github/workflows/security-scan.yml 13name: Pentrova Security Scan 14 15on: 16 push: 17 branches: [main, develop] 18 pull_request: 19 branches: [main] 20 21jobs: 22 security-scan: 23 name: Pentrova Security Scan 24 runs-on: ubuntu-latest 25 timeout-minutes: 90 26 steps: 27 - name: Run Pentrova Scan 28 id: scan 29 run: | 30 RESULT=$(curl -s -w "\\n%{http_code}" -X POST "\${{ secrets.PENTROVA_URL }}/api/ci/scan" \\ 31 -H "X-API-Key: \${{ secrets.PENTROVA_API_KEY }}" \\ 32 -H "Content-Type: application/json" \\ 33 -d '{ 34 "target_url": "\${{ secrets.STAGING_URL }}", 35 "scan_type": "web", 36 "fail_on": {"critical": 0, "high": 0}, 37 "wait": true, 38 "timeout": 3600 39 }') 40 HTTP_CODE=$(echo "$RESULT" | tail -1) 41 BODY=$(echo "$RESULT" | sed '$d') 42 if [ "$HTTP_CODE" != "200" ]; then 43 echo "::error::Scan API returned HTTP $HTTP_CODE" 44 echo "$BODY" 45 exit 1 46 fi 47 echo "$BODY" | jq . 48 SCAN_ID=$(echo "$BODY" | jq -r '.scan_id // empty') 49 echo "scan_id=$SCAN_ID" >> $GITHUB_OUTPUT 50 PASS=$(echo "$BODY" | jq -r '.pass') 51 if [ "$PASS" != "true" ]; then 52 REASON=$(echo "$BODY" | jq -r '.reason // "Unknown"') 53 echo "::error::Security scan FAILED â $REASON" 54 exit 1 55 fi 56 echo "::notice::Security scan PASSED" 57 58 - name: Upload SARIF to GitHub Security 59 if: always() 60 continue-on-error: true 61 run: | 62 SCAN_ID="\${{ steps.scan.outputs.scan_id }}" 63 if [ -n "$SCAN_ID" ]; then 64 curl -s "\${{ secrets.PENTROVA_URL }}/api/ci/scan/$SCAN_ID/sarif" \\ 65 -H "X-API-Key: \${{ secrets.PENTROVA_API_KEY }}" \\ 66 -o results.sarif 67 fi 68 69 - name: Upload SARIF results 70 if: always() 71 uses: github/codeql-action/upload-sarif@v3 72 continue-on-error: true 73 with: 74 sarif_file: results.sarif 75 category: pentrova-security 76
77# ââ API Scan (uncomment to also scan your backend API) ââ 78# - name: Run Pentrova API Scan 79# run: | 80# RESULT=$(curl -s -w "\\\\n%{http_code}" -X POST "\${{ secrets.PENTROVA_URL }}/api/ci/scan" \\\\ 81# -H "X-API-Key: \${{ secrets.PENTROVA_API_KEY }}" \\\\ 82# -H "Content-Type: application/json" \\\\ 83# -d '{ 84# "target_url": "\${{ secrets.STAGING_URL }}", 85# "scan_type": "api", 86# "spec_url": "\${{ secrets.STAGING_URL }}/openapi.json", 87# "fail_on": {"critical": 0, "high": 0}, 88# "wait": true 89# }') 90# HTTP_CODE=$(echo "$RESULT" | tail -1) 91# BODY=$(echo "$RESULT" | sed '$d') 92# echo "$BODY" | jq . 93# PASS=$(echo "$BODY" | jq -r '.pass') 94# [ "$PASS" = "true" ] || exit 1`,gs=`# Include in .gitlab-ci.yml 95security_scan: 96 stage: test 97 image: alpine:latest 98 before_script: 99 - apk add --no-cache curl jq 100 script: 101 - | 102 RESULT=$(curl -s -X POST "\${PENTROVA_URL}/api/ci/scan" \\ 103 -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 104 -H "Content-Type: application/json" \\ 105 -d "{ 106 \\"target_url\\": \\"\${STAGING_URL}\\", 107 \\"scan_type\\": \\"web\\", 108 \\"fail_on\\": {\\"critical\\": 0, \\"high\\": 0}, 109 \\"wait\\": true, 110 \\"timeout\\": 3600 111 }") 112 echo "$RESULT" | jq . 113 PASS=$(echo "$RESULT" | jq -r '.pass') 114 SCAN_ID=$(echo "$RESULT" | jq -r '.scan_id // empty') 115 if [ "$PASS" != "true" ]; then 116 echo "Security scan FAILED" 117 exit 1 118 fi 119 echo "Security scan PASSED" 120 # Download JUnit report for GitLab test reporting 121 if [ -n "$SCAN_ID" ]; then 122 curl -s "\${PENTROVA_URL}/api/ci/scan/$SCAN_ID/junit" \\ 123 -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 124 -o pentrova-report.xml 125 fi 126 artifacts: 127 reports: 128 junit: pentrova-report.xml 129 when: always 130 rules: 131 - if: $CI_MERGE_REQUEST_ID 132 - if: $CI_COMMIT_BRANCH == "main" 133 134# ââ API Scan (uncomment to also scan your backend API) ââ 135# api_security_scan: 136# stage: test 137# image: alpine:latest 138# before_script: 139# - apk add --no-cache curl jq 140# script: 141# - | 142# RESULT=$(curl -s -X POST "\${PENTROVA_URL}/api/ci/scan" \\ 143# -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 144# -H "Content-Type: application/json" \\ 145# -d "{ 146# \\"target_url\\": \\"\${STAGING_URL}\\", 147# \\"scan_type\\": \\"api\\", 148# \\"spec_url\\": \\"\${STAGING_URL}/openapi.json\\", 149# \\"auth_config\\": {\\"type\\": \\"bearer\\", \\"token\\": \\"\${API_TOKEN}\\"}, 150# \\"fail_on\\": {\\"critical\\": 0, \\"high\\": 0}, 151# \\"wait\\": true 152# }") 153# PASS=$(echo "$RESULT" | jq -r '.pass') 154# [ "$PASS" = "true" ] || exit 1 155# rules: 156# - if: $CI_MERGE_REQUEST_ID 157# - if: $CI_COMMIT_BRANCH == "main"`,fs=`// Add this stage to your Jenkinsfile 158pipeline { 159 agent any 160 environment { 161 PENTROVA_URL = credentials('pentrova-url') 162 PENTROVA_API_KEY = credentials('pentrova-api-key') 163 STAGING_URL = credentials('staging-url') 164 } 165 stages { 166 stage('Security Scan') { 167 steps { 168 script { 169 def result = sh( 170 script: """ 171 curl -s -X POST "\${PENTROVA_URL}/api/ci/scan" \\ 172 -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 173 -H "Content-Type: application/json" \\ 174 -d '{ 175 "target_url": "\${STAGING_URL}", 176 "scan_type": "web", 177 "fail_on": {"critical": 0, "high": 0}, 178 "wait": true, 179 "timeout": 3600 180 }' 181 """, 182 returnStdout: true 183 ).trim() 184 185 def json = readJSON text: result
186 env.SCAN_ID = json.scan_id ?: '' 187 188 if (json.pass != true) { 189 error("Security scan FAILED: \${json.reason}") 190 } 191 echo "Security scan PASSED" 192 } 193 } 194 post { 195 always { 196 script { 197 if (env.SCAN_ID) { 198 sh """ 199 curl -s "\${PENTROVA_URL}/api/ci/scan/\${env.SCAN_ID}/junit" \\ 200 -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 201 -o pentrova-report.xml 202 """ 203 junit allowEmptyResults: true, testResults: 'pentrova-report.xml' 204 } 205 } 206 } 207 } 208 } 209 } 210} 211 212// ââ API Scan (add this stage alongside the web scan stage) ââ 213// stage('API Security Scan') { 214// steps { 215// script { 216// def result = sh( 217// script: """ 218// curl -s -X POST "\${PENTROVA_URL}/api/ci/scan" \\ 219// -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 220// -H "Content-Type: application/json" \\ 221// -d '{ 222// "target_url": "\${STAGING_URL}", 223// "scan_type": "api", 224// "spec_url": "\${STAGING_URL}/openapi.json", 225// "auth_config": {"type": "bearer", "token": "\${API_TOKEN}"}, 226// "fail_on": {"critical": 0, "high": 0}, 227// "wait": true 228// }' 229// """, 230// returnStdout: true 231// ).trim() 232// def json = readJSON text: result 233// if (json.pass != true) { 234// error("API security scan FAILED: \${json.reason}") 235// } 236// } 237// } 238// }`,ys=`# azure-pipelines.yml 239trigger: 240 branches: 241 include: 242 - main 243 - develop 244 245pr: 246 branches: 247 include: 248 - main 249 250pool: 251 vmImage: 'ubuntu-latest' 252 253steps: 254 - task: Bash@3 255 displayName: 'Pentrova Security Scan' 256 inputs: 257 targetType: 'inline' 258 script: | 259 RESULT=$(curl -s -w "\\n%{http_code}" -X POST "$(PENTROVA_URL)/api/ci/scan" \\ 260 -H "X-API-Key: $(PENTROVA_API_KEY)" \\ 261 -H "Content-Type: application/json" \\ 262 -d '{ 263 "target_url": "$(STAGING_URL)", 264 "scan_type": "web", 265 "fail_on": {"critical": 0, "high": 0}, 266 "wait": true, 267 "timeout": 3600 268 }') 269 HTTP_CODE=$(echo "$RESULT" | tail -1) 270 BODY=$(echo "$RESULT" | sed '$$d') 271 if [ "$HTTP_CODE" != "200" ]; then 272 echo "##vso[task.logissue type=error]Scan API returned HTTP $HTTP_CODE" 273 exit 1 274 fi 275 echo "$BODY" | jq . 276 PASS=$(echo "$BODY" | jq -r '.pass') 277 if [ "$PASS" != "true" ]; then 278 REASON=$(echo "$BODY" | jq -r '.reason // "Unknown"') 279 echo "##vso[task.logissue type=error]Security scan FAILED â $REASON" 280 exit 1 281 fi 282 echo "##vso[task.complete result=Succeeded;]Security scan PASSED" 283 env: 284 PENTROVA_URL: $(PENTROVA_URL) 285 PENTROVA_API_KEY: $(PENTROVA_API_KEY) 286 STAGING_URL: $(STAGING_URL) 287 288# ââ API Scan (add as a separate step) ââ 289# - task: Bash@3 290# displayName: 'Pentrova API Security Scan' 291# inputs: 292# targetType: 'inline' 293# script: | 294# RESULT=$(curl -s -X POST "$(PENTROVA_URL)/api/ci/scan" \\ 295# -H "X-API-Key: $(PENTROVA_API_KEY)" \\ 296# -H "Content-Type: application/json" \\ 297# -d '{ 298# "target_url": "$(STAGING_URL)", 299# "scan_type": "api", 300# "spec_url": "$(STAGING_URL)/openapi.json", 301# "auth_config": {"type": "bearer", "token": "$(API_TOKEN)"}, 302# "fail_on": {"critical": 0, "high": 0}, 303# "wait": true 304# }') 305# echo "$RESULT" | jq . 306# PASS=$(echo "$RESULT" | jq -r '.pass') 307# if [ "$PASS" != "true" ]; then 308# echo "##vso[task.logissue type=error]API scan FAILED" 309# exit 1 310# fi 311# env: 312# PENTROVA_URL: $(PENTROVA_URL) 313# PENTROVA_API_KEY: $(PENTROVA_API_KEY) 314# STAGING_URL: $(STAGING_URL) 315# API_TOKEN: $(API_TOKEN)`,js=`# .circleci/config.yml 316version: 2.1 317 318jobs: 319 security-scan: 320 docker: 321 - image: cimg/base:stable 322 steps: 323 - run: 324 name: Install dependencies 325 command: sudo apt-get update && sudo apt-get install -y jq 326 - run: 327 name: Pentrova Security Scan 328 command: | 329 RESULT=$(curl -s -w "\\n%{http_code}" -X POST "\${PENTROVA_URL}/api/ci/scan" \\ 330 -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 331 -H "Content-Type: application/json" \\ 332 -d '{ 333 "target_url": "'\${STAGING_URL}'", 334 "scan_type": "web", 335 "fail_on": {"critical": 0, "high": 0}, 336 "wait": true, 337 "timeout": 3600 338 }') 339 HTTP_CODE=$(echo "$RESULT" | tail -1) 340 BODY=$(echo "$RESULT" | sed '$$d') 341 if [ "$HTTP_CODE" != "200" ]; then 342 echo "Scan API returned HTTP $HTTP_CODE" 343 echo "$BODY" 344 exit 1 345 fi 346 echo "$BODY" | jq . 347 PASS=$(echo "$BODY" | jq -r '.pass') 348 if [ "$PASS" != "true" ]; then 349 echo "Security scan FAILED" 350 exit 1 351 fi 352 echo "Security scan PASSED" 353 354workflows: 355 security: 356 jobs: 357 - security-scan: 358 filters: 359 branches: 360 only: [main, develop] 361
362# ââ API Scan (add as a separate job) ââ 363# api-security-scan: 364# docker: 365# - image: cimg/base:stable 366# steps: 367# - run: 368# name: Install dependencies 369# command: sudo apt-get update && sudo apt-get install -y jq 370# - run: 371# name: Pentrova API Security Scan 372# command: | 373# RESULT=$(curl -s -X POST "\${PENTROVA_URL}/api/ci/scan" \\ 374# -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 375# -H "Content-Type: application/json" \\ 376# -d '{ 377# "target_url": "'\${STAGING_URL}'", 378# "scan_type": "api", 379# "spec_url": "'\${STAGING_URL}'/openapi.json", 380# "auth_config": {"type": "bearer", "token": "'\${API_TOKEN}'"}, 381# "fail_on": {"critical": 0, "high": 0}, 382# "wait": true 383# }') 384# echo "$RESULT" | jq . 385# PASS=$(echo "$RESULT" | jq -r '.pass') 386# [ "$PASS" = "true" ] || exit 1`,bs=`# bitbucket-pipelines.yml 387image: alpine:latest 388 389pipelines: 390 default: 391 - step: 392 name: Pentrova Security Scan 393 script: 394 - apk add --no-cache curl jq 395 - | 396 RESULT=$(curl -s -w "\\n%{http_code}" -X POST "\${PENTROVA_URL}/api/ci/scan" \\ 397 -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 398 -H "Content-Type: application/json" \\ 399 -d "{ 400 \\"target_url\\": \\"\${STAGING_URL}\\", 401 \\"scan_type\\": \\"web\\", 402 \\"fail_on\\": {\\"critical\\": 0, \\"high\\": 0}, 403 \\"wait\\": true, 404 \\"timeout\\": 3600 405 }") 406 HTTP_CODE=$(echo "$RESULT" | tail -1) 407 BODY=$(echo "$RESULT" | sed '$$d') 408 if [ "$HTTP_CODE" != "200" ]; then 409 echo "Scan API returned HTTP $HTTP_CODE" 410 exit 1 411 fi 412 echo "$BODY" | jq . 413 PASS=$(echo "$BODY" | jq -r '.pass') 414 if [ "$PASS" != "true" ]; then 415 echo "Security scan FAILED" 416 exit 1 417 fi 418 echo "Security scan PASSED" 419 420 pull-requests: 421 '**': 422 - step: 423 name: PR Security Scan 424 script: 425 - apk add --no-cache curl jq 426 - | 427 # Same scan command as above 428 RESULT=$(curl -s -X POST "\${PENTROVA_URL}/api/ci/scan" \\ 429 -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 430 -H "Content-Type: application/json" \\ 431 -d "{\\"target_url\\": \\"\${STAGING_URL}\\", \\"scan_type\\": \\"web\\", \\"wait\\": true}") 432 PASS=$(echo "$RESULT" | jq -r '.pass') 433 [ "$PASS" = "true" ] || exit 1 434 435# ââ API Scan (add as a separate step under pipelines.default) ââ 436# - step: 437# name: Pentrova API Security Scan 438# script: 439# - apk add --no-cache curl jq 440# - | 441# RESULT=$(curl -s -X POST "\${PENTROVA_URL}/api/ci/scan" \\ 442# -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 443# -H "Content-Type: application/json" \\ 444# -d "{ 445# \\"target_url\\": \\"\${STAGING_URL}\\", 446# \\"scan_type\\": \\"api\\", 447# \\"spec_url\\": \\"\${STAGING_URL}/openapi.json\\", 448# \\"auth_config\\": {\\"type\\": \\"bearer\\", \\"token\\": \\"\${API_TOKEN}\\"}, 449# \\"fail_on\\": {\\"critical\\": 0, \\"high\\": 0}, 450# \\"wait\\": true 451# }") 452# PASS=$(echo "$RESULT" | jq -r '.pass') 453# [ "$PASS" = "true" ] || exit 1`,Ns=`#!/usr/bin/env bash 454set -euo pipefail 455# ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 456# Pentrova â Generic CI/CD integration script 457# Works with any CI system (Travis CI, Drone, Woodpecker, etc.) 458# 459# Required environment variables: 460# PENTROVA_URL â Your Pentrova instance URL 461# PENTROVA_API_KEY â API key with scan:create permission 462# TARGET_URL â URL to scan 463# ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 464 465: "\${PENTROVA_URL:?Set PENTROVA_URL}" "\${PENTROVA_API_KEY:?Set PENTROVA_API_KEY}" "\${TARGET_URL:?Set TARGET_URL}" 466 467echo "Starting Pentrova scan on \${TARGET_URL}..." 468 469RESULT=$(curl -sf -w "\\n%{http_code}" -X POST "\${PENTROVA_URL}/api/ci/scan" \\ 470 -H "X-API-Key: \${PENTROVA_API_KEY}" \\ 471 -H "Content-Type: application/json" \\ 472 -d "{ 473 \\"target_url\\": \\"\${TARGET_URL}\\", 474 \\"scan_type\\": \\"web\\", 475 \\"fail_on\\": {\\"critical\\": 0, \\"high\\": 0}, 476 \\"wait\\": true, 477 \\"timeout\\": 3600 478 }") 479 480HTTP_CODE=$(echo "$RESULT" | tail -1) 481BODY=$(echo "$RESULT" | sed '$$d') 482 483if [ "$HTTP_CODE" != "200" ]; then 484 echo "ERROR: Scan API returned HTTP $HTTP_CODE" 485 echo "$BODY" 486 exit 1 487fi 488 489echo "$BODY" | jq . 490 491PASS=$(echo "$BODY" | jq -r '.pass // "null"') 492REASON=$(echo "$BODY" | jq -r '.reason // "N/A"') 493SCAN_ID=$(echo "$BODY" | jq -r '.scan_id // empty') 494 495if [ "$PASS" = "true" ]; then 496 echo "â SECURITY SCAN PASSED â $REASON" 497 exit 0 498else 499 echo "â SECURITY SCAN FAILED â $REASON" 500 # Optionally download SARIF report 501 if [ -n "$SCAN_ID" ]; then 502 curl -s "\${PENTROVA_URL}/api/ci/scan/$SCAN_ID/sarif" \\ 503 -H "X-API-Key: \${PENTROVA_API_KEY}" -o pentrova-results.sarif 504 echo "SARIF report saved to pentrova-results.sarif" 505 fi 506 exit 1 507fi 508 509# ââ API Scan example âââââââââââââââââââââââââââââââââââââââââ 510# To scan API endpoints, change the request body: 511# -d "{ 512# \\\\"target_url\\\\": \\\\"\${TARGET_URL}\\\\", 513# \\\\"scan_type\\\\": \\\\"api\\\\", 514# \\\\"spec_url\\\\": \\\\"\${TARGET_URL}/openapi.json\\\\", 515# \\\\"fail_on\\\\": {\\\\"critical\\\\": 0, \\\\"high\\\\": 0}, 516# \\\\"wait\\\\": true 517# }" 518# 519# Or use the downloadable pentrova-scan.sh template which supports: 520# SCAN_TYPE=api SPEC_URL=\${TARGET_URL}/openapi.json bash pentrova-scan.sh`,W=[{id:"github-actions",label:"GitHub Actions",snippet:xs,lang:"yaml",filename:".github/workflows/security-scan.yml"},{id:"gitlab-ci",label:"GitLab CI",snippet:gs,lang:"yaml",filename:".gitlab-ci.yml"},{id:"jenkins",label:"Jenkins",snippet:fs,lang:"groovy",filename:"Jenkinsfile"},{id:"azure-devops",label:"Azure DevOps",snippet:ys,lang:"yaml",filename:"azure-pipelines.yml"},{id:"circleci",label:"CircleCI",snippet:js,lang:"yaml",filename:".circleci/config.yml"},{id:"bitbucket",label:"Bitbucket",snippet:bs,lang:"yaml",filename:"bitbucket-pipelines.yml"},{id:"generic",label:"Generic / Shell",snippet:Ns,lang:"bash",filename:"pentrova-scan.sh"}],_s=[{method:"POST",path:"/api/ci/scan",title:"Trigger Scan",description:"Start a Pentrova security scan from your CI/CD pipeline. Supports both synchronous (blocking) and asynchronous (non-blocking) modes.",requestBody:[{name:"target_url",type:"string",required:!0,description:"URL to scan (e.g. https://staging.myapp.com)"},{name:"scan_type",type:'"web" | "api"',required:!1,default:'"web"',description:'Type of scan. "web" for website crawl, "api" for API endpoint scan using an OpenAPI/Swagger spec.'},{name:"spec_url",type:"string",required:!1,default:"null",description:'URL to fetch OpenAPI/Swagger spec from. Required when scan_type is "api" (e.g. https://staging.myapp.com/openapi.json).'},{name:"api_base_url",type:"string",required:!1,default:"null",description:"Override base URL where the API is hosted. Defaults to the value extracted from the spec. Useful when spec is hosted separately from the API."},{name:"scan_mode",type:"string",required:!1,default:'"UNAUTHENTICATED"',description:'"UNAUTHENTICATED" or "AUTH_LLM_LOGIN" for authenticated scanning.'},{name:"login_url",type:"string",required:!1,default:"null",description:"Login page URL (required when scan_mode is AUTH_LLM_LOGIN)."},{name:"username",type:"string",required:!1,default:"null",description:"Login username for authenticated scanning."},{name:"password",type:"string",required:!1,default:"null",description:"Login password for authenticated scanning."},{name:"fail_on",type:"object",required:!1,default:'{"critical":0,"high":0}',description:"Quality gate thresholds. Keys: critical, high, medium, low. Value = max allowed (0 = fail on any, null = ignore)."},{name:"wait",type:"boolean",required:!1,default:"true",description:"true = block until scan completes (sync). false = return immediately (async)."},{name:"timeout",type:"integer",required:!1,default:"3600",description:"Max wait time in seconds (60â14400). Only applies when wait=true."},{name:"callback_url",type:"string",required:!1,default:"null",description:"Webhook URL â receives POST with results when scan completes (async mode)."},{name:"tags",type:"string[]",required:!1,default:"null",description:'Metadata tags (e.g. ["pr-123", "branch:main"]).'},{name:"auth_config",type:"object",required:!1,default:"null",description:'Authentication config for API scans. Supports types: "bearer" ({type:"bearer", token:"..."}), "api_key" ({type:"api_key", header:"X-API-Key", value:"..."}), "basic" ({type:"basic", username:"...", password:"..."}), "oauth2" ({type:"oauth2", client_id:"...", client_secret:"...", token_url:"..."}).'},{name:"custom_headers",type:"object",required:!1,default:"null",description:'Custom headers to include in every request during the API scan (e.g. {"X-Tenant-ID": "abc", "Accept": "application/json"}).'},{name:"scope_regex",type:"string",required:!1,default:"null",description:"Regex to restrict scan to matching URLs only."}
520],responseExample:`{ 521 "scan_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", 522 "status": "completed", 523 "pass": true, 524 "reason": "All severity thresholds passed", 525 "severity_counts": { 526 "critical": 0, 527 "high": 0, 528 "medium": 3, 529 "low": 7, 530 "info": 12 531 }, 532 "total_findings": 22, 533 "duration_seconds": 847, 534 "scan_url": "https://your-instance.com/scans/a1b2c.../detail", 535 "report_url": "https://your-instance.com/api/reports/scan/a1b2c...", 536 "sarif_url": "https://your-instance.com/api/ci/scan/a1b2c.../sarif", 537 "top_findings": [ 538 {"type": "xss_reflected", "severity": "HIGH", "url": "https://...", "parameter": "q"} 539 ] 540}`,errorCodes:[{code:403,description:"API key missing, invalid, or lacks scan:create permission. Also returned when billing check fails (no credits / no subscription)."},{code:422,description:"Invalid request body â missing target_url, invalid scan_type, or malformed fail_on object."},{code:503,description:"Billing service temporarily unavailable."}]},{method:"GET",path:"/api/ci/scan/{scan_id}/status",title:"Poll Status",description:"Check scan progress and quality gate result. Use this endpoint to poll when running in async mode (wait=false).",requestBody:[],responseExample:`{ 541 "scan_id": "a1b2c3d4-...", 542 "status": "completed", 543 "pass": false, 544 "reason": "2 CRITICAL findings exceed threshold (max 0)", 545 "severity_counts": {"critical": 2, "high": 5, "medium": 3, "low": 1, "info": 8}, 546 "total_findings": 19, 547 "scan_url": "https://your-instance.com/scans/a1b2c.../detail", 548 "sarif_url": "https://your-instance.com/api/ci/scan/a1b2c.../sarif" 549}`,errorCodes:[{code:404,description:"Scan ID not found."},{code:403,description:"Access denied â scan belongs to a different tenant."}]},{method:"GET",path:"/api/ci/scan/{scan_id}/sarif",title:"SARIF Export",description:"Download findings in SARIF 2.1.0 format. Upload to GitHub Security tab via github/codeql-action/upload-sarif.",requestBody:[],responseExample:`{ 550 "$schema": "https://raw.githubusercontent.com/.../sarif-schema-2.1.0.json", 551 "version": "2.1.0", 552 "runs": [{ 553 "tool": { "driver": { "name": "Pentrova", "rules": [...] } }, 554 "results": [...] 555 }] 556}`,errorCodes:[{code:404,description:"Scan not found."},{code:403,description:"Access denied."}]},{method:"GET",path:"/api/ci/scan/{scan_id}/junit",title:"JUnit Export",description:"Download findings as JUnit XML. Jenkins and GitLab CI can display this as a native test report.",requestBody:[],responseExample:`<?xml version="1.0" ?> 557<testsuite name="Pentrova" tests="22" failures="5" errors="0"> 558 <testcase classname="security.critical" name="sql_injection at /api/users"> 559 <failure message="CRITICAL sql_injection" type="sql_injection"> 560 SQL injection found in parameter 'id' 561 </failure> 562 </testcase> 563 ... 564</testsuite>`,errorCodes:[{code:404,description:"Scan not found."},{code:403,description:"Access denied."}]}];function Ss({apiKeys:c,onKeyCreated:l,keysLoading:n}){const{toast:t}=C(),{user:p,currentTenant:i}=De(),o=p!=null&&p.is_platform_admin?"platform_admin":(i==null?void 0:i.role)||(p==null?void 0:p.role)||"",h=["platform_admin","tenant_owner","tenant_admin"].includes(o),[x,g]=a.useState("key"),[y,r]=a.useState(!1),[b,N]=a.useState(!1),[u,A]=a.useState(""),[E,T]=a.useState("never"),[s,j]=a.useState(null),[P,_]=a.useState(!1),[I,D]=a.useState(!1),[G,Te]=a.useState(""),[z,re]=a.useState(!1),[ce,k]=a.useState("idle"),[we,H]=a.useState(""),K=c.length>0,Ee=typeof window<"u"?window.location.origin:"https://your-instance.com",q=i!=null&&i.id?`cicd_${i.id}_`:"cicd_",[Ie,$e]=a.useState(()=>{try{return localStorage.getItem(`${q}secrets_done`)==="true"}catch{return!1}}),[oe,Re]=a.useState(()=>{try{return localStorage.getItem(`${q}pipeline_done`)==="true"}catch{return!1}}),Ce=()=>{$e(!0);try{localStorage.setItem(`${q}secrets_done`,"true")}catch{}},ke=()=>{Re(!0);try{localStorage.setItem(`${q}pipeline_done`,"true")}catch{}},M=[{id:"key",label:"Create API Key",done:K},{id:"secrets",label:"Configure Secrets",done:Ie},{id:"pipeline",label:"Add Pipeline",done:oe}],le=d=>{var f;return((f=M.find(L=>L.id===d))==null?void 0:f.label)??"previous step"},Le=async()=>
564{if(u.trim()){N(!0);try{const d=E==="never"?null:parseInt(E),f=await ue.create({name:u.trim(),permissions:["scan:read","scan:create"],expires_in_days:d});j(f),_(!0),r(!1),A(""),T("never"),l(),t({variant:"success",title:"API key created â copy it now!"})}catch(d){t({title:"Failed to create key",description:Be(d,"Unknown error"),variant:"destructive"})}finally{N(!1)}}},Oe=async d=>{await B(d)?(D(!0),t({variant:"success",title:"Copied to clipboard"}),setTimeout(()=>D(!1),2e3)):t({title:"Failed to copy â please select and copy manually",variant:"destructive"})},Ue=async()=>{var d,f;if(!G.trim()){t({title:"Enter an API key to test",variant:"destructive"});return}re(!0),k("idle"),H("");try{(await ie.testConnectionWithKey(G.trim())).has_scan_permission?k("success"):(k("fail"),H("Key is valid but lacks scan:create permission."))}catch(L){k("fail"),((d=L.response)==null?void 0:d.status)===401||((f=L.response)==null?void 0:f.status)===403?H("Invalid API key or unauthorized."):H("Cannot reach server. Check your URL.")}finally{re(!1)}};return e.jsxs("div",{className:"glass-card p-6 rounded-xl",children:[e.jsx("div",{className:"flex items-center gap-2 mb-6",children:M.map((d,f)=>e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(m,{variant:"ghost",size:"lg","aria-pressed":x===d.id,onClick:()=>g(d.id),className:`h-auto gap-2 px-3 py-1.5 rounded-full ${x===d.id?"bg-primary text-primary-foreground shadow-e2":d.done?"bg-success/10 text-success-text":"bg-muted/50 text-muted-foreground hover:bg-muted/80 hover:text-foreground"}`,children:[d.done?e.jsx(J,{className:"w-4 h-4","aria-hidden":"true"}):e.jsx("span",{"aria-hidden":"true",className:"w-5 h-5 rounded-full bg-current/10 flex items-center justify-center text-xs font-bold",children:f+1}),e.jsx("span",{className:"sr-only sm:not-sr-only",children:d.label})]}),f<M.length-1&&e.jsx("div",{className:"w-8 h-px bg-border hidden sm:block"})]},d.id))}),e.jsxs(Z,{mode:"wait",children:[x==="key"&&e.jsxs($.div,{initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.15},children:[s&&e.jsx("div",{className:"mb-4 p-4 rounded-lg border border-warning/30 bg-warning/10",children:e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(Ge,{className:"w-5 h-5 text-warning mt-0.5 flex-shrink-0"}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:"text-sm font-semibold mb-2",children:"Save your API key â it won't be shown again"}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("code",{className:"flex-1 text-sm bg-card px-3 py-2 rounded-lg font-mono break-all border",children:P?s.key:"â¢".repeat(40)}),e.jsx(m,{size:"sm",variant:"ghost",onClick:()=>_(!P),children:P?e.jsx(rs,{className:"w-4 h-4"}):e.jsx(Ae,{className:"w-4 h-4"})}),e.jsxs(m,{size:"sm",variant:"outline",onClick:()=>Oe(s.key),children:[I?e.jsx(X,{className:"w-4 h-4 text-success"}):e.jsx(U,{className:"w-4 h-4"}),e.jsx("span",{className:"ml-1",children:I?"Copied":"Copy"})]})]})]}),e.jsx(m,{size:"sm",variant:"ghost",onClick:()=>j(null),className:"text-muted-foreground",children:"Dismiss"})]})}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsxs("h3",{className:"text-lg font-semibold flex items-center gap-2",children:[e.jsx(O,{className:"w-5 h-5 text-primary"}),"API Key"]}),e.jsx("p",{className:"text-sm text-muted-foreground mt-1",children:K?`You have ${c.length} active key${c.length>1?"s":""}. You're ready for step 2.`:h?"Create an API key with scan:create permission to get started.":"Contact your organization admin to create an API key."})]}),h&&e.jsxs(m,{onClick:()=>r(!0),className:"gradient-primary hover:opacity-90",children:[e.jsx(He,{className:"w-4 h-4 mr-2"}),"Create Key"]})]}),n?e.jsx("div",{className:"flex justify-center py-8",children:e.jsx(V,{className:"w-5 h-5 animate-spin text-muted-foreground"})}):K?e.jsxs("div",{className:"mt-4 space-y-2",children:[c.slice(0,3).map(d=>e.jsxs("div",{className:"flex items-center justify-between p-3 rounded-lg border border-border bg-muted/20",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(O,{className:"w-4 h-4 text-muted-foreground"}),e.jsx("span",{className:"font-medium text-sm",children:d.name}),e.jsxs("code",{className:"text-xs bg-muted px-2 py-0.5 rounded-sm font-mono",children:[d.key_prefix,"â¢â¢â¢â¢â¢"]})]}),e.jsx("div",{className:"flex gap-1",children:d.permissions.map(f=>e.jsx(R,{variant:"outline",className:"text-xs",children:f},f))})]},d.id)),c.length>3&&e.jsxs("p",{className:"text-xs text-muted-foreground text-center",children:["+",c.length-3," more â ",e.jsx(ne,{to:"/settings",className:"text-primary underline",children:"manage in Settings"})]})]}):null,K&&e.jsx("div",{className:"mt-4 flex justify-end",children:e.jsxs(m,{onClick:()=>g("secrets"),variant:"outline",size:"sm",children:["Next: Configure Secrets ",e.jsx(ee,{className:"w-4 h-4 ml-1"})]})}),e.jsx(We,{open:y,onOpenChange:r,children:e.jsxs(Qe,{children:[e.jsxs(Ze,{children:[e.jsx(es,{children:"Create API Key"}),e.jsx(ss,{children:"This key will have scan:create permission for CI/CD pipelines."})]}),e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{children:[e.jsx(te,{htmlFor:"wiz_key_name",children:"Key Name *"}),e.jsx(se,{id:"wiz_key_name",placeholder:"e.g. GitHub Actions â Production",value:u,onChange:d=>A(d.target.value)})]}),e.jsxs("div",{children:[e.jsx(te,{children:"Expiration"}),e.jsxs(je,{value:E,onValueChange:T,children:[e.jsx(be,{children:e.jsx(Ne,{})}),e.jsxs(_e,{children:[e.jsx(w,{value:"never",children:"Never"}),e.jsx(w,{value:"30",children:"30 days"}),e.jsx(w,{value:"90",children:"90 days"}),e.jsx(w,{value:"180",children:"180 days"}),e.jsx(w,{value:"365",children:"1 year"})]})]})]}),e.jsxs("div",{className:"p-3 rounded-lg bg-muted/50 text-xs text-muted-foreground",children:[e.jsx("strong",{children:"Permissions:"})," scan:read, scan:create"]})]}),e.jsxs(ts,{children:[e.jsx(m,{variant:"outline",onClick:()=>r(!1),children:"Cancel"}),e.jsx(m,{onClick:Le,disabled:b||!u.trim(),className:"gradient-primary",children:b?e.jsxs(e.Fragment,{children:[e.jsx(V,{className:"w-4 h-4 mr-2 animate-spin"}),"Creating..."]}):e.jsxs(e.Fragment,{children:[e.jsx(O,{className:"w-4 h-4 mr-2"}),"Create Key"]})})]})]})})]},"key"),x==="secrets"&&e.jsxs($.div,{initial:{opacity:0,y:8}
564,animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.15},children:[e.jsxs("h3",{className:"text-lg font-semibold flex items-center gap-2 mb-3",children:[e.jsx(he,{className:"w-5 h-5 text-primary"}),"Configure CI Secrets"]}),e.jsx("p",{className:"text-sm text-muted-foreground mb-4",children:"Add these environment variables / secrets in your CI/CD provider's settings."}),e.jsx("div",{className:"space-y-3",children:[{name:"PENTROVA_URL",value:Ee,description:"Your Pentrova instance URL"},{name:"PENTROVA_API_KEY",value:"pk_live_â¢â¢â¢â¢â¢â¢â¢â¢â¢â¢â¢â¢â¢â¢â¢â¢â¢",description:"The API key you just created"},{name:"STAGING_URL",value:"https://staging.yourapp.com",description:"Target URL to scan"},{name:"SPEC_URL",value:"https://staging.yourapp.com/openapi.json",description:"OpenAPI spec URL (for API scans only, optional)"}].map(d=>e.jsxs("div",{className:"flex items-center gap-3 p-3 rounded-lg border border-border bg-muted/20",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("code",{className:"text-sm font-mono font-semibold text-primary-text",children:d.name}),e.jsxs("span",{className:"text-xs text-muted-foreground",children:["â ",d.description]})]}),e.jsx("code",{className:"text-xs text-muted-foreground font-mono mt-1 block truncate",children:d.value})]}),e.jsx(m,{size:"sm",variant:"ghost",onClick:()=>{B(d.value).then(f=>{t(f?{variant:"success",title:`${d.name} copied`}:{title:"Failed to copy",variant:"destructive"})})},children:e.jsx(U,{className:"w-3.5 h-3.5"})})]},d.name))}),e.jsxs("div",{className:"mt-6 p-4 rounded-lg border border-border bg-muted/10",children:[e.jsxs("h4",{className:"text-sm font-semibold flex items-center gap-2 mb-3",children:[e.jsx(Ke,{className:"w-4 h-4 text-primary-text"}),"Test Connection"]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(se,{placeholder:"Paste your API key to test...",value:G,onChange:d=>{Te(d.target.value),k("idle")},className:"font-mono text-xs",type:"password"}),e.jsxs(m,{onClick:Ue,disabled:z||!G.trim(),size:"sm",variant:"outline",children:[z?e.jsx(V,{className:"w-4 h-4 animate-spin"}):e.jsx(cs,{className:"w-4 h-4"}),e.jsx("span",{className:"ml-1.5",children:z?"Testing...":"Test"})]})]}),e.jsxs(Z,{children:[ce==="success"&&e.jsxs($.div,{initial:{opacity:0,height:0},animate:{opacity:1,height:"auto"},exit:{opacity:0,height:0},className:"flex items-center gap-2 mt-3 text-sm text-success",children:[e.jsx(J,{className:"w-4 h-4"}),"Connection successful â API key is valid with scan:create permission."]}),ce==="fail"&&e.jsxs($.div,{initial:{opacity:0,height:0},animate:{opacity:1,height:"auto"},exit:{opacity:0,height:0},className:"flex items-center gap-2 mt-3 text-sm text-destructive",children:[e.jsx(qe,{className:"w-4 h-4"}),we]})]})]}),e.jsxs("div",{className:"mt-4 flex justify-between",children:[e.jsxs(m,{onClick:()=>g("key"),variant:"ghost",size:"sm",children:["Back to ",le("key")]}),e.jsxs(m,{onClick:()=>{Ce(),g("pipeline")},variant:"outline",size:"sm",children:["Next: Add Pipeline ",e.jsx(ee,{className:"w-4 h-4 ml-1"})]})]})]},"secrets"),x==="pipeline"&&e.jsxs($.div,{initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:-8},transition:{duration:.15},children:[e.jsxs("h3",{className:"text-lg font-semibold flex items-center gap-2 mb-2",children:[e.jsx(ve,{className:"w-5 h-5 text-primary"}),"Add Pipeline Configuration"]}),e.jsx("p",{className:"text-sm text-muted-foreground mb-1",children:"Choose your CI provider below and copy the template. Templates are pre-configured with your instance URL."}),e.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[e.jsxs(m,{onClick:()=>g("secrets"),variant:"ghost",size:"sm",children:["Back to ",le("secrets")]}),!oe&&e.jsxs(m,{onClick:ke,variant:"outline",size:"sm",className:"text-xs",children:["Mark as Done ",e.jsx(J,{className:"w-3.5 h-3.5 ml-1"})]})]})]},"pipeline")]})]})}function vs(){const{toast:c}=C(),[l,n]=a.useState("0"),[t,p]=a.useState("0"),[i,o]=a.useState(""),[h,x]=a.useState(""),[g,y]=a.useState(!1),r=a.useMemo(()=>{const u={};return u.critical=l!==""?parseInt(l)||0:null,u.high=t!==""?parseInt(t)||0:null,i!==""&&(u.medium=parseInt(i)||0),h!==""&&(u.low=parseInt(h)||0),JSON.stringify(u,null,2)},[l,t,i,h]),b=async()=>{await B(r)?(y(!0),c({variant:"success",title:"Quality gate JSON copied"}),setTimeout(()=>y(!1),2e3)):c({title:"Failed to copy â please select and copy manually",variant:"destructive"})},N=[{label:"Critical",value:l,set:n,hint:"0 = fail on any"},{label:"High",value:t,set:p,hint:"0 = fail on any"},{label:"Medium",value:i,set:o,hint:"Empty = ignore"},{label:"Low",value:h,set:x,hint:"Empty = ignore"}];return e.jsxs("div",{className:"glass-card p-6 rounded-xl",children:[e.jsxs("h2",{className:"text-xl font-semibold mb-2 flex items-center gap-2",children:[e.jsx(he,{className:"w-5 h-5 text-primary"}),"Quality Gate Configuration"]}),e.jsxs("p",{className:"text-sm text-muted-foreground mb-5",children:["Configure maximum allowed findings per severity. The scan will fail if any threshold is exceeded. Only ",e.jsx("strong",{children:"Confirmed"})," findings count toward the gate â ",e.jsx("strong",{children:"Potential (needs-review)"}),"findings never fail a build (consistent with the PDF report's risk score)."]}),e.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4",children:N.map(u=>e.jsxs("div",{className:"space-y-1.5",children:[e.jsx(te,{className:`text-sm font-semibold ${is(u.label).text}`,children:u.label}),e.jsx(se,{type:"number",min:"0",placeholder:"null (ignore)",value:u.value,onChange:A=>u.set(A.target.value),className:"font-mono text-sm"}),e.jsx("p",{className:"text-xs text-muted-foreground",children:u.hint})]},u.label))}),e.jsxs("div",{className:"mt-5",children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsxs("span",{className:"text-sm font-medium text-muted-foreground",children:["Generated ",e.jsx("code",{className:"text-xs bg-muted/50 px-1 rounded-sm",children:"fail_on"})," JSON"]}),e.jsxs(m,{size:"sm",variant:"ghost",onClick:b,children:[g?e.jsx(X,{className:"w-3.5 h-3.5 mr-1 text-success"}):e.jsx(U,{className:"w-3.5 h-3.5 mr-1"}),g?"Copied":"Copy"]})]}),e.jsx(F,{code:r,language:"json",showLineNumbers:!1,maxHeight:"120px",filename:"fail_on"})]})]})}function As(){const[c,l]=a.useState(null),n={POST:"bg-success/10 text-success-text border-success/30",GET:"bg-primary/10 text-primary-text border-primary/30"};return e.jsxs("div",{className:"glass-card p-6 rounded-xl",children:[e.jsxs("h2",{className:"text-xl font-semibold mb-5 flex items-center gap-2",children:[e.jsx(Pe,{className:"w-5 h-5 text-primary"}),"API Reference"]}),e.jsx("div",{className:"space-y-3",children:_s.map(t=>{const p=c===t.path;return e.jsxs("div",{className:"border border-border rounded-lg overflow-hidden transition-[color,background-color,border-color,box-shadow,transform,opacity]",children:[e.jsxs(m,{variant:"ghost",size:"lg","aria-expanded":p,onClick:()=>l(p?null:t.path),className:"w-full h-auto justify-start gap-3 p-4 hover:bg-muted/30 text-left",children:[e.jsx(R,{className:`text-xs font-bold px-2.5 py-0.5 border ${n[t.method]||""}`,children:t.method}),e.jsx("code",{className:"text-sm font-mono flex-1",children:t.path}),e.jsx("span",{className:"text-sm text-muted-foreground hidden sm:inline mr-2",children:t.title}),p?e.jsx(os,{className:"w-4 h-4 text-muted-foreground","aria-hidden":"true"}):e.jsx(ee,{className:"w-4 h-4 text-muted-foreground","aria-hidden":"true"})]}),e.jsx(Z,{children:p&&e.jsx($.div,{initial:{height:0,opacity:0}
564,animate:{height:"auto",opacity:1},exit:{height:0,opacity:0},transition:{duration:.2},className:"overflow-hidden",children:e.jsxs("div",{className:"px-4 pb-4 border-t border-border pt-4 space-y-4",children:[e.jsx("p",{className:"text-sm text-muted-foreground",children:t.description}),t.requestBody.length>0&&e.jsxs("div",{children:[e.jsx("h4",{className:"text-sm font-semibold mb-2",children:"Request Body"}),e.jsx("div",{className:"border border-border rounded-lg overflow-hidden",children:e.jsxs(xe,{scrollRegionLabel:"Request body parameters, scrollable",children:[e.jsx(ge,{className:"sr-only",children:"Request body parameters"}),e.jsx(fe,{children:e.jsxs(Y,{className:"bg-muted/50 hover:bg-muted/50",children:[e.jsx(S,{className:"h-auto px-3 py-2 font-semibold text-foreground",children:"Parameter"}),e.jsx(S,{className:"h-auto px-3 py-2 font-semibold text-foreground",children:"Type"}),e.jsx(S,{className:"h-auto px-3 py-2 font-semibold text-foreground hidden sm:table-cell",children:"Default"}),e.jsx(S,{className:"h-auto px-3 py-2 font-semibold text-foreground",children:"Description"})]})}),e.jsx(ye,{children:t.requestBody.map(i=>e.jsxs(Y,{className:"border-b-0 border-t border-border hover:bg-muted/20",children:[e.jsxs(v,{className:"px-3 py-2 font-mono font-medium",children:[i.name,i.required&&e.jsx("span",{className:"text-destructive ml-0.5",children:"*"}),e.jsxs("span",{className:"block sm:hidden text-xs font-normal text-muted-foreground",children:["default ",i.default||"â"]})]}),e.jsx(v,{className:"px-3 py-2 text-muted-foreground font-mono",children:i.type}),e.jsx(v,{className:"px-3 py-2 text-muted-foreground font-mono hidden sm:table-cell",children:i.default||"â"}),e.jsx(v,{className:"px-3 py-2 text-muted-foreground",children:i.description})]},i.name))})]})})]}),e.jsxs("div",{children:[e.jsx("h4",{className:"text-sm font-semibold mb-2",children:"Response Example"}),e.jsx(F,{code:t.responseExample,language:t.responseExample.trim().startsWith("{")||t.responseExample.trim().startsWith("[")?"json":"text",showLineNumbers:!1,maxHeight:"300px"})]}),t.errorCodes.length>0&&e.jsxs("div",{children:[e.jsx("h4",{className:"text-sm font-semibold mb-2",children:"Error Codes"}),e.jsx("div",{className:"space-y-1.5",children:t.errorCodes.map(i=>e.jsxs("div",{className:"flex items-start gap-2 text-xs",children:[e.jsx(R,{variant:"outline",className:"text-destructive border-destructive/30 font-mono shrink-0",children:i.code}),e.jsx("span",{className:"text-muted-foreground",children:i.description})]},i.code))})]})]})})})]},t.path)})})]})}function Ps(){const{toast:c}=C(),l=typeof window<"u"?window.location.origin:"https://your-instance.com",[n,t]=a.useState(null),p=[{id:"markdown",label:"Markdown",code:`[](${l}/settings/cicd)`},{id:"html",label:"HTML",code:`<a href="${l}/settings/cicd"><img src="https://img.shields.io/badge/security-Pentrova-blue?logo=shield" alt="Security: Pentrova" /></a>`}],i=async(o,h)=>{await B(o)?(t(h),c({variant:"success",title:"Badge snippet copied"}),setTimeout(()=>t(null),2e3)):c({title:"Failed to copy â please select and copy manually",variant:"destructive"})};return e.jsxs("div",{className:"glass-card p-6 rounded-xl",children:[e.jsxs("h2",{className:"text-xl font-semibold mb-2 flex items-center gap-2",children:[e.jsx(ps,{className:"w-5 h-5 text-primary"}),"Status Badge"]}),e.jsx("p",{className:"text-sm text-muted-foreground mb-4",children:"Add a security badge to your README to show your project uses automated Pentrova scanning."}),e.jsxs("div",{className:"flex items-center gap-3 mb-4 p-3 rounded-lg bg-muted/30 border border-border",children:[e.jsx("span",{className:"text-sm text-muted-foreground",children:"Preview:"}),e.jsx("img",{src:"https://img.shields.io/badge/security-Pentrova-blue?logo=shield",alt:"Security: Pentrova",width:112,height:20,className:"h-5"})]}),e.jsx("div",{className:"space-y-3",children:p.map(o=>e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between mb-1",children:[e.jsx("span",{className:"text-xs font-semibold text-muted-foreground uppercase tracking-wider",children:o.label}),e.jsxs(m,{size:"compact",variant:"ghost",onClick:()=>i(o.code,o.id),children:[n===o.id?e.jsx(X,{className:"w-3 h-3 mr-1 text-success"}):e.jsx(U,{className:"w-3 h-3 mr-1"}),n===o.id?"Copied":"Copy"]})]}),e.jsx("code",{className:"block text-xs bg-muted/50 p-2.5 rounded-lg font-mono break-all text-muted-foreground",children:o.code})]},o.id))})]})}function Ts(){const c=typeof window<"u"?window.location.origin:"https://your-instance.com",l=`# Web Scan (scan a website) 565curl -X POST "${c}/api/ci/scan" \\ 566 -H "X-API-Key: pk_live_your_api_key_here" \\ 567 -H "Content-Type: application/json" \\ 568 -d '{ 569 "target_url": "https://staging.example.com", 570 "scan_type": "web", 571 "fail_on": {"critical": 0, "high": 0}, 572 "wait": true, 573 "timeout": 3600 574 }' 575 576# API Scan (scan backend API endpoints via OpenAPI spec) 577curl -X POST "${c}/api/ci/scan" \\ 578 -H "X-API-Key: pk_live_your_api_key_here" \\ 579 -H "Content-Type: application/json" \\ 580 -d '{ 581 "target_url": "https://staging.example.com", 582 "scan_type": "api", 583 "spec_url": "https://staging.example.com/openapi.json", 584 "fail_on": {"critical": 0, "high": 0}, 585 "wait": true, 586 "timeout": 3600 587 }'`;return e.jsxs("div",{className:"glass-card p-6 rounded-xl",children:[e.jsxs("h2",{className:"text-xl font-semibold mb-3 flex items-center gap-2",children:[e.jsx(O,{className:"w-5 h-5 text-primary"}),"Authentication"]}),e.jsxs("p",{className:"text-sm text-muted-foreground mb-4",children:["All CI/CD endpoints require the ",e.jsx("code",{className:"text-xs bg-muted px-1.5 py-0.5 rounded-sm font-mono",children:"X-API-Key"})," header. Create a key with ",e.jsx("code",{className:"text-xs bg-muted px-1.5 py-0.5 rounded-sm font-mono",children:"scan:create"})," permission in Settings."]}),e.jsx(F,{code:l,language:"bash",filename:"authentication-example.sh"}),e.jsxs("div",{className:"mt-4 flex items-center gap-3",children:[e.jsx(m,{variant:"outline",size:"sm",asChild:!0,children:e.jsxs(ne,{to:"/settings",children:[e.jsx(O,{className:"w-3.5 h-3.5 mr-1.5"}),"Manage API Keys",e.jsx(ds,{className:"w-3.5 h-3.5 ml-1.5"})]})}),e.jsx("span",{className:"text-xs text-muted-foreground",children:"Keys are scoped to your tenant and respect RBAC permissions."})]})]})}function ws(){const{toast:c}
587=C(),[l,n]=a.useState([]),[t,p]=a.useState(0),[i,o]=a.useState(!0),[h,x]=a.useState(!1),[g,y]=a.useState(0),[r,b]=a.useState(""),N=10,u=a.useCallback(async()=>{o(!0),x(!1);try{const s=await ie.listScans({limit:N,offset:g*N,...r?{status:r}:{}});if(g>0&&s.scans.length===0&&s.total>0){y(j=>Math.max(0,j-1));return}n(s.scans),p(s.total)}catch{n([]),p(0),x(!0)}finally{o(!1)}},[g,r]);a.useEffect(()=>{u()},[u]);const A=Math.ceil(t/N),E=s=>{switch(s.toLowerCase()){case"completed":return"bg-success/10 text-success-text";case"running":case"pending":return"bg-primary/10 text-primary-text";case"failed":return"bg-destructive/10 text-destructive-text";default:return"bg-muted/50 text-muted-foreground"}},T=async(s,j)=>{try{const P=`/ci/scan/${s}/${j}`,_=await Ye.get(P,{responseType:"blob"}),I=j==="sarif"?"sarif":"xml",D=j==="sarif"?"application/sarif+json":"application/xml";Se(_.data,`pentrova-${s.slice(0,8)}.${I}`,D)}catch{c({title:`Failed to download ${j.toUpperCase()} report`,variant:"destructive"})}};return e.jsxs("div",{className:"glass-card p-6 rounded-xl",children:[e.jsxs("div",{className:"flex items-center justify-between mb-4",children:[e.jsxs("h2",{className:"text-xl font-semibold flex items-center gap-2",children:[e.jsx(Q,{className:"w-5 h-5 text-primary"}),"CI/CD Scan History"]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(je,{value:r,onValueChange:s=>{b(s==="all"?"":s),y(0)},children:[e.jsx(be,{className:"w-[140px] h-8 text-xs",children:e.jsx(Ne,{placeholder:"All statuses"})}),e.jsxs(_e,{children:[e.jsx(w,{value:"all",children:"All statuses"}),e.jsx(w,{value:"completed",children:"Completed"}),e.jsx(w,{value:"running",children:"Running"}),e.jsx(w,{value:"failed",children:"Failed"})]})]}),e.jsxs(R,{variant:"outline",className:"text-xs",children:[t," scan",t!==1?"s":""]})]})]}),i?e.jsx("div",{className:"flex items-center justify-center py-8",children:e.jsx(V,{className:"w-5 h-5 animate-spin text-muted-foreground"})}):h?e.jsx(Ve,{variant:"compact",title:"Couldn't load scan history",description:"We couldn't load your CI/CD scan history. This is a load error, not an empty history â please retry.",onRetry:()=>{u()},retryLabel:"Retry"}):l.length===0&&r?e.jsx(de,{variant:"compact",icon:e.jsx(ls,{className:"h-6 w-6 text-muted-foreground"}),title:`No CI/CD scans with status "${r}"`,description:"This filter excludes every scan in your history. Nothing failed to load.","data-testid":"cicd-scans-filtered-empty",action:e.jsx(m,{variant:"outline",size:"sm",onClick:()=>{b(""),y(0)},children:"Show all scans"})}):l.length===0?e.jsx(de,{variant:"compact",icon:e.jsx(Q,{className:"h-6 w-6 text-muted-foreground"}),title:"No CI/CD scans found",description:"Trigger a scan from your pipeline to see history here.","data-testid":"cicd-scans-empty"}):e.jsxs(e.Fragment,{children:[e.jsxs(xe,{scrollRegionLabel:"CI/CD scan history, scrollable",children:[e.jsx(ge,{className:"sr-only",children:"Scan history"}),e.jsx(fe,{children:e.jsxs(Y,{className:"hover:bg-transparent",children:[e.jsx(S,{className:"h-auto pb-2 px-0 pr-4 text-xs font-semibold uppercase",children:"Target"}),e.jsx(S,{className:"h-auto pb-2 px-0 pr-4 text-xs font-semibold uppercase hidden sm:table-cell",children:"Type"}),e.jsx(S,{className:"h-auto pb-2 px-0 pr-4 text-xs font-semibold uppercase",children:"Status"}),e.jsx(S,{className:"h-auto pb-2 px-0 pr-4 text-xs font-semibold uppercase hidden lg:table-cell",children:"Webhook"}),e.jsx(S,{className:"h-auto pb-2 px-0 pr-4 text-xs font-semibold uppercase hidden md:table-cell",children:"Created"}),e.jsx(S,{className:"h-auto pb-2 px-0 pr-4 text-xs font-semibold uppercase hidden md:table-cell",children:"Duration"}),e.jsx(S,{className:"h-auto pb-2 px-0 text-xs font-semibold uppercase",children:"Actions"})]})}),e.jsx(ye,{className:"divide-y divide-border",children:l.map(s=>{var j,P;return e.jsxs(Y,{className:"border-b-0 group",children:[e.jsxs(v,{className:"py-3 px-0 pr-4",children:[e.jsx("div",{className:"font-medium text-foreground truncate max-w-[200px]",title:s.target_url,children:s.target_url}),s.tags&&s.tags.length>0&&e.jsx("div",{className:"flex gap-1 mt-0.5",children:s.tags.slice(0,3).map(_=>e.jsx("span",{className:"text-xs px-1 py-px rounded-sm bg-muted/50 text-muted-foreground font-mono",children:_},_))}),e.jsx("span",{className:"block sm:hidden text-xs text-muted-foreground",children:((j=s.scan_type)==null?void 0:j.toUpperCase())||"WEB"}),e.jsxs("span",{className:"block md:hidden text-xs text-muted-foreground",children:[s.created_at?pe(s.created_at):"-",s.elapsed_seconds>0?` · ${Math.round(s.elapsed_seconds)}s`:""]})]}),e.jsx(v,{className:"py-3 px-0 pr-4 hidden sm:table-cell",children:e.jsx(R,{variant:"outline",className:"text-xs",children:((P=s.scan_type)==null?void 0:P.toUpperCase())||"WEB"})}),e.jsx(v,{className:"py-3 px-0 pr-4",children:e.jsx("span",{className:`inline-block px-2 py-0.5 rounded-sm text-xs font-semibold ${E(s.status)}`,children:ns(s.status)})}),e.jsx(v,{className:"py-3 px-0 pr-4 hidden lg:table-cell",children:s.callback_url?e.jsx("span",{className:`inline-block px-2 py-0.5 rounded-sm text-xs font-medium ${s.callback_status==="delivered"?"bg-success/10 text-success-text":s.callback_status==="failed"?"bg-destructive/10 text-destructive-text":s.callback_status==="pending"?"bg-primary/10 text-primary-text":(s.callback_status==="skipped","bg-muted/50 text-muted-foreground")}`,title:s.callback_url,children:s.callback_status==="delivered"?"â Delivered":s.callback_status==="failed"?"â Failed":s.callback_status==="pending"?"â³ Pending":s.callback_status||"â"}):e.jsx("span",{className:"text-xs text-muted-foreground",children:"â"})}),e.jsx(v,{className:"py-3 px-0 pr-4 text-xs text-muted-foreground whitespace-nowrap hidden md:table-cell",children:s.created_at?pe(s.created_at):"-"}),e.jsx(v,{className:"py-3 px-0 pr-4 text-xs text-muted-foreground whitespace-nowrap hidden md:table-cell",children:s.elapsed_seconds>0?`${Math.round(s.elapsed_seconds)}s`:"-"}),e.jsx(v,{className:"py-3 px-0",children:e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(m,{variant:"ghost",size:"compact",asChild:!0,children:e.jsxs(ne,{to:`/scans/${s.dashboard_id||s.scan_id}/summary`,children:[e.jsx(Ae,{className:"w-3 h-3 mr-1"}),"View"]}
587)}),e.jsxs(m,{variant:"ghost",size:"compact",onClick:()=>T(s.scan_id,"sarif"),children:[e.jsx(ae,{className:"w-3 h-3 mr-1"}),"SARIF"]}),e.jsxs(m,{variant:"ghost",size:"compact",onClick:()=>T(s.scan_id,"junit"),children:[e.jsx(ae,{className:"w-3 h-3 mr-1"}),"JUnit"]})]})})]},s.scan_id)})})]}),A>1&&e.jsxs("div",{className:"flex items-center justify-between mt-4 pt-3 border-t border-border",children:[e.jsxs("span",{className:"text-xs text-muted-foreground",children:["Page ",g+1," of ",A," (",t," total)"]}),e.jsxs("div",{className:"flex gap-1",children:[e.jsx(m,{variant:"outline",size:"sm",disabled:g===0,onClick:()=>y(s=>s-1),children:"Previous"}),e.jsx(m,{variant:"outline",size:"sm",disabled:g>=A-1,onClick:()=>y(s=>s+1),children:"Next"})]})]})]})]})}function et(){Xe("CI/CD Integration");const{toast:c}=C(),[l,n]=a.useState([]),[t,p]=a.useState(!0),[i,o]=a.useState(null),[h,x]=a.useState("github-actions"),g=a.useCallback(async()=>{p(!0);try{const r=await ue.list(!1);n(r)}catch{}finally{p(!1)}},[]);a.useEffect(()=>{g()},[g]);const y=async r=>{o(r);try{const b=await ie.downloadTemplate(r),N=as(b.headers["content-disposition"],`pentrova-${r}`);Se(b.data,N),c({variant:"success",title:"Template downloaded"})}catch{c({title:"Failed to download template",variant:"destructive"})}finally{o(null)}};return e.jsxs("div",{className:"p-4 sm:p-6 lg:p-8 space-y-6 max-w-6xl mx-auto",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"p-2.5 rounded-xl bg-primary/10",children:e.jsx(Q,{className:"w-6 h-6 text-primary-text"})}),e.jsxs("div",{children:[e.jsx("h1",{className:"text-2xl font-bold tracking-tight",children:"CI/CD Integration"}),e.jsx("p",{className:"text-muted-foreground text-sm",children:"Automate security scans in every pipeline â GitHub, GitLab, Jenkins, Azure DevOps & more"})]})]}),e.jsx(Ss,{apiKeys:l,onKeyCreated:g,keysLoading:t}),e.jsxs("div",{className:"glass-card p-6 rounded-xl",children:[e.jsxs("div",{className:"flex items-center justify-between mb-4",children:[e.jsxs("h2",{className:"text-xl font-semibold flex items-center gap-2",children:[e.jsx(ve,{className:"w-5 h-5 text-primary"}),"Pipeline Templates"]}),e.jsxs(R,{variant:"outline",className:"text-xs",children:[W.length," providers"]})]}),e.jsxs(Fe,{value:h,onValueChange:x,children:[e.jsx("div",{className:"overflow-x-auto -mx-1 px-1 pb-1",children:e.jsx(ze,{className:"mb-4 inline-flex min-w-max",children:W.map(r=>e.jsx(Me,{value:r.id,className:"text-xs sm:text-sm whitespace-nowrap",children:r.label},r.id))})}),W.map(r=>e.jsxs(Je,{value:r.id,children:[e.jsxs("div",{className:"flex items-center justify-between mb-3",children:[e.jsxs("span",{className:"text-sm text-muted-foreground",children:["Paste into ",e.jsx("code",{className:"text-xs bg-muted px-1.5 py-0.5 rounded-sm font-mono",children:r.filename})]}),e.jsxs(m,{size:"sm",variant:"outline",onClick:()=>y(r.id),disabled:i===r.id,children:[e.jsx(ae,{className:"w-3.5 h-3.5 mr-1.5"}),i===r.id?"Downloading...":"Download File"]})]}),e.jsx(F,{code:r.snippet,language:r.lang,filename:r.filename})]},r.id))]})]}),e.jsx(vs,{}),e.jsx(As,{}),e.jsx(ws,{}),e.jsx(Ps,{}),e.jsx(Ts,{})]})}export{et as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.