4
0

myspace.tmpl 27 KB

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