Instruction: Create an AUTARCH web template for the 'Threat Intel' page. It should extend base.html, have a page header, and use AUTARCH's CSS variables and UI patterns (sections, tab bars, data tables, buttons).

Input:
Template: threat_intel.html
Sections: 9
Tabs: 2
Forms/API calls: 0
Has JavaScript: True

Output:
{% extends "base.html" %}
{% block title %}AUTARCH — Threat Intelligence{% endblock %}

{% block content %}
<div class="page-header">
    <h1>Threat Intelligence</h1>
    <p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
        IOC management, threat feeds, and correlation analysis.
    </p>
</div>

<!-- Tab Bar -->
<div class="tab-bar">
    <button class="tab active" data-tab-group="ti" data-tab="iocs" onclick="showTab('ti','iocs')">IOCs</button>
    <button class="tab" data-tab-group="ti" data-tab="feeds" onclick="showTab('ti','feeds')">Feeds</button>
    <button class="tab" data-tab-group="ti" data-tab="correlate" onclick="showTab('ti','correlate')">Correlate</button>
</div>

<!-- ==================== IOCs TAB ==================== -->
<div class="tab-content active" data-tab-group="ti" data-tab="iocs">

<div class="section">
    <h2>Add Indicator of Compromise</h2>
    <div class="form-row">
        <div class="form-group">
            <label>IOC Value</label>
            <input type="text" id="ioc-value" placeholder="IP, domain, hash, URL, email..." oninput="iocAutoType()">
        </div>
        <div class="form-group" style="max-width:160px">
            <label>Type</label>
            <select id="ioc-type">
                <option value="auto">Auto-detect</option>
                <option value="ipv4">IPv4</option>
                <option value="ipv6">IPv6</option>
                <option value="domain">Domain</option>
                <option value="url">URL</option>
                <option value="email">Email</option>
                <option value="md5">MD5</option>
                <option value="sha1">SHA1</option>
                <option value="sha256">SHA256</option>
                <option value="filename">Filename</option>
                <option value="cve">CVE</option>
            </select>
        </div>
        <div class="form-group" style="max-width:140px">
            <label>Severity</label>
            <select id="ioc-severity">
                <option value="low">Low</option>
                <option value="medium" selected>Medium</option>
                <option value="high">High</option>
                <option value="critical">Critical</option>
            </select>
        </div>
    </div>
    <div class="form-row">
        <div class="form-group">
            <label>Tags (comma-separated)</label>
            <input type="text" id="ioc-tags" placeholder="malware, phishing, c2, apt...">
        </div>
        <div class="form-group">
            <label>Description</label>
            <input type="text" id="ioc-desc" placeholder="Brief description">
        </div>
    </div>
    <div class="tool-actions">
        <button class="btn btn-primary" onclick="iocAdd()">Add IOC</button>
    </div>
</div>

<div class="section">
    <h2>IOC Database</h2>
    <div class="input-row">
        <input type="text" id="ioc-search" placeholder="Search IOCs..." oninput="iocFilter()">
        <select id="ioc-filter-type" onchange="iocFilter()" style="max-width:150px">
            <option value="">All Types</option>
            <option value="ipv4">IPv4</option>
            <option value="ipv6">IPv6</option>
            <option value="domain">Domain</option>
            <option value="url">URL</option>
            <option value="email">Email</option>
            <option value="md5">MD5</option>
            <option value="sha1">SHA1</option>
            <option value="sha256">SHA256</option>
            <option value="filename">Filename</option>
            <option value="cve">CVE</option>
        </select>
        <select id="ioc-filter-severity" onchange="iocFilter()" style="max-width:130px">
            <option value="">All Severities</option>
            <option value="critical">Critical</option>
            <option value="high">High</option>
            <option value="medium">Medium</option>
            <option value="low">Low</option>
        </select>
    </div>
    <div style="max-height:500px;overflow-y:auto">
        <table class="data-table">
            <thead>
                <tr>
                    <th>Value</th><th>Type</th><th>Severity</th>
                    <th>Tags</th><th>Added</th><th>Actions</th>
                </tr>
            </thead>
            <tbody id="ioc-table">
                <tr><td colspan="6" class="empty-state">No IOCs loaded. Add one above or import.</td></tr>
            </tbody>
        </table>
    </div>
    <div style="margin-top:8px;font-size:0.8rem;color:var(--text-muted)">
        Showing <span id="ioc-count">0</span> indicators
    </div>
</div>

<div class="section">
    <h2>Bulk Import / Export</h2>
    <div class="form-group">
        <label>Bulk Import (one IOC per line, or paste JSON/CSV)</label>
        <textarea id="ioc-bulk-input" rows="6" placeholder="192.168.1.1&#10;evil-domain.com&#10;d41d8cd98f00b204e9800998ecf8427e&#10;..."></textarea>
    </div>
    <div class="tool-actions">
        <button class="btn btn-primary btn-small" onclick="iocBulkImport()">Import</button>
        <button class="btn btn-small" onclick="iocExport('json')">Export JSON</button>
        <button class="btn btn-small" onclick="iocExport('csv')">Export CSV</button>
        <button class="btn btn-small" onclick="iocExport('stix')">Export STIX</button>
    </div>
    <pre class="output-panel" id="ioc-import-output" style="min-height:0"></pre>
</div>

</div>

<!-- ==================== FEEDS TAB ==================== -->
<div class="tab-content" data-tab-group="ti" data-tab="feeds">

<div class="section">
    <h2>Add Threat Feed</h2>
    <div class="form-row">
        <div class="form-group">
            <label>Feed Name</label>
            <input type="text" id="feed-name" placeholder="e.g. AlienVault OTX">
        </div>
        <div class="form-group" style="max-width:160px">
            <label>Type</label>
            <select id="feed-type">
                <option value="url_list">URL List (plaintext)</option>
                <option value="csv">CSV</option>
                <option value="json">JSON</option>
                <option value="stix">STIX/TAXII</option>
                <option value="misp">MISP</option>
                <option value="api">REST API</option>
            </select>
        </div>
    </div>
    <div class="form-row">
        <div class="form-group">
            <label>Feed URL</label>
            <input type="text" id="feed-url" placeholder="https://feeds.example.com/indicators.txt">
        </div>
        <div class="form-group" style="max-width:220px">
            <label>API Key (optional)</label>
            <input type="password" id="feed-apikey" placeholder="API key">
        </div>
    </div>
    <div class="tool-actions">
        <button class="btn btn-primary" onclick="feedAdd()">Add Feed</button>
    </div>
</div>

<div class="section">
    <h2>Configured Feeds</h2>
    <div class="tool-actions">
        <button class="btn btn-small" onclick="feedRefresh()">Refresh List</button>
        <button class="btn btn-primary btn-small" onclick="feedFetchAll()">Fetch All Feeds</button>
    </div>
    <table class="data-table">
        <thead>
            <tr>
                <th>Name</th><th>Type</th><th>URL</th><th>IOCs</th>
                <th>Last Fetched</th><th>Actions</th>
            </tr>
        </thead>
        <tbody id="feed-table">
            <tr><td colspan="6" class="empty-state">No feeds configured.</td></tr>
        </tbody>
    </table>
</div>

<div class="section">
    <h2>Feed Statistics</h2>
    <div class="stats-grid" id="feed-stats">
        <div class="stat-card">
            <div class="stat-label">Total Feeds</div>
            <div class="stat-value" id="stat-total-feeds">0</div>
        </div>
        <div class="stat-card">
            <div class="stat-label">Total IOCs from Feeds</div>
            <div class="stat-value" id="stat-feed-iocs">0</div>
        </div>
        <div class="stat-card">
            <div class="stat-label">Last Updated</div>
            <div class="stat-value small" id="stat-last-update">--</div>
        </div>
    </div>
</div>

</div>

<!-- ==================== CORRELATE TAB ==================== -->
<div class="tab-content" data-tab-group="ti" data-tab="correlate">

<div class="section">
    <h2>Reputation Lookup</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Check IP/domain/hash reputation against VirusTotal, AbuseIPDB, and local IOC database.
    </p>
    <div class="form-row">
        <div class="form-group">
            <label>Query (IP, domain, or hash)</label>
            <input type="text" id="rep-query" placeholder="8.8.8.8 or evil.com or sha256...">
        </div>
        <div class="form-group" style="max-width:180px">
            <label>Service</label>
            <select id="rep-service">
                <option value="all">All Services</option>
                <option value="local">Local IOC DB</option>
                <option value="virustotal">VirusTotal</option>
                <option value="abuseipdb">AbuseIPDB</option>
            </select>
        </div>
    </div>
    <div class="form-row">
        <div class="form-group" style="max-width:280px">
            <label>VirusTotal API Key</label>
            <input type="password" id="vt-apikey" placeholder="VT API key (saved in session)">
        </div>
        <div class="form-group" style="max-width:280px">
            <label>AbuseIPDB API Key</label>
            <input type="password" id="abuse-apikey" placeholder="AbuseIPDB API key">
        </div>
    </div>
    <div class="tool-actions">
        <button id="btn-rep-lookup" class="btn btn-primary" onclick="repLookup()">Lookup</button>
    </div>
    <pre class="output-panel scrollable" id="rep-output"></pre>
</div>

<div class="section">
    <h2>Blocklist Generator</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Generate blocklists from your IOC database for firewalls and security tools.
    </p>
    <div class="form-row">
        <div class="form-group" style="max-width:200px">
            <label>Output Format</label>
            <select id="bl-format">
                <option value="plain">Plain (one per line)</option>
                <option value="iptables">iptables rules</option>
                <option value="pf">PF (BSD)</option>
                <option value="nginx">Nginx deny</option>
                <option value="hosts">Hosts file</option>
                <option value="snort">Snort rules</option>
                <option value="suricata">Suricata rules</option>
            </select>
        </div>
        <div class="form-group" style="max-width:200px">
            <label>Minimum Severity</label>
            <select id="bl-severity">
                <option value="low">Low+</option>
                <option value="medium" selected>Medium+</option>
                <option value="high">High+</option>
                <option value="critical">Critical only</option>
            </select>
        </div>
        <div class="form-group" style="max-width:160px">
            <label>IOC Types</label>
            <select id="bl-type">
                <option value="all">All</option>
                <option value="ipv4">IPs only</option>
                <option value="domain">Domains only</option>
                <option value="url">URLs only</option>
            </select>
        </div>
    </div>
    <div class="tool-actions">
        <button class="btn btn-primary btn-small" onclick="blGenerate()">Generate Blocklist</button>
        <button class="btn btn-small" onclick="blCopy()">Copy to Clipboard</button>
        <button class="btn btn-small" onclick="blDownload()">Download</button>
    </div>
    <pre class="output-panel scrollable" id="bl-output"></pre>
</div>

<div class="section">
    <h2>Alerts</h2>
    <div class="tool-actions">
        <button class="btn btn-small" onclick="alertsRefresh()">Refresh Alerts</button>
        <button class="btn btn-danger btn-small" onclick="alertsClearAll()">Clear All</button>
    </div>
    <div id="alerts-list" style="max-height:400px;overflow-y:auto">
        <div class="empty-state">No alerts. Alerts appear when IOCs match network traffic or log data.</div>
    </div>
</div>

</div>

<script>
/* ── Threat Intelligence ── */
function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;'); }

var _iocCache = [];

function sevBadge(sev) {
    var cls = sev === 'critical' ? 'badge-fail' : sev === 'high' ? 'badge-high'
            : sev === 'medium' ? 'badge-medium' : 'badge-low';
    return '<span class="badge ' + cls + '">' + esc(sev) + '</span>';
}

/* ── Auto-detect IOC type ── */
function iocAutoType() {
    var v = document.getElementById('ioc-value').value.trim();
    var sel = document.getElementById('ioc-type');
    if (!v || sel.value !== 'auto') return;
    // Keep on auto, but show detected type via placeholder
}

function iocDetectType(val) {
    if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d+)?$/.test(val)) return 'ipv4';
    if (/^[0-9a-fA-F:]{3,}$/.test(val) && val.indexOf(':') > 0) return 'ipv6';
    if (/^https?:\/\//i.test(val)) return 'url';
    if (/^[^@]+@[^@]+\.[^@]+$/.test(val)) return 'email';
    if (/^[a-fA-F0-9]{32}$/.test(val)) return 'md5';
    if (/^[a-fA-F0-9]{40}$/.test(val)) return 'sha1';
    if (/^[a-fA-F0-9]{64}$/.test(val)) return 'sha256';
    if (/^CVE-\d{4}-\d+$/i.test(val)) return 'cve';
    if (/^[a-zA-Z0-9]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}$/.test(val)) return 'domain';
    return 'filename';
}

function iocAdd() {
    var value = document.getElementById('ioc-value').value.trim();
    if (!value) { alert('Enter an IOC value.'); return; }
    var iocType = document.getElementById('ioc-type').value;
    if (iocType === 'auto') iocType = iocDetectType(value);
    postJSON('/threat-intel/ioc/add', {
        value: value,
        type: iocType,
        severity: document.getElementById('ioc-severity').value,
        tags: document.getElementById('ioc-tags').value.trim(),
        description: document.getElementById('ioc-desc').value.trim()
    }).then(function(data) {
        if (data.error) { alert('Error: ' + data.error); return; }
        document.getElementById('ioc-value').value = '';
        document.getElementById('ioc-tags').value = '';
        document.getElementById('ioc-desc').value = '';
        iocLoad();
    });
}

function iocLoad() {
    fetchJSON('/threat-intel/iocs').then(function(data) {
        _iocCache = data.iocs || [];
        iocRender(_iocCache);
    });
}

function iocRender(iocs) {
    var tbody = document.getElementById('ioc-table');
    document.getElementById('ioc-count').textContent = iocs.length;
    if (!iocs.length) {
        tbody.innerHTML = '<tr><td colspan="6" class="empty-state">No IOCs match filters.</td></tr>';
        return;
    }
    var html = '';
    iocs.forEach(function(ioc) {
        var tags = (ioc.tags || '').split(',').filter(Boolean).map(function(t) {
            return '<span style="background:var(--bg-input);padding:1px 6px;border-radius:4px;font-size:0.72rem;margin-right:3px">' + esc(t.trim()) + '</span>';
        }).join('');
        html += '<tr>'
            + '<td><code style="font-size:0.8rem">' + esc(ioc.value) + '</code></td>'
            + '<td>' + esc(ioc.type) + '</td>'
            + '<td>' + sevBadge(ioc.severity) + '</td>'
            + '<td>' + tags + '</td>'
            + '<td style="font-size:0.8rem;color:var(--text-muted)">' + esc(ioc.added || '--') + '</td>'
            + '<td><button class="btn btn-danger btn-small" onclick="iocDelete(\'' + esc(ioc.id || ioc.value) + '\')">Delete</button></td>'
            + '</tr>';
    });
    tbody.innerHTML = html;
}

function iocFilter() {
    var q = document.getElementById('ioc-search').value.trim().toLowerCase();
    var typeF = document.getElementById('ioc-filter-type').value;
    var sevF = document.getElementById('ioc-filter-severity').value;
    var filtered = _iocCache.filter(function(ioc) {
        if (typeF && ioc.type !== typeF) return false;
        if (sevF && ioc.severity !== sevF) return false;
        if (q && ioc.value.toLowerCase().indexOf(q) < 0
            && (ioc.tags || '').toLowerCase().indexOf(q) < 0
            && (ioc.description || '').toLowerCase().indexOf(q) < 0) return false;
        return true;
    });
    iocRender(filtered);
}

function iocDelete(id) {
    if (!confirm('Delete this IOC?')) return;
    postJSON('/threat-intel/ioc/delete', {id: id}).then(function(data) {
        if (data.error) { alert('Error: ' + data.error); return; }
        iocLoad();
    });
}

function iocBulkImport() {
    var raw = document.getElementById('ioc-bulk-input').value.trim();
    if (!raw) { alert('Enter IOCs to import.'); return; }
    postJSON('/threat-intel/ioc/bulk-import', {data: raw}).then(function(data) {
        if (data.error) { renderOutput('ioc-import-output', 'Error: ' + data.error); return; }
        renderOutput('ioc-import-output', 'Imported ' + (data.imported || 0) + ' IOCs. Skipped: ' + (data.skipped || 0));
        document.getElementById('ioc-bulk-input').value = '';
        iocLoad();
    });
}

function iocExport(format) {
    window.open('/threat-intel/ioc/export?format=' + encodeURIComponent(format), '_blank');
}

/* ── Feeds ── */
function feedAdd() {
    var name = document.getElementById('feed-name').value.trim();
    var url = document.getElementById('feed-url').value.trim();
    if (!name || !url) { alert('Feed name and URL are required.'); return; }
    postJSON('/threat-intel/feed/add', {
        name: name,
        type: document.getElementById('feed-type').value,
        url: url,
        api_key: document.getElementById('feed-apikey').value.trim()
    }).then(function(data) {
        if (data.error) { alert('Error: ' + data.error); return; }
        document.getElementById('feed-name').value = '';
        document.getElementById('feed-url').value = '';
        document.getElementById('feed-apikey').value = '';
        feedRefresh();
    });
}

function feedRefresh() {
    fetchJSON('/threat-intel/feeds').then(function(data) {
        var feeds = data.feeds || [];
        document.getElementById('stat-total-feeds').textContent = feeds.length;
        document.getElementById('stat-feed-iocs').textContent = data.total_iocs || 0;
        document.getElementById('stat-last-update').textContent = data.last_update || '--';
        if (!feeds.length) {
            document.getElementById('feed-table').innerHTML =
                '<tr><td colspan="6" class="empty-state">No feeds configured.</td></tr>';
            return;
        }
        var html = '';
        feeds.forEach(function(f) {
            html += '<tr>'
                + '<td>' + esc(f.name) + '</td>'
                + '<td>' + esc(f.type) + '</td>'
                + '<td style="max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'
                + '<a href="' + esc(f.url) + '" target="_blank">' + esc(f.url) + '</a></td>'
                + '<td>' + esc(f.ioc_count || 0) + '</td>'
                + '<td style="font-size:0.8rem;color:var(--text-muted)">' + esc(f.last_fetched || 'never') + '</td>'
                + '<td>'
                + '<button class="btn btn-primary btn-small" onclick="feedFetch(\'' + esc(f.id || f.name) + '\')">Fetch</button> '
                + '<button class="btn btn-danger btn-small" onclick="feedDelete(\'' + esc(f.id || f.name) + '\')">Delete</button>'
                + '</td></tr>';
        });
        document.getElementById('feed-table').innerHTML = html;
    });
}

function feedFetch(id) {
    postJSON('/threat-intel/feed/fetch', {id: id}).then(function(data) {
        if (data.error) { alert('Error: ' + data.error); return; }
        alert('Fetched ' + (data.new_iocs || 0) + ' new IOCs from feed.');
        feedRefresh();
        iocLoad();
    });
}

function feedFetchAll() {
    postJSON('/threat-intel/feed/fetch-all', {}).then(function(data) {
        if (data.error) { alert('Error: ' + data.error); return; }
        alert('Fetched ' + (data.total_new || 0) + ' new IOCs from all feeds.');
        feedRefresh();
        iocLoad();
    });
}

function feedDelete(id) {
    if (!confirm('Delete this feed?')) return;
    postJSON('/threat-intel/feed/delete', {id: id}).then(function(data) {
        if (data.error) { alert('Error: ' + data.error); return; }
        feedRefresh();
    });
}

/* ── Correlation ── */
function repLookup() {
    var query = document.getElementById('rep-query').value.trim();
    if (!query) { alert('Enter an IP, domain, or hash to look up.'); return; }
    var btn = document.getElementById('btn-rep-lookup');
    setLoading(btn, true);
    postJSON('/threat-intel/correlate/lookup', {
        query: query,
        service: document.getElementById('rep-service').value,
        vt_key: document.getElementById('vt-apikey').value.trim(),
        abuse_key: document.getElementById('abuse-apikey').value.trim()
    }).then(function(data) {
        setLoading(btn, false);
        if (data.error) { renderOutput('rep-output', 'Error: ' + data.error); return; }
        var lines = ['=== Reputation Report: ' + query + ' ===', ''];
        if (data.local) {
            lines.push('--- Local IOC DB ---');
            lines.push('  Match: ' + (data.local.found ? 'YES' : 'No'));
            if (data.local.found) {
                lines.push('  Severity: ' + data.local.severity);
                lines.push('  Tags: ' + (data.local.tags || 'none'));
            }
            lines.push('');
        }
        if (data.virustotal) {
            lines.push('--- VirusTotal ---');
            lines.push('  Malicious: ' + (data.virustotal.malicious || 0) + '/' + (data.virustotal.total || 0) + ' engines');
            lines.push('  Reputation: ' + (data.virustotal.reputation || '--'));
            if (data.virustotal.tags) lines.push('  Tags: ' + data.virustotal.tags);
            lines.push('');
        }
        if (data.abuseipdb) {
            lines.push('--- AbuseIPDB ---');
            lines.push('  Confidence Score: ' + (data.abuseipdb.confidence || 0) + '%');
            lines.push('  Total Reports: ' + (data.abuseipdb.total_reports || 0));
            lines.push('  Country: ' + (data.abuseipdb.country || '--'));
            lines.push('  ISP: ' + (data.abuseipdb.isp || '--'));
            lines.push('');
        }
        renderOutput('rep-output', lines.join('\n'));
    }).catch(function() { setLoading(btn, false); });
}

function blGenerate() {
    postJSON('/threat-intel/blocklist/generate', {
        format: document.getElementById('bl-format').value,
        min_severity: document.getElementById('bl-severity').value,
        ioc_type: document.getElementById('bl-type').value
    }).then(function(data) {
        if (data.error) { renderOutput('bl-output', 'Error: ' + data.error); return; }
        renderOutput('bl-output', data.blocklist || 'No IOCs match the selected criteria.');
    });
}

function blCopy() {
    var el = document.getElementById('bl-output');
    if (!el.textContent) return;
    navigator.clipboard.writeText(el.textContent).then(function() {
        alert('Blocklist copied to clipboard.');
    });
}

function blDownload() {
    var el = document.getElementById('bl-output');
    if (!el.textContent) return;
    var blob = new Blob([el.textContent], {type: 'text/plain'});
    var a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'blocklist_' + document.getElementById('bl-format').value + '.txt';
    a.click();
}

function alertsRefresh() {
    fetchJSON('/threat-intel/alerts').then(function(data) {
        var alerts = data.alerts || [];
        var container = document.getElementById('alerts-list');
        if (!alerts.length) {
            container.innerHTML = '<div class="empty-state">No alerts. Alerts appear when IOCs match network traffic or log data.</div>';
            return;
        }
        var html = '';
        alerts.forEach(function(a) {
            var sevCls = a.severity === 'critical' ? 'badge-fail' : a.severity === 'high' ? 'badge-high'
                       : a.severity === 'medium' ? 'badge-medium' : 'badge-low';
            html += '<div class="threat-item">'
                + '<span class="badge ' + sevCls + '">' + esc(a.severity) + '</span>'
                + '<div><div class="threat-message">' + esc(a.message) + '</div>'
                + '<div class="threat-category">' + esc(a.source || '--') + ' &mdash; ' + esc(a.timestamp || '') + '</div>'
                + '</div></div>';
        });
        container.innerHTML = html;
    });
}

function alertsClearAll() {
    if (!confirm('Clear all alerts?')) return;
    postJSON('/threat-intel/alerts/clear', {}).then(function(data) {
        alertsRefresh();
    });
}

/* ── Init ── */
document.addEventListener('DOMContentLoaded', function() {
    iocLoad();
    feedRefresh();
});
</script>
{% endblock %}


Instruction: Create an AUTARCH web template for the 'Upnp' page. It should extend base.html, have a page header, and use AUTARCH's CSS variables and UI patterns (sections, tab bars, data tables, buttons).

Input:
Template: upnp.html
Sections: 5
Tabs: 0
Forms/API calls: 5
Has JavaScript: False

Output:
{% extends "base.html" %}
{% block title %}UPnP - AUTARCH{% endblock %}

{% block content %}
<div class="page-header">
    <h1>UPnP Port Manager</h1>
    {% if available %}
    <form method="POST" action="{{ url_for('upnp.refresh') }}">
        <button type="submit" class="btn btn-primary">Refresh All</button>
    </form>
    {% endif %}
</div>

{% if not available %}
<div class="section">
    <div class="empty-state">
        <p>upnpc (miniupnpc) is not installed.</p>
        <p style="margin-top:8px"><code>sudo apt install miniupnpc</code></p>
    </div>
</div>
{% else %}

<div class="stats-grid">
    <div class="stat-card">
        <div class="stat-label">External IP</div>
        <div class="stat-value small">{{ external_ip }}</div>
    </div>
    <div class="stat-card">
        <div class="stat-label">Internal IP</div>
        <div class="stat-value small">{{ internal_ip }}</div>
    </div>
    <div class="stat-card">
        <div class="stat-label">Configured Ports</div>
        <div class="stat-value">{{ mappings|length }}</div>
    </div>
    <div class="stat-card">
        <div class="stat-label">Cron Job</div>
        <div class="stat-value small">
            <span class="status-dot {{ 'active' if cron.installed else 'inactive' }}"></span>
            {{ cron.interval if cron.installed else 'Not installed' }}
        </div>
    </div>
</div>

<div class="section">
    <h2>Configured Mappings</h2>
    {% if mappings %}
    <table class="data-table">
        <thead><tr><th>Port</th><th>Protocol</th><th>Actions</th></tr></thead>
        <tbody>
        {% for m in mappings %}
        <tr>
            <td>{{ m.port }}</td>
            <td>{{ m.protocol }}</td>
            <td>
                <form method="POST" action="{{ url_for('upnp.remove') }}" style="display:inline">
                    <input type="hidden" name="port" value="{{ m.port }}">
                    <input type="hidden" name="protocol" value="{{ m.protocol }}">
                    <button type="submit" class="btn btn-small btn-danger">Remove</button>
                </form>
            </td>
        </tr>
        {% endfor %}
        </tbody>
    </table>
    {% else %}
    <div class="empty-state">No port mappings configured.</div>
    {% endif %}
</div>

<div class="section">
    <h2>Add Mapping</h2>
    <form method="POST" action="{{ url_for('upnp.add') }}" class="form-inline">
        <div class="form-group">
            <label>Port</label>
            <input type="number" name="port" placeholder="443" min="1" max="65535" required>
        </div>
        <div class="form-group">
            <label>Protocol</label>
            <select name="protocol">
                <option value="TCP">TCP</option>
                <option value="UDP">UDP</option>
            </select>
        </div>
        <button type="submit" class="btn btn-success">Add</button>
    </form>
</div>

<div class="section">
    <h2>Cron Job</h2>
    {% if cron.installed %}
    <p style="margin-bottom:12px">Running every <strong>{{ cron.interval }}</strong></p>
    <p style="margin-bottom:12px;color:var(--text-muted);font-size:0.85rem"><code>{{ cron.line }}</code></p>
    <form method="POST" action="{{ url_for('upnp.cron') }}">
        <input type="hidden" name="action" value="uninstall">
        <button type="submit" class="btn btn-danger">Uninstall Cron Job</button>
    </form>
    {% else %}
    <p style="margin-bottom:12px;color:var(--text-secondary)">No cron job installed for automatic UPnP refresh.</p>
    <form method="POST" action="{{ url_for('upnp.cron') }}" class="form-inline">
        <input type="hidden" name="action" value="install">
        <div class="form-group">
            <label>Interval (hours)</label>
            <input type="number" name="hours" value="12" min="1" max="24">
        </div>
        <button type="submit" class="btn btn-success">Install Cron Job</button>
    </form>
    {% endif %}
</div>

<div class="section">
    <h2>Current Router Mappings</h2>
    <pre>{{ current_mappings }}</pre>
</div>

{% endif %}
{% endblock %}


Instruction: Create an AUTARCH web template for the 'Vuln Scanner' page. It should extend base.html, have a page header, and use AUTARCH's CSS variables and UI patterns (sections, tab bars, data tables, buttons).

Input:
Template: vuln_scanner.html
Sections: 6
Tabs: 2
Forms/API calls: 1
Has JavaScript: True

Output:
{% extends "base.html" %}
{% block title %}AUTARCH — Vulnerability Scanner{% endblock %}
{% block content %}
<div class="page-header" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap">
    <div>
        <h1>Vulnerability Scanner</h1>
        <p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
            Port scanning, CVE matching, default credential checking, header &amp; SSL analysis, Nuclei integration.
        </p>
    </div>
    <a href="{{ url_for('offense.index') }}" class="btn btn-sm" style="margin-left:auto">&larr; Offense</a>
</div>

<!-- Tab Bar -->
<div class="tab-bar">
    <button class="tab active" data-tab-group="vs" data-tab="scan" onclick="showTab('vs','scan')">Scan</button>
    <button class="tab" data-tab-group="vs" data-tab="templates" onclick="showTab('vs','templates');vsLoadTemplates()">Templates</button>
    <button class="tab" data-tab-group="vs" data-tab="results" onclick="showTab('vs','results');vsLoadScans()">Results</button>
</div>

<!-- ==================== SCAN TAB ==================== -->
<div class="tab-content active" data-tab-group="vs" data-tab="scan">
<div class="section">
    <h2>New Scan</h2>
    <div style="display:grid;grid-template-columns:1fr auto;gap:1rem;align-items:end;max-width:900px">
        <div>
            <div class="form-group" style="margin-bottom:0.75rem">
                <label>Target (IP / hostname / URL)</label>
                <input type="text" id="vs-target" class="form-control" placeholder="192.168.1.1 or example.com" onkeypress="if(event.key==='Enter')vsStartScan()">
            </div>
            <div style="display:flex;gap:0.75rem;align-items:end;flex-wrap:wrap">
                <div class="form-group" style="margin:0;min-width:140px">
                    <label>Profile</label>
                    <select id="vs-profile" class="form-control" onchange="vsProfileChanged()">
                        <option value="quick">Quick</option>
                        <option value="standard" selected>Standard</option>
                        <option value="full">Full</option>
                        <option value="custom">Custom</option>
                    </select>
                </div>
                <div class="form-group" style="margin:0;flex:1;min-width:180px">
                    <label>Ports <span style="color:var(--text-muted);font-size:0.75rem">(optional, overrides profile)</span></label>
                    <input type="text" id="vs-ports" class="form-control" placeholder="e.g. 1-1024,8080,8443">
                </div>
                <button id="vs-start-btn" class="btn btn-primary" onclick="vsStartScan()">Start Scan</button>
            </div>
        </div>
        <div id="vs-profile-desc" style="font-size:0.8rem;color:var(--text-muted);max-width:220px;padding-bottom:4px">
            Port scan + service detection + CVE matching + headers + SSL
        </div>
    </div>
</div>

<!-- Active Scan Progress -->
<div id="vs-active-scan" class="section" style="display:none">
    <h2>Active Scan</h2>
    <div style="display:flex;align-items:center;gap:1rem;margin-bottom:0.75rem">
        <div style="flex:1;background:var(--bg-input);border-radius:6px;height:24px;overflow:hidden">
            <div id="vs-progress-bar" style="height:100%;background:var(--accent);transition:width 0.3s;width:0%"></div>
        </div>
        <span id="vs-progress-pct" style="font-size:0.85rem;font-weight:600;min-width:40px;text-align:right">0%</span>
    </div>
    <div id="vs-progress-msg" style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.75rem"></div>

    <!-- Severity Summary Badges -->
    <div id="vs-severity-badges" style="display:flex;gap:0.5rem;flex-wrap:wrap;margin-bottom:1rem">
        <span class="vs-sev-badge" style="background:rgba(239,68,68,0.2);color:var(--danger)">Critical: <strong id="vs-cnt-critical">0</strong></span>
        <span class="vs-sev-badge" style="background:rgba(230,126,34,0.2);color:#e67e22">High: <strong id="vs-cnt-high">0</strong></span>
        <span class="vs-sev-badge" style="background:rgba(245,158,11,0.2);color:var(--warning)">Medium: <strong id="vs-cnt-medium">0</strong></span>
        <span class="vs-sev-badge" style="background:rgba(59,130,246,0.2);color:#3b82f6">Low: <strong id="vs-cnt-low">0</strong></span>
        <span class="vs-sev-badge" style="background:rgba(139,143,168,0.15);color:var(--text-muted)">Info: <strong id="vs-cnt-info">0</strong></span>
    </div>

    <!-- Findings Table -->
    <div style="overflow-x:auto">
        <table class="data-table" style="font-size:0.82rem">
            <thead>
                <tr>
                    <th style="width:90px">Severity</th>
                    <th>Finding</th>
                    <th style="width:140px">Service</th>
                    <th style="width:60px">Port</th>
                    <th>Details</th>
                </tr>
            </thead>
            <tbody id="vs-findings-body"></tbody>
        </table>
    </div>
    <div id="vs-no-findings" style="display:none;color:var(--text-muted);font-size:0.85rem;padding:1rem 0">
        No findings yet...
    </div>
</div>

<!-- Standalone Tools -->
<div class="section" style="margin-top:1.5rem">
    <h2>Standalone Checks</h2>
    <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1rem;max-width:900px">
        <!-- Headers Check -->
        <div class="card">
            <h4>Security Headers</h4>
            <div class="form-group" style="margin-bottom:0.5rem">
                <input type="text" id="vs-hdr-url" class="form-control" placeholder="https://example.com" onkeypress="if(event.key==='Enter')vsCheckHeaders()">
            </div>
            <button class="btn btn-primary btn-small" onclick="vsCheckHeaders()">Check Headers</button>
            <div id="vs-hdr-results" style="margin-top:0.75rem;font-size:0.82rem"></div>
        </div>
        <!-- SSL Check -->
        <div class="card">
            <h4>SSL/TLS Analysis</h4>
            <div style="display:flex;gap:0.5rem">
                <div class="form-group" style="flex:1;margin-bottom:0.5rem">
                    <input type="text" id="vs-ssl-host" class="form-control" placeholder="example.com" onkeypress="if(event.key==='Enter')vsCheckSSL()">
                </div>
                <div class="form-group" style="width:70px;margin-bottom:0.5rem">
                    <input type="number" id="vs-ssl-port" class="form-control" value="443" min="1" max="65535">
                </div>
            </div>
            <button class="btn btn-primary btn-small" onclick="vsCheckSSL()">Check SSL</button>
            <div id="vs-ssl-results" style="margin-top:0.75rem;font-size:0.82rem"></div>
        </div>
        <!-- Creds Check -->
        <div class="card">
            <h4>Default Credentials</h4>
            <div class="form-group" style="margin-bottom:0.5rem">
                <input type="text" id="vs-cred-target" class="form-control" placeholder="192.168.1.1" onkeypress="if(event.key==='Enter')vsCheckCreds()">
            </div>
            <button class="btn btn-primary btn-small" onclick="vsCheckCreds()">Check Creds</button>
            <div id="vs-cred-results" style="margin-top:0.75rem;font-size:0.82rem"></div>
        </div>
    </div>
</div>
</div>

<!-- ==================== TEMPLATES TAB ==================== -->
<div class="tab-content" data-tab-group="vs" data-tab="templates">
<div class="section">
    <h2>Nuclei Templates</h2>
    <div id="vs-nuclei-status" style="margin-bottom:1rem;font-size:0.85rem"></div>
    <div class="form-group" style="max-width:400px;margin-bottom:1rem">
        <input type="text" id="vs-tmpl-search" class="form-control" placeholder="Search templates..." oninput="vsFilterTemplates()">
    </div>
    <div id="vs-templates-list" style="max-height:500px;overflow-y:auto;font-size:0.82rem"></div>
    <div id="vs-templates-loading" style="color:var(--text-muted);font-size:0.85rem">
        <div class="spinner-inline"></div> Loading templates...
    </div>
</div>
</div>

<!-- ==================== RESULTS TAB ==================== -->
<div class="tab-content" data-tab-group="vs" data-tab="results">
<div class="section">
    <h2>Scan History</h2>
    <div class="tool-actions" style="margin-bottom:1rem">
        <button class="btn btn-primary btn-small" onclick="vsLoadScans()">Refresh</button>
    </div>
    <div style="overflow-x:auto">
        <table class="data-table" style="font-size:0.82rem">
            <thead>
                <tr>
                    <th>Target</th>
                    <th>Date</th>
                    <th>Profile</th>
                    <th>Status</th>
                    <th>Findings</th>
                    <th>Severity Breakdown</th>
                    <th style="width:140px">Actions</th>
                </tr>
            </thead>
            <tbody id="vs-history-body"></tbody>
        </table>
    </div>
    <div id="vs-no-history" style="display:none;color:var(--text-muted);font-size:0.85rem;padding:1rem 0">
        No scans recorded yet.
    </div>
</div>

<!-- Detailed Results Panel (shown when clicking a scan row) -->
<div id="vs-detail-panel" class="section" style="display:none">
    <div style="display:flex;align-items:center;gap:1rem;margin-bottom:1rem">
        <h2 id="vs-detail-title" style="margin:0">Scan Details</h2>
        <button class="btn btn-sm" onclick="document.getElementById('vs-detail-panel').style.display='none'">&times; Close</button>
    </div>
    <div id="vs-detail-summary" style="margin-bottom:1rem;font-size:0.85rem"></div>
    <div style="overflow-x:auto">
        <table class="data-table" style="font-size:0.82rem">
            <thead>
                <tr>
                    <th style="width:90px">Severity</th>
                    <th>Finding</th>
                    <th style="width:100px">Type</th>
                    <th style="width:140px">Service</th>
                    <th style="width:60px">Port</th>
                    <th>Details</th>
                </tr>
            </thead>
            <tbody id="vs-detail-findings"></tbody>
        </table>
    </div>
    <div id="vs-detail-services" style="margin-top:1rem"></div>
</div>
</div>

<style>
.vs-sev-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 0.78rem;
    font-weight: 500;
}
.vs-sev-critical { background: var(--danger); color: #fff; }
.vs-sev-high { background: #e67e22; color: #fff; }
.vs-sev-medium { background: var(--warning); color: #000; }
.vs-sev-low { background: #3b82f6; color: #fff; }
.vs-sev-info { background: var(--bg-input); color: var(--text-muted); }
.hdr-good { color: #22c55e; }
.hdr-weak { color: #f59e0b; }
.hdr-missing { color: var(--danger); }
.spinner-inline {
    display: inline-block; width: 14px; height: 14px;
    border: 2px solid var(--border); border-top-color: var(--accent);
    border-radius: 50%; animation: spin 0.8s linear infinite;
    vertical-align: middle; margin-right: 6px;
}
@keyframes spin { to { transform: rotate(360deg) } }
.vs-tmpl-item {
    padding: 4px 8px;
    border-bottom: 1px solid var(--border);
    font-family: monospace;
}
.vs-tmpl-item:hover {
    background: var(--bg-input);
}
.vs-detail-row:hover {
    background: var(--bg-input);
    cursor: pointer;
}
</style>

<script>
/* ── State ── */
let vsActivePoll = null;
let vsAllTemplates = [];

/* ── Severity Badge Helper ── */
function vsSevBadge(sev) {
    sev = (sev || 'info').toLowerCase();
    const colors = {
        critical: 'background:var(--danger);color:#fff',
        high: 'background:#e67e22;color:#fff',
        medium: 'background:var(--warning);color:#000',
        low: 'background:#3b82f6;color:#fff',
        info: 'background:var(--bg-input);color:var(--text-muted)'
    };
    return '<span style="display:inline-block;padding:2px 10px;border-radius:12px;font-size:0.75rem;font-weight:600;' +
           (colors[sev] || colors.info) + '">' + sev.toUpperCase() + '</span>';
}

/* ── Profile description ── */
const profileDescs = {
    quick: 'Fast port scan + top service CVEs',
    standard: 'Port scan + service detection + CVE matching + headers + SSL',
    full: 'All ports + full CVE + default creds + headers + SSL + nuclei',
    custom: 'User-defined parameters'
};
function vsProfileChanged() {
    const p = document.getElementById('vs-profile').value;
    document.getElementById('vs-profile-desc').textContent = profileDescs[p] || '';
}

/* ── Start Scan ── */
function vsStartScan() {
    const target = document.getElementById('vs-target').value.trim();
    if (!target) return;
    const profile = document.getElementById('vs-profile').value;
    const ports = document.getElementById('vs-ports').value.trim();
    const btn = document.getElementById('vs-start-btn');

    setLoading(btn, true);
    postJSON('/vuln-scanner/scan', { target, profile, ports })
    .then(d => {
        setLoading(btn, false);
        if (!d.ok) { alert('Error: ' + (d.error || 'Unknown')); return; }
        document.getElementById('vs-active-scan').style.display = '';
        document.getElementById('vs-findings-body').innerHTML = '';
        document.getElementById('vs-no-findings').style.display = '';
        vsResetCounts();
        vsCheckScan(d.job_id);
    })
    .catch(e => { setLoading(btn, false); alert('Error: ' + e.message); });
}

function vsResetCounts() {
    ['critical','high','medium','low','info'].forEach(s => {
        document.getElementById('vs-cnt-' + s).textContent = '0';
    });
}

/* ── Poll Scan ── */
function vsCheckScan(jobId) {
    if (vsActivePoll) clearInterval(vsActivePoll);
    vsActivePoll = setInterval(() => {
        fetchJSON('/vuln-scanner/scan/' + jobId)
        .then(d => {
            if (!d.ok) { clearInterval(vsActivePoll); vsActivePoll = null; return; }
            // Progress
            const pct = d.progress || 0;
            document.getElementById('vs-progress-bar').style.width = pct + '%';
            document.getElementById('vs-progress-pct').textContent = pct + '%';
            document.getElementById('vs-progress-msg').textContent = d.progress_message || '';

            // Summary counts
            const sum = d.summary || {};
            ['critical','high','medium','low','info'].forEach(s => {
                document.getElementById('vs-cnt-' + s).textContent = sum[s] || 0;
            });

            // Findings
            const findings = d.findings || [];
            if (findings.length > 0) {
                document.getElementById('vs-no-findings').style.display = 'none';
                let html = '';
                for (const f of findings) {
                    html += '<tr>' +
                        '<td>' + vsSevBadge(f.severity) + '</td>' +
                        '<td>' + esc(f.title || '') + '</td>' +
                        '<td>' + esc(f.service || '') + '</td>' +
                        '<td>' + (f.port || '') + '</td>' +
                        '<td style="font-size:0.78rem;color:var(--text-secondary);max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' +
                            esc((f.description || '').substring(0, 150)) + '</td>' +
                        '</tr>';
                }
                document.getElementById('vs-findings-body').innerHTML = html;
            }

            // Done?
            if (d.status === 'complete' || d.status === 'error') {
                clearInterval(vsActivePoll);
                vsActivePoll = null;
                if (d.status === 'error') {
                    document.getElementById('vs-progress-msg').innerHTML =
                        '<span style="color:var(--danger)">Error: ' + esc(d.error || 'Unknown') + '</span>';
                } else {
                    document.getElementById('vs-progress-msg').innerHTML =
                        '<span style="color:#22c55e">Scan complete</span>';
                }
            }
        })
        .catch(() => { clearInterval(vsActivePoll); vsActivePoll = null; });
    }, 2000);
}

/* ── Load Scans ── */
function vsLoadScans() {
    fetchJSON('/vuln-scanner/scans')
    .then(d => {
        const scans = d.scans || [];
        const noHist = document.getElementById('vs-no-history');
        const body = document.getElementById('vs-history-body');
        if (!scans.length) {
            noHist.style.display = '';
            body.innerHTML = '';
            return;
        }
        noHist.style.display = 'none';
        let html = '';
        for (const s of scans) {
            const sum = s.summary || {};
            const dateStr = s.started ? new Date(s.started).toLocaleString() : '';
            const statusColor = s.status === 'complete' ? '#22c55e' : (s.status === 'error' ? 'var(--danger)' : 'var(--warning)');
            const sevBreak =
                (sum.critical ? '<span style="color:var(--danger)">' + sum.critical + 'C</span> ' : '') +
                (sum.high ? '<span style="color:#e67e22">' + sum.high + 'H</span> ' : '') +
                (sum.medium ? '<span style="color:var(--warning)">' + sum.medium + 'M</span> ' : '') +
                (sum.low ? '<span style="color:#3b82f6">' + sum.low + 'L</span> ' : '') +
                (sum.info ? '<span style="color:var(--text-muted)">' + sum.info + 'I</span>' : '');
            html += '<tr class="vs-detail-row" onclick="vsViewScan(\'' + s.job_id + '\')">' +
                '<td>' + esc(s.target) + '</td>' +
                '<td style="font-size:0.78rem">' + esc(dateStr) + '</td>' +
                '<td>' + esc(s.profile) + '</td>' +
                '<td style="color:' + statusColor + '">' + esc(s.status) + '</td>' +
                '<td><strong>' + (s.findings_count || 0) + '</strong></td>' +
                '<td>' + (sevBreak || '<span style="color:var(--text-muted)">none</span>') + '</td>' +
                '<td style="white-space:nowrap">' +
                    '<button class="btn btn-sm" onclick="event.stopPropagation();vsViewScan(\'' + s.job_id + '\')">View</button> ' +
                    '<button class="btn btn-sm" onclick="event.stopPropagation();vsExportScan(\'' + s.job_id + '\')">Export</button> ' +
                    '<button class="btn btn-sm btn-danger" onclick="event.stopPropagation();vsDeleteScan(\'' + s.job_id + '\')">Del</button>' +
                '</td></tr>';
        }
        body.innerHTML = html;
    });
}

/* ── View Scan Details ── */
function vsViewScan(jobId) {
    fetchJSON('/vuln-scanner/scan/' + jobId)
    .then(d => {
        if (!d.ok) return;
        const panel = document.getElementById('vs-detail-panel');
        panel.style.display = '';
        document.getElementById('vs-detail-title').textContent = 'Scan: ' + (d.target || jobId);

        const sum = d.summary || {};
        let sumHtml = '<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin-bottom:0.5rem">' +
            '<span class="vs-sev-badge" style="background:rgba(239,68,68,0.2);color:var(--danger)">Critical: <strong>' + (sum.critical || 0) + '</strong></span>' +
            '<span class="vs-sev-badge" style="background:rgba(230,126,34,0.2);color:#e67e22">High: <strong>' + (sum.high || 0) + '</strong></span>' +
            '<span class="vs-sev-badge" style="background:rgba(245,158,11,0.2);color:var(--warning)">Medium: <strong>' + (sum.medium || 0) + '</strong></span>' +
            '<span class="vs-sev-badge" style="background:rgba(59,130,246,0.2);color:#3b82f6">Low: <strong>' + (sum.low || 0) + '</strong></span>' +
            '<span class="vs-sev-badge" style="background:rgba(139,143,168,0.15);color:var(--text-muted)">Info: <strong>' + (sum.info || 0) + '</strong></span>' +
            '</div>';
        sumHtml += '<div>Status: <strong>' + esc(d.status || '') + '</strong> | Profile: <strong>' + esc(d.profile || '') + '</strong>';
        if (d.completed) sumHtml += ' | Completed: ' + new Date(d.completed).toLocaleString();
        sumHtml += '</div>';
        document.getElementById('vs-detail-summary').innerHTML = sumHtml;

        const findings = d.findings || [];
        if (findings.length) {
            let fhtml = '';
            for (const f of findings) {
                fhtml += '<tr>' +
                    '<td>' + vsSevBadge(f.severity) + '</td>' +
                    '<td>' + esc(f.title || '') + '</td>' +
                    '<td>' + esc(f.type || '') + '</td>' +
                    '<td>' + esc(f.service || '') + '</td>' +
                    '<td>' + (f.port || '') + '</td>' +
                    '<td style="font-size:0.78rem;color:var(--text-secondary)">' + esc((f.description || '').substring(0, 200));
                if (f.reference) fhtml += ' <a href="' + esc(f.reference) + '" target="_blank" style="font-size:0.75rem">[ref]</a>';
                if (f.cvss) fhtml += ' <span style="color:var(--accent);font-size:0.75rem">CVSS:' + esc(f.cvss) + '</span>';
                fhtml += '</td></tr>';
            }
            document.getElementById('vs-detail-findings').innerHTML = fhtml;
        } else {
            document.getElementById('vs-detail-findings').innerHTML =
                '<tr><td colspan="6" style="color:var(--text-muted);text-align:center">No findings</td></tr>';
        }

        // Services discovered
        const services = d.services || [];
        if (services.length) {
            let shtml = '<h3 style="margin-top:0.5rem">Discovered Services</h3>' +
                '<table class="data-table" style="font-size:0.82rem"><thead><tr><th>Port</th><th>Protocol</th><th>Service</th><th>Version</th></tr></thead><tbody>';
            for (const s of services) {
                shtml += '<tr><td>' + s.port + '</td><td>' + esc(s.protocol || 'tcp') + '</td>' +
                    '<td>' + esc(s.service || '') + '</td><td>' + esc(s.version || '') + '</td></tr>';
            }
            shtml += '</tbody></table>';
            document.getElementById('vs-detail-services').innerHTML = shtml;
        } else {
            document.getElementById('vs-detail-services').innerHTML = '';
        }

        panel.scrollIntoView({ behavior: 'smooth' });
    });
}

/* ── Delete Scan ── */
function vsDeleteScan(jobId) {
    if (!confirm('Delete this scan?')) return;
    fetch('/vuln-scanner/scan/' + jobId, { method: 'DELETE' })
    .then(r => r.json())
    .then(() => {
        vsLoadScans();
        document.getElementById('vs-detail-panel').style.display = 'none';
    });
}

/* ── Export Scan ── */
function vsExportScan(jobId) {
    window.open('/vuln-scanner/scan/' + jobId + '/export?format=json', '_blank');
}

/* ── Check Headers ── */
function vsCheckHeaders() {
    const url = document.getElementById('vs-hdr-url').value.trim();
    if (!url) return;
    const div = document.getElementById('vs-hdr-results');
    div.innerHTML = '<div class="spinner-inline"></div> Checking...';

    postJSON('/vuln-scanner/headers', { url })
    .then(d => {
        if (!d.ok) { div.innerHTML = '<span style="color:var(--danger)">Error: ' + esc(d.error || 'Unknown') + '</span>'; return; }
        let html = '<div style="margin-bottom:0.5rem">Score: <strong>' + (d.score || 0) + '%</strong>';
        if (d.server) html += ' | Server: <strong>' + esc(d.server) + '</strong>';
        html += '</div>';
        const headers = d.headers || {};
        for (const [hdr, info] of Object.entries(headers)) {
            const cls = 'hdr-' + info.rating;
            const icon = info.present ? '&#10003;' : '&#10007;';
            html += '<div style="padding:2px 0"><span class="' + cls + '">' + icon + '</span> ' + esc(hdr);
            if (info.value) html += ' <span style="color:var(--text-muted);font-size:0.75rem">' + esc(info.value).substring(0, 60) + '</span>';
            html += '</div>';
        }
        div.innerHTML = html;
    })
    .catch(e => { div.innerHTML = '<span style="color:var(--danger)">' + e.message + '</span>'; });
}

/* ── Check SSL ── */
function vsCheckSSL() {
    const host = document.getElementById('vs-ssl-host').value.trim();
    if (!host) return;
    const port = parseInt(document.getElementById('vs-ssl-port').value) || 443;
    const div = document.getElementById('vs-ssl-results');
    div.innerHTML = '<div class="spinner-inline"></div> Checking...';

    postJSON('/vuln-scanner/ssl', { host, port })
    .then(d => {
        if (!d.ok || d.error) {
            div.innerHTML = '<span style="color:var(--danger)">Error: ' + esc(d.error || 'Unknown') + '</span>';
            return;
        }
        let html = '<div>Valid: <strong class="' + (d.valid ? 'hdr-good' : 'hdr-missing') + '">' + (d.valid ? 'Yes' : 'No') + '</strong></div>';
        html += '<div>Protocol: ' + esc(d.protocol || '?') + '</div>';
        html += '<div>Cipher: ' + esc(d.cipher || '?') + '</div>';
        if (d.key_size) html += '<div>Key size: ' + d.key_size + ' bits</div>';
        if (d.expires) html += '<div>Expires: ' + esc(d.expires) + '</div>';
        if (d.issuer && typeof d.issuer === 'object') {
            html += '<div>Issuer: ' + esc(d.issuer.organizationName || d.issuer.commonName || JSON.stringify(d.issuer)) + '</div>';
        }
        const issues = d.issues || [];
        for (const issue of issues) {
            html += '<div class="hdr-missing">[!] ' + esc(issue) + '</div>';
        }
        const weak = d.weak_ciphers || [];
        for (const wc of weak) {
            html += '<div class="hdr-missing">[!] Weak cipher: ' + esc(wc) + '</div>';
        }
        if (!issues.length && !weak.length && d.valid) {
            html += '<div class="hdr-good" style="margin-top:4px">No issues detected</div>';
        }
        div.innerHTML = html;
    })
    .catch(e => { div.innerHTML = '<span style="color:var(--danger)">' + e.message + '</span>'; });
}

/* ── Check Creds ── */
function vsCheckCreds() {
    const target = document.getElementById('vs-cred-target').value.trim();
    if (!target) return;
    const div = document.getElementById('vs-cred-results');
    div.innerHTML = '<div class="spinner-inline"></div> Scanning ports & testing credentials...';

    postJSON('/vuln-scanner/creds', { target })
    .then(d => {
        if (!d.ok) { div.innerHTML = '<span style="color:var(--danger)">Error: ' + esc(d.error || 'Unknown') + '</span>'; return; }
        const found = d.found || [];
        let html = '<div style="margin-bottom:0.5rem">' + (d.services_checked || 0) + ' services checked</div>';
        if (found.length) {
            for (const c of found) {
                html += '<div style="color:var(--danger);padding:2px 0">[!] ' + esc(c.service) + ': <strong>' +
                    esc(c.username) + ':' + esc(c.password) + '</strong></div>';
            }
        } else {
            html += '<div class="hdr-good">No default credentials found</div>';
        }
        div.innerHTML = html;
    })
    .catch(e => { div.innerHTML = '<span style="color:var(--danger)">' + e.message + '</span>'; });
}

/* ── Load Templates ── */
function vsLoadTemplates() {
    const list = document.getElementById('vs-templates-list');
    const loading = document.getElementById('vs-templates-loading');
    const status = document.getElementById('vs-nuclei-status');
    loading.style.display = '';
    list.innerHTML = '';

    fetchJSON('/vuln-scanner/templates')
    .then(d => {
        loading.style.display = 'none';
        if (d.installed) {
            status.innerHTML = '<span class="hdr-good">&#10003; Nuclei installed</span>' +
                ' <span style="color:var(--text-muted)">(' + esc(d.nuclei_path) + ')</span>';
        } else {
            status.innerHTML = '<span class="hdr-missing">&#10007; Nuclei not installed</span>' +
                ' <span style="color:var(--text-muted)">Install: go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest</span>';
        }

        vsAllTemplates = d.templates || [];
        const cats = d.categories || [];

        if (cats.length) {
            let catHtml = '<div style="margin-bottom:0.75rem;display:flex;gap:0.5rem;flex-wrap:wrap">';
            for (const cat of cats) {
                const count = vsAllTemplates.filter(t => t.startsWith(cat + '/')).length;
                catHtml += '<span class="vs-sev-badge" style="background:var(--bg-input);color:var(--accent);cursor:pointer" ' +
                    'onclick="document.getElementById(\'vs-tmpl-search\').value=\'' + cat + '/\';vsFilterTemplates()">' +
                    esc(cat) + ' (' + count + ')</span>';
            }
            catHtml += '</div>';
            list.innerHTML = catHtml;
        }

        vsRenderTemplates(vsAllTemplates.slice(0, 200));
    })
    .catch(() => { loading.style.display = 'none'; });
}

function vsFilterTemplates() {
    const q = document.getElementById('vs-tmpl-search').value.trim().toLowerCase();
    const filtered = q ? vsAllTemplates.filter(t => t.toLowerCase().includes(q)) : vsAllTemplates;
    vsRenderTemplates(filtered.slice(0, 200));
}

function vsRenderTemplates(templates) {
    const container = document.getElementById('vs-templates-list');
    // Preserve category badges if they exist
    const badges = container.querySelector('div');
    let html = badges ? badges.outerHTML : '';
    if (templates.length) {
        html += '<div style="margin-bottom:0.5rem;color:var(--text-muted)">Showing ' + templates.length +
            (templates.length >= 200 ? '+ (use search to filter)' : '') + ' templates</div>';
        for (const t of templates) {
            html += '<div class="vs-tmpl-item">' + esc(t) + '</div>';
        }
    } else {
        html += '<div style="color:var(--text-muted)">No templates found</div>';
    }
    container.innerHTML = html;
}

/* ── Helpers (use global esc, postJSON, fetchJSON, setLoading, showTab) ── */
</script>
{% endblock %}


Instruction: Create an AUTARCH web template for the 'Webapp Scanner' page. It should extend base.html, have a page header, and use AUTARCH's CSS variables and UI patterns (sections, tab bars, data tables, buttons).

Input:
Template: webapp_scanner.html
Sections: 0
Tabs: 4
Forms/API calls: 6
Has JavaScript: True

Output:
{% extends "base.html" %}
{% block title %}Web Scanner — AUTARCH{% endblock %}
{% block content %}
<div class="page-header">
    <h1>Web Application Scanner</h1>
    <p class="text-muted">Directory brute, subdomain enum, vuln scanning, header analysis</p>
</div>

<div class="tabs">
    <button class="tab active" onclick="switchTab('quick')">Quick Scan</button>
    <button class="tab" onclick="switchTab('dirbust')">Dir Brute</button>
    <button class="tab" onclick="switchTab('subdomain')">Subdomains</button>
    <button class="tab" onclick="switchTab('vuln')">Vuln Scan</button>
    <button class="tab" onclick="switchTab('crawl')">Crawl</button>
</div>

<!-- Quick Scan -->
<div id="tab-quick" class="tab-content active">
    <div class="card" style="max-width:900px">
        <h3>Quick Scan</h3>
        <div style="display:flex;gap:0.5rem;align-items:end">
            <div class="form-group" style="flex:1;margin:0">
                <input type="text" id="qs-url" class="form-control" placeholder="https://example.com" onkeypress="if(event.key==='Enter')quickScan()">
            </div>
            <button class="btn btn-primary" onclick="quickScan()">Scan</button>
        </div>
        <div id="qs-results" style="margin-top:1rem"></div>
    </div>
</div>

<!-- Dir Brute -->
<div id="tab-dirbust" class="tab-content" style="display:none">
    <div class="card" style="max-width:900px">
        <h3>Directory Bruteforce</h3>
        <div class="form-group">
            <label>Target URL</label>
            <input type="text" id="db-url" class="form-control" placeholder="https://example.com">
        </div>
        <div class="form-group">
            <label>Extensions (comma-separated, empty for none)</label>
            <input type="text" id="db-ext" class="form-control" placeholder=".php,.html,.txt,.bak">
        </div>
        <button class="btn btn-primary" onclick="startDirbust()">Start</button>
        <div id="db-status" style="margin-top:0.5rem"></div>
        <div id="db-results" style="margin-top:1rem"></div>
    </div>
</div>

<!-- Subdomain -->
<div id="tab-subdomain" class="tab-content" style="display:none">
    <div class="card" style="max-width:900px">
        <h3>Subdomain Enumeration</h3>
        <div style="display:flex;gap:0.5rem;align-items:end">
            <div class="form-group" style="flex:1;margin:0">
                <input type="text" id="sd-domain" class="form-control" placeholder="example.com">
            </div>
            <label style="white-space:nowrap;font-size:0.85rem"><input type="checkbox" id="sd-ct" checked> CT Logs</label>
            <button class="btn btn-primary" onclick="subdomainEnum()">Enumerate</button>
        </div>
        <div id="sd-results" style="margin-top:1rem"></div>
    </div>
</div>

<!-- Vuln Scan -->
<div id="tab-vuln" class="tab-content" style="display:none">
    <div class="card" style="max-width:900px">
        <h3>Vulnerability Scanner</h3>
        <div class="form-group">
            <label>Target URL (with parameters preferred)</label>
            <input type="text" id="vs-url" class="form-control" placeholder="https://example.com/search?q=test">
        </div>
        <div style="display:flex;gap:1rem;margin:0.5rem 0;font-size:0.85rem">
            <label><input type="checkbox" id="vs-sqli" checked> SQL Injection</label>
            <label><input type="checkbox" id="vs-xss" checked> Cross-Site Scripting</label>
        </div>
        <button class="btn btn-primary" onclick="vulnScan()">Scan</button>
        <div id="vs-results" style="margin-top:1rem"></div>
    </div>
</div>

<!-- Crawl -->
<div id="tab-crawl" class="tab-content" style="display:none">
    <div class="card" style="max-width:900px">
        <h3>Web Crawler / Spider</h3>
        <div style="display:flex;gap:0.5rem;align-items:end">
            <div class="form-group" style="flex:1;margin:0">
                <input type="text" id="cr-url" class="form-control" placeholder="https://example.com">
            </div>
            <div class="form-group" style="width:100px;margin:0">
                <input type="number" id="cr-max" class="form-control" value="50" min="1" max="500" title="Max pages">
            </div>
            <button class="btn btn-primary" onclick="startCrawl()">Crawl</button>
        </div>
        <div id="cr-results" style="margin-top:1rem"></div>
    </div>
</div>

<style>
.hdr-good{color:#22c55e}.hdr-weak{color:#f59e0b}.hdr-missing{color:var(--danger)}
.sev-high{color:var(--danger);font-weight:700}.sev-medium{color:#f59e0b;font-weight:600}.sev-low{color:var(--text-muted)}
.tech-badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:0.75rem;margin:2px;background:var(--bg-input);color:var(--accent)}
.spinner-inline{display:inline-block;width:14px;height:14px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin 0.8s linear infinite;vertical-align:middle;margin-right:6px}
@keyframes spin{to{transform:rotate(360deg)}}
</style>

<script>
let dbPoll=null;

function switchTab(name){
    document.querySelectorAll('.tab').forEach((t,i)=>t.classList.toggle('active',
        ['quick','dirbust','subdomain','vuln','crawl'][i]===name));
    document.querySelectorAll('.tab-content').forEach(c=>c.style.display='none');
    document.getElementById('tab-'+name).style.display='block';
}

function quickScan(){
    const url=document.getElementById('qs-url').value.trim();
    if(!url) return;
    const div=document.getElementById('qs-results');
    div.innerHTML='<div class="spinner-inline"></div> Scanning...';
    fetch('/web-scanner/quick',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url})})
    .then(r=>r.json()).then(d=>{
        if(!d.ok){div.innerHTML='Error: '+esc(d.error);return}
        let html=`<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">`;
        // General
        html+=`<div class="card"><h4>General</h4>
            <div>Status: <strong>${d.status_code}</strong></div>
            <div>Server: <strong>${esc(d.server||'hidden')}</strong></div>
            ${d.technologies&&d.technologies.length?'<div style="margin-top:0.5rem">Tech: '+d.technologies.map(t=>`<span class="tech-badge">${t}</span>`).join('')+'</div>':''}
            ${d.redirects&&d.redirects.length?'<div style="margin-top:0.5rem;font-size:0.8rem">Redirects: '+d.redirects.map(r=>r.status+' → '+esc(r.url)).join(' → ')+'</div>':''}
        </div>`;
        // Security headers
        if(d.security_headers){
            html+=`<div class="card"><h4>Security Headers</h4>`;
            for(const[h,info] of Object.entries(d.security_headers)){
                const cls='hdr-'+info.rating;
                html+=`<div style="font-size:0.8rem;padding:2px 0"><span class="${cls}">${info.present?'&#10003;':'&#10007;'}</span> ${h}${info.value?' <span style="color:var(--text-muted)">'+esc(info.value).slice(0,60)+'</span>':''}</div>`;
            }
            html+=`</div>`;
        }
        html+=`</div>`;
        // SSL
        if(d.ssl&&Object.keys(d.ssl).length>1){
            html+=`<div class="card" style="margin-top:1rem"><h4>SSL/TLS</h4>
                <div>Valid: <strong class="${d.ssl.valid?'hdr-good':'hdr-missing'}">${d.ssl.valid?'Yes':'No'}</strong></div>
                <div>Protocol: ${esc(d.ssl.protocol||'?')}</div>
                <div>Cipher: ${esc(d.ssl.cipher||'?')}</div>
                ${d.ssl.expires?'<div>Expires: '+esc(d.ssl.expires)+'</div>':''}
                ${(d.ssl.issues||[]).map(i=>'<div class="hdr-missing">[!] '+esc(i)+'</div>').join('')}
            </div>`;
        }
        div.innerHTML=html;
    }).catch(e=>{div.innerHTML='Error: '+e.message});
}

function startDirbust(){
    const url=document.getElementById('db-url').value.trim();
    if(!url) return;
    const ext=document.getElementById('db-ext').value.split(',').map(e=>e.trim()).filter(Boolean);
    document.getElementById('db-status').innerHTML='<div class="spinner-inline"></div> Bruteforcing...';
    document.getElementById('db-results').innerHTML='';
    fetch('/web-scanner/dirbust',{method:'POST',headers:{'Content-Type':'application/json'},
        body:JSON.stringify({url,extensions:ext})})
    .then(r=>r.json()).then(d=>{
        if(!d.ok){document.getElementById('db-status').innerHTML='Error: '+esc(d.error);return}
        if(dbPoll) clearInterval(dbPoll);
        dbPoll=setInterval(()=>{
            fetch('/web-scanner/dirbust/'+d.job_id).then(r=>r.json()).then(s=>{
                document.getElementById('db-status').innerHTML=s.done?
                    `Done. Found ${s.found.length} paths (tested ${s.tested}/${s.total})`:
                    `<div class="spinner-inline"></div> ${s.tested}/${s.total} tested, ${s.found.length} found`;
                if(s.found.length){
                    document.getElementById('db-results').innerHTML='<table class="data-table"><thead><tr><th>Status</th><th>Path</th><th>Size</th><th>Type</th></tr></thead><tbody>'+
                        s.found.map(f=>`<tr><td>${f.status}</td><td><a href="${esc(url+f.path)}" target="_blank">${esc(f.path)}</a></td><td>${f.size}</td><td style="font-size:0.75rem">${esc(f.content_type)}</td></tr>`).join('')+
                        '</tbody></table>';
                }
                if(s.done){clearInterval(dbPoll);dbPoll=null}
            });
        },2000);
    });
}

function subdomainEnum(){
    const domain=document.getElementById('sd-domain').value.trim();
    if(!domain) return;
    const div=document.getElementById('sd-results');
    div.innerHTML='<div class="spinner-inline"></div> Enumerating...';
    fetch('/web-scanner/subdomain',{method:'POST',headers:{'Content-Type':'application/json'},
        body:JSON.stringify({domain,use_ct:document.getElementById('sd-ct').checked})})
    .then(r=>r.json()).then(d=>{
        if(!d.ok){div.innerHTML='Error: '+esc(d.error);return}
        const subs=d.subdomains||[];
        div.innerHTML=`<div style="margin-bottom:0.5rem"><strong>${subs.length}</strong> subdomains found for <strong>${esc(d.domain)}</strong></div>`+
            (subs.length?'<div style="column-count:3;font-size:0.85rem;font-family:monospace">'+subs.map(s=>`<div>${esc(s)}</div>`).join('')+'</div>':'');
    });
}

function vulnScan(){
    const url=document.getElementById('vs-url').value.trim();
    if(!url) return;
    const div=document.getElementById('vs-results');
    div.innerHTML='<div class="spinner-inline"></div> Scanning for vulnerabilities...';
    fetch('/web-scanner/vuln',{method:'POST',headers:{'Content-Type':'application/json'},
        body:JSON.stringify({url,sqli:document.getElementById('vs-sqli').checked,xss:document.getElementById('vs-xss').checked})})
    .then(r=>r.json()).then(d=>{
        if(!d.ok){div.innerHTML='Error: '+esc(d.error);return}
        const findings=d.findings||[];
        if(!findings.length){div.innerHTML=`<div style="color:#22c55e">No vulnerabilities found. Tested ${d.urls_tested||0} URL(s).</div>`;return}
        div.innerHTML=`<div style="margin-bottom:0.5rem"><strong class="sev-high">${findings.length} finding(s)</strong></div>
            <table class="data-table"><thead><tr><th>Severity</th><th>Type</th><th>Parameter</th><th>Description</th><th>Payload</th></tr></thead><tbody>`+
            findings.map(f=>`<tr><td class="sev-${f.severity}">${f.severity.toUpperCase()}</td>
                <td>${f.type.toUpperCase()}</td><td>${esc(f.parameter||'')}</td>
                <td style="font-size:0.8rem">${esc(f.description)}</td>
                <td style="font-family:monospace;font-size:0.75rem">${esc(f.payload||'')}</td></tr>`).join('')+
            '</tbody></table>';
    });
}

function startCrawl(){
    const url=document.getElementById('cr-url').value.trim();
    if(!url) return;
    const max=+document.getElementById('cr-max').value||50;
    const div=document.getElementById('cr-results');
    div.innerHTML='<div class="spinner-inline"></div> Crawling...';
    fetch('/web-scanner/crawl',{method:'POST',headers:{'Content-Type':'application/json'},
        body:JSON.stringify({url,max_pages:max})})
    .then(r=>r.json()).then(d=>{
        if(!d.ok){div.innerHTML='Error: '+esc(d.error);return}
        const pages=d.pages||[];
        div.innerHTML=`<div style="margin-bottom:0.5rem"><strong>${pages.length}</strong> pages crawled</div>
            <table class="data-table"><thead><tr><th>Status</th><th>URL</th><th>Title</th><th>Size</th><th>Forms</th></tr></thead><tbody>`+
            pages.map(p=>`<tr><td>${p.status}</td><td style="max-width:400px;overflow:hidden;text-overflow:ellipsis">
                <a href="${esc(p.url)}" target="_blank" style="font-size:0.8rem">${esc(p.url)}</a></td>
                <td style="font-size:0.8rem">${esc(p.title||'')}</td><td>${p.size}</td><td>${p.forms}</td></tr>`).join('')+
            '</tbody></table>';
    });
}

function esc(s){return s?String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'):''}
</script>
{% endblock %}


Instruction: Create an AUTARCH web template for the 'Wifi Audit' page. It should extend base.html, have a page header, and use AUTARCH's CSS variables and UI patterns (sections, tab bars, data tables, buttons).

Input:
Template: wifi_audit.html
Sections: 9
Tabs: 2
Forms/API calls: 0
Has JavaScript: True

Output:
{% extends "base.html" %}
{% block title %}AUTARCH — WiFi Audit{% endblock %}

{% block content %}
<div class="page-header">
    <h1>WiFi Auditing</h1>
    <p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
        Wireless network scanning, attack tools, and monitoring.
    </p>
</div>

<!-- Tab Bar -->
<div class="tab-bar">
    <button class="tab active" data-tab-group="wifi" data-tab="scan" onclick="showTab('wifi','scan')">Scan</button>
    <button class="tab" data-tab-group="wifi" data-tab="attack" onclick="showTab('wifi','attack')">Attack</button>
    <button class="tab" data-tab-group="wifi" data-tab="monitor" onclick="showTab('wifi','monitor')">Monitor</button>
</div>

<!-- ==================== SCAN TAB ==================== -->
<div class="tab-content active" data-tab-group="wifi" data-tab="scan">

<div class="section">
    <h2>Wireless Interfaces</h2>
    <div class="tool-actions">
        <button class="btn btn-small" onclick="wifiRefreshIfaces()">Refresh</button>
        <button class="btn btn-primary btn-small" onclick="wifiMonitorMode(true)">Enable Monitor Mode</button>
        <button class="btn btn-danger btn-small" onclick="wifiMonitorMode(false)">Disable Monitor Mode</button>
    </div>
    <table class="data-table">
        <thead><tr><th>Interface</th><th>Mode</th><th>Driver</th><th>Chipset</th><th>Status</th></tr></thead>
        <tbody id="wifi-iface-list">
            <tr><td colspan="5" class="empty-state">Click Refresh to list wireless interfaces.</td></tr>
        </tbody>
    </table>
    <div class="form-group" style="max-width:300px;margin-top:12px">
        <label>Selected Interface</label>
        <select id="wifi-iface-select">
            <option value="">-- refresh to populate --</option>
        </select>
    </div>
</div>

<div class="section">
    <h2>Network Scan</h2>
    <div class="input-row">
        <input type="number" id="wifi-scan-duration" placeholder="Duration (seconds)" value="15" min="5" max="120" style="max-width:200px">
        <button id="btn-wifi-scan" class="btn btn-primary" onclick="wifiScanNetworks()">Scan Networks</button>
    </div>
    <table class="data-table">
        <thead>
            <tr>
                <th>BSSID</th><th>SSID</th><th>Channel</th>
                <th>Encryption</th><th>Signal</th><th>Clients</th>
            </tr>
        </thead>
        <tbody id="wifi-scan-results">
            <tr><td colspan="6" class="empty-state">No scan results yet.</td></tr>
        </tbody>
    </table>
</div>

</div>

<!-- ==================== ATTACK TAB ==================== -->
<div class="tab-content" data-tab-group="wifi" data-tab="attack">

<div class="section">
    <h2>Deauthentication Attack</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Send deauthentication frames to disconnect clients from an access point.
    </p>
    <div class="form-row">
        <div class="form-group">
            <label>Target BSSID</label>
            <input type="text" id="deauth-bssid" placeholder="AA:BB:CC:DD:EE:FF">
        </div>
        <div class="form-group">
            <label>Client MAC (optional, blank = broadcast)</label>
            <input type="text" id="deauth-client" placeholder="AA:BB:CC:DD:EE:FF">
        </div>
        <div class="form-group" style="max-width:120px">
            <label>Count</label>
            <input type="number" id="deauth-count" value="10" min="1" max="9999">
        </div>
    </div>
    <div class="tool-actions">
        <button id="btn-deauth" class="btn btn-danger" onclick="wifiDeauth()">Send Deauth</button>
    </div>
    <pre class="output-panel" id="deauth-output"></pre>
</div>

<div class="section">
    <h2>Handshake Capture</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Capture WPA/WPA2 four-way handshake from a target network.
    </p>
    <div class="form-row">
        <div class="form-group">
            <label>Target BSSID</label>
            <input type="text" id="hs-bssid" placeholder="AA:BB:CC:DD:EE:FF">
        </div>
        <div class="form-group" style="max-width:120px">
            <label>Channel</label>
            <input type="number" id="hs-channel" placeholder="6" min="1" max="196">
        </div>
    </div>
    <div class="tool-actions">
        <button id="btn-hs-capture" class="btn btn-primary" onclick="wifiCaptureHandshake()">Capture Handshake</button>
        <button class="btn btn-small" onclick="wifiStopCapture()">Stop Capture</button>
    </div>
    <pre class="output-panel" id="hs-output"></pre>
</div>

<div class="section">
    <h2>WPS Attack</h2>
    <div class="tool-actions">
        <button id="btn-wps-scan" class="btn btn-small" onclick="wifiWpsScan()">Scan WPS Networks</button>
    </div>
    <table class="data-table">
        <thead><tr><th>BSSID</th><th>SSID</th><th>WPS Version</th><th>Locked</th><th>Action</th></tr></thead>
        <tbody id="wps-scan-results">
            <tr><td colspan="5" class="empty-state">Click Scan to find WPS-enabled networks.</td></tr>
        </tbody>
    </table>
    <pre class="output-panel" id="wps-output" style="margin-top:12px"></pre>
</div>

<div class="section">
    <h2>Crack Handshake</h2>
    <div class="form-row">
        <div class="form-group">
            <label>Capture File Path</label>
            <input type="text" id="crack-file" placeholder="/path/to/capture.cap">
        </div>
        <div class="form-group">
            <label>Wordlist Path</label>
            <input type="text" id="crack-wordlist" placeholder="/path/to/wordlist.txt">
        </div>
    </div>
    <div class="tool-actions">
        <button id="btn-crack" class="btn btn-primary" onclick="wifiCrackHandshake()">Crack</button>
    </div>
    <pre class="output-panel scrollable" id="crack-output"></pre>
</div>

</div>

<!-- ==================== MONITOR TAB ==================== -->
<div class="tab-content" data-tab-group="wifi" data-tab="monitor">

<div class="section">
    <h2>Rogue AP Detection</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Save a baseline of known APs, then detect rogue or evil-twin access points.
    </p>
    <div class="tool-actions">
        <button id="btn-baseline" class="btn btn-primary btn-small" onclick="wifiSaveBaseline()">Save Baseline</button>
        <button id="btn-detect-rogue" class="btn btn-danger btn-small" onclick="wifiDetectRogue()">Detect Rogue APs</button>
    </div>
    <pre class="output-panel scrollable" id="rogue-output"></pre>
</div>

<div class="section">
    <h2>Packet Capture</h2>
    <div class="input-row">
        <select id="cap-filter" style="max-width:200px">
            <option value="">No Filter</option>
            <option value="beacon">Beacons</option>
            <option value="probe">Probes</option>
            <option value="deauth">Deauth Frames</option>
            <option value="data">Data Frames</option>
            <option value="eapol">EAPOL (Handshakes)</option>
        </select>
        <button id="btn-cap-start" class="btn btn-primary btn-small" onclick="wifiStartCapture()">Start Capture</button>
        <button class="btn btn-danger btn-small" onclick="wifiStopPacketCapture()">Stop Capture</button>
    </div>
    <pre class="output-panel scrollable" id="cap-live-output" style="max-height:300px"></pre>
</div>

<div class="section">
    <h2>Saved Captures</h2>
    <div class="tool-actions">
        <button class="btn btn-small" onclick="wifiListCaptures()">Refresh List</button>
    </div>
    <table class="data-table">
        <thead><tr><th>Filename</th><th>Size</th><th>Date</th><th>Actions</th></tr></thead>
        <tbody id="captures-list">
            <tr><td colspan="4" class="empty-state">Click Refresh to list saved capture files.</td></tr>
        </tbody>
    </table>
</div>

</div>

<script>
/* ── WiFi Audit ── */
function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;'); }

function wifiRefreshIfaces() {
    fetchJSON('/wifi/interfaces').then(function(data) {
        if (data.error) { renderOutput('wifi-iface-list', data.error); return; }
        var ifaces = data.interfaces || [];
        var sel = document.getElementById('wifi-iface-select');
        sel.innerHTML = '';
        if (!ifaces.length) {
            document.getElementById('wifi-iface-list').innerHTML =
                '<tr><td colspan="5" class="empty-state">No wireless interfaces found.</td></tr>';
            sel.innerHTML = '<option value="">No interfaces</option>';
            return;
        }
        var html = '';
        ifaces.forEach(function(ifc) {
            html += '<tr><td>' + esc(ifc.name) + '</td><td>' + esc(ifc.mode || 'managed') + '</td>'
                + '<td>' + esc(ifc.driver || '--') + '</td><td>' + esc(ifc.chipset || '--') + '</td>'
                + '<td><span class="badge ' + (ifc.up ? 'badge-pass' : 'badge-fail') + '">'
                + (ifc.up ? 'UP' : 'DOWN') + '</span></td></tr>';
            var opt = document.createElement('option');
            opt.value = ifc.name;
            opt.textContent = ifc.name + ' (' + (ifc.mode || 'managed') + ')';
            sel.appendChild(opt);
        });
        document.getElementById('wifi-iface-list').innerHTML = html;
    });
}

function wifiGetIface() {
    var sel = document.getElementById('wifi-iface-select');
    return sel ? sel.value : '';
}

function wifiMonitorMode(enable) {
    var iface = wifiGetIface();
    if (!iface) { alert('Select a wireless interface first.'); return; }
    postJSON('/wifi/monitor-mode', {interface: iface, enable: enable}).then(function(data) {
        if (data.error) { alert('Error: ' + data.error); return; }
        alert(data.message || (enable ? 'Monitor mode enabled' : 'Monitor mode disabled'));
        wifiRefreshIfaces();
    });
}

function wifiScanNetworks() {
    var btn = document.getElementById('btn-wifi-scan');
    var iface = wifiGetIface();
    if (!iface) { alert('Select a wireless interface first.'); return; }
    var duration = parseInt(document.getElementById('wifi-scan-duration').value) || 15;
    setLoading(btn, true);
    postJSON('/wifi/scan', {interface: iface, duration: duration}).then(function(data) {
        setLoading(btn, false);
        if (data.error) {
            document.getElementById('wifi-scan-results').innerHTML =
                '<tr><td colspan="6" class="empty-state">Error: ' + esc(data.error) + '</td></tr>';
            return;
        }
        var nets = data.networks || [];
        if (!nets.length) {
            document.getElementById('wifi-scan-results').innerHTML =
                '<tr><td colspan="6" class="empty-state">No networks found.</td></tr>';
            return;
        }
        var html = '';
        nets.forEach(function(n) {
            var sigClass = n.signal > -50 ? 'badge-pass' : n.signal > -70 ? 'badge-medium' : 'badge-fail';
            html += '<tr><td><code>' + esc(n.bssid) + '</code></td>'
                + '<td>' + esc(n.ssid || '<hidden>') + '</td>'
                + '<td>' + esc(n.channel) + '</td>'
                + '<td>' + esc(n.encryption || 'Open') + '</td>'
                + '<td><span class="badge ' + sigClass + '">' + esc(n.signal) + ' dBm</span></td>'
                + '<td>' + esc(n.clients || 0) + '</td></tr>';
        });
        document.getElementById('wifi-scan-results').innerHTML = html;
    }).catch(function() { setLoading(btn, false); });
}

function wifiDeauth() {
    var bssid = document.getElementById('deauth-bssid').value.trim();
    if (!bssid) { alert('Enter a target BSSID.'); return; }
    var iface = wifiGetIface();
    if (!iface) { alert('Select a wireless interface first.'); return; }
    var btn = document.getElementById('btn-deauth');
    setLoading(btn, true);
    postJSON('/wifi/deauth', {
        interface: iface,
        bssid: bssid,
        client: document.getElementById('deauth-client').value.trim(),
        count: parseInt(document.getElementById('deauth-count').value) || 10
    }).then(function(data) {
        setLoading(btn, false);
        renderOutput('deauth-output', data.message || data.error || 'Done');
    }).catch(function() { setLoading(btn, false); });
}

function wifiCaptureHandshake() {
    var bssid = document.getElementById('hs-bssid').value.trim();
    if (!bssid) { alert('Enter a target BSSID.'); return; }
    var iface = wifiGetIface();
    if (!iface) { alert('Select a wireless interface first.'); return; }
    var btn = document.getElementById('btn-hs-capture');
    setLoading(btn, true);
    postJSON('/wifi/capture-handshake', {
        interface: iface,
        bssid: bssid,
        channel: parseInt(document.getElementById('hs-channel').value) || 0
    }).then(function(data) {
        setLoading(btn, false);
        renderOutput('hs-output', data.message || data.error || 'Capture started');
    }).catch(function() { setLoading(btn, false); });
}

function wifiStopCapture() {
    postJSON('/wifi/stop-capture', {}).then(function(data) {
        renderOutput('hs-output', data.message || data.error || 'Stopped');
    });
}

function wifiWpsScan() {
    var iface = wifiGetIface();
    if (!iface) { alert('Select a wireless interface first.'); return; }
    var btn = document.getElementById('btn-wps-scan');
    setLoading(btn, true);
    postJSON('/wifi/wps-scan', {interface: iface}).then(function(data) {
        setLoading(btn, false);
        if (data.error) {
            document.getElementById('wps-scan-results').innerHTML =
                '<tr><td colspan="5" class="empty-state">Error: ' + esc(data.error) + '</td></tr>';
            return;
        }
        var nets = data.networks || [];
        if (!nets.length) {
            document.getElementById('wps-scan-results').innerHTML =
                '<tr><td colspan="5" class="empty-state">No WPS networks found.</td></tr>';
            return;
        }
        var html = '';
        nets.forEach(function(n) {
            html += '<tr><td><code>' + esc(n.bssid) + '</code></td>'
                + '<td>' + esc(n.ssid || '<hidden>') + '</td>'
                + '<td>' + esc(n.wps_version || '--') + '</td>'
                + '<td><span class="badge ' + (n.locked ? 'badge-fail' : 'badge-pass') + '">'
                + (n.locked ? 'Yes' : 'No') + '</span></td>'
                + '<td><button class="btn btn-danger btn-small" onclick="wifiWpsAttack(\''
                + esc(n.bssid) + '\')">Attack</button></td></tr>';
        });
        document.getElementById('wps-scan-results').innerHTML = html;
    }).catch(function() { setLoading(btn, false); });
}

function wifiWpsAttack(bssid) {
    var iface = wifiGetIface();
    if (!iface) { alert('Select a wireless interface first.'); return; }
    renderOutput('wps-output', 'Starting WPS attack on ' + bssid + '...');
    postJSON('/wifi/wps-attack', {interface: iface, bssid: bssid}).then(function(data) {
        renderOutput('wps-output', data.message || data.error || 'Done');
    });
}

function wifiCrackHandshake() {
    var file = document.getElementById('crack-file').value.trim();
    var wordlist = document.getElementById('crack-wordlist').value.trim();
    if (!file || !wordlist) { alert('Provide both capture file and wordlist paths.'); return; }
    var btn = document.getElementById('btn-crack');
    setLoading(btn, true);
    renderOutput('crack-output', 'Cracking... this may take a while.');
    postJSON('/wifi/crack', {file: file, wordlist: wordlist}).then(function(data) {
        setLoading(btn, false);
        renderOutput('crack-output', data.message || data.error || 'Done');
    }).catch(function() { setLoading(btn, false); });
}

function wifiSaveBaseline() {
    var iface = wifiGetIface();
    if (!iface) { alert('Select a wireless interface first.'); return; }
    var btn = document.getElementById('btn-baseline');
    setLoading(btn, true);
    postJSON('/wifi/baseline', {interface: iface}).then(function(data) {
        setLoading(btn, false);
        renderOutput('rogue-output', data.message || data.error || 'Baseline saved');
    }).catch(function() { setLoading(btn, false); });
}

function wifiDetectRogue() {
    var iface = wifiGetIface();
    if (!iface) { alert('Select a wireless interface first.'); return; }
    var btn = document.getElementById('btn-detect-rogue');
    setLoading(btn, true);
    postJSON('/wifi/detect-rogue', {interface: iface}).then(function(data) {
        setLoading(btn, false);
        if (data.error) { renderOutput('rogue-output', 'Error: ' + data.error); return; }
        var rogues = data.rogue_aps || [];
        if (!rogues.length) {
            renderOutput('rogue-output', 'No rogue access points detected. Environment clean.');
            return;
        }
        var lines = ['=== ROGUE APs DETECTED ==='];
        rogues.forEach(function(r) {
            lines.push('[!] BSSID: ' + r.bssid + '  SSID: ' + (r.ssid || '<hidden>')
                + '  Channel: ' + r.channel + '  Reason: ' + (r.reason || 'unknown'));
        });
        renderOutput('rogue-output', lines.join('\n'));
    }).catch(function() { setLoading(btn, false); });
}

function wifiStartCapture() {
    var iface = wifiGetIface();
    if (!iface) { alert('Select a wireless interface first.'); return; }
    var filter = document.getElementById('cap-filter').value;
    renderOutput('cap-live-output', 'Starting packet capture...');
    postJSON('/wifi/packet-capture/start', {interface: iface, filter: filter}).then(function(data) {
        renderOutput('cap-live-output', data.message || data.error || 'Capture running');
    });
}

function wifiStopPacketCapture() {
    postJSON('/wifi/packet-capture/stop', {}).then(function(data) {
        renderOutput('cap-live-output', data.message || data.error || 'Capture stopped');
        wifiListCaptures();
    });
}

function wifiListCaptures() {
    fetchJSON('/wifi/captures').then(function(data) {
        if (data.error) { return; }
        var caps = data.captures || [];
        if (!caps.length) {
            document.getElementById('captures-list').innerHTML =
                '<tr><td colspan="4" class="empty-state">No saved captures.</td></tr>';
            return;
        }
        var html = '';
        caps.forEach(function(c) {
            html += '<tr><td><code>' + esc(c.filename) + '</code></td>'
                + '<td>' + esc(c.size || '--') + '</td>'
                + '<td>' + esc(c.date || '--') + '</td>'
                + '<td><button class="btn btn-small" onclick="wifiDownloadCapture(\''
                + esc(c.filename) + '\')">Download</button> '
                + '<button class="btn btn-danger btn-small" onclick="wifiDeleteCapture(\''
                + esc(c.filename) + '\')">Delete</button></td></tr>';
        });
        document.getElementById('captures-list').innerHTML = html;
    });
}

function wifiDownloadCapture(filename) {
    window.open('/wifi/captures/download?file=' + encodeURIComponent(filename), '_blank');
}

function wifiDeleteCapture(filename) {
    if (!confirm('Delete capture "' + filename + '"?')) return;
    postJSON('/wifi/captures/delete', {filename: filename}).then(function(data) {
        if (data.success) wifiListCaptures();
    });
}
</script>
{% endblock %}