Skip to content

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

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions