Autarch/web/templates/iphone_exploit.html

523 lines
26 KiB
HTML
Raw Permalink Normal View History

{% extends "base.html" %}
{% block title %}iPhone Exploit - AUTARCH{% endblock %}
{% block content %}
<div class="page-header">
<h1>iPhone USB Exploitation</h1>
</div>
<!-- Status Cards -->
<div class="stats-grid" style="grid-template-columns:repeat(auto-fit,minmax(140px,1fr))">
<div class="stat-card">
<div class="stat-label">Tools Found</div>
<div class="stat-value small">{{ status.found }} / {{ status.total }}</div>
</div>
<div class="stat-card" id="card-devices">
<div class="stat-label">Devices</div>
<div class="stat-value small" id="device-count">--</div>
</div>
<div class="stat-card" id="card-paired">
<div class="stat-label">Paired</div>
<div class="stat-value small" id="pair-status">--</div>
</div>
</div>
<!-- Missing Tools Warning -->
{% if status.missing %}
<div class="card" style="padding:0.8rem 1rem;margin:1rem 0;background:#442;color:#ffa;border-radius:4px;font-size:0.9rem">
Missing tools: {{ status.missing | join(', ') }}. Install libimobiledevice and ifuse for full functionality.
</div>
{% endif %}
<!-- Device Selector -->
<div class="card" style="margin:1rem 0;padding:0.8rem 1rem;display:flex;align-items:center;gap:0.8rem;flex-wrap:wrap">
<label style="font-weight:600">Device:</label>
<select id="device-select" style="flex:1;min-width:200px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
<option value="">-- Select --</option>
</select>
<button class="btn btn-sm" onclick="refreshDevices()">Refresh</button>
</div>
<!-- Tabs -->
<div class="tabs" style="display:flex;gap:0;border-bottom:2px solid var(--border-color);margin-bottom:1rem">
<button class="tab-btn active" data-tab="device" onclick="switchTab('device',this)">Device</button>
<button class="tab-btn" data-tab="capture" onclick="switchTab('capture',this)">Capture</button>
<button class="tab-btn" data-tab="apps" onclick="switchTab('apps',this)">Apps</button>
<button class="tab-btn" data-tab="backup" onclick="switchTab('backup',this)">Backup</button>
<button class="tab-btn" data-tab="fs" onclick="switchTab('fs',this)">Filesystem</button>
<button class="tab-btn" data-tab="profiles" onclick="switchTab('profiles',this)">Profiles</button>
</div>
<!-- ── Device Tab ── -->
<div class="tab-panel active" id="tab-device">
<div class="card" style="padding:1rem;margin-bottom:1rem">
<h3>Device Management</h3>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin:0.8rem 0">
<button class="btn" onclick="iphDeviceInfo()">Device Info</button>
<button class="btn" onclick="iphFingerprint()">Full Fingerprint</button>
<button class="btn btn-primary" onclick="iphReconExport()">Export Recon Report</button>
</div>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin:0.8rem 0">
<button class="btn" onclick="iphPair()">Pair</button>
<button class="btn" onclick="iphValidate()">Validate Pair</button>
<button class="btn btn-warning" onclick="iphUnpair()">Unpair</button>
</div>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.8rem 0">
<button class="btn" onclick="iphGetName()">Get Name</button>
<div>
<label style="font-size:0.85rem">New name:</label>
<input type="text" id="iph-name" placeholder="My iPhone" style="width:150px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn" onclick="iphSetName()">Set Name</button>
</div>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin:0.8rem 0">
<button class="btn btn-warning" onclick="iphRestart()">Restart</button>
<button class="btn btn-danger" onclick="iphShutdown()">Shutdown</button>
<button class="btn" onclick="iphSleep()">Sleep</button>
</div>
</div>
<div class="card output-panel" id="device-output" style="padding:1rem;max-height:400px;overflow-y:auto">
<pre style="margin:0;white-space:pre-wrap;font-size:0.85rem">Select a device and run a command...</pre>
</div>
</div>
<!-- ── Capture Tab ── -->
<div class="tab-panel" id="tab-capture" style="display:none">
<div class="card" style="padding:1rem;margin-bottom:1rem">
<h3>Capture</h3>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin:0.8rem 0">
<button class="btn" onclick="iphScreenshot()">Screenshot</button>
<button class="btn" onclick="iphCrashReports()">Crash Reports</button>
</div>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.8rem 0">
<div>
<label style="font-size:0.85rem">Duration (s):</label>
<input type="number" id="iph-syslog-dur" value="5" style="width:60px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn" onclick="iphSyslog()">Syslog Dump</button>
</div>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.8rem 0">
<div>
<label style="font-size:0.85rem">Grep pattern:</label>
<input type="text" id="iph-grep-pat" value="password|token|key|secret" style="width:250px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<div>
<label style="font-size:0.85rem">Duration (s):</label>
<input type="number" id="iph-grep-dur" value="5" style="width:60px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn btn-warning" onclick="iphSyslogGrep()">Syslog Grep (Sensitive)</button>
</div>
</div>
<div class="card output-panel" id="capture-output" style="padding:1rem;max-height:400px;overflow-y:auto">
<pre style="margin:0;white-space:pre-wrap;font-size:0.85rem">Ready for capture operations...</pre>
</div>
</div>
<!-- ── Apps Tab ── -->
<div class="tab-panel" id="tab-apps" style="display:none">
<div class="card" style="padding:1rem;margin-bottom:1rem">
<h3>App Management</h3>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.8rem 0">
<div>
<label style="font-size:0.85rem">App type:</label>
<select id="iph-app-type" style="padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
<option value="user">User</option>
<option value="system">System</option>
<option value="all">All</option>
</select>
</div>
<button class="btn" onclick="iphListApps()">List Apps</button>
</div>
<!-- Install IPA -->
<div style="margin:0.8rem 0;padding:0.8rem;background:var(--bg-secondary);border-radius:4px">
<strong>Install IPA</strong>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin-top:0.5rem">
<div>
<label style="font-size:0.85rem">IPA file:</label>
<input type="file" id="iph-ipa-file" accept=".ipa" style="font-size:0.85rem">
</div>
<button class="btn" onclick="iphInstallApp()">Install</button>
</div>
</div>
<!-- Uninstall -->
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.8rem 0">
<div>
<label style="font-size:0.85rem">Bundle ID:</label>
<input type="text" id="iph-uninstall-bid" placeholder="com.example.app" style="width:250px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn btn-danger" onclick="iphUninstallApp()">Uninstall</button>
</div>
</div>
<div class="card output-panel" id="apps-output" style="padding:1rem;max-height:400px;overflow-y:auto">
<pre style="margin:0;white-space:pre-wrap;font-size:0.85rem">Ready for app operations...</pre>
</div>
</div>
<!-- ── Backup Tab ── -->
<div class="tab-panel" id="tab-backup" style="display:none">
<div class="card" style="padding:1rem;margin-bottom:1rem">
<h3>Backup & Data Extraction</h3>
<!-- Create Backup -->
<div style="margin:0.8rem 0;padding:0.8rem;background:var(--bg-secondary);border-radius:4px">
<strong>Create Backup</strong>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin-top:0.5rem">
<label><input type="checkbox" id="iph-bkup-enc"> Encrypted</label>
<div>
<label style="font-size:0.85rem">Password:</label>
<input type="password" id="iph-bkup-pwd" placeholder="(if encrypted)" style="width:150px;padding:0.4rem;background:var(--bg-primary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn btn-primary" onclick="iphBackupCreate()">Create Backup</button>
</div>
</div>
<!-- List & Select Backup -->
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.8rem 0">
<button class="btn" onclick="iphBackupList()">List Backups</button>
<div>
<label style="font-size:0.85rem">Backup path:</label>
<input type="text" id="iph-bkup-path" placeholder="/path/to/backup" style="width:300px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
</div>
<!-- Extract Data -->
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin:0.8rem 0">
<button class="btn" onclick="iphBkupSms()">Extract SMS/iMessage</button>
<button class="btn" onclick="iphBkupContacts()">Extract Contacts</button>
<button class="btn" onclick="iphBkupCalls()">Extract Call Log</button>
<button class="btn" onclick="iphBkupNotes()">Extract Notes</button>
</div>
<!-- Browse Files -->
<div style="margin:0.8rem 0;padding:0.8rem;background:var(--bg-secondary);border-radius:4px">
<strong>Browse Backup Files</strong>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin-top:0.5rem">
<div>
<label style="font-size:0.85rem">Domain filter:</label>
<input type="text" id="iph-bkup-domain" placeholder="(optional)" style="width:150px;padding:0.4rem;background:var(--bg-primary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<div>
<label style="font-size:0.85rem">Path filter:</label>
<input type="text" id="iph-bkup-pathf" placeholder="(optional)" style="width:150px;padding:0.4rem;background:var(--bg-primary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn" onclick="iphBkupFiles()">Browse Files</button>
</div>
</div>
<!-- Extract File -->
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.8rem 0">
<div>
<label style="font-size:0.85rem">File hash:</label>
<input type="text" id="iph-bkup-hash" placeholder="SHA1 hash" style="width:320px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<div>
<label style="font-size:0.85rem">Output name:</label>
<input type="text" id="iph-bkup-outname" placeholder="(optional)" style="width:150px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn" onclick="iphBkupExtract()">Extract File</button>
</div>
</div>
<div class="card output-panel" id="backup-output" style="padding:1rem;max-height:400px;overflow-y:auto">
<pre style="margin:0;white-space:pre-wrap;font-size:0.85rem">Ready for backup operations...</pre>
</div>
</div>
<!-- ── Filesystem Tab ── -->
<div class="tab-panel" id="tab-fs" style="display:none">
<div class="card" style="padding:1rem;margin-bottom:1rem">
<h3>Filesystem (ifuse)</h3>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin:0.8rem 0">
<button class="btn" onclick="iphMount()">Mount Filesystem</button>
</div>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.8rem 0">
<div>
<label style="font-size:0.85rem">Bundle ID:</label>
<input type="text" id="iph-mount-bid" placeholder="com.example.app" style="width:250px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn" onclick="iphMountApp()">Mount App Documents</button>
</div>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.8rem 0">
<div>
<label style="font-size:0.85rem">Mountpoint:</label>
<input type="text" id="iph-umount" placeholder="/path/to/mount" style="width:300px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn btn-warning" onclick="iphUnmount()">Unmount</button>
</div>
</div>
<div class="card output-panel" id="fs-output" style="padding:1rem;max-height:400px;overflow-y:auto">
<pre style="margin:0;white-space:pre-wrap;font-size:0.85rem">Ready for filesystem operations...</pre>
</div>
</div>
<!-- ── Profiles Tab ── -->
<div class="tab-panel" id="tab-profiles" style="display:none">
<div class="card" style="padding:1rem;margin-bottom:1rem">
<h3>Configuration Profiles</h3>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin:0.8rem 0">
<button class="btn" onclick="iphProfilesList()">List Profiles</button>
</div>
<!-- Install Profile -->
<div style="margin:0.8rem 0;padding:0.8rem;background:var(--bg-secondary);border-radius:4px">
<strong>Install Profile</strong>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin-top:0.5rem">
<div>
<label style="font-size:0.85rem">Profile (.mobileconfig / .mobileprovision):</label>
<input type="file" id="iph-profile-file" accept=".mobileconfig,.mobileprovision" style="font-size:0.85rem">
</div>
<button class="btn" onclick="iphProfileInstall()">Install</button>
</div>
</div>
<!-- Remove Profile -->
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.8rem 0">
<div>
<label style="font-size:0.85rem">Profile ID:</label>
<input type="text" id="iph-profile-id" placeholder="profile-identifier" style="width:250px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn btn-danger" onclick="iphProfileRemove()">Remove</button>
</div>
</div>
<div class="card output-panel" id="profiles-output" style="padding:1rem;max-height:400px;overflow-y:auto">
<pre style="margin:0;white-space:pre-wrap;font-size:0.85rem">Ready for profile operations...</pre>
</div>
</div>
<!-- ── Network (Port Forward) ── -->
<div class="card" style="padding:1rem;margin-top:1rem">
<h3>Port Forward (iproxy)</h3>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:end;margin:0.5rem 0">
<div>
<label style="font-size:0.85rem">Local port:</label>
<input type="number" id="iph-pf-local" placeholder="2222" style="width:80px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<div>
<label style="font-size:0.85rem">Device port:</label>
<input type="number" id="iph-pf-device" placeholder="22" style="width:80px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px">
</div>
<button class="btn" onclick="iphPortForward()">Forward</button>
</div>
<div class="card output-panel" id="net-output" style="padding:0.8rem;max-height:200px;overflow-y:auto;margin-top:0.5rem">
<pre style="margin:0;white-space:pre-wrap;font-size:0.85rem">Ready...</pre>
</div>
</div>
<style>
.tab-btn{padding:0.6rem 1.2rem;background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:0.95rem;border-bottom:2px solid transparent;margin-bottom:-2px}
.tab-btn.active{color:var(--accent-primary);border-bottom-color:var(--accent-primary);font-weight:600}
.tab-btn:hover{color:var(--text-primary)}
.btn-warning{background:#a85;}
.btn-danger{background:#a44;}
.btn-warning:hover{background:#b96;}
.btn-danger:hover{background:#b55;}
.output-panel pre{color:var(--text-secondary)}
</style>
<script>
const API = '/iphone-exploit';
let currentUDID = '';
function switchTab(name, btn) {
document.querySelectorAll('.tab-panel').forEach(p => p.style.display = 'none');
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById('tab-' + name).style.display = '';
btn.classList.add('active');
}
function getUDID() {
currentUDID = document.getElementById('device-select').value;
if (!currentUDID) {
alert('Select a device first');
return null;
}
return currentUDID;
}
function out(panel, html) {
document.querySelector('#' + panel + ' pre').innerHTML = html;
}
function outJson(panel, data) {
out(panel, escHtml(JSON.stringify(data, null, 2)));
}
function escHtml(s) {
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
async function api(endpoint, body, panel) {
out(panel, 'Working...');
try {
const res = await fetch(API + endpoint, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
});
const data = await res.json();
outJson(panel, data);
return data;
} catch(e) {
out(panel, 'Error: ' + escHtml(e.message));
return null;
}
}
async function apiForm(endpoint, formData, panel) {
out(panel, 'Uploading...');
try {
const res = await fetch(API + endpoint, {method:'POST', body: formData});
const data = await res.json();
outJson(panel, data);
return data;
} catch(e) {
out(panel, 'Error: ' + escHtml(e.message));
return null;
}
}
async function refreshDevices() {
try {
const res = await fetch(API + '/devices', {method:'POST', headers:{'Content-Type':'application/json'}, body:'{}'});
const data = await res.json();
const sel = document.getElementById('device-select');
const prev = sel.value;
sel.innerHTML = '<option value="">-- Select --</option>';
(data.devices || []).forEach(d => {
const opt = document.createElement('option');
opt.value = d.udid;
opt.textContent = (d.name || 'iOS Device') + ' - ' + (d.model || '') + ' iOS ' + (d.ios_version || '') + ' [' + d.udid.substring(0,12) + '...]';
sel.appendChild(opt);
});
document.getElementById('device-count').textContent = (data.devices || []).length;
if (prev) sel.value = prev;
} catch(e) {
console.error('Device refresh failed:', e);
}
}
// ── Device ──
function iphDeviceInfo() { const u=getUDID();if(!u)return; api('/device-info',{udid:u},'device-output'); }
function iphFingerprint() { const u=getUDID();if(!u)return; api('/fingerprint',{udid:u},'device-output'); }
function iphReconExport() { const u=getUDID();if(!u)return; api('/recon/export',{udid:u},'device-output'); }
function iphPair() { const u=getUDID();if(!u)return; api('/pair',{udid:u},'device-output'); }
function iphValidate() { const u=getUDID();if(!u)return; api('/validate-pair',{udid:u},'device-output'); }
function iphUnpair() { const u=getUDID();if(!u)return; if(!confirm('Unpair device?'))return; api('/unpair',{udid:u},'device-output'); }
function iphGetName() { const u=getUDID();if(!u)return; api('/get-name',{udid:u},'device-output'); }
function iphSetName() {
const u=getUDID();if(!u)return;
const name = document.getElementById('iph-name').value.trim();
if(!name){alert('Enter a name');return;}
api('/set-name',{udid:u, name:name},'device-output');
}
function iphRestart() { const u=getUDID();if(!u)return; if(!confirm('Restart device?'))return; api('/restart',{udid:u},'device-output'); }
function iphShutdown() { const u=getUDID();if(!u)return; if(!confirm('Shutdown device?'))return; api('/shutdown',{udid:u},'device-output'); }
function iphSleep() { const u=getUDID();if(!u)return; api('/sleep',{udid:u},'device-output'); }
// ── Capture ──
function iphScreenshot() { const u=getUDID();if(!u)return; api('/screenshot',{udid:u},'capture-output'); }
function iphCrashReports() { const u=getUDID();if(!u)return; api('/crash-reports',{udid:u},'capture-output'); }
function iphSyslog() {
const u=getUDID();if(!u)return;
api('/syslog',{udid:u, duration:parseInt(document.getElementById('iph-syslog-dur').value)||5},'capture-output');
}
function iphSyslogGrep() {
const u=getUDID();if(!u)return;
api('/syslog-grep',{udid:u,
pattern:document.getElementById('iph-grep-pat').value,
duration:parseInt(document.getElementById('iph-grep-dur').value)||5},'capture-output');
}
// ── Apps ──
function iphListApps() {
const u=getUDID();if(!u)return;
api('/apps/list',{udid:u, type:document.getElementById('iph-app-type').value},'apps-output');
}
function iphInstallApp() {
const u=getUDID();if(!u)return;
const file = document.getElementById('iph-ipa-file').files[0];
if(!file){alert('Select an IPA file');return;}
const fd = new FormData();
fd.append('udid', u);
fd.append('file', file);
apiForm('/apps/install', fd, 'apps-output');
}
function iphUninstallApp() {
const u=getUDID();if(!u)return;
const bid = document.getElementById('iph-uninstall-bid').value.trim();
if(!bid){alert('Enter bundle ID');return;}
if(!confirm('Uninstall '+bid+'?'))return;
api('/apps/uninstall',{udid:u, bundle_id:bid},'apps-output');
}
// ── Backup ──
function iphBackupCreate() {
const u=getUDID();if(!u)return;
const enc = document.getElementById('iph-bkup-enc').checked;
const pwd = document.getElementById('iph-bkup-pwd').value;
out('backup-output','Creating backup (this may take several minutes)...');
api('/backup/create',{udid:u, encrypted:enc, password:pwd},'backup-output');
}
function iphBackupList() { api('/backup/list',{},'backup-output'); }
function _getBkupPath() {
const p = document.getElementById('iph-bkup-path').value.trim();
if(!p){alert('Enter backup path (list backups first)');return null;}
return p;
}
function iphBkupSms() { const p=_getBkupPath();if(!p)return; api('/backup/sms',{backup_path:p},'backup-output'); }
function iphBkupContacts() { const p=_getBkupPath();if(!p)return; api('/backup/contacts',{backup_path:p},'backup-output'); }
function iphBkupCalls() { const p=_getBkupPath();if(!p)return; api('/backup/calls',{backup_path:p},'backup-output'); }
function iphBkupNotes() { const p=_getBkupPath();if(!p)return; api('/backup/notes',{backup_path:p},'backup-output'); }
function iphBkupFiles() {
const p=_getBkupPath();if(!p)return;
api('/backup/files',{backup_path:p,
domain:document.getElementById('iph-bkup-domain').value.trim(),
path_filter:document.getElementById('iph-bkup-pathf').value.trim()},'backup-output');
}
function iphBkupExtract() {
const p=_getBkupPath();if(!p)return;
const hash = document.getElementById('iph-bkup-hash').value.trim();
if(!hash){alert('Enter file hash');return;}
api('/backup/extract-file',{backup_path:p, file_hash:hash,
output_name:document.getElementById('iph-bkup-outname').value.trim()||null},'backup-output');
}
// ── Filesystem ──
function iphMount() { const u=getUDID();if(!u)return; api('/fs/mount',{udid:u},'fs-output'); }
function iphMountApp() {
const u=getUDID();if(!u)return;
const bid = document.getElementById('iph-mount-bid').value.trim();
if(!bid){alert('Enter bundle ID');return;}
api('/fs/mount-app',{udid:u, bundle_id:bid},'fs-output');
}
function iphUnmount() {
const mp = document.getElementById('iph-umount').value.trim();
if(!mp){alert('Enter mountpoint');return;}
api('/fs/unmount',{mountpoint:mp},'fs-output');
}
// ── Profiles ──
function iphProfilesList() { const u=getUDID();if(!u)return; api('/profiles/list',{udid:u},'profiles-output'); }
function iphProfileInstall() {
const u=getUDID();if(!u)return;
const file = document.getElementById('iph-profile-file').files[0];
if(!file){alert('Select a profile file');return;}
const fd = new FormData();
fd.append('udid', u);
fd.append('file', file);
apiForm('/profiles/install', fd, 'profiles-output');
}
function iphProfileRemove() {
const u=getUDID();if(!u)return;
const pid = document.getElementById('iph-profile-id').value.trim();
if(!pid){alert('Enter profile ID');return;}
if(!confirm('Remove profile '+pid+'?'))return;
api('/profiles/remove',{udid:u, profile_id:pid},'profiles-output');
}
// ── Network ──
function iphPortForward() {
const u=getUDID();if(!u)return;
const lp = document.getElementById('iph-pf-local').value;
const dp = document.getElementById('iph-pf-device').value;
if(!lp||!dp){alert('Enter both ports');return;}
api('/port-forward',{udid:u, local_port:parseInt(lp), device_port:parseInt(dp)},'net-output');
}
// Init
refreshDevices();
</script>
{% endblock %}