4
0

leet.tmpl 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Gitleaks Security Findings Report</title>
  7. <style>
  8. :root {
  9. --primary-color: #00ff00;
  10. --primary-color-rgb: 0, 255, 0;
  11. --primary-dark: #00cc00;
  12. --primary-light: #66ff66;
  13. --secondary-color: #111111;
  14. --surface-color: #000000;
  15. --border-color: #00ff00;
  16. --text-color: #00ff00;
  17. --text-light: #00cc00;
  18. --text-inverse: #000000;
  19. --error-color: #ff0000;
  20. --warning-color: #ffff00;
  21. --success-color: #00ff00;
  22. --highlight-color: rgba(0, 255, 0, 0.2);
  23. --highlight-secret: rgba(255, 0, 0, 0.2);
  24. --shadow: 0 0 10px rgba(0, 255, 0, 0.8);
  25. --radius: 0;
  26. --font-mono: 'Courier New', monospace;
  27. --font-main: 'Courier New', monospace;
  28. }
  29. * {
  30. margin: 0;
  31. padding: 0;
  32. box-sizing: border-box;
  33. }
  34. html, body {
  35. height: 100%;
  36. font-family: var(--font-main);
  37. font-size: 16px;
  38. line-height: 1.5;
  39. color: var(--text-color);
  40. background-color: #000000;
  41. background-image: url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h40v40H0V0zm20 10a10 10 0 1 1 0 20 10 10 0 0 1 0-20zm0 5a5 5 0 1 0 0 10 5 5 0 0 0 0-10z' fill='%23003300' fill-opacity='0.5' fill-rule='evenodd'/%3E%3C/svg%3E");
  42. }
  43. .app-container {
  44. display: flex;
  45. flex-direction: column;
  46. height: 100%;
  47. max-width: 100%;
  48. overflow: hidden;
  49. border: 1px solid #00ff00;
  50. box-shadow: 0 0 20px rgba(0, 255, 0, 0.5);
  51. position: relative;
  52. }
  53. .app-container::before {
  54. content: "";
  55. position: absolute;
  56. top: 0;
  57. left: 0;
  58. right: 0;
  59. height: 5px;
  60. background: repeating-linear-gradient(
  61. 90deg,
  62. #00ff00,
  63. #00ff00 10px,
  64. #000000 10px,
  65. #000000 20px
  66. );
  67. z-index: 10;
  68. }
  69. .app-container::after {
  70. content: "";
  71. position: absolute;
  72. bottom: 0;
  73. left: 0;
  74. right: 0;
  75. height: 5px;
  76. background: repeating-linear-gradient(
  77. 90deg,
  78. #00ff00,
  79. #00ff00 10px,
  80. #000000 10px,
  81. #000000 20px
  82. );
  83. z-index: 10;
  84. }
  85. .app-header {
  86. background-color: #111111;
  87. border-bottom: 1px solid #00ff00;
  88. height: auto;
  89. padding: 10px 15px;
  90. position: relative;
  91. display: flex;
  92. align-items: center;
  93. justify-content: space-between;
  94. flex-shrink: 0;
  95. box-shadow: var(--shadow);
  96. z-index: 10;
  97. }
  98. .logo h1 {
  99. font-size: 20px;
  100. font-weight: bold;
  101. text-transform: uppercase;
  102. letter-spacing: 2px;
  103. margin: 0;
  104. padding: 0;
  105. text-shadow: 0 0 5px #00ff00;
  106. }
  107. .logo h1::before {
  108. content: "[ ";
  109. }
  110. .logo h1::after {
  111. content: " ]_";
  112. animation: blink 1s step-end infinite;
  113. }
  114. @keyframes blink {
  115. 0%, 100% { opacity: 1; }
  116. 50% { opacity: 0; }
  117. }
  118. .btn {
  119. display: inline-flex;
  120. align-items: center;
  121. justify-content: center;
  122. background-color: #000000;
  123. color: #00ff00;
  124. border: 1px solid #00ff00;
  125. font-family: 'Courier New', monospace;
  126. text-transform: lowercase;
  127. letter-spacing: 1px;
  128. transition: all 0.2s;
  129. font-size: 0.875rem;
  130. font-weight: 500;
  131. cursor: pointer;
  132. padding: 0.5rem 1rem;
  133. }
  134. .btn::before {
  135. content: "[ ";
  136. }
  137. .btn::after {
  138. content: " ]";
  139. }
  140. .btn:hover {
  141. background-color: #00ff00;
  142. color: #000000;
  143. box-shadow: 0 0 10px #00ff00;
  144. }
  145. .btn-primary {
  146. background-color: #000000;
  147. color: #00ff00;
  148. }
  149. .btn-primary:hover {
  150. background-color: #00ff00;
  151. color: #000000;
  152. }
  153. .btn-sm {
  154. padding: 0.25rem 0.5rem;
  155. font-size: 0.75rem;
  156. }
  157. .app-main {
  158. flex: 1;
  159. overflow: auto;
  160. padding: 1.5rem;
  161. position: relative;
  162. }
  163. .report-info {
  164. background-color: #111111;
  165. border: 1px solid #00ff00;
  166. padding: 15px;
  167. margin-bottom: 20px;
  168. position: relative;
  169. box-shadow: 0 0 10px rgba(0, 255, 0, 0.3);
  170. }
  171. .report-info h2 {
  172. color: #00ff00;
  173. text-transform: uppercase;
  174. letter-spacing: 1px;
  175. margin-top: 0;
  176. text-shadow: 0 0 5px #00ff00;
  177. }
  178. .report-info h2::before {
  179. content: "//";
  180. margin-right: 10px;
  181. }
  182. .report-date {
  183. color: #00cc00;
  184. font-style: italic;
  185. font-size: 0.875rem;
  186. }
  187. .report-stats {
  188. display: flex;
  189. gap: 1.5rem;
  190. margin-top: 1rem;
  191. flex-wrap: wrap;
  192. }
  193. .stat-item {
  194. display: flex;
  195. flex-direction: column;
  196. gap: 0.25rem;
  197. }
  198. .stat-value {
  199. color: #00ff00;
  200. font-weight: bold;
  201. text-shadow: 0 0 5px #00ff00;
  202. font-size: 1.25rem;
  203. }
  204. .stat-label {
  205. text-transform: uppercase;
  206. font-size: 10px;
  207. letter-spacing: 1px;
  208. color: var(--text-light);
  209. }
  210. .table-wrapper {
  211. overflow-x: auto;
  212. border: 1px solid #00ff00;
  213. box-shadow: 0 0 10px rgba(0, 255, 0, 0.3);
  214. }
  215. .findings-table {
  216. width: 100%;
  217. border-collapse: collapse;
  218. font-size: 0.875rem;
  219. }
  220. .findings-table th {
  221. background-color: #111111;
  222. color: #00ff00;
  223. font-weight: bold;
  224. text-transform: uppercase;
  225. letter-spacing: 1px;
  226. padding: 10px;
  227. border: 1px solid #004400;
  228. position: sticky;
  229. top: 0;
  230. z-index: 1;
  231. text-align: left;
  232. }
  233. .findings-table td {
  234. background-color: #000000;
  235. border: 1px solid #004400;
  236. padding: 10px;
  237. font-size: 13px;
  238. vertical-align: top;
  239. max-width: 300px; /* Limit width of all cells */
  240. overflow-wrap: break-word;
  241. }
  242. .findings-table tr:hover td {
  243. background-color: #001100;
  244. }
  245. .findings-table th:nth-child(1) { width: 12%; } /* Rule */
  246. .findings-table th:nth-child(2) { width: 20%; } /* File */
  247. .findings-table th:nth-child(3) { width: 25%; } /* Description */
  248. .findings-table th:nth-child(4) { width: 20%; } /* Secret */
  249. .findings-table th:nth-child(5) { width: 23%; } /* Metadata */
  250. .filters {
  251. background-color: #111111;
  252. padding: 15px;
  253. border: 1px solid #00ff00;
  254. margin-bottom: 20px;
  255. box-shadow: 0 0 10px rgba(0, 255, 0, 0.3);
  256. display: flex;
  257. gap: 1rem;
  258. flex-wrap: wrap;
  259. }
  260. .filter-group {
  261. display: flex;
  262. align-items: center;
  263. gap: 0.5rem;
  264. }
  265. .filter-label {
  266. color: #00ff00;
  267. font-weight: bold;
  268. text-transform: uppercase;
  269. font-size: 12px;
  270. letter-spacing: 1px;
  271. }
  272. .filter-input {
  273. background-color: #000000;
  274. border: 1px solid #00ff00;
  275. color: #00ff00;
  276. padding: 8px 10px;
  277. font-family: 'Courier New', monospace;
  278. font-size: 0.875rem;
  279. }
  280. .filter-input:focus {
  281. outline: none;
  282. box-shadow: 0 0 10px #00ff00;
  283. }
  284. .secret-container {
  285. position: relative;
  286. }
  287. .match-toggle {
  288. display: inline-block;
  289. margin-top: 0.5rem;
  290. background-color: #000000;
  291. color: #00ff00;
  292. border: 1px solid #00ff00;
  293. font-family: 'Courier New', monospace;
  294. text-transform: lowercase;
  295. font-size: 11px;
  296. letter-spacing: 1px;
  297. transition: all 0.2s;
  298. cursor: pointer;
  299. padding: 0.25rem 0.5rem;
  300. }
  301. .match-toggle:hover {
  302. background-color: #00ff00;
  303. color: #000000;
  304. }
  305. .hidden {
  306. display: none !important;
  307. }
  308. .secret-match {
  309. font-family: var(--font-mono);
  310. font-size: 0.875rem;
  311. word-break: break-all;
  312. white-space: pre-wrap;
  313. display: block;
  314. background-color: #110000;
  315. border: 1px solid #ff0000;
  316. color: #ff0000;
  317. font-weight: bold;
  318. padding: 8px;
  319. overflow-x: auto;
  320. }
  321. .tag-list {
  322. display: flex;
  323. gap: 0.25rem;
  324. flex-wrap: wrap;
  325. }
  326. .tag {
  327. display: inline-block;
  328. background-color: #001100;
  329. color: #00ff00;
  330. border: 1px solid #00ff00;
  331. text-transform: lowercase;
  332. font-size: 10px;
  333. letter-spacing: 1px;
  334. padding: 2px 5px;
  335. }
  336. .meta-row {
  337. display: grid;
  338. grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  339. gap: 0.5rem;
  340. margin-top: 0.5rem;
  341. font-size: 0.75rem;
  342. color: var(--text-light);
  343. }
  344. .meta-item {
  345. display: flex;
  346. align-items: center;
  347. gap: 0.25rem;
  348. }
  349. .meta-label {
  350. font-weight: 500;
  351. }
  352. .meta-value {
  353. font-family: var(--font-mono);
  354. word-break: break-all;
  355. color: #00ffff;
  356. }
  357. .description-toggle {
  358. cursor: pointer;
  359. color: #00ff00;
  360. font-size: 0.875rem;
  361. margin-left: 0.5rem;
  362. display: inline-flex;
  363. align-items: center;
  364. justify-content: center;
  365. width: 20px;
  366. height: 20px;
  367. background-color: #000000;
  368. border: 1px solid #00ff00;
  369. vertical-align: middle;
  370. }
  371. .description-toggle:hover {
  372. background-color: #00ff00;
  373. color: #000000;
  374. }
  375. .description-expanded {
  376. white-space: normal;
  377. }
  378. .description-collapsed {
  379. white-space: nowrap;
  380. overflow: hidden;
  381. text-overflow: ellipsis;
  382. max-width: 250px;
  383. display: inline-block;
  384. }
  385. .commit-link {
  386. color: #00ffff;
  387. text-decoration: underline;
  388. }
  389. .commit-link:hover {
  390. text-decoration: none;
  391. text-shadow: 0 0 5px #00ffff;
  392. }
  393. /* File path styling */
  394. .file-path-container {
  395. max-width: 100%;
  396. }
  397. .file-path {
  398. display: inline-block;
  399. max-width: 100%;
  400. word-wrap: break-word;
  401. word-break: break-all;
  402. }
  403. .match-content {
  404. font-family: var(--font-mono);
  405. font-size: 0.875rem;
  406. word-break: break-all;
  407. white-space: pre-wrap;
  408. display: block;
  409. background-color: #001100;
  410. border: 1px solid #00ff00;
  411. padding: 8px;
  412. color: #00ff00;
  413. margin-top: 0.5rem;
  414. max-height: 300px;
  415. overflow-y: auto;
  416. }
  417. .app-footer {
  418. background-color: #111111;
  419. color: #00ff00;
  420. border-top: 1px solid #00ff00;
  421. padding: 10px 15px;
  422. font-size: 12px;
  423. display: flex;
  424. align-items: center;
  425. justify-content: space-between;
  426. flex-shrink: 0;
  427. }
  428. /* Random binary background */
  429. .app-main::before {
  430. content: "";
  431. position: fixed;
  432. top: 0;
  433. left: 0;
  434. right: 0;
  435. bottom: 0;
  436. background-color: rgba(0, 0, 0, 0.9);
  437. background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200' viewBox='0 0 200 200'%3E%3Cg fill='%23003300' fill-opacity='0.1'%3E%3Ctext x='0' y='10' font-family='monospace' font-size='10'%3E01001010111001010101%3C/text%3E%3Ctext x='0' y='20' font-family='monospace' font-size='10'%3E10010111001010101010%3C/text%3E%3Ctext x='0' y='30' font-family='monospace' font-size='10'%3E01011100101010101001%3C/text%3E%3Ctext x='0' y='40' font-family='monospace' font-size='10'%3E10110010101010100101%3C/text%3E%3Ctext x='0' y='50' font-family='monospace' font-size='10'%3E01100101010101001011%3C/text%3E%3Ctext x='0' y='60' font-family='monospace' font-size='10'%3E11001010101010010110%3C/text%3E%3Ctext x='0' y='70' font-family='monospace' font-size='10'%3E10010101010100101100%3C/text%3E%3Ctext x='0' y='80' font-family='monospace' font-size='10'%3E00101010101001011001%3C/text%3E%3Ctext x='0' y='90' font-family='monospace' font-size='10'%3E01010101010010110010%3C/text%3E%3Ctext x='0' y='100' font-family='monospace' font-size='10'%3E10101010100101100101%3C/text%3E%3Ctext x='0' y='110' font-family='monospace' font-size='10'%3E01010101001011001010%3C/text%3E%3Ctext x='0' y='120' font-family='monospace' font-size='10'%3E10101010010110010101%3C/text%3E%3Ctext x='0' y='130' font-family='monospace' font-size='10'%3E01010100101100101010%3C/text%3E%3Ctext x='0' y='140' font-family='monospace' font-size='10'%3E10101001011001010101%3C/text%3E%3Ctext x='0' y='150' font-family='monospace' font-size='10'%3E01010010110010101010%3C/text%3E%3Ctext x='0' y='160' font-family='monospace' font-size='10'%3E10100101100101010101%3C/text%3E%3Ctext x='0' y='170' font-family='monospace' font-size='10'%3E01001011001010101010%3C/text%3E%3Ctext x='0' y='180' font-family='monospace' font-size='10'%3E10010110010101010101%3C/text%3E%3Ctext x='0' y='190' font-family='monospace' font-size='10'%3E00101100101010101010%3C/text%3E%3Ctext x='0' y='200' font-family='monospace' font-size='10'%3E01011001010101010101%3C/text%3E%3C/g%3E%3C/svg%3E");
  438. pointer-events: none;
  439. opacity: 0.3;
  440. z-index: -1;
  441. }
  442. @media (max-width: 768px) {
  443. .app-header {
  444. padding: 8px 10px;
  445. }
  446. .app-main {
  447. padding: 1rem;
  448. }
  449. .report-stats {
  450. flex-direction: column;
  451. gap: 0.75rem;
  452. }
  453. .filters {
  454. flex-direction: column;
  455. gap: 0.75rem;
  456. }
  457. .app-footer {
  458. flex-direction: column;
  459. height: auto;
  460. padding: 0.75rem 1rem;
  461. gap: 0.5rem;
  462. justify-content: center;
  463. text-align: center;
  464. }
  465. }
  466. </style>
  467. </head>
  468. <body>
  469. <div class="app-container">
  470. <header class="app-header">
  471. <div class="logo">
  472. <h1>Gitleaks Security Findings</h1>
  473. </div>
  474. </header>
  475. <main class="app-main">
  476. <div class="report-info">
  477. <h2>Security Scan Report</h2>
  478. <p class="report-date">Generated on {{now | date "Jan 02, 2006 15:04:05 MST"}}</p>
  479. <div class="report-stats">
  480. <div class="stat-item">
  481. <span class="stat-value">{{len .}}</span>
  482. <span class="stat-label">Total Findings</span>
  483. </div>
  484. <div class="stat-item">
  485. <span class="stat-value" id="filesCount">-</span>
  486. <span class="stat-label">Files Affected</span>
  487. </div>
  488. <div class="stat-item">
  489. <span class="stat-value" id="rulesCount">-</span>
  490. <span class="stat-label">Unique Rules Triggered</span>
  491. </div>
  492. <div class="stat-item" id="scanModeContainer">
  493. <span class="stat-value" id="scanMode">-</span>
  494. <span class="stat-label">Scan Mode</span>
  495. </div>
  496. </div>
  497. </div>
  498. <div class="filters">
  499. <div class="filter-group">
  500. <label class="filter-label" for="filterRule">Filter by Rule:</label>
  501. <select class="filter-input" id="filterRule">
  502. <option value="all">All Rules</option>
  503. <!-- Rule options will be populated by JavaScript -->
  504. </select>
  505. </div>
  506. <div class="filter-group">
  507. <label class="filter-label" for="filterFile">Filter by File:</label>
  508. <input type="text" class="filter-input" id="filterFile" placeholder="Enter filename...">
  509. </div>
  510. <div class="filter-group">
  511. <button class="btn btn-primary btn-sm" id="resetFilters">Reset Filters</button>
  512. </div>
  513. </div>
  514. <div class="table-wrapper">
  515. <table class="findings-table" id="findingsTable">
  516. <thead>
  517. <tr>
  518. <th>Rule</th>
  519. <th>File</th>
  520. <th>Description</th>
  521. <th>Secret</th>
  522. <th>Metadata</th>
  523. </tr>
  524. </thead>
  525. <tbody>
  526. {{- range . }}
  527. <tr data-rule="{{.RuleID}}" data-file="{{.File}}">
  528. <td>{{.RuleID}}</td>
  529. <td>
  530. <div class="file-path-container">
  531. <span class="file-path" title="{{.File}}">{{.File}}</span>
  532. </div>
  533. <div class="tag-list">
  534. {{- range .Tags }}
  535. <span class="tag">{{.}}</span>
  536. {{- end}}
  537. </div>
  538. <div class="meta-row">
  539. <div class="meta-item">
  540. <span class="meta-label">Line:</span>
  541. <span class="meta-value">{{.StartLine}}</span>
  542. </div>
  543. </div>
  544. </td>
  545. <td>
  546. <span class="description-text">{{.Description}}</span>
  547. <span class="description-toggle" title="Expand/Collapse">↕</span>
  548. </td>
  549. <td>
  550. <div class="secret-container" data-secret="{{.Secret}}" data-match="{{.Match}}">
  551. <div class="secret-match">{{.Secret}}</div>
  552. <button type="button" class="match-toggle" title="Show/Hide Full Match Context">Show Context</button>
  553. <div class="match-content hidden" data-raw-match="{{.Match}}">{{.Match}}</div>
  554. </div>
  555. </td>
  556. <td>
  557. <div class="meta-row">
  558. <div class="meta-item">
  559. <span class="meta-label">Entropy:</span>
  560. <span class="meta-value">{{printf "%.2f" .Entropy}}</span>
  561. </div>
  562. {{- if .Commit}}
  563. <div class="meta-item commit-info">
  564. <span class="meta-label">Commit:</span>
  565. <span class="meta-value">{{if gt (len .Commit) 7}}{{printf "%.7s" .Commit}}{{else}}{{.Commit}}{{end}}</span>
  566. </div>
  567. {{- if .Author}}
  568. <div class="meta-item commit-info">
  569. <span class="meta-label">Author:</span>
  570. <span class="meta-value">{{.Author}}</span>
  571. </div>
  572. {{- end}}
  573. {{- if .Date}}
  574. <div class="meta-item commit-info">
  575. <span class="meta-label">Date:</span>
  576. <span class="meta-value">{{.Date}}</span>
  577. </div>
  578. {{- end}}
  579. {{- if .Link}}
  580. <div class="meta-item commit-info">
  581. <span class="meta-label">Link:</span>
  582. <span class="meta-value"><a href="{{.Link}}" target="_blank" class="commit-link">View Commit</a></span>
  583. </div>
  584. {{- end}}
  585. {{- else}}
  586. {{- if .Author}}
  587. <div class="meta-item">
  588. <span class="meta-label">Author:</span>
  589. <span class="meta-value">{{.Author}}</span>
  590. </div>
  591. {{- end}}
  592. {{- end}}
  593. </div>
  594. {{- if not .Match}}
  595. <div class="match-content" data-raw-match="">-</div>
  596. {{- end}}
  597. </td>
  598. </tr>
  599. {{- end }}
  600. </tbody>
  601. </table>
  602. </div>
  603. </main>
  604. <footer class="app-footer">
  605. <div>Generated by Gitleaks</div>
  606. <div>Total Findings: <strong>{{len .}}</strong></div>
  607. </footer>
  608. </div>
  609. <script>
  610. // Process data to collect unique files and rules
  611. function processData() {
  612. const rows = document.querySelectorAll('#findingsTable tbody tr');
  613. const uniqueRules = new Set();
  614. const uniqueFiles = new Set();
  615. let isGitMode = false;
  616. if (rows.length > 0) {
  617. // Check if first finding has commit data to determine mode
  618. const firstRow = rows[0];
  619. const commitCells = firstRow.querySelectorAll('.commit-info');
  620. isGitMode = commitCells.length > 0 && commitCells[0].textContent.trim() !== '';
  621. }
  622. // Set scan mode
  623. document.getElementById('scanMode').textContent = isGitMode ? 'Git' : 'Directory';
  624. // Adjust UI based on mode
  625. if (isGitMode) {
  626. // Ensure commit info columns are visible for git mode
  627. document.querySelectorAll('.commit-info').forEach(el => {
  628. el.style.display = 'block';
  629. });
  630. } else {
  631. // Hide commit-specific UI elements for directory mode
  632. document.querySelectorAll('.commit-info').forEach(el => {
  633. el.style.display = 'none';
  634. });
  635. }
  636. rows.forEach(row => {
  637. uniqueRules.add(row.dataset.rule);
  638. uniqueFiles.add(row.dataset.file);
  639. });
  640. // Update stats
  641. document.getElementById('filesCount').textContent = uniqueFiles.size;
  642. document.getElementById('rulesCount').textContent = uniqueRules.size;
  643. // Populate rule filter dropdown
  644. const ruleFilter = document.getElementById('filterRule');
  645. const sortedRules = Array.from(uniqueRules).sort();
  646. sortedRules.forEach(rule => {
  647. const option = document.createElement('option');
  648. option.value = rule;
  649. option.textContent = rule;
  650. ruleFilter.appendChild(option);
  651. });
  652. }
  653. // Hide toggle button if match is same as secret
  654. function hideRedundantToggleButtons() {
  655. document.querySelectorAll('.secret-container').forEach(container => {
  656. const secret = container.getAttribute('data-secret');
  657. const match = container.getAttribute('data-match');
  658. const toggleButton = container.querySelector('.match-toggle');
  659. // If secret and match are the same, or if match is empty, hide the toggle button
  660. if ((secret && match && secret.trim() === match.trim()) || !match) {
  661. if (toggleButton) {
  662. toggleButton.style.display = 'none';
  663. }
  664. }
  665. });
  666. }
  667. // Setup toggle buttons
  668. function setupToggleButtons() {
  669. document.querySelectorAll('.match-toggle').forEach(btn => {
  670. btn.addEventListener('click', function() {
  671. const matchContent = this.nextElementSibling;
  672. if (matchContent.classList.contains('hidden')) {
  673. matchContent.classList.remove('hidden');
  674. this.textContent = 'Hide Context';
  675. } else {
  676. matchContent.classList.add('hidden');
  677. this.textContent = 'Show Context';
  678. }
  679. });
  680. });
  681. // Setup description toggle
  682. document.querySelectorAll('.description-text').forEach(descriptionText => {
  683. const toggleBtn = descriptionText.nextElementSibling;
  684. if (!toggleBtn || !toggleBtn.classList.contains('description-toggle')) return;
  685. // Initial state: collapsed
  686. descriptionText.classList.add('description-collapsed');
  687. toggleBtn.addEventListener('click', () => {
  688. if (descriptionText.classList.contains('description-collapsed')) {
  689. descriptionText.classList.remove('description-collapsed');
  690. descriptionText.classList.add('description-expanded');
  691. toggleBtn.textContent = '↑';
  692. } else {
  693. descriptionText.classList.remove('description-expanded');
  694. descriptionText.classList.add('description-collapsed');
  695. toggleBtn.textContent = '↕';
  696. }
  697. });
  698. });
  699. }
  700. // Type effect for headers (optional)
  701. function typeEffect() {
  702. const headers = document.querySelectorAll('h2');
  703. headers.forEach(header => {
  704. const text = header.textContent;
  705. header.textContent = '';
  706. let i = 0;
  707. const interval = setInterval(() => {
  708. if (i < text.length) {
  709. header.textContent += text.charAt(i);
  710. i++;
  711. } else {
  712. clearInterval(interval);
  713. }
  714. }, 50);
  715. });
  716. }
  717. // Filter functionality
  718. function applyFilters() {
  719. const ruleFilter = document.getElementById('filterRule').value;
  720. const fileFilter = document.getElementById('filterFile').value.toLowerCase();
  721. const rows = document.querySelectorAll('#findingsTable tbody tr');
  722. rows.forEach(row => {
  723. const ruleMatch = ruleFilter === 'all' || row.dataset.rule === ruleFilter;
  724. const fileMatch = fileFilter === '' || row.dataset.file.toLowerCase().includes(fileFilter);
  725. if (ruleMatch && fileMatch) {
  726. row.style.display = '';
  727. } else {
  728. row.style.display = 'none';
  729. }
  730. });
  731. // Update visible count
  732. const visibleFindings = document.querySelectorAll('#findingsTable tbody tr:not([style*="display: none"])').length;
  733. document.querySelector('.app-footer strong').textContent = visibleFindings;
  734. }
  735. document.getElementById('filterRule').addEventListener('change', applyFilters);
  736. document.getElementById('filterFile').addEventListener('input', applyFilters);
  737. document.getElementById('resetFilters').addEventListener('click', function() {
  738. document.getElementById('filterRule').value = 'all';
  739. document.getElementById('filterFile').value = '';
  740. applyFilters();
  741. });
  742. // Initialize
  743. document.addEventListener('DOMContentLoaded', function() {
  744. processData();
  745. hideRedundantToggleButtons(); // Hide toggle buttons for matching secrets
  746. setupToggleButtons();
  747. typeEffect(); // Add the typing effect
  748. });
  749. </script>
  750. </body>
  751. </html>