🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
{% extends "base.html" %}{% block title %}{{ t('repo') }} – PaganOS{% endblock %}
{% block head %}
<style>
/* Repo v2 – czytelna lista pakietów */
.repo-head{display:flex;justify-content:space-between;align-items:flex-end;gap:1rem;flex-wrap:wrap;margin-bottom:1.25rem}
.repo-title{font-size:1.9rem;font-weight:800;color:var(--text-bright);letter-spacing:-0.02em;margin:0 0 0.2rem}
.repo-title.soft{color:var(--muted);font-weight:600}
.repo-sub{color:var(--muted);font-size:0.92rem;max-width:560px}
.stat-unit{font-size:0.8rem;color:var(--muted);font-weight:600}
.repo-filters{display:flex;flex-direction:column;gap:0.85rem}
.repo-search{display:flex;gap:0.5rem;flex-wrap:wrap;align-items:center}
.repo-search input{flex:1;min-width:220px}
.repo-search.sort{width:auto}
.repo-cats{display:flex;gap:0.4rem;flex-wrap:wrap;align-items:center}
.repo-cats.lbl{font-size:0.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.06em;margin-right:0.2rem}
a.rchip{display:inline-flex;align-items:center;gap:0.35rem;padding:0.28rem 0.8rem;border-radius:999px;font-size:0.8rem;
color:var(--text);border:1px solid var(--border);background:var(--bg2);transition:var(--transition)}
a.rchip:hover{border-color:var(--accent);color:var(--accent);text-decoration:none}
a.rchip.on{background:rgba(63,185,80,0.12);border-color:var(--accent);color:var(--accent);font-weight:600}
.rchip.n{font-size:0.68rem;opacity:0.7;font-weight:600}
.repo-count{font-size:0.8rem;color:var(--muted)}
.repo-card{padding:0;overflow:hidden}
.repo-table{margin:0}
.repo-table thead th{background:var(--card);border-bottom:1px solid var(--border-light)}
.repo-table td{vertical-align:middle}
.repo-table tbody tr{cursor:default;transition:background 0.12s}
.repo-table tbody tr:hover{background:rgba(255,255,255,0.025)}
.pkg-cell{min-width:220px}
.pkg-name{font-size:0.95rem;font-weight:700;color:var(--text-bright)}
.pkg-name:hover{color:var(--accent)}
.pkg-desc{font-size:0.75rem;color:var(--muted);margin-top:0.15rem;max-width:420px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ver-mono{font-family:var(--mono);font-size:0.82rem;color:var(--text)}
.num{text-align:right;font-variant-numeric:tabular-nums}
.dl-btn{padding:0.3rem 0.7rem;font-size:0.85rem}
.paginate{display:flex;gap:0.3rem;justify-content:center;align-items:center;padding:0.9rem 1.25rem;border-top:1px solid var(--border);flex-wrap:wrap}
.paginate.info{color:var(--muted);font-size:0.78rem;margin-right:auto}
@media(max-width:720px){.pkg-desc{display:none}}
</style>
{% endblock %}
{% block content %}
<header class="repo-head">
<div>
<h1 class="repo-title"> {{ t('repo') }} <span class="soft">/ {{ cat or 'stable' }}</span></h1>
</div>
<div style="display:flex;gap:0.5rem">
<a class="btn btn-ghost btn-sm" href="https://repo.paganlinux.eu/stable/repo.json" target="_blank" rel="noopener" title="Index kategorii stable (JSON)"> repo.json</a>
<a class="btn btn-ghost btn-sm" href="https://git.paganlinux.eu/recipes" target="_blank" rel="noopener"> Receptury</a>
</div>
</header>
<div class="stats">
<div class="stat"><div class="stat-val">{{ total }}</div><div class="stat-label">{{ t('pkg_count') }}</div></div>
<div class="stat"><div class="stat-val">{{ "%.2f"|format(total_size_gb) }} <span class="stat-unit">GB</span></div><div class="stat-label">{{ t('pkg_size_total') }}</div></div>
{% if categories|length > 1 %}
{% for c in categories %}
<div class="stat"><div class="stat-val">{{ counts.get(c, 0) }}</div><div class="stat-label">{{ cat_icons.get(c,'') }} {{ c }}<br><span style="font-size:0.7rem;font-weight:400">{{ "%.1f"|format(cat_sizes.get(c,0)/1048576) }} MB</span></div></div>
{% endfor %}
{% else %}
<div class="stat"><div class="stat-val">{{ "%.1f"|format(cat_sizes.get(categories[0],0)/1048576) }} <span class="stat-unit">MB</span></div><div class="stat-label">{{ t('size') }} ({{ categories[0] }})</div></div>
<div class="stat"><div class="stat-val">x86_64</div><div class="stat-label">Architektura</div></div>
{% endif %}
</div>
<div class="card repo-filters">
<form class="repo-search" method="get" action="/repo">
{% if cat %}<input type="hidden" name="cat" value="{{ cat }}">{% endif %}
<input type="search" name="q" value="{{ search }}" placeholder=" Szukaj pakietu po nazwie, kategorii, opisie…" aria-label="Szukaj">
<select class="sort" name="sort" aria-label="Sortowanie">
<option value="name" {% if sort=='name' %}selected{% endif %}>{{ t('repo_name_header') }}</option>
<option value="version" {% if sort=='version' %}selected{% endif %}>{{ t('repo_version') }}</option>
<option value="size" {% if sort=='size' %}selected{% endif %}>{{ t('size') }} ↓</option>
</select>
<button class="btn btn-primary btn-sm" type="submit">Szukaj</button>
</form>
<div class="repo-cats">
<span class="lbl">Kategorie:</span>
<a class="rchip {% if not cat %}on{% endif %}" href="/repo?q={{ search|urlencode }}&sort={{ sort }}">Wszystkie <span class="n">{{ total }}</span></a>
{% for c in categories %}
<a class="rchip {% if cat==c %}on{% endif %}" href="/repo?cat={{ c }}&q={{ search|urlencode }}&sort={{ sort }}">{{ cat_icons.get(c,'') }} {{ c }} <span class="n">{{ counts.get(c,0) }}</span></a>
{% endfor %}
</div>
{% if total_pkgs %}
<div class="repo-count">
Pokazuję {{ page * per_page - per_page + 1 }}–{{ [page * per_page, total_pkgs]|min }} z <strong>{{ total_pkgs }}</strong> pakietów
{% if search %} (filtr: „{{ search }}"){% endif %}
</div>
{% endif %}
</div>
<div class="card repo-card">
{% if packages %}
<table class="table repo-table">
<thead><tr>
<th>{{ t('repo_name_header') }}</th>
<th>{{ t('repo_version') }}</th>
<th>{{ t('category') }}</th>
<th class="num">{{ t('size') }}</th>
<th class="num" style="width:{% if is_admin %}150{% else %}110{% endif %}px">Akcje</th>
</tr></thead>
<tbody>
{% for p in packages %}
<tr>
<td class="pkg-cell">
<a class="pkg-name" href="/repo/package/{{ p.category }}/{{ p.filename }}">{{ p.name }}</a>
{% if p.description %}<div class="pkg-desc" title="{{ p.description }}">{{ p.description[:100] }}</div>{% endif %}
</td>
<td><code class="ver-mono">{{ p.version }}</code></td>
<td><span class="chip">{{ p.category }}</span></td>
<td class="num"><span style="font-family:var(--mono);font-size:0.8rem;color:var(--muted)">{{ p.size_mb }} MB</span></td>
<td class="num" style="white-space:nowrap">
<a class="btn btn-primary btn-sm dl-btn" href="/repo/download/{{ p.category }}/{{ p.filename }}" title="Pobierz {{ p.filename }}">⬇ Pobierz</a>
{% if is_admin %}
<button type="button" class="btn btn-danger btn-sm" style="padding:0.3rem 0.55rem" onclick="repoDel(this)" data-cat="{{ p.category }}" data-file="{{ p.filename }}" title="Usuń pakiet z repo"><svg class="i16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M10 11v6M14 11v6"/></svg></button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if total_pages > 1 %}
<div class="paginate">
<span class="info">Strona {{ page }} / {{ total_pages }}</span>
{% if page > 1 %}<a class="btn btn-sm" href="/repo?page={{ page-1 }}&cat={{ cat }}&q={{ search|urlencode }}&sort={{ sort }}">← Poprzednia</a>{% endif %}
{% for p in range(1, total_pages+1) %}
{% if p == page %}<span class="btn btn-sm btn-primary">{{ p }}</span>
{% elif p <= 2 or p > total_pages-2 or (p >= page-1 and p <= page+1) %}
<a class="btn btn-sm" href="/repo?page={{ p }}&cat={{ cat }}&q={{ search|urlencode }}&sort={{ sort }}">{{ p }}</a>
{% elif p == 3 or p == total_pages-2 %}<span class="btn btn-sm disabled" style="opacity:0.4">…</span>
{% endif %}
{% endfor %}
{% if page < total_pages %}<a class="btn btn-sm" href="/repo?page={{ page+1 }}&cat={{ cat }}&q={{ search|urlencode }}&sort={{ sort }}">Następna →</a>{% endif %}
</div>
{% endif %}
{% else %}
<div class="empty-state" style="padding:3rem 1rem;text-align:center">
<div style="font-size:2.2rem;margin-bottom:0.5rem"></div>
<div style="font-weight:700;color:var(--text-bright)">{{ t('repo_no_results') }}</div>
<p style="color:var(--muted);font-size:0.85rem;margin-top:0.4rem">Zmień frazę wyszukiwania albo kategorię.</p>
<a class="btn btn-ghost" href="/repo" style="margin-top:1rem">← Pokaż wszystkie</a>
</div>
{% endif %}
</div>
<script>
/* Usuwanie pakietu (tylko admin – przycisk nie jest renderowany dla reszty). */
function repoDel(btn) {
var file = btn.getAttribute('data-file');
var cat = btn.getAttribute('data-cat');
if (!confirm(' Usunąć pakiet ' + file + ' z kategorii ' + cat + '?')) return;
var meta = document.querySelector('meta[name="csrf-token"]');
var fd = new FormData();
fd.append('csrf_token', meta? meta.getAttribute('content'): '');
fd.append('category', cat);
fd.append('filename', file);
btn.disabled = true;
fetch('/repo/api/delete-package', { method: 'POST', body: fd })
.then(function (r) { return r.json(); })
.then(function (d) {
if (d.ok) { location.reload(); }
else { alert(d.error || 'Błąd usuwania'); btn.disabled = false; }
})
.catch(function () { alert('Błąd sieci'); btn.disabled = false; });
}
</script>
{% endblock %}