Files
AppStore/public/js/app.js
2026-04-11 11:40:44 -05:00

122 lines
4.3 KiB
JavaScript

document.addEventListener('DOMContentLoaded', loadApps);
async function loadApps() {
const res = await fetch('/api/apps');
if (res.status === 401) { location.href = '/login'; return; }
const apps = await res.json();
const grid = document.getElementById('apps');
const empty = document.getElementById('empty');
if (apps.length === 0) {
grid.style.display = 'none';
empty.style.display = 'block';
return;
}
grid.innerHTML = apps.map(app => `
<div class="app-card" onclick="showApp('${app.id}')">
<div class="app-icon">
${app.latest_icon
? `<img src="/icons/${app.latest_icon}" alt="${app.name}">`
: app.name.charAt(0).toUpperCase()}
</div>
<div class="app-info">
<div class="app-name">${esc(app.name)}</div>
<div class="app-bundle">${esc(app.bundle_id)}</div>
<div class="app-version">v${esc(app.latest_version || '?')}${app.latest_build_number ? ` (${esc(app.latest_build_number)})` : ''} &middot; ${timeAgo(app.latest_uploaded_at)}</div>
</div>
<div class="app-action">
<a class="install-btn" href="itms-services://?action=download-manifest&url=${encodeURIComponent(location.origin + '/api/manifest/' + app.latest_build_id)}" onclick="event.stopPropagation()">Install</a>
</div>
</div>
`).join('');
}
async function showApp(appId) {
const res = await fetch(`/api/apps/${appId}`);
const app = await res.json();
const modal = document.getElementById('modal');
const body = document.getElementById('modal-body');
const icon = app.builds[0]?.icon_filename;
body.innerHTML = `
<div class="modal-header">
<div class="app-icon">
${icon ? `<img src="/icons/${icon}" alt="${app.name}">` : app.name.charAt(0).toUpperCase()}
</div>
<div>
<div class="app-name" style="font-size:18px">${esc(app.name)}</div>
<div class="app-bundle">${esc(app.bundle_id)}</div>
</div>
</div>
<h3 style="font-size:14px;color:var(--text-muted);margin-bottom:8px">Builds (${app.builds.length})</h3>
<div class="build-list">
${app.builds.map(b => `
<div class="build-item" id="build-${b.id}">
<div class="build-meta">
<div class="build-version">v${esc(b.version)} (${esc(b.build_number || '?')})</div>
<div class="build-date">${new Date(b.uploaded_at + 'Z').toLocaleDateString()} &middot; ${formatSize(b.size)}</div>
${b.notes ? `<div class="build-notes">${esc(b.notes)}</div>` : ''}
</div>
<div class="build-actions">
<a class="install-btn btn-sm" href="itms-services://?action=download-manifest&url=${encodeURIComponent(location.origin + '/api/manifest/' + b.id)}">Install</a>
<button class="delete-btn" onclick="deleteBuild('${b.id}', '${appId}')" title="Delete">&times;</button>
</div>
</div>
`).join('')}
</div>
<div class="modal-footer">
<button class="btn btn-sm btn-danger" onclick="deleteApp('${appId}')">Delete App</button>
</div>
`;
modal.style.display = 'flex';
modal.onclick = (e) => { if (e.target === modal) closeModal(); };
}
function closeModal() {
document.getElementById('modal').style.display = 'none';
}
async function deleteBuild(buildId, appId) {
if (!confirm('Delete this build?')) return;
await fetch(`/api/builds/${buildId}`, { method: 'DELETE' });
closeModal();
loadApps();
}
async function deleteApp(appId) {
if (!confirm('Delete this app and all its builds?')) return;
await fetch(`/api/apps/${appId}`, { method: 'DELETE' });
closeModal();
loadApps();
}
function esc(s) {
if (!s) return '';
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function formatSize(bytes) {
if (!bytes) return '';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function timeAgo(dateStr) {
if (!dateStr) return '';
const d = new Date(dateStr + 'Z');
const now = new Date();
const diff = (now - d) / 1000;
if (diff < 60) return 'just now';
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
if (diff < 604800) return Math.floor(diff / 86400) + 'd ago';
return d.toLocaleDateString();
}