Skip to content

Code donation: A different HTML output that would use the JSON report #264

@paul-hammant

Description

@paul-hammant

Report as you'd initially see it

image

Report after clicking to expand the summary

image

HTML + JS + DataTables.js:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Report: GoTestWAF testing Web Application Firewall</title>
    <link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css">

    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
        h2 { margin-top: 40px; }

        .tooltip {
            position: relative;
            display: inline-block;
            cursor: pointer;
        }

        .tooltip .tooltiptext {
            visibility: hidden;
            width: 600px;
            background-color: #f9f9f9;
            color: #333;
            text-align: left;
            border-radius: 6px;
            padding: 10px;
            position: absolute;
            z-index: 1;
            bottom: 125%; /* Position above the text */
            left: 50%;
            margin-left: -150px;
            opacity: 0;
            transition: opacity 0.3s;
            box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.1);
        }

        .tooltip:hover .tooltiptext {
            visibility: visible;
            opacity: 1;
        }
    </style>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
</head>
<body>
    <h1>Report: GoTestWAF testing Web Application Firewall</h1>
    <div id="summary"></div>
    <div id="tables"></div>

    <script>
        function toggleSummary() {
            const summaryContent = document.getElementById('summary-content');
            const toggleButton = document.getElementById('toggle-summary');
            if (summaryContent.style.display === 'none') {
                summaryContent.style.display = 'block';
                toggleButton.innerHTML = '[V] Hide Summary';
            } else {
                summaryContent.style.display = 'none';
                toggleButton.innerHTML = '[&gt;] Show Summary';
            }
        }
        function escapeHtml(unsafe) {
            return unsafe
                .replace(/&/g, "&amp;")
                .replace(/</g, "&lt;")
                .replace(/>/g, "&gt;")
                .replace(/"/g, "&quot;")
                .replace(/'/g, "&#039;");
        }
        function formatDate(dateString) {
            const date = new Date(dateString);
            const year = date.getFullYear();
            const month = String(date.getMonth() + 1).padStart(2, '0');
            const day = String(date.getDate()).padStart(2, '0');
            const hours = String(date.getHours()).padStart(2, '0');
            const minutes = String(date.getMinutes()).padStart(2, '0');
            const seconds = String(date.getSeconds()).padStart(2, '0');
            return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
        }

        function generateSummaryTables(summary) {
            const descriptions = {
                "true_positive_tests": "Malicious requests that were correctly identified and blocked by the WAF, preventing them from reaching the application.",
                "true_negative_tests": "Legitimate requests that were correctly allowed through by the WAF without being flagged or blocked."
            };
            let html = '';
            for (const [key, value] of Object.entries(summary)) {
                const description = descriptions[key] || key.replace(/_/g, ' ');
                html += `<h3>${description}</h3>`;
                html += '<table><thead><tr>';
                for (const header in value) {
                    if (header !== 'test_sets') {
                        html += `<th>${header.replace(/_/g, ' ')}</th>`;
                    }
                }
                html += '</tr></thead><tbody><tr>';
                for (const cell in value) {
                    if (cell !== 'test_sets') {
                        const cellValue = value[cell];
                        if (typeof cellValue === 'object') {
                            html += '<td><table><tbody>';
                            for (const [subKey, subValue] of Object.entries(cellValue)) {
                                html += `<tr><th>${subKey.replace(/_/g, ' ')}</th><td>${subValue}</td></tr>`;
                            }
                            html += '</tbody></table></td>';
                        } else {
                            html += `<td>${cellValue}</td>`;
                        }
                    }
                }
                html += '</tr></tbody></table>';

                if (value.test_sets) {
                    const tableId = `table-${key.replace(/\s+/g, '-')}-test-sets`;
                    html += `<table id="${tableId}" class="display"><thead><tr><th>Set</th><th>Test</th><th>Percentage</th><th>Sent</th><th>Blocked</th><th>Bypassed</th><th>Unresolved</th><th>Failed</th></tr></thead><tbody>`;
                    for (const [set, tests] of Object.entries(summary[key].test_sets)) {
                        for (const [test, results] of Object.entries(tests)) {
                            html += `<tr><td>${set}</td><td>${test}</td>`;
                            html += `<td>${results.percentage !== undefined ? results.percentage : 'N/A'}</td>`;
                            html += `<td>${results.sent !== undefined ? results.sent : 'N/A'}</td>`;
                            html += `<td>${results.blocked !== undefined ? results.blocked : 'N/A'}</td>`;
                            html += `<td>${results.bypassed !== undefined ? results.bypassed : 'N/A'}</td>`;
                            html += `<td>${results.unresolved !== undefined ? results.unresolved : 'N/A'}</td>`;
                            html += `<td>${results.failed !== undefined ? results.failed : 'N/A'}</td></tr>`;
                        }
                    }
                    html += '</tbody></table>';
                }
            }
            return html;
        }

        fetch('output.json')
            .then(response => response.json())
            .then(data => {
                const summaryDiv = document.getElementById('summary');
                const tablesDiv = document.getElementById('tables');

                // Display summary
                const summaryHtml = `
                    <p><strong>Date:</strong> ${formatDate(data.date)}</p>
                    <p><strong>Project Name:</strong> ${data.project_name}</p>
                    <p><strong>URL:</strong> ${data.url}</p>
                    <p><strong>Score:</strong> ${data.score}</p>
                        <div id="summary-section">
                            <button id="toggle-summary" onclick="toggleSummary()">[&gt;] Show Summary</button>
                            <div id="summary-content" style="display: none;">
                                ${generateSummaryTables(data.summary)}
                            </div>
                        </div>
                `;

                summaryDiv.innerHTML = summaryHtml;

                // Function to create tables
                function createTable(title, testData) {
                    const tableId = `table-${title.replace(/\s+/g, '-')}`;
                    const tooltipText = {
                        "true negative tests payloads - allowed": `Legitimate requests that were correctly allowed through by the WAF without being flagged or blocked.`,
                        "true negative tests payloads - blocked": `Legitimate requests that should have been allowed through by the WAF, but were incorrectly blocked as if they were malicious.`,
                        "true negative tests payloads - unresolved": `Legitimate requests where the WAF's response was unclear or the result could not be determined.`,
                        "true positive tests payloads - blocked": `Malicious requests that were correctly identified and blocked by the WAF, preventing them from reaching the application.`,
                        "true positive tests payloads - bypassed": `Malicious requests that should have been detected and blocked by the WAF, but instead incorrectly bypassed the WAF as it failed to identify them.`,
                        "true positive tests payloads - unresolved": `Malicious requests that should have been detected and blocked by the WAF, but the outcome is unclear or indeterminate due to an ambiguous response or incomplete analysis by the WAF.`
                    };
                    let tableHtml = `<h2 class="tooltip">${tooltipText[title]}<span class="tooltiptext">${title}</span></h2><table id="${tableId}" class="display"><thead><tr>`;
                    const headers = Object.keys(testData[0]);
                    // Move 'payload' to the end
                    const reorderedHeaders = headers.filter(header => header !== 'payload').concat('payload');
                    reorderedHeaders.forEach(header => {
                        tableHtml += `<th>${header}</th>`;
                    });
                    tableHtml += `</tr></thead><tbody>`;
                    testData.forEach(row => {
                        tableHtml += `<tr>`;
                        reorderedHeaders.forEach(header => {
                            if (header === 'payload') {
                                let payload = row[header];
                                if (payload.length > 256) {
                                    payload = payload.substring(0, 256) + '...';
                                }
                                tableHtml += `<td><pre>${escapeHtml(payload)}</pre></td>`;
                            } else if (header === 'test_result') {
                                let icon = '';
                                if (row[header] === 'passed') {
                                    icon = '✅';
                                } else if (row[header] === 'failed') {
                                    icon = '❌';
                                } else if (row[header] === 'unknown') {
                                    icon = '❓';
                                }
                                tableHtml += `<td>${icon}</td>`;
                            } else {
                                tableHtml += `<td>${row[header]}</td>`;
                            }
                        });
                        tableHtml += `</tr>`;
                    });
                    tableHtml += `</tbody></table>`;
                    return tableHtml;
                }

                // Create tables for each test type
                const testTypes = ['true_positive_tests_payloads', 'true_negative_tests_payloads'];
                testTypes.forEach(type => {
                    for (const [key, value] of Object.entries(data[type])) {
                        tablesDiv.innerHTML += createTable(`${type.replace(/_/g, ' ')} - ${key}`, value);
                    }
                });
                // Initialize DataTables for each table
                $(document).ready(function() {
                    $('table.display').DataTable();
                });
            })
            .catch(error => console.error('Error loading JSON:', error));
    </script>
</body>
</html>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions