This commit is contained in:
Mateusz Gruszczyński committed 2026-10-22 11:52:18 +02:00
1 parent ccccdc2031
commit cd64b9a14e
7 files changed
+123 -88

No files matched your search

+2
View File
@@ -12,6 +12,8 @@ Jeden kontener buildera buduje z jednego commita RustProxy:
## Panel repozytorium (RustProxy Package Registry)
The dashboard interface defaults to **English (`en-GB`)**, including its dates, navigation, notifications, and installation instructions. The **Docker discovery agent** installation card is shown only for **Linux (DEB/RPM)**, never for OPNsense or native FreeBSD. Package listings/API also omit any stray agent PKG files for the FreeBSD channels.
Strony startowe wszystkich trzech repozytoriów działają teraz jako **lekka aplikacja Python** (`dashboard/`) za istniejącym Nginx. Wygląd nawiązuje do panelu RustProxy 0.34.6 (ciemny, jasny i kremowy motyw). **APT, RPM i pkg są nadal obsługiwane bezpośrednio przez Nginx**, więc mechanizm aktualizacji paczek, ścieżki plików, podpisy GPG i atomowe przełączanie `current` nie zmieniają się.
Panel udostępnia:
+6 -6
View File
@@ -1,13 +1,13 @@
516d7009f920efa26f60acbd859fa62cd3c7e23953c805173150145b1428a033 .env.example
17298e7669ed82b4c98abfce803e1a3f0a265b0abdc2567bf1316aa15e98aad3 Dockerfile
ef47c4a3fad51d17ad690b0d6aa440d6ef3e355c5e9bd1824368f5450b4b7957 README.md
0a6ca99db70dafd639ff677e68c4ca28109d29b718229c40c7d0b14622c8011a README.md
862263fa1f46c20f0d1e4dac5ffcc75abd55c08211b2c3864c5f8764b9d87793 dashboard/.dockerignore
ced0339cb93478c2faf7b7b74215fe03375086e11ca957bd8892b52802fbbd3a dashboard/Dockerfile
4bd84a56b756d6109a4fde23c6fcb2fef1b290302312a55046b8487cb9c36d74 dashboard/app.py
345dbb2c4293405092445e159ce23053894d0c2335457f7299622ca42382dc05 dashboard/web/app.js
bd2ba3a11ee715ba41a94f3301bec4ce83d7177489190a755e165112df50f112 dashboard/app.py
a86070f70fe5fe159ac5b3e3c7643724f40761af3eb3a6fd40d584652e230c80 dashboard/web/app.js
f1ac0fb5c4da97db09ff328cb86f4bc763fbdd84d7310d6e8e713be1c8707bb9 dashboard/web/favicon.svg
c7427bf31438cbbec9152c93674088bb2a16ea1d9a499b3c5c85beee2c543dda dashboard/web/index.html
a3834770bcc9c0548bf387fb52795f3f6cb15b253f7d3819df9be147b04ebb8e dashboard/web/style.css
37a6c0a8f6d10cca3147ed19043cd801eb76f4506e6fb94702a1841fc971e242 dashboard/web/index.html
4ea25df28ed237a616085ac183aaf760cdbed3bf957e8f21859274513da14d0d dashboard/web/style.css
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data/.gitkeep
328b8b8075208e0c92793cf70c6d8999775faf94abf5d839101e6d3ba35597cf docker-compose.yml
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 gpg/.gitkeep
@@ -70,5 +70,5 @@ e82c52ef15183a539e96909a39e44db68c8301543950e145008b563524b0b9b9 tests/freebsd-
c5781dd2335cc85e72daffe807c7ba24481b6c2e8bc95f4012be4ea1baeac9a8 tests/log-rotation.sh
aab57cd66dc157dc2359d8d57612c174570a7ea09fe753428dce8faad231ba3c tests/opnsense-plugin.sh
1227d503f784f7765c5950b603e4258ea155c2fdc3779886a7b0c403320be2a8 tests/static.sh
ff0fe09dcd33124644a9babdf951193ebb358015997b302c7e3775a6c7a4d762 tests/test_dashboard.py
9d33b7053024dee7b241d87182a895a7b2ee81c7094a97a3428a9b6036a5052a tests/test_dashboard.py
c8de9ebbf51e017643d0735f27b960a6cf3c9b7122f6de32858f6b7dcc7ba9e9 tests/toolchains.sh
+3
View File
@@ -138,6 +138,9 @@ def package_entry(path: Path, current: Path, site: str, kind: str, channel: str,
if not parsed or not path.is_file():
return None
name, version, arch = parsed
# Discovery agent is packaged for Linux DEB/RPM only; never advertise it as FreeBSD pkg.
if channel in ('opnsense', 'freebsd') and name == 'rustproxy-docker-agent':
return None
try:
stat = path.stat()
except OSError:
+45 -40
View File
@@ -5,10 +5,10 @@
const el = (id) => document.getElementById(id);
const CHANNEL_LABELS = {linux:'Linux',opnsense:'OPNsense',freebsd:'FreeBSD'};
const INSTALL_NAMES = {deb:'Debian / Ubuntu',rpm:'RHEL / Rocky / Alma / Fedora',opnsense:'OPNsense',freebsd:'FreeBSD native'};
const SECTION_LABELS = {overview:'Przegląd',packages:'Paczki i wersje',install:'Instalacja',repository:'Repozytoria'};
const SECTION_LABELS = {overview:'Overview',packages:'Packages & versions',install:'Installation',repository:'Repositories'};
const esc = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[char]));
const collator = new Intl.Collator('pl', {numeric:true,sensitivity:'base'});
const formatNumber = new Intl.NumberFormat('pl-PL');
const collator = new Intl.Collator('en', {numeric:true,sensitivity:'base'});
const formatNumber = new Intl.NumberFormat('en-GB');
const site = ['linux','opnsense','freebsd'].includes(document.body.dataset.site) ? document.body.dataset.site : 'linux';
const state = {
site, catalog:null, section:'overview', installing:site === 'linux' ? 'deb' : site,
@@ -20,13 +20,13 @@
if (!value) return '—';
const time = new Date(value);
if (Number.isNaN(time.valueOf())) return '—';
return new Intl.DateTimeFormat('pl-PL', {year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}).format(time);
return new Intl.DateTimeFormat('en-GB', {year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}).format(time);
};
const formatShortDate = (value) => {
if (!value) return '—';
const time = new Date(value);
if (Number.isNaN(time.valueOf())) return '—';
return new Intl.DateTimeFormat('pl-PL', {day:'2-digit',month:'short',year:'numeric'}).format(time);
return new Intl.DateTimeFormat('en-GB', {day:'2-digit',month:'short',year:'numeric'}).format(time);
};
const formatSize = (value) => {
const size = Number(value);
@@ -43,7 +43,7 @@
return '#';
};
const origin = (url) => typeof url === 'string' ? url.replace(/\/$/, '') : '';
const dateLabel = (kind) => kind === 'build' ? 'Data zapisana przez builder' : 'Data pliku (starsze buildy bez znacznika czasu)';
const dateLabel = (kind) => kind === 'build' ? 'Build timestamp recorded by the builder' : 'File modification time (older builds without a build timestamp)';
const setText = (id, value) => {const node = el(id);if (node) node.textContent = value;};
const versionText = (b) => b?.app_version ? `v${b.app_version}` : '—';
@@ -82,7 +82,7 @@
? `PKG · ${build?.opnsense_series || 'FreeBSD'}` : `PKG · ${build?.freebsd_abi || 'Native'}`;
const version = build?.package_version || (live ? build?.app_version : '') || '—';
return `<article class="build-card${live ? '' : ' off'}">
<div class="build-card-top"><span>${esc(CHANNEL_LABELS[channel].toUpperCase())} / ${channel === 'linux' ? 'SIGNED' : 'PKG'}</span><span class="pill ${live ? 'live' : 'missing'}">${live ? 'PUBLISHED' : 'BRAK BUILDU'}</span></div>
<div class="build-card-top"><span>${esc(CHANNEL_LABELS[channel].toUpperCase())} / ${channel === 'linux' ? 'SIGNED' : 'PKG'}</span><span class="pill ${live ? 'live' : 'missing'}">${live ? 'PUBLISHED' : 'NOT BUILT'}</span></div>
<div class="build-card-version">${esc(version)}</div><div class="build-card-sub">${esc(subtitle)}</div>
<div class="build-card-details"><div><small>BUILD DATE</small><strong title="${esc(dateLabel(build?.date_source))}">${esc(formatDate(build?.built_at))}</strong></div><div><small>COMMIT</small><strong class="commit">${esc(commit)}</strong></div></div>
</article>`;
@@ -92,7 +92,7 @@
return `<span class="format-label${pkg.format === 'PKG' ? ' pkg' : ''}">${esc(pkg.format)}</span>`;
}
function tableRow(pkg, compact = false) {
const download = `<a class="download-link" href="${esc(safeLink(pkg.download_url))}" target="_blank" rel="noopener noreferrer" download title="Pobierz ${esc(pkg.name)} ${esc(pkg.version)}">${compact ? 'Pobierz' : 'Pobierz plik'} <span>↗</span></a>`;
const download = `<a class="download-link" href="${esc(safeLink(pkg.download_url))}" target="_blank" rel="noopener noreferrer" download title="Download ${esc(pkg.name)} ${esc(pkg.version)}">${compact ? 'Download' : 'Download file'} <span>↗</span></a>`;
const first = `<span class="artifact-name" title="${esc(pkg.name)}">${esc(pkg.name)}</span><span class="artifact-arch">${esc(pkg.architecture)}</span>`;
const format = `${formatBadge(pkg)}<span class="channel-tag ${pkg.format === 'PKG' ? 'pkg' : ''}">${esc(CHANNEL_LABELS[pkg.channel] || pkg.channel)}</span>`;
const date = `<span class="date-cell" title="${esc(dateLabel(pkg.date_source))}">${esc(formatDate(pkg.built_at))}</span>`;
@@ -112,29 +112,29 @@
setText('stat-channels', `${data.summary?.channels_published || 0} / 3`);
setText('stat-date', formatShortDate(data.summary?.latest_build_at));
setText('stat-version', heroVersion);
setText('stat-release', main?.package_version ? `Pakiet: ${main.package_version}` : 'Brak opublikowanej wersji');
setText('stat-date-sub', 'Data ostatniej publikacji');
setText('stat-release', main?.package_version ? `Package: ${main.package_version}` : 'No release published yet');
setText('stat-date-sub', 'Latest publication date');
setText('pipeline-updated', formatDate(data.generated_at));
const ready = !!data.ready;
el('hero-status').innerHTML = `<span class="pulse-dot"></span> ${ready ? 'REPOZYTORIUM AKTYWNE' : 'OCZEKIWANIE NA BUILD'}`;
el('hero-status').innerHTML = `<span class="pulse-dot"></span> ${ready ? 'REPOSITORY ONLINE' : 'WAITING FOR BUILD'}`;
el('hero-status').classList.toggle('waiting', !ready);
el('sidebar-status').classList.toggle('waiting', !ready);
el('sidebar-status').querySelector('span').textContent = ready ? 'REPOSITORY ONLINE' : 'OCZEKIWANIE NA DANE';
el('sidebar-status').querySelector('span').textContent = ready ? 'REPOSITORY ONLINE' : 'WAITING FOR DATA';
const side = el('side-channels');
side.innerHTML = ['linux','opnsense','freebsd'].map((channel) => `<span class="side-channel${data.builds[channel]?.published ? '' : ' disabled'}"><i class="tiny-dot"></i>${esc(CHANNEL_LABELS[channel])}<small>${channel === 'linux' ? 'APT / DNF' : 'PKG'}</small></span>`).join('');
el('build-grid').innerHTML = ['linux','opnsense','freebsd'].map((channel) => buildCard(data.builds[channel],channel)).join('');
const recent = data.packages.slice(0,6);
el('recent-rows').innerHTML = recent.length ? recent.map((pkg) => tableRow(pkg,true)).join('') : '<tr><td colspan="5" class="empty-table">Nie opublikowano jeszcze żadnych paczek. Katalog zapełni się po pierwszym buildzie.</td></tr>';
const recent = data.packages.filter((pkg) => state.site === 'linux' || pkg.channel === state.site).slice(0,6);
el('recent-rows').innerHTML = recent.length ? recent.map((pkg) => tableRow(pkg,true)).join('') : '<tr><td colspan="5" class="empty-table">No packages published yet. The catalog will populate after the first build.</td></tr>';
}
function filteredPackages() {
const arr = state.catalog?.packages || [];
const search = state.search.trim().toLocaleLowerCase('pl-PL');
const search = state.search.trim().toLocaleLowerCase('en-GB');
const filtered = arr.filter((pkg) => {
if (state.channel !== 'all' && pkg.channel !== state.channel) return false;
if (state.format !== 'all' && pkg.format !== state.format) return false;
if (!search) return true;
return [pkg.name,pkg.version,pkg.channel,pkg.format,pkg.architecture].some((field) => String(field ?? '').toLocaleLowerCase('pl-PL').includes(search));
return [pkg.name,pkg.version,pkg.channel,pkg.format,pkg.architecture].some((field) => String(field ?? '').toLocaleLowerCase('en-GB').includes(search));
});
if (state.sort === 'name') filtered.sort((a,b) => collator.compare(a.name,b.name) || collator.compare(b.version,a.version));
if (state.sort === 'version') filtered.sort((a,b) => collator.compare(b.version,a.version) || collator.compare(a.name,b.name));
@@ -147,10 +147,10 @@
if (!data) return;
const all = filteredPackages();
const shown = all.slice(0,state.rows);
setText('packages-total', `${formatNumber.format(data.summary.packages_count)} paczek`);
setText('filter-count', `${formatNumber.format(all.length)} ${all.length === 1 ? 'wynik' : 'wyników'}`);
setText('result-range', all.length ? `Wyświetlono ${shown.length} z ${all.length}` : 'Brak pasujących paczek');
el('catalog-rows').innerHTML = shown.length ? shown.map((pkg) => tableRow(pkg)).join('') : '<tr><td colspan="6" class="empty-table">Brak paczek spełniających kryteria. Zmień filtry lub poczekaj na publikację.</td></tr>';
setText('packages-total', `${formatNumber.format(data.summary.packages_count)} packages`);
setText('filter-count', `${formatNumber.format(all.length)} ${all.length === 1 ? 'result' : 'results'}`);
setText('result-range', all.length ? `Showing ${shown.length} of ${all.length}` : 'No matching packages');
el('catalog-rows').innerHTML = shown.length ? shown.map((pkg) => tableRow(pkg)).join('') : '<tr><td colspan="6" class="empty-table">No packages match these filters. Adjust your search or check back after a new release.</td></tr>';
el('show-more').hidden = shown.length >= all.length;
}
@@ -163,9 +163,9 @@
const component = data.apt_component || 'main';
if (kind === 'deb') {
return {
description:'Dodaj podpisane repozytorium APT, odśwież listę pakietów i zainstaluj RustProxy.',
description:'Add the signed APT repository, update the package list, and install RustProxy.',
signature:'GPG SIGNED',
note:'Obsługiwane: Debian / Ubuntu · architektura amd64.',
note:'Supported: Debian / Ubuntu · amd64 architecture.',
reference:`${linux}/apt/dists/${encodeURIComponent(distribution)}/InRelease`,
agent:'sudo apt install rustproxy-docker-agent',
code:[
@@ -182,9 +182,9 @@
}
if (kind === 'rpm') {
return {
description:'Dodaj podpisane repozytorium DNF/YUM i zainstaluj serwer RustProxy.',
description:'Add the signed DNF/YUM repository and install the RustProxy server.',
signature:'GPG SIGNED',
note:'Obsługiwane: RHEL / Rocky / Alma / Fedora · architektura x86_64.',
note:'Supported: RHEL / Rocky / Alma / Fedora · x86_64 architecture.',
reference:`${linux}/rpm/x86_64/repodata/repomd.xml`,
agent:'sudo dnf install rustproxy-docker-agent',
code:[
@@ -206,11 +206,11 @@
}
if (kind === 'opnsense') {
return {
description:'Pobierz konfigurację repozytorium pkg i zainstaluj plugin os-rustproxy.',
description:'Fetch the pkg repository configuration and install the os-rustproxy plugin.',
signature:'UNSIGNED PKG',
note:'Repozytorium pkg nie ma podpisanego katalogu. Korzystaj tylko z zaufanego hosta HTTPS.',
note:'The pkg repository index is unsigned. Only use a trusted HTTPS host.',
reference:`${opnsense}/RustProxy.conf`,
agent:'sudo apt install rustproxy-docker-agent',
agent:null,
code:[
'mkdir -p /usr/local/etc/pkg/repos',
`fetch -o /usr/local/etc/pkg/repos/RustProxy.conf ${opnsense}/RustProxy.conf`,
@@ -220,11 +220,11 @@
};
}
return {
description:'Zainstaluj natywny pakiet rustproxy i włącz usługę rc.d.',
description:'Install the native rustproxy package and enable its rc.d service.',
signature:'UNSIGNED PKG',
note:'Dostępne po ustawieniu FREEBSD_NATIVE_ENABLED=1. Katalog pkg jest niesygnowany.',
note:'Requires FREEBSD_NATIVE_ENABLED=1. The pkg index is unsigned.',
reference:`${freebsd}/RustProxy-FreeBSD.conf`,
agent:'sudo dnf install rustproxy-docker-agent',
agent:null,
code:[
'mkdir -p /usr/local/etc/pkg/repos',
`fetch -o /usr/local/etc/pkg/repos/RustProxy-FreeBSD.conf ${freebsd}/RustProxy-FreeBSD.conf`,
@@ -251,7 +251,12 @@
el('install-signature').classList.toggle('live', instructionsData.signature === 'GPG SIGNED');
setText('install-command', instructionsData.code);
setText('install-note', instructionsData.note);
setText('agent-command', instructionsData.agent);
// Docker discovery agent is only built for Linux DEB/RPM.
const hasDockerAgent = (kind === 'deb' || kind === 'rpm') && !!instructionsData.agent;
el('agent-card').hidden = !hasDockerAgent;
el('install-extra-grid').classList.toggle('single-card', !hasDockerAgent);
setText('trust-index', hasDockerAgent ? '02' : '01');
setText('agent-command', hasDockerAgent ? instructionsData.agent : '');
el('install-reference').href = safeLink(instructionsData.reference);
}
@@ -269,7 +274,7 @@
const distribution = encodeURIComponent(data.apt_distribution || 'stable');
el('endpoints-grid').innerHTML = [
endpointsCard('Linux','GPG SIGNED',url.linux,[
{name:'Klucz publiczny GPG',url:`${url.linux}/repo-key.gpg`},
{name:'GPG public key',url:`${url.linux}/repo-key.gpg`},
{name:'APT InRelease',url:`${url.linux}/apt/dists/${distribution}/InRelease`},
{name:'RPM repomd.xml',url:`${url.linux}/rpm/x86_64/repodata/repomd.xml`},
{name:'Build info',url:`${url.linux}/LINUX-BUILD-INFO.txt`},
@@ -290,13 +295,13 @@
el('metadata-table').innerHTML = ['linux','opnsense','freebsd'].map((channel) => {
const b = data.builds[channel] || {};
const rows = [
['WERSJA APLIKACJI',b.app_version || '—','latest-value'],
['WERSJA PACZKI',b.package_version || '—',''],
['DATA BUILDU',formatDate(b.built_at),''],
['APPLICATION VERSION',b.app_version || '—','latest-value'],
['PACKAGE VERSION',b.package_version || '—',''],
['BUILD DATE',formatDate(b.built_at),''],
['SOURCE COMMIT',b.source_commit || '—',''],
['REWIZJA PAKOWANIA',b.packaging_revision || '—',''],
['PACKAGING REVISION',b.packaging_revision || '—',''],
];
return `<div class="meta-block"><h3>${esc(CHANNEL_LABELS[channel])}</h3>${rows.map(([label,value,klass]) => `<div class="meta-row"><small>${esc(label)}</small><strong class="${klass}"${label === 'DATA BUILDU' ? ` title="${esc(dateLabel(b.date_source))}"` : ''}>${esc(value)}</strong></div>`).join('')}</div>`;
return `<div class="meta-block"><h3>${esc(CHANNEL_LABELS[channel])}</h3>${rows.map(([label,value,klass]) => `<div class="meta-row"><small>${esc(label)}</small><strong class="${klass}"${label === 'BUILD DATE' ? ` title="${esc(dateLabel(b.date_source))}"` : ''}>${esc(value)}</strong></div>`).join('')}</div>`;
}).join('');
}
@@ -325,7 +330,7 @@
el('error-banner').hidden = true;
} catch (err) {
el('error-banner').hidden = false;
setText('last-sync','Błąd odczytu');
setText('last-sync','Load failed');
console.error('RustProxy repository:',err);
} finally {
state.refreshing = false;
@@ -349,8 +354,8 @@
input.remove();
if (!copied) throw new Error('Clipboard unsupported');
}
toast('Skopiowano do schowka');
} catch (_) { toast('Nie udało się skopiować — zaznacz polecenia ręcznie'); }
toast('Copied to clipboard');
} catch (_) { toast('Copy failed. Select and copy the command manually.'); }
}
function init() {
+42 -42
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="pl" data-theme="dark">
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
@@ -19,13 +19,13 @@
</a>
<div class="edition">INDEPENDENT BY DESIGN <span>BUILD DISTRIBUTION</span></div>
<div class="sidebar-label">WORKSPACE / 01</div>
<nav class="main-navigation" aria-label="Nawigacja panelu">
<button class="nav active" data-section="overview" aria-current="page"><span>01</span><b>Przegląd</b><span class="nav-arrow">↗</span></button>
<button class="nav" data-section="packages"><span>02</span><b>Paczki i wersje</b><span class="nav-arrow">↗</span></button>
<button class="nav" data-section="install"><span>03</span><b>Instalacja</b><span class="nav-arrow">↗</span></button>
<button class="nav" data-section="repository"><span>04</span><b>Repozytoria</b><span class="nav-arrow">↗</span></button>
<nav class="main-navigation" aria-label="Dashboard navigation">
<button class="nav active" data-section="overview" aria-current="page"><span>01</span><b>Overview</b><span class="nav-arrow">↗</span></button>
<button class="nav" data-section="packages"><span>02</span><b>Packages &amp; versions</b><span class="nav-arrow">↗</span></button>
<button class="nav" data-section="install"><span>03</span><b>Installation</b><span class="nav-arrow">↗</span></button>
<button class="nav" data-section="repository"><span>04</span><b>Repositories</b><span class="nav-arrow">↗</span></button>
</nav>
<div class="sidebar-label sidebar-label--second">DOSTĘPNE KANAŁY</div>
<div class="sidebar-label sidebar-label--second">AVAILABLE CHANNELS</div>
<div class="side-channels" id="side-channels">
<span class="side-channel"><i class="tiny-dot"></i> Linux <small>APT / DNF</small></span>
<span class="side-channel"><i class="tiny-dot"></i> OPNsense <small>PKG</small></span>
@@ -34,97 +34,97 @@
<div class="sidebar-bottom">
<div class="small-label">REPOSITORY ENGINE</div>
<p>Python / HTTP API<br>Nginx / Signed APT + RPM<br>FreeBSD pkg catalog</p>
<div class="local-tag" id="sidebar-status"><i></i><span>OCZEKIWANIE NA DANE</span></div>
<div class="local-tag" id="sidebar-status"><i></i><span>WAITING FOR DATA</span></div>
<div class="copyright">© RustProxy <span id="year"></span></div>
</div>
</aside>
<div class="workspace">
<header class="topbar">
<div class="crumb"><span>RUSTPROXY</span><b>/</b><span>REPOSITORY</span><b>/</b><strong id="crumb-section">Przegląd</strong></div>
<div class="crumb"><span>RUSTPROXY</span><b>/</b><span>REPOSITORY</span><b>/</b><strong id="crumb-section">Overview</strong></div>
<div class="top-actions">
<span class="live-sync" id="last-sync"><i></i> Odczyt danych…</span>
<button class="quiet icon-button" id="refresh" title="Odśwież listę paczek" aria-label="Odśwież dane">
<span class="live-sync" id="last-sync"><i></i> Loading data…</span>
<button class="quiet icon-button" id="refresh" title="Refresh package list" aria-label="Refresh data">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 7v5h-5M4 17v-5h5"/><path d="M5 9a8 8 0 0 1 13.4-2.2L20 12M4 12l1.6 5.2A8 8 0 0 0 19 15"/></svg>
</button>
<button class="quiet theme-switch" id="theme-toggle" title="Zmień motyw" aria-label="Zmień motyw"><span class="theme-glyph">◐</span><span id="theme-name">Dark</span></button>
<button class="quiet theme-switch" id="theme-toggle" title="Change theme" aria-label="Change theme"><span class="theme-glyph">◐</span><span id="theme-name">Dark</span></button>
</div>
</header>
<main class="main-content">
<div class="error-banner" id="error-banner" hidden role="status">Nie można pobrać katalogu. Automatyczna próba ponowienia za minutę. <button id="retry-inline">Spróbuj teraz</button></div>
<div class="error-banner" id="error-banner" hidden role="status">Could not load the catalog. Retrying automatically in one minute. <button id="retry-inline">Retry now</button></div>
<section class="view" id="view-overview" aria-labelledby="overview-title">
<div class="page-head">
<div>
<div class="eyebrow"><span class="eyebrow-line"></span> DISTRIBUTION CONTROL ROOM <span class="eyebrow-separator">/</span> LIVE CATALOG</div>
<h1 id="overview-title">Pakiety. Wersje.<br><em>Pełna kontrola.</em></h1>
<p class="lead">Jedno miejsce dla wydań RustProxy na Linux, OPNsense i FreeBSD. Aktualne buildy, daty publikacji i gotowe instrukcje instalacji.</p>
<h1 id="overview-title">Packages. Versions.<br><em>Stay in control.</em></h1>
<p class="lead">One place for RustProxy releases on Linux, OPNsense, and FreeBSD. Current builds, release dates, and ready-to-use install commands.</p>
</div>
<div class="page-head-side"><div class="subtle-label">STATUS / LIVE DATA</div><span class="active-status" id="hero-status"><span class="pulse-dot"></span> ODCZYTYWANIE</span><div class="muted-detail">Odświeżanie automatyczne co 60 sekund</div></div>
<div class="page-head-side"><div class="subtle-label">STATUS / LIVE DATA</div><span class="active-status" id="hero-status"><span class="pulse-dot"></span> LOADING</span><div class="muted-detail">Automatically refreshes every 60 seconds</div></div>
</div>
<div class="hero-grid">
<div class="hero-release">
<div class="hero-header"><span><span class="dot-amber"></span> NAJNOWSZE WYDANIE <span id="hero-channel">LINUX</span></span><span class="hero-corner">R / P — 01</span></div>
<div class="hero-header"><span><span class="dot-amber"></span> LATEST RELEASE <span id="hero-channel">LINUX</span></span><span class="hero-corner">R / P — 01</span></div>
<div class="hero-version-label">RUSTPROXY / APPLICATION VERSION</div>
<div class="hero-version" id="hero-version">—</div>
<div class="hero-foot">
<div><small>ZBUDOWANO / OPUBLIKOWANO</small><strong id="hero-date">Brak informacji</strong></div>
<div><small>BUILT / PUBLISHED</small><strong id="hero-date">Not available</strong></div>
<div><small>SOURCE COMMIT</small><strong class="mono" id="hero-commit">—</strong></div>
<button class="hero-link" data-go-section="packages">Pokaż wydania <span aria-hidden="true">↗</span></button>
<button class="hero-link" data-go-section="packages">View releases <span aria-hidden="true">↗</span></button>
</div>
<div class="hero-background-rings" aria-hidden="true"></div>
</div>
<div class="pipeline-panel">
<div class="pipeline-head"><span class="small-label">BUILD PIPELINE</span><span class="pipeline-live"><i class="tiny-dot"></i> READ ONLY</span></div>
<p>Od źródeł do gotowych paczek.</p>
<p>From source code to installable packages.</p>
<div class="pipeline-list">
<div class="pipeline-step"><span class="step-index">01</span><span class="step-info"><b>Git / source</b><small>RustProxy upstream</small></span><span class="step-right">GIT <span>↗</span></span></div>
<div class="pipeline-connector"></div>
<div class="pipeline-step"><span class="step-index">02</span><span class="step-info"><b>Build / package</b><small>Rust + Go + FreeBSD</small></span><span class="step-right">CI <span>↗</span></span></div>
<div class="pipeline-connector"></div>
<div class="pipeline-step"><span class="step-index">03</span><span class="step-info"><b>Signed repository</b><small>APT / RPM / PKG</small></span><span class="step-right step-success">LIVE <span>●</span></span></div>
<div class="pipeline-step"><span class="step-index">03</span><span class="step-info"><b>Repository publish</b><small>APT / RPM / PKG</small></span><span class="step-right step-success">LIVE <span>●</span></span></div>
</div>
<div class="pipeline-footer">Ostatni odczyt <span id="pipeline-updated">—</span></div>
<div class="pipeline-footer">Last sync <span id="pipeline-updated">—</span></div>
</div>
</div>
<div class="stats-grid" aria-label="Statystyki repozytorium">
<div class="stat"><span>01 / PACZEK W KATALOGU</span><strong id="stat-packages">—</strong><small>Wszystkie opublikowane artefakty</small></div>
<div class="stat"><span>02 / AKTYWNE KANAŁY</span><strong id="stat-channels">—</strong><small>Linux · OPNsense · FreeBSD</small></div>
<div class="stat"><span>03 / OSTATNI BUILD</span><strong class="stat-date" id="stat-date">—</strong><small id="stat-date-sub">UTC / czas publikacji</small></div>
<div class="stat"><span>04 / AKTUALNA WERSJA</span><strong id="stat-version">—</strong><small id="stat-release">Wersja aplikacji</small></div>
<div class="stats-grid" aria-label="Repository statistics">
<div class="stat"><span>01 / CATALOG PACKAGES</span><strong id="stat-packages">—</strong><small>All published artifacts</small></div>
<div class="stat"><span>02 / ACTIVE CHANNELS</span><strong id="stat-channels">—</strong><small>Linux · OPNsense · FreeBSD</small></div>
<div class="stat"><span>03 / LATEST BUILD</span><strong class="stat-date" id="stat-date">—</strong><small id="stat-date-sub">Latest publication time</small></div>
<div class="stat"><span>04 / CURRENT VERSION</span><strong id="stat-version">—</strong><small id="stat-release">Application version</small></div>
</div>
<div class="section-heading"><div><span class="section-index">01 / RELEASE CHANNELS</span><h2>Ostatnie buildy</h2></div><button class="text-link" data-go-section="packages">Cały katalog <span>↗</span></button></div>
<div class="build-grid" id="build-grid"><div class="skeleton-card">Ładowanie kanałów…</div></div>
<div class="section-heading"><div><span class="section-index">01 / RELEASE CHANNELS</span><h2>Latest builds</h2></div><button class="text-link" data-go-section="packages">View catalog <span>↗</span></button></div>
<div class="build-grid" id="build-grid"><div class="skeleton-card">Loading channels…</div></div>
<div class="section-heading section-heading--second"><div><span class="section-index">02 / RECENT ARTIFACTS</span><h2>Najnowsze paczki</h2></div><button class="text-link" data-go-section="packages">Przeglądaj paczki <span>↗</span></button></div>
<div class="section-heading section-heading--second"><div><span class="section-index">02 / RECENT ARTIFACTS</span><h2>Recent packages</h2></div><button class="text-link" data-go-section="packages">Browse packages <span>↗</span></button></div>
<div class="card recent-card">
<div class="table-scroller"><table class="data-table"><thead><tr><th>PAKIET</th><th>FORMAT / KANAŁ</th><th>WERSJA PACZKI</th><th>DATA BUILDU</th><th class="table-action-col">PLIK</th></tr></thead><tbody id="recent-rows"><tr><td colspan="5" class="empty-table">Ładowanie danych…</td></tr></tbody></table></div>
<div class="table-scroller"><table class="data-table"><thead><tr><th>PACKAGE</th><th>FORMAT / CHANNEL</th><th>PACKAGE VERSION</th><th>BUILD DATE</th><th class="table-action-col">FILE</th></tr></thead><tbody id="recent-rows"><tr><td colspan="5" class="empty-table">Loading data…</td></tr></tbody></table></div>
</div>
</section>
<section class="view" id="view-packages" aria-labelledby="packages-title" hidden>
<div class="page-head compact-head"><div><div class="eyebrow"><span class="eyebrow-line"></span> ARTIFACT EXPLORER <span class="eyebrow-separator">/</span> VERSIONS</div><h1 id="packages-title">Katalog <em>paczek.</em></h1><p class="lead">Wyszukuj, filtruj, sortuj i pobieraj opublikowane pliki. Wszystkie dane są odczytywane bezpośrednio z aktualnego repozytorium.</p></div><div class="count-pill" id="packages-total">0 paczek</div></div>
<div class="filter-panel"><div class="search-field"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m16.5 16.5 4 4"/></svg><input id="search-input" type="search" placeholder="Szukaj nazwy, wersji, architektury..." aria-label="Szukaj paczek" autocomplete="off"></div><label class="filter-input">KANAŁ<select id="filter-channel"><option value="all">Wszystkie</option><option value="linux">Linux</option><option value="opnsense">OPNsense</option><option value="freebsd">FreeBSD</option></select></label><label class="filter-input">FORMAT<select id="filter-format"><option value="all">Wszystkie</option><option value="DEB">DEB</option><option value="RPM">RPM</option><option value="PKG">PKG</option></select></label><label class="filter-input">SORTOWANIE<select id="filter-sort"><option value="date">Najnowsze</option><option value="version">Wersja ↓</option><option value="name">Nazwa A–Z</option><option value="size">Rozmiar ↓</option></select></label></div>
<div class="result-topline"><span id="filter-count">Ładowanie…</span><span>Wersje i daty pochodzą z paczek i metadanych buildu</span></div>
<div class="card catalog-card"><div class="table-scroller"><table class="data-table data-table--catalog"><thead><tr><th>PAKIET</th><th>KANAŁ</th><th>WERSJA</th><th>DATA BUILDU</th><th>ROZMIAR</th><th>POBIERZ</th></tr></thead><tbody id="catalog-rows"><tr><td colspan="6" class="empty-table">Ładowanie danych…</td></tr></tbody></table></div><div class="catalog-bottom"><span id="result-range">—</span><button class="quiet show-more" id="show-more" hidden>Załaduj kolejne 25 ↓</button></div></div>
<div class="page-head compact-head"><div><div class="eyebrow"><span class="eyebrow-line"></span> ARTIFACT EXPLORER <span class="eyebrow-separator">/</span> VERSIONS</div><h1 id="packages-title">Package <em>catalog.</em></h1><p class="lead">Search, filter, sort, and download published files. All details are read directly from the current repository.</p></div><div class="count-pill" id="packages-total">0 packages</div></div>
<div class="filter-panel"><div class="search-field"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m16.5 16.5 4 4"/></svg><input id="search-input" type="search" placeholder="Search by name, version, architecture..." aria-label="Search packages" autocomplete="off"></div><label class="filter-input">CHANNEL<select id="filter-channel"><option value="all">All</option><option value="linux">Linux</option><option value="opnsense">OPNsense</option><option value="freebsd">FreeBSD</option></select></label><label class="filter-input">FORMAT<select id="filter-format"><option value="all">All</option><option value="DEB">DEB</option><option value="RPM">RPM</option><option value="PKG">PKG</option></select></label><label class="filter-input">SORT BY<select id="filter-sort"><option value="date">Newest first</option><option value="version">Version ↓</option><option value="name">Name A–Z</option><option value="size">Size ↓</option></select></label></div>
<div class="result-topline"><span id="filter-count">Loading…</span><span>Versions and dates are read from packages and build metadata</span></div>
<div class="card catalog-card"><div class="table-scroller"><table class="data-table data-table--catalog"><thead><tr><th>PACKAGE</th><th>CHANNEL</th><th>VERSION</th><th>BUILD DATE</th><th>SIZE</th><th>DOWNLOAD</th></tr></thead><tbody id="catalog-rows"><tr><td colspan="6" class="empty-table">Loading data…</td></tr></tbody></table></div><div class="catalog-bottom"><span id="result-range">—</span><button class="quiet show-more" id="show-more" hidden>Load 25 more ↓</button></div></div>
</section>
<section class="view" id="view-install" aria-labelledby="install-title" hidden>
<div class="page-head compact-head"><div><div class="eyebrow"><span class="eyebrow-line"></span> GET STARTED <span class="eyebrow-separator">/</span> INSTALLATION</div><h1 id="install-title">Zainstaluj <em>RustProxy.</em></h1><p class="lead">Gotowe polecenia dodania repozytorium i instalacji. Wybierz platformę, skopiuj komendy do terminala.</p></div></div>
<div class="install-tabs" role="tablist" aria-label="Wybór systemu"><button role="tab" aria-selected="true" data-install="deb" class="install-tab active">Debian / Ubuntu <small>APT · amd64</small></button><button role="tab" aria-selected="false" data-install="rpm" class="install-tab">RHEL / Rocky <small>DNF · x86_64</small></button><button role="tab" aria-selected="false" data-install="opnsense" class="install-tab">OPNsense <small>FreeBSD pkg</small></button><button role="tab" aria-selected="false" data-install="freebsd" class="install-tab">FreeBSD <small>Native pkg</small></button></div>
<div class="install-card card"><div class="install-card-head"><div><span class="section-index">TERMINAL / INSTALL</span><h2 id="install-tab-title">Debian / Ubuntu</h2><p id="install-description">Dodanie podpisanego repo APT i instalacja serwera.</p></div><span class="pill" id="install-signature">GPG SIGNED</span></div><div class="code-window"><div class="code-topbar"><span><i class="tiny-dot"></i><i class="tiny-dot"></i><i class="tiny-dot"></i> <b>install.sh</b></span><button class="copy-code" id="copy-install" data-copy-target="install-command">Kopiuj <span>↗</span></button></div><pre><code id="install-command">Przygotowywanie poleceń…</code></pre></div><div class="install-foot"><span id="install-note">Wymagane uprawnienia sudo.</span><a id="install-reference" class="text-link" href="#">Pliki repozytorium ↗</a></div></div>
<div class="install-extra-grid"><div class="card hint-card"><span class="hint-icon">01</span><h3>Docker discovery agent</h3><p>Linux ma również osobną paczkę <code>rustproxy-docker-agent</code>. Instalujesz ją niezależnie od głównego serwera.</p><div class="inline-command"><code id="agent-command">sudo apt install rustproxy-docker-agent</code><button data-copy-target="agent-command" class="quiet">Kopiuj</button></div></div><div class="card hint-card"><span class="hint-icon">02</span><h3>Podpisy i zaufanie</h3><p>Repozytoria APT i DNF w builderze są podpisane GPG. Repozytoria OPNsense i natywnego FreeBSD używają obecnie katalogów <code>signature_type: none</code>; korzystaj z zaufanego źródła HTTPS.</p></div></div>
<div class="page-head compact-head"><div><div class="eyebrow"><span class="eyebrow-line"></span> GET STARTED <span class="eyebrow-separator">/</span> INSTALLATION</div><h1 id="install-title">Install <em>RustProxy.</em></h1><p class="lead">Ready-to-run commands to add the repository and install RustProxy. Select a platform and copy the commands to your terminal.</p></div></div>
<div class="install-tabs" role="tablist" aria-label="Choose operating system"><button role="tab" aria-selected="true" data-install="deb" class="install-tab active">Debian / Ubuntu <small>APT · amd64</small></button><button role="tab" aria-selected="false" data-install="rpm" class="install-tab">RHEL / Rocky <small>DNF · x86_64</small></button><button role="tab" aria-selected="false" data-install="opnsense" class="install-tab">OPNsense <small>FreeBSD pkg</small></button><button role="tab" aria-selected="false" data-install="freebsd" class="install-tab">FreeBSD <small>Native pkg</small></button></div>
<div class="install-card card"><div class="install-card-head"><div><span class="section-index">TERMINAL / INSTALL</span><h2 id="install-tab-title">Debian / Ubuntu</h2><p id="install-description">Add the signed APT repository and install the server.</p></div><span class="pill" id="install-signature">GPG SIGNED</span></div><div class="code-window"><div class="code-topbar"><span><i class="tiny-dot"></i><i class="tiny-dot"></i><i class="tiny-dot"></i> <b>install.sh</b></span><button class="copy-code" id="copy-install" data-copy-target="install-command">Copy <span>↗</span></button></div><pre><code id="install-command">Preparing commands…</code></pre></div><div class="install-foot"><span id="install-note">Requires sudo privileges.</span><a id="install-reference" class="text-link" href="#">Repository files ↗</a></div></div>
<div class="install-extra-grid single-card" id="install-extra-grid"><div class="card hint-card" id="agent-card" hidden><span class="hint-icon">01</span><h3>Docker discovery agent</h3><p>Linux also provides a separate <code>rustproxy-docker-agent</code> package. Install it separately from the main server.</p><div class="inline-command"><code id="agent-command">sudo apt install rustproxy-docker-agent</code><button data-copy-target="agent-command" class="quiet">Copy</button></div></div><div class="card hint-card"><span class="hint-icon" id="trust-index">01</span><h3>Signatures and trust</h3><p>APT and DNF repositories are GPG-signed. OPNsense and native FreeBSD currently use <code>signature_type: none</code> catalogs; use a trusted HTTPS source.</p></div></div>
</section>
<section class="view" id="view-repository" aria-labelledby="repository-title" hidden>
<div class="page-head compact-head"><div><div class="eyebrow"><span class="eyebrow-line"></span> REPOSITORY ENDPOINTS <span class="eyebrow-separator">/</span> METADATA</div><h1 id="repository-title">Źródła <em>pakietów.</em></h1><p class="lead">Adresy publicznych repozytoriów, podpisów i katalogów paczek. Aktualne metadane pochodzą z opublikowanych buildów.</p></div></div>
<div class="page-head compact-head"><div><div class="eyebrow"><span class="eyebrow-line"></span> REPOSITORY ENDPOINTS <span class="eyebrow-separator">/</span> METADATA</div><h1 id="repository-title">Package <em>sources.</em></h1><p class="lead">Public repository URLs, signatures, and package indexes. Current metadata comes from published builds.</p></div></div>
<div class="endpoints-grid" id="endpoints-grid"></div>
<div class="section-heading section-heading--second"><div><span class="section-index">SOURCE TRACE</span><h2>Informacje o wydaniach</h2></div></div>
<div class="card metadata-card"><div class="meta-table" id="metadata-table"><span>Wczytywanie…</span></div></div>
<div class="section-heading section-heading--second"><div><span class="section-index">SOURCE TRACE</span><h2>Release metadata</h2></div></div>
<div class="card metadata-card"><div class="meta-table" id="metadata-table"><span>Loading…</span></div></div>
</section>
<footer class="page-footer"><span>RUSTPROXY <b>/</b> PACKAGE REGISTRY</span><span>Live catalog · Python / Nginx <b>·</b> <span id="footer-time">—</span></span></footer>
+1
View File
@@ -246,6 +246,7 @@ h3{font-size:16px;font-weight:575;letter-spacing:-.3px}
.code-window pre{color:var(--text);font:12px/1.9 var(--mono);overflow:auto;margin:0;padding:21px 24px;min-height:170px;tab-size:2;white-space:pre}
.install-foot{display:flex;justify-content:space-between;gap:15px;margin-top:17px;color:var(--muted);font-size:11px}
.install-extra-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:16px}
.install-extra-grid.single-card{grid-template-columns:1fr}
.hint-card{padding:24px 26px}
.hint-icon{display:grid;place-items:center;width:34px;height:34px;border:1px solid var(--pill-live-line);color:var(--green);font:11px var(--mono);margin-bottom:18px}
.hint-card h3{margin-bottom:10px}
+24
View File
@@ -73,6 +73,17 @@ class CatalogTests(RepoFixture, unittest.TestCase):
self.assertIn("/freebsd/FreeBSD%3A15%3Aamd64/",freebsd["download_url"])
self.assertEqual("2026-10-22T08:45:00Z",freebsd["built_at"])
def test_discovery_agent_is_linux_only(self):
self.make_catalog()
self.make_file("opnsense/26.7/amd64/rustproxy-docker-agent-0.34.6_123.pkg")
self.make_file("freebsd/FreeBSD:15:amd64/rustproxy-docker-agent-0.34.6_123.pkg")
catalog = app.generate_catalog(self.repo)
self.assertEqual(6, catalog["summary"]["packages_count"])
agents = [p for p in catalog["packages"] if p["name"] == "rustproxy-docker-agent"]
self.assertEqual(2, len(agents))
self.assertEqual({"linux"}, {p["channel"] for p in agents})
self.assertEqual({"DEB", "RPM"}, {p["format"] for p in agents})
def test_mtime_backwards_compatibility(self):
self.make_file("LINUX-BUILD-INFO.txt",contents="app_version=0.21.0\npackage_release=5\n")
self.make_file("rpm/x86_64/rustproxy-0.21.0-5.x86_64.rpm")
@@ -128,6 +139,19 @@ class HttpTests(RepoFixture, unittest.TestCase):
self.addCleanup(self.server.shutdown)
self.base = f"http://127.0.0.1:{self.server.server_port}"
def test_default_english_and_agent_markup(self):
with urlopen(self.base + "/freebsd/") as response:
html = response.read().decode("utf-8")
self.assertIn('<html lang="en"', html)
self.assertIn('<h1 id="packages-title">Package <em>catalog.</em></h1>', html)
self.assertIn('id="agent-card" hidden', html)
self.assertIn('data-site="freebsd"', html)
with urlopen(self.base + "/ui/app.js") as response:
js = response.read().decode("utf-8")
self.assertIn("new Intl.DateTimeFormat('en-GB'", js)
self.assertIn("el('agent-card').hidden = !hasDockerAgent", js)
self.assertIn("agent:null", js)
def test_http_catalog_page_assets_head(self):
with urlopen(self.base + "/api/v1/packages") as response:
self.assertIn("no-store",response.headers["Cache-Control"])