Add builder service: scaffold, ASC API, devices UI, fastlane profile manager
Phase 1-3 of the builder subsystem on the Mac mini: - Express + SQLite + sessions scaffolding, LAN-only service on port 3090 - App Store Connect JWT client (ES256 signing, devices/profiles/bundleIds) - Device management UI with Apple-side registration - Fastlane sigh wrapper with profile cache + auto-install to ~/Library/ - launchd plist + deploy script for Mac mini supervision
This commit is contained in:
83
builder/public/js/devices.js
Normal file
83
builder/public/js/devices.js
Normal file
@@ -0,0 +1,83 @@
|
||||
const $ = (s) => document.querySelector(s);
|
||||
const esc = (s) => { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; };
|
||||
|
||||
function toast(msg, kind = '') {
|
||||
const t = $('#toast');
|
||||
t.textContent = msg;
|
||||
t.className = 'toast show ' + kind;
|
||||
setTimeout(() => t.classList.remove('show'), 3500);
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const res = await fetch('/api/devices');
|
||||
if (res.status === 401) { location.href = '/login'; return; }
|
||||
const devices = await res.json();
|
||||
const container = $('#devices-container');
|
||||
|
||||
if (!devices.length) {
|
||||
container.innerHTML = '<div class="card"><p style="color:var(--text-muted)">No devices registered yet.</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr><th>Name</th><th>UDID</th><th>Status</th><th>Added</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${devices.map(d => `
|
||||
<tr>
|
||||
<td>${esc(d.name) || '<span class="mono" style="color:var(--text-muted)">unnamed</span>'}</td>
|
||||
<td class="mono">${esc(d.udid)}</td>
|
||||
<td>${d.synced_at
|
||||
? '<span class="badge synced">Synced</span>'
|
||||
: '<span class="badge unsynced">Local only</span>'}</td>
|
||||
<td class="mono">${esc(d.added_at)}</td>
|
||||
<td><button class="delete-btn" data-udid="${esc(d.udid)}" title="Delete">×</button></td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
container.querySelectorAll('.delete-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Remove this device locally? (It will remain in the Apple portal.)')) return;
|
||||
const udid = btn.getAttribute('data-udid');
|
||||
const r = await fetch(`/api/devices/${udid}`, { method: 'DELETE' });
|
||||
if (r.ok) { toast('Removed', 'success'); load(); }
|
||||
else toast('Delete failed', 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#add-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
const body = {
|
||||
udid: form.udid.value.trim(),
|
||||
name: form.name.value.trim(),
|
||||
};
|
||||
const btn = form.querySelector('button[type=submit]');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Registering…';
|
||||
|
||||
const r = await fetch('/api/devices', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await r.json().catch(() => ({}));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Add Device';
|
||||
|
||||
if (r.ok) {
|
||||
toast(data.synced ? 'Registered with Apple' : 'Saved locally (ASC not configured)', 'success');
|
||||
form.reset();
|
||||
load();
|
||||
} else {
|
||||
toast(data.error || 'Register failed', 'error');
|
||||
}
|
||||
});
|
||||
|
||||
load();
|
||||
Reference in New Issue
Block a user