diff --git a/payload/user/remote_access/pager-webui/server.py b/payload/user/remote_access/pager-webui/server.py
index a9e294e..2faeaa0 100644
--- a/payload/user/remote_access/pager-webui/server.py
+++ b/payload/user/remote_access/pager-webui/server.py
@@ -3133,6 +3133,18 @@ def h_attacks_export_hc22000(ctx):
'hashcat': 'hashcat -m 22000 %s -a 0 wordlist.txt' % outname}
+def h_attacks_download_hc22000(ctx):
+ name = _unquote_plus(ctx.args[0])
+ if not re.match(r'^handshakes_\d+\.hc22000$', name):
+ return 400, {'error': 'invalid name'}
+ full = os.path.join('/root/loot/hc22000', name)
+ if not os.path.isfile(full):
+ return 404, {'error': 'not found'}
+ with open(full, 'rb') as f:
+ body = f.read()
+ return 200, Download(body, 'application/octet-stream', name)
+
+
def h_attacks_deauth(ctx):
body = ctx.body or {}
bssid = (body.get('bssid') or '').strip().upper()
@@ -4277,6 +4289,7 @@ ROUTER.add('POST', r'/api/attacks/stop', h_attacks_stop)
ROUTER.add('GET', r'/api/attacks/status', h_attacks_status)
ROUTER.add('POST', r'/api/attacks/capture', h_attacks_capture)
ROUTER.add('GET', r'/api/attacks/export/hc22000', h_attacks_export_hc22000)
+ROUTER.add('GET', r'/api/attacks/export/hc22000/([^/]+)', h_attacks_download_hc22000)
ROUTER.add('POST', r'/api/attacks/deauth', h_attacks_deauth)
ROUTER.add('GET', r'/api/attacks/clients', h_attacks_clients)
ROUTER.add('GET', r'/api/health', h_health)
diff --git a/payload/user/remote_access/pager-webui/www/css/app.css b/payload/user/remote_access/pager-webui/www/css/app.css
index 4c68be2..8dc940d 100644
--- a/payload/user/remote_access/pager-webui/www/css/app.css
+++ b/payload/user/remote_access/pager-webui/www/css/app.css
@@ -185,6 +185,7 @@ body {
.badge { display: inline-block; padding: 2px 10px; border-radius: 10px; font-size: 11px; }
.badge.on { background: #e8f5e9; color: #2e7d32; }
.badge.off { background: #fff3e0; color: #e65100; }
+.badge.warn { background: #fff8e1; color: #f57f17; }
.badge.unknown { background: #eeeeee; color: #616161; }
.btn {
background: var(--primary); color: #fff; border: 0; border-radius: 2px;
@@ -468,6 +469,7 @@ html.dark .modal { background: #303030; }
.pineap-infobox { border-radius: 2px; padding: 10px 12px; margin-top: 10px; font-size: 13px; display: flex; flex-direction: column; gap: 8px; }
.pineap-infobox.error { background: #fdecea; color: #b71c1c; border: 1px solid #f5c6cb; }
.pineap-infobox.info { background: #e3f2fd; color: #0d47a1; border: 1px solid #90caf9; }
+.pineap-infobox.warn { background: #fff8e1; color: #f57f17; border: 1px solid #ffe082; }
.pineap-infobox-actions { display: flex; gap: 8px; flex-wrap: wrap; }
html.dark .pineap-infobox.error { background: #4a2020; color: #ffb4a9; border-color: #6b2d2d; }
html.dark .pineap-infobox.info { background: #10263a; color: #9cc7f0; border-color: #1d3a54; }
diff --git a/payload/user/remote_access/pager-webui/www/js/app.js b/payload/user/remote_access/pager-webui/www/js/app.js
index 58b7671..d665200 100644
--- a/payload/user/remote_access/pager-webui/www/js/app.js
+++ b/payload/user/remote_access/pager-webui/www/js/app.js
@@ -29,6 +29,7 @@ const App = (() => {
const railItems = [
{ key: 'dashboard', label: 'Dashboard', hash: '#/dashboard', icon: 'dashboard' },
+ { key: 'attacks', label: 'Attacks', hash: '#/attacks', icon: 'attack' },
{ key: 'pineap', label: 'PineAP', hash: '#/pineap', icon: 'wifi' },
{ key: 'recon', label: 'Recon', hash: '#/recon', icon: 'recon' },
{ key: 'logging', label: 'Logging', hash: '#/logging', icon: 'logging' },
@@ -393,6 +394,10 @@ const App = (() => {
const routes = {
'#/dashboard': 'dashboard',
+ '#/attacks': 'attacks',
+ '#/attacks/wpa': 'attacks_wpa',
+ '#/attacks/open': 'attacks_open',
+ '#/attacks/enterprise': 'attacks_enterprise',
'#/pineap': 'pineap',
'#/pineap/open': 'pineap_open',
'#/pineap/evilwpa': 'pineap_evilwpa',
diff --git a/payload/user/remote_access/pager-webui/www/js/icons.js b/payload/user/remote_access/pager-webui/www/js/icons.js
index abd2539..487fc93 100644
--- a/payload/user/remote_access/pager-webui/www/js/icons.js
+++ b/payload/user/remote_access/pager-webui/www/js/icons.js
@@ -1,6 +1,7 @@
'use strict';
window.PineappleIcons = {
+ attack: '',
dashboard: '',
pineap: '',
recon: '',
diff --git a/payload/user/remote_access/pager-webui/www/js/views.js b/payload/user/remote_access/pager-webui/www/js/views.js
index 17f4802..1addee9 100644
--- a/payload/user/remote_access/pager-webui/www/js/views.js
+++ b/payload/user/remote_access/pager-webui/www/js/views.js
@@ -1143,6 +1143,411 @@ function reconFiltered(rows, q, colsArr) {
return rows.filter((r) => colsArr.some((c) => String(r[c.key] == null ? '' : r[c.key]).toLowerCase().indexOf(ql) !== -1));
}
+// ---------------------------------------------------------------------------
+// Attacks: one-click Evil WPA / Open / Enterprise launchers.
+// ---------------------------------------------------------------------------
+
+const ATTACK_TABS = [
+ { label: 'Overview', hash: '#/attacks' },
+ { label: 'Evil WPA', hash: '#/attacks/wpa' },
+ { label: 'Open AP', hash: '#/attacks/open' },
+ { label: 'Evil Enterprise', hash: '#/attacks/enterprise' }
+];
+
+function attacksShell(root, activeHash) {
+ root.appendChild(h('h1', { class: 'page-title', text: 'Attacks' }));
+ tabBar(root, ATTACK_TABS, activeHash);
+ return h('div', {}, h('div', { class: 'muted', style: 'font-size:12px;margin:8px 0' },
+ 'Targets: only networks you are authorized to test. The device is the source of truth — every change is verified against it.'));
+}
+
+function attackBadge(ap) {
+ if (!ap) return badge(false);
+ if (!ap.enabled) return h('span', { class: 'badge off', text: 'OFF' });
+ return h('span', { class: 'badge' + (ap.live ? ' on' : ' warn'), text: ap.live ? 'LIVE' : 'CONFIGURED' });
+}
+
+function attackStatusCard() {
+ const card = h('div', { class: 'pineap-title-card' });
+ card.appendChild(h('div', { class: 'pineap-card-title' }, 'Status'));
+ const rows = h('div', { style: 'font-size:13px;line-height:1.9' });
+ card.appendChild(rows);
+ const append = (label, node) => rows.appendChild(
+ h('div', { class: 'row' }, h('div', { style: 'min-width:130px', text: label }), node));
+ return { card, rows, append };
+}
+
+function verifiedToast(result) {
+ if (result && result.verified) App.toast('Applied and verified on device');
+ else if (result && result.ok) App.toast('Applied (verification pending — radio reloading)', 'error');
+ else App.toast('Deploy failed', 'error');
+}
+
+views.attacks = (root) => {
+ const box = attacksShell(root, '#/attacks');
+ const wrap = h('div', { class: 'pineap-title-card-container' });
+ box.appendChild(wrap);
+ const kinds = [
+ ['wpa', 'Evil WPA (PSK)', 'Clone a WPA2/WPA3-PSK network and capture the four-way handshake.', '#/attacks/wpa'],
+ ['open', 'Evil Open', 'Advertise an open network and watch who connects.', '#/attacks/open'],
+ ['enterprise', 'Evil Enterprise', 'Serve WPA2/3-Enterprise with PineAPE and harvest 802.1X credentials.', '#/attacks/enterprise']
+ ];
+ const statusEls = {};
+ kinds.forEach(([kind, label, desc, hash]) => {
+ const card = h('div', { class: 'pineap-title-card' });
+ const st = h('span', { class: 'badge', text: '—' });
+ statusEls[kind] = st;
+ card.appendChild(h('div', { class: 'pineap-card-title' },
+ h('a', { href: hash, style: 'color:var(--primary);cursor:pointer',
+ onclick: (e) => { e.preventDefault(); App.go(hash); } }, label)));
+ card.appendChild(h('div', { class: 'muted', style: 'font-size:12px;margin:6px 0', text: desc }));
+ card.appendChild(h('div', { class: 'row' }, st,
+ h('a', { class: 'btn ghost', href: hash, style: 'text-decoration:none',
+ onclick: (e) => { e.preventDefault(); App.go(hash); } }, 'Configure')));
+ wrap.appendChild(card);
+ });
+ function load() {
+ PagerAPI.get('/api/attacks/status').then((r) => {
+ const s = r.data || {};
+ const live = (x) => !!(x && x.enabled);
+ const summary = {
+ wpa: live(s.wpa && s.wpa.radio0) || live(s.wpa && s.wpa.radio1) ? 'LIVE' : 'OFF',
+ open: live(s.open && s.open.radio0) || live(s.open && s.open.radio1) ? 'LIVE' : 'OFF',
+ enterprise: live(s.enterprise && s.enterprise.ap) ? 'LIVE' : 'OFF'
+ };
+ Object.keys(summary).forEach((k) => {
+ statusEls[k].textContent = summary[k];
+ statusEls[k].className = 'badge ' + (summary[k] === 'LIVE' ? 'on' : 'off');
+ });
+ }).catch(() => {});
+ }
+ load();
+ const iv = setInterval(load, 5000);
+ return { destroy: () => clearInterval(iv) };
+};
+
+function attackLauncher(kind, opts) {
+ return (root) => {
+ const box = attacksShell(root, '#/attacks/' + kind);
+ const form = h('div', { class: 'pineap-title-card' });
+ form.appendChild(h('div', { class: 'pineap-card-title' },
+ opts.title + (opts.subtitle ? ' — ' + opts.subtitle : '')));
+ box.appendChild(form);
+ const f = h('div', {});
+ form.appendChild(f);
+
+ const ssidIn = h('input', { id: 'atk-ssid' });
+ const hiddenCb = h('input', { type: 'checkbox', id: 'atk-hidden' });
+ const chanSel = h('select', { id: 'atk-channel' });
+ chanSelect(chanSel, null);
+ const bandHint = h('div', { class: 'muted', style: 'font-size:12px;margin-top:4px' });
+ let pskIn = null, encSel = null;
+ chanSel.addEventListener('change', () => {
+ const b = bandOfChannel(chanSel.value);
+ bandHint.textContent = b === '6' ? '6 GHz requires WPA3 (SAE/OWE).' : '';
+ if (encSel) {
+ Array.prototype.forEach.call(encSel.options, (o) => {
+ o.disabled = b === '6' && o.value !== 'sae' && o.value !== 'owe';
+ });
+ if (b === '6' && encSel.value === 'psk2') encSel.value = 'sae';
+ }
+ });
+ f.appendChild(h('label', {}, 'SSID (target network)', ssidIn));
+ if (opts.passphrase) {
+ pskIn = h('input', { id: 'atk-psk', type: 'password', autocomplete: 'new-password' });
+ encSel = h('select', { id: 'atk-enc' });
+ (opts.encodings || EVIL_ENC).forEach(([v, l]) => encSel.appendChild(h('option', { value: v, text: l })));
+ f.appendChild(h('label', {}, 'Passphrase', pskIn));
+ f.appendChild(h('label', {}, 'Encryption', encSel));
+ }
+ let bssidIn = null, coSel = null;
+ if (opts.bssid) {
+ bssidIn = h('input', { id: 'atk-bssid', placeholder: 'AA:BB:CC:DD:EE:FF' });
+ f.appendChild(h('label', {}, 'BSSID (spoof, optional)', bssidIn));
+ }
+ if (opts.country) {
+ coSel = h('select', { id: 'atk-country' });
+ OPEN_COUNTRIES.forEach(([v, l]) => coSel.appendChild(h('option', { value: v, text: l })));
+ f.appendChild(h('label', {}, 'Country', coSel));
+ }
+ f.appendChild(h('label', { class: 'switch' }, hiddenCb, h('span', { class: 'track' }), 'Hidden'));
+ f.appendChild(h('label', {}, 'Channel', chanSel));
+ f.appendChild(bandHint);
+ f.appendChild(h('div', { class: 'row', style: 'margin-top:10px' },
+ h('div', {}, btn('Deploy Attack', () => {
+ const body = {
+ kind, ssid: ssidIn.value.trim(),
+ hidden: hiddenCb.checked,
+ channel: chanSel.value ? parseInt(chanSel.value, 10) : null
+ };
+ if (pskIn) { body.passphrase = pskIn.value; body.enctype = encSel.value; }
+ if (bssidIn) body.bssid = bssidIn.value.trim();
+ if (coSel) body.country = coSel.value;
+ PagerAPI.post('/api/attacks/deploy', body)
+ .then((r) => { verifiedToast(r.data || {}); load(); })
+ .catch((e) => { App.toast(e.message || 'Deploy failed', 'error'); });
+ })),
+ h('div', {}, btn('Stop Attack', () => {
+ PagerAPI.post('/api/attacks/stop', { kind })
+ .then(() => { App.toast('Attack stopped'); load(); })
+ .catch(() => App.toast('Stop failed', 'error'));
+ }, 'danger')),
+ h('div', { class: 'muted', style: 'align-self:center;font-size:12px' },
+ 'Deploy reconfigures the radio — you may be disconnected briefly.')));
+
+ const status = attackStatusCard();
+ box.appendChild(status.card);
+
+ const hsBox = h('div', {});
+ const captureBox = h('div', { class: 'pineap-title-card' });
+ captureBox.appendChild(h('div', { class: 'pineap-card-title' }, 'Monitor Capture'));
+ const capBody = h('div', { style: 'font-size:13px' });
+ captureBox.appendChild(capBody);
+ box.appendChild(captureBox);
+
+ let capIface = 'wlan0mon';
+ function capRow(st) {
+ capBody.innerHTML = '';
+ const run = !!(st && st.running);
+ capBody.appendChild(h('div', { class: 'row' },
+ h('span', { text: run ? ('Capturing on ' + (st.iface || capIface)) : 'Not capturing' }),
+ h('div', {}, btn(run ? 'Stop Capture' : 'Start Capture', () => {
+ PagerAPI.post('/api/attacks/capture', {
+ iface: capIface, action: run ? 'stop' : 'start'
+ }).then((r) => capRow(r.data || {})).catch(() => App.toast('Capture failed', 'error'));
+ }))));
+ capBody.appendChild(h('div', { class: 'muted', style: 'font-size:12px;margin-top:4px',
+ text: run && st.path ? st.path : '' }));
+ }
+
+ if (opts.export) {
+ const exp = h('div', { class: 'pineap-title-card' });
+ exp.appendChild(h('div', { class: 'pineap-card-title' }, 'Hashcat Export'));
+ const expBody = h('div', { style: 'font-size:13px;line-height:1.8' });
+ exp.appendChild(expBody);
+ expBody.appendChild(h('div', {}, btn('Export .hc22000', () => {
+ PagerAPI.get('/api/attacks/export/hc22000').then((r) => {
+ const d = r.data || {};
+ if (d.file) {
+ expBody.appendChild(h('div', { class: 'row', style: 'margin-top:8px' },
+ h('a', { class: 'btn ghost', href: '/api/attacks/export/hc22000/' + d.name,
+ style: 'text-decoration:none', download: d.name }, 'Download ' + d.name),
+ h('code', { style: 'font-size:12px', text: d.hashcat || '' })));
+ App.toast('Export ready');
+ } else App.toast('No captures to export', 'error');
+ }).catch(() => App.toast('Export failed', 'error'));
+ })));
+ box.appendChild(exp);
+ }
+
+ let deauthCard = null;
+ if (opts.deauth) {
+ deauthCard = deauthPanel();
+ box.appendChild(deauthCard);
+ }
+
+ function load() {
+ PagerAPI.get('/api/attacks/status').then((r) => {
+ const s = r.data || {};
+ const ap = s[kind] || {};
+ const which = (ap.radio0 && ap.radio0.enabled) ? ap.radio0
+ : (ap.radio1 && ap.radio1.enabled) ? ap.radio1 : null;
+ status.rows.innerHTML = '';
+ status.append('Attack', attackBadge(which));
+ status.append('SSID', h('span', { text: which && which.ssid ? which.ssid : '—' }));
+ status.append('Interface', h('span', { text: which ? which.iface : '—' }));
+ status.append('Band', h('span', { text: which && which.band ? which.band + ' GHz' : '—' }));
+ status.append('Channel', h('span', { text: which && which.channel != null ? which.channel : '—' }));
+ if (which && !which.live) {
+ status.append('Note', h('span', { class: 'pineap-infobox warn',
+ text: 'Configured but interface not live yet — radio reload may still be in progress.' }));
+ }
+ capIface = (which && (which.band === '5' || which.band === '6')) ? 'wlan1mon' : 'wlan0mon';
+ if (opts.handshakes) {
+ hsBox.innerHTML = '';
+ hsBox.appendChild(h('div', { class: 'pineap-card-title', text: 'Handshakes Captured (' + (s.handshakes || 0) + ')' }));
+ PagerAPI.get('/api/pineap/handshakes').then((hr) => {
+ const hd = (hr.data || {}).parsed || [];
+ const rows = hd.slice(-10).reverse().map((x) => ({
+ ssid: x.ssid || '—', bssid: x.bssid || '—',
+ when: fmtTime(x.ts || 0), type: x.type || ''
+ }));
+ hsBox.appendChild(rows.length ? table(
+ [{ key: 'when', label: 'When' }, { key: 'ssid', label: 'SSID' },
+ { key: 'bssid', label: 'BSSID' }, { key: 'type', label: 'Type' }], rows)
+ : h('div', { class: 'empty', text: 'No handshakes yet.' }));
+ }).catch(() => {});
+ box.appendChild(hsBox);
+ }
+ }).catch(() => {});
+ PagerAPI.post('/api/attacks/capture', { action: 'status' }).then((r) => capRow(r.data || {})).catch(() => {});
+ }
+ load();
+ const iv = setInterval(load, 5000);
+ return { destroy: () => clearInterval(iv) };
+ };
+}
+
+function deauthPanel() {
+ const wrap = h('div', { class: 'pineap-title-card' });
+ wrap.appendChild(h('div', { class: 'pineap-card-title' }, 'Deauth Targeting'));
+ const body = h('div', { style: 'font-size:13px' });
+ wrap.appendChild(body);
+ const ssidBox = h('input', { placeholder: 'SSID to find clients for' });
+ const apSel = h('select', {});
+ const clTable = h('div', {});
+ body.appendChild(h('div', { class: 'row' }, ssidBox,
+ h('div', {}, btn('Find', () => {
+ const q = ssidBox.value.trim();
+ if (!q) return;
+ PagerAPI.get('/api/attacks/clients?ssid=' + encodeURIComponent(q)).then((r) => {
+ const d = r.data || {};
+ apSel.innerHTML = '';
+ (d.aps || []).forEach((a) => apSel.appendChild(h('option', {
+ value: (a.bssid || '') + '|' + (a.channel || 1),
+ text: (a.ssid || q) + ' — ' + (a.bssid || '?') + ' ch' + (a.channel || 1)
+ })));
+ if (!(d.aps || []).length) apSel.appendChild(h('option', { value: '|1', text: 'No APs found — check SSID' }));
+ clTable.innerHTML = '';
+ const cl = (d.clients || []).slice(0, 30);
+ if (!cl.length) {
+ clTable.appendChild(h('div', { class: 'empty', text: 'No devices in recon yet.' }));
+ return;
+ }
+ clTable.appendChild(table(
+ [{ key: 'mac', label: 'MAC' }, { key: 'freq', label: 'Freq (MHz)' },
+ { key: 'signal', label: 'Signal' }, { key: 'packets', label: 'Packets' },
+ { key: '_deauth', label: '' }],
+ cl.map((c) => {
+ const mac = c.mac || c.client_mac || '';
+ return { mac: mac || '—', freq: c.freq || '', signal: c.signal || '',
+ packets: c.packets || '', _deauth: btn('Deauth', () => {
+ const parts = apSel.value.split('|');
+ if (!mac || !parts[0]) { App.toast('Pick an AP and device first', 'error'); return; }
+ PagerAPI.post('/api/attacks/deauth', {
+ bssid: parts[0], client: mac, channel: parseInt(parts[1], 10)
+ }).then(() => App.toast('Deauth frames sent'))
+ .catch((e) => App.toast(e.message || 'Deauth failed', 'error'));
+ }, 'danger') };
+ })));
+ }).catch(() => App.toast('Lookup failed', 'error'));
+ }))));
+ body.appendChild(apSel);
+ body.appendChild(clTable);
+ body.appendChild(h('div', { class: 'muted', style: 'font-size:12px;margin-top:4px',
+ text: 'Only deauth targets you are authorized to test.' }));
+ return wrap;
+}
+
+views.attacks_wpa = attackLauncher('wpa', {
+ title: 'Evil WPA',
+ subtitle: 'WPA2-PSK / WPA3-SAE / WPA3-OWE evil twin with handshake capture',
+ passphrase: true,
+ encodings: EVIL_ENC,
+ handshakes: true,
+ export: true,
+ deauth: true
+});
+
+views.attacks_open = attackLauncher('open', {
+ title: 'Evil Open',
+ subtitle: 'Open network evil twin',
+ bssid: true,
+ country: true
+});
+
+views.attacks_enterprise = (root) => {
+ const box = attacksShell(root, '#/attacks/enterprise');
+ const form = h('div', { class: 'pineap-title-card' });
+ form.appendChild(h('div', { class: 'pineap-card-title' },
+ 'Evil Enterprise — WPA2/3-Enterprise with PineAPE credential harvest'));
+ box.appendChild(form);
+ const f = h('div', {});
+ form.appendChild(f);
+ const ssidIn = h('input', { id: 'ent-ssid' });
+ const encSel = h('select', { id: 'ent-enc' });
+ [['wpa2', 'WPA2 Enterprise'], ['wpa3', 'WPA3 Enterprise']]
+ .forEach(([v, l]) => encSel.appendChild(h('option', { value: v, text: l })));
+ const pskIn = h('input', { id: 'ent-pass', type: 'password', autocomplete: 'new-password' });
+ const hiddenCb = h('input', { type: 'checkbox', id: 'ent-hidden' });
+ f.appendChild(h('label', {}, 'SSID', ssidIn));
+ f.appendChild(h('label', {}, 'Encryption', encSel));
+ f.appendChild(h('label', {}, 'Passphrase (EAP server secret)', pskIn));
+ f.appendChild(h('label', { class: 'switch' }, hiddenCb, h('span', { class: 'track' }), 'Hidden'));
+ f.appendChild(h('div', { class: 'row', style: 'margin-top:10px' },
+ h('div', {}, btn('Deploy Attack', () => {
+ PagerAPI.post('/api/attacks/deploy', {
+ kind: 'enterprise', ssid: ssidIn.value.trim(),
+ enctype: encSel.value, passphrase: pskIn.value, hidden: hiddenCb.checked
+ }).then((r) => { verifiedToast(r.data || {}); load(); })
+ .catch((e) => App.toast(e.message || 'Deploy failed', 'error'));
+ })),
+ h('div', {}, btn('Stop Attack', () => {
+ PagerAPI.post('/api/attacks/stop', { kind: 'enterprise' })
+ .then(() => { App.toast('Attack stopped'); load(); })
+ .catch(() => App.toast('Stop failed', 'error'));
+ }, 'danger'))));
+
+ const status = attackStatusCard();
+ box.appendChild(status.card);
+
+ const pApe = h('div', { class: 'pineap-title-card' });
+ pApe.appendChild(h('div', { class: 'pineap-card-title' }, 'PineAPE'));
+ const pApeBody = h('div', {});
+ pApe.appendChild(pApeBody);
+ box.appendChild(pApe);
+ const authCb = h('input', { type: 'checkbox', id: 'ent-auth' });
+ authCb.addEventListener('change', () => PagerAPI.post('/api/pineap/hostapd', { pineape_auth_pass: authCb.checked })
+ .then(load).catch(() => { authCb.checked = !authCb.checked; App.toast('Failed', 'error'); }));
+
+ const tables = [['Basic Data', '/api/pineap/enterprise/basic', 'basic'],
+ ['Challenge Data', '/api/pineap/enterprise/challenge', 'challenge']]
+ .map(([name, endpoint, tableKey]) => {
+ const tb = h('div', { class: 'pineap-title-card' });
+ const body = h('div', {});
+ tb.appendChild(h('div', { class: 'pineap-card-title-flex' },
+ h('span', { text: name }),
+ h('span', { class: 'toolbar-spacer' }),
+ btn('Clear', () => PagerAPI.post('/api/pineap/enterprise/clear', { table: tableKey }).then(load), 'danger')));
+ tb.appendChild(body);
+ box.appendChild(tb);
+ return { body, endpoint };
+ });
+
+ function load() {
+ PagerAPI.get('/api/attacks/status').then((r) => {
+ const s = r.data || {};
+ const ent = s.enterprise || {};
+ const ap = ent.ap || null;
+ status.rows.innerHTML = '';
+ status.append('Attack', attackBadge(ap));
+ status.append('SSID', h('span', { text: ap && ap.ssid ? ap.ssid : '—' }));
+ status.append('Interface', h('span', { text: ap ? ap.iface : '—' }));
+ status.append('Band', h('span', { text: '2.4 GHz' }));
+ status.append('Channel', h('span', { text: ap && ap.channel != null ? ap.channel : '—' }));
+ status.append('Credentials', h('span', { text: String(ent.creds || 0) }));
+ pApeBody.innerHTML = '';
+ pApeBody.appendChild(h('label', { class: 'switch' }, authCb, h('span', { class: 'track' }), 'Auth Pass Capture'));
+ if (ent.pineape) {
+ pApeBody.appendChild(h('div', { class: 'muted', style: 'font-size:12px',
+ text: 'PineAPE ' + (ent.pineape.enabled ? 'enabled' : 'disabled') }));
+ }
+ }).catch(() => {});
+ tables.forEach((t) => {
+ PagerAPI.get(t.endpoint).then((r) => {
+ const rows = (r.data.rows || []).slice();
+ t.body.innerHTML = '';
+ const cols = rows.length ? Object.keys(rows[0]).map((k) => ({ label: k, key: k }))
+ : [{ label: '—', key: '_none' }];
+ t.body.appendChild(table(cols, rows));
+ if (!rows.length) t.body.appendChild(h('div', { class: 'empty', text: 'No data captured.' }));
+ }).catch(() => {});
+ });
+ }
+ load();
+ const iv = setInterval(load, 5000);
+ return { destroy: () => clearInterval(iv) };
+};
function reconEncBucket(enc) {
const s = (enc || '').trim();
if (s === 'Open') return 'Open';