feat: merge attacks into PineAP menu; auto channel; recon fixes; richer reports

- Rail: Attacks tab removed; PineAP becomes a grouped menu (Evil WPA /
  Evil Open / Evil Enterprise / Impersonation / Clients / Filtering);
  old #/attacks* hashes redirect to their PineAP equivalents.
- PineAP tabs gain Evil Enterprise; stock Open AP / Evil WPA / Enterprise
  pages replaced by the verified one-click launchers (status, capture,
  export, deauth, playbooks).
- Channel selects gain an Auto option: deploy resolves the target SSID's
  last-seen channel from recon.db (verified unit-tested end to end).
- Harness: pi.dev prompt section removed; Copy Token inline; robot icon.
- Recon: compare checkboxes no longer hide the AP list (multi-select
  stays visible, rows highlighted, clients table no longer suppressed);
  Previous Scans buttons moved above the dropdown with a Delete All;
  encryption chips + buckets now distinguish WPA2/WPA3 PSK vs Enterprise
  (AKM suites decoded from recon bitfield bits 32-47); scan JSON carries
  GPS when a fix exists; Reports tab shows a GPS column.
- fix: restore top-level EVIL_ENC definition lost in the repo (deployed
  build had it; repo would have thrown at init).
This commit is contained in:
2026-08-18 22:35:51 -05:00
parent b6be1c40a8
commit 60a726dc70
8 changed files with 375 additions and 572 deletions
@@ -29,12 +29,20 @@ 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: 'pineap', label: 'PineAP', hash: '#/pineap', icon: 'pineap', children: [
{ key: 'pineap_evilwpa', label: 'Evil WPA', hash: '#/pineap/evilwpa', icon: 'attack' },
{ key: 'pineap_open', label: 'Evil Open', hash: '#/pineap/open', icon: 'wifi' },
{ key: 'pineap_enterprise', label: 'Evil Enterprise', hash: '#/pineap/enterprise', icon: 'record' },
{ key: 'pineap_impersonation', label: 'Impersonation', hash: '#/pineap/impersonation', icon: 'place' },
{ key: 'pineap_clients', label: 'Clients', hash: '#/pineap/clients', icon: 'pager' },
{ key: 'pineap_filtering', label: 'Filtering', hash: '#/pineap/filtering', icon: 'settings' }
]
},
{ key: 'recon', label: 'Recon', hash: '#/recon', icon: 'recon' },
{ key: 'logging', label: 'Logging', hash: '#/logging', icon: 'logging' },
{ key: 'modules', label: 'Payloads', hash: '#/modules', icon: 'modules' },
{ key: 'harness', label: 'Harness', hash: '#/harness', icon: 'extension' },
{ key: 'harness', label: 'Harness', hash: '#/harness', icon: 'robot' },
{ key: 'settings', label: 'Settings', hash: '#/settings', icon: 'settings' }
];
const railDividers = new Set(['logging']);
@@ -63,6 +71,13 @@ const App = (() => {
rail.appendChild(d);
}
rail.appendChild(railEntry(it));
if (it.children) {
it.children.forEach((sub) => {
const s = railEntry(sub);
s.classList.add('sub');
rail.appendChild(s);
});
}
});
const foot = document.createElement('div');
foot.className = 'rail-footer';
@@ -85,11 +100,22 @@ const App = (() => {
function route() {
closeToolbarMenus();
const hash = (location.hash || '#/dashboard').replace(/\/+$/, '');
let hash = (location.hash || '#/dashboard').replace(/\/+$/, '');
if (hash === '#/recon/survey') {
location.hash = '#/recon';
return;
}
if (hash.indexOf('#/attacks') === 0) {
const map = {
'#/attacks': '#/pineap',
'#/attacks/wpa': '#/pineap/evilwpa',
'#/attacks/open': '#/pineap/open',
'#/attacks/enterprise': '#/pineap/enterprise'
};
hash = map[hash] || '#/pineap';
location.replace(hash);
return;
}
const name = routes[hash];
if (currentView && currentView.destroy) currentView.destroy();
els.content.innerHTML = '';
@@ -103,9 +129,13 @@ const App = (() => {
currentView = views[name](els.content);
}
const key = keyOf(hash);
const subs = els.rail.querySelectorAll('.entry.sub');
const subActive = Array.prototype.some.call(subs, (s) => s.getAttribute('href') === hash);
Array.prototype.forEach.call(els.rail.querySelectorAll('.entry'), (a) => {
const href = a.getAttribute('href');
a.classList.toggle('active', !!href && (href === hash || keyOf(href) === key));
const exact = !!href && href === hash;
const groupActive = !subActive && !!href && keyOf(href) === key;
a.classList.toggle('active', exact || groupActive);
});
}
@@ -395,13 +425,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',
'#/pineap/enterprise': 'pineap_enterprise',
'#/pineap/open': 'pineap_open',
'#/pineap/impersonation': 'pineap_impersonation',
'#/pineap/clients': 'pineap_clients',
'#/pineap/filtering': 'pineap_filtering',