(() => {
const $ = (id) => document.getElementById(id);
const esc = (value) => String(value ?? '').replace(/[&<>"']/g, (c) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const state = {data:null, view:'overview', install:'apt', search:'', channel:'all', format:'all'};
const titles = {overview:'Overview',packages:'Packages',install:'Installation',repositories:'Repositories'};
const channelNames = {debian:'Debian / Ubuntu',rhel:'RHEL / AlmaLinux / Rocky Linux',suse:'SUSE / openSUSE'};
const fmtDate = (value) => value ? new Intl.DateTimeFormat('en-GB',{dateStyle:'medium',timeStyle:'short'}).format(new Date(value)) : '-';
const fmtSize = (n) => n < 1024 ? `${n} B` : n < 1048576 ? `${(n/1024).toFixed(1)} KiB` : `${(n/1048576).toFixed(1)} MiB`;
function toast(text){const t=$('toast');t.textContent=text;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),1600)}
function navigate(view){state.view=view;document.querySelectorAll('.view').forEach(x=>x.classList.toggle('active',x.id===view));document.querySelectorAll('.nav').forEach(x=>x.classList.toggle('active',x.dataset.view===view));$('view-title').textContent=titles[view]||view;location.hash=view}
function packageRow(p){return `
${esc(p.name)}
${esc(p.version)}
${esc(channelNames[p.channel]||p.channel)}${esc(p.format)}${esc(fmtSize(p.size_bytes))}Download `}
function renderOverview(){const d=state.data;if(!d)return;const debian=d.builds.debian||{},rhel=d.builds.rhel||{},suse=d.builds.suse||{};const latest=[debian,rhel,suse].find(x=>x.app_version)||{};$('latest-version').textContent=latest.app_version||'-';$('latest-build').textContent=fmtDate(latest.built_at);$('debian-status').textContent=debian.published?'READY':'NOT BUILT';$('rhel-status').textContent=rhel.published?'READY':'NOT BUILT';$('suse-status').textContent=suse.published?'READY':'NOT BUILT';$('recent-packages').innerHTML=d.packages.slice(0,6).map(packageRow).join('')||'No packages have been published yet.
'}
function renderPackages(){if(!state.data)return;const q=state.search.toLowerCase();const list=state.data.packages.filter(p=>(state.channel==='all'||p.channel===state.channel)&&(state.format==='all'||p.format===state.format)&&(!q||`${p.name} ${p.version} ${p.architecture}`.toLowerCase().includes(q)));$('packages-table').innerHTML=list.map(p=>`${esc(p.name)}
${esc(p.version)}
${esc(channelNames[p.channel]||p.channel)}${esc(p.format)}${esc(fmtDate(p.built_at))}Download `).join('')||'No matching packages.
'}
function instruction(kind){const d=state.data;const url=d.repository_url,dist=d.apt_distribution,comp=d.apt_component;if(kind==='dnf')return {title:'RHEL / AlmaLinux / Rocky Linux',desc:'Install from the signed DNF repository.',code:`sudo rpm --import ${url}/repo-key.gpg\n\nsudo tee /etc/yum.repos.d/rustbox.repo >/dev/null <<'REPO'\n[rustbox]\nname=RustBox Package Repository\nbaseurl=${url}/rpm/$basearch\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=${url}/repo-key.gpg\nREPO\n\nsudo dnf install rustbox`};if(kind==='suse')return {title:'SUSE / openSUSE',desc:'Install from the signed Zypper repository.',code:`sudo rpm --import ${url}/repo-key.gpg\nsudo zypper addrepo --refresh --gpgcheck ${url}/suse/x86_64/ rustbox\nsudo zypper refresh rustbox\nsudo zypper install rustbox`};return {title:'Debian / Ubuntu',desc:'Install from the signed APT repository.',code:`curl -fsSL ${url}/repo-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/rustbox.gpg\n\necho "deb [signed-by=/usr/share/keyrings/rustbox.gpg] ${url}/apt ${dist} ${comp}" | sudo tee /etc/apt/sources.list.d/rustbox.list\n\nsudo apt update\nsudo apt install rustbox`}}
function renderInstall(){if(!state.data)return;const x=instruction(state.install);$('install-title').textContent=x.title;$('install-desc').textContent=x.desc;$('install-command').textContent=x.code;document.querySelectorAll('[data-install]').forEach(b=>b.classList.toggle('active',b.dataset.install===state.install))}
function repoCard(title,url,links){return `${esc(title)}
${esc(url)}`}
function renderRepositories(){const d=state.data;if(!d)return;const u=d.repository_url,dist=encodeURIComponent(d.apt_distribution);$('repo-cards').innerHTML=repoCard('Debian / Ubuntu',`${u}/apt`,[['GPG public key',`${u}/repo-key.gpg`],['APT InRelease',`${u}/apt/dists/${dist}/InRelease`],['Build info',`${u}/DEBIAN-BUILD-INFO.txt`]])+repoCard('RHEL / AlmaLinux / Rocky Linux',`${u}/rpm/x86_64`,[['GPG public key',`${u}/repo-key.gpg`],['RPM repomd.xml',`${u}/rpm/x86_64/repodata/repomd.xml`],['Metadata signature',`${u}/rpm/x86_64/repodata/repomd.xml.asc`],['Build info',`${u}/RHEL-BUILD-INFO.txt`]])+repoCard('SUSE / openSUSE',`${u}/suse/x86_64`,[['GPG public key',`${u}/repo-key.gpg`],['RPM repomd.xml',`${u}/suse/x86_64/repodata/repomd.xml`],['Metadata signature',`${u}/suse/x86_64/repodata/repomd.xml.asc`],['Build info',`${u}/SUSE-BUILD-INFO.txt`]]);$('metadata').innerHTML=['debian','rhel','suse'].map(ch=>{const b=d.builds[ch]||{};return ``}).join('')}
function renderAll(){renderOverview();renderPackages();renderInstall();renderRepositories()}
async function refresh(){const btn=$('refresh'),icon=btn.querySelector('.refresh-icon');btn.disabled=true;icon?.classList.add('spinning');try{const r=await fetch('/api/v1/packages',{cache:'no-store'});if(!r.ok)throw new Error(`HTTP ${r.status}`);state.data=await r.json();renderAll();$('last-sync').textContent=`Last refresh: ${fmtDate(state.data.generated_at)}`;$('status-text').textContent=state.data.ready?'Repository ready':'Waiting for first build'}catch(e){$('status-text').textContent='Repository unavailable';console.error(e)}finally{btn.disabled=false;icon?.classList.remove('spinning')}}
function setTheme(theme){document.documentElement.dataset.theme=theme;const next=theme==='dark'?'light':'dark';const btn=$('theme-toggle');btn.setAttribute('aria-label',`Switch to ${next} theme`);btn.title=`Switch to ${next} theme`;localStorage.setItem('rustbox-repo-theme',theme)}
document.querySelectorAll('[data-view]').forEach(b=>b.addEventListener('click',()=>navigate(b.dataset.view)));
document.querySelectorAll('[data-go]').forEach(b=>b.addEventListener('click',()=>navigate(b.dataset.go)));
document.querySelectorAll('[data-install]').forEach(b=>b.addEventListener('click',()=>{state.install=b.dataset.install;renderInstall()}));
$('search').addEventListener('input',e=>{state.search=e.target.value;renderPackages()});$('channel').addEventListener('change',e=>{state.channel=e.target.value;renderPackages()});$('format').addEventListener('change',e=>{state.format=e.target.value;renderPackages()});$('refresh').addEventListener('click',refresh);$('copy-install').addEventListener('click',async()=>{try{await navigator.clipboard.writeText($('install-command').textContent);toast('Copied')}catch{toast('Copy failed')}});$('theme-toggle').addEventListener('click',()=>setTheme(document.documentElement.dataset.theme==='dark'?'light':'dark'));
try{setTheme(localStorage.getItem('rustbox-repo-theme')||'dark')}catch{}navigate(location.hash.slice(1)||'overview');window.addEventListener('hashchange',()=>navigate(location.hash.slice(1)||'overview'));refresh();setInterval(()=>{if(!document.hidden)refresh()},60000);
})();