PageSourceSearch

https://app.pentrova.ai/assets/CICDIntegration-LCvodFNX.js

js pentrova.ai collected 2026-10-05 14:00:23 UTC 62,902 bytes, 587 lines download raw bytes

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:`[![Security: Pentrova](https://img.shields.io/badge/security-Pentrova-blue?logo=shield)](${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.