🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
{% extends "admin/layout.html" %}
{% block admin_title %}Obrazy ISO{% endblock %}
{% block admin_h1 %}Obrazy ISO{% endblock %}
{% block admin_sub %}Upload obrazów – po wgraniu otrzymasz link https://paganlinux.eu/download/iso/<wersja>{% endblock %}
{% block admin_head %}
<style>
.iso-prog{margin-top:0.9rem;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0.7rem 0.9rem;background:var(--bg2)}
.iso-prog-head{display:flex;justify-content:space-between;align-items:center;gap:1rem;font-size:0.8rem;font-weight:600;margin-bottom:0.45rem}
.iso-prog-track{height:8px;border-radius:99px;background:var(--bg);border:1px solid var(--border);overflow:hidden}
.iso-prog-fill{height:100%;width:0;border-radius:99px;background:linear-gradient(90deg,#2ea043,#3fb950);transition:width 0.25s ease}
.iso-prog-sub{font-size:0.72rem;color:var(--muted);margin-top:0.45rem;word-break:break-word}
.iso-prog.err .iso-prog-fill{background:linear-gradient(90deg,#da3633,#f85149)}
.iso-prog.done .iso-prog-fill{background:linear-gradient(90deg,#238636,#3fb950)}
.iso-clamp{max-width:230px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.78rem;color:var(--muted)}
.iso-edit-row td{background:var(--bg2)}
.iso-edit-box{display:grid;grid-template-columns:1fr 1fr;gap:0.75rem;align-items:start}
.iso-edit-box textarea{width:100%;min-height:74px;font-size:0.8rem}
.iso-edit-actions{display:flex;gap:0.4rem;align-items:center;margin-top:0.5rem}
@media(max-width:700px){.iso-edit-box{grid-template-columns:1fr}}
</style>
{% endblock %}
{% block admin_content %}
<div class="card" style="margin-bottom:1rem">
<div class="card-header"><h2><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="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M17 8l-5-5-5 5"/><path d="M12 3v12"/></svg> {{ t('iso_upload_title') }}</h2></div>
<form id="iso-upload-form" method="POST" action="/admin/iso/upload" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:0.75rem">
<div>
<label style="font-size:0.75rem">{{ t('iso_version') }} (np. 0.0.1-kde)</label>
<input name="version" id="iso-version" required placeholder="0.0.1-kde" style="width:100%">
</div>
<div>
<label style="font-size:0.75rem">{{ t('iso_file') }} (.iso)</label>
<input type="file" name="file" id="iso-file" accept=".iso" required style="width:100%">
</div>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:0.75rem;margin-top:0.75rem">
<div>
<label style="font-size:0.75rem">Opis – krótka informacja o wersji (widoczna na /download)</label>
<textarea name="description" id="iso-description" rows="2" placeholder="np. KDE Plasma – pierwsze wydanie z pełnym pulpitem KDE."></textarea>
</div>
<div>
<label style="font-size:0.75rem">Zmiany / changelog – każda linia to jeden wpis</label>
<textarea name="changes" id="iso-changes" rows="2" placeholder="np.: - dodano: instalator z podziałem dysku - poprawiono: dźwięk po restarcie"></textarea>
</div>
</div>
<div style="display:flex;align-items:center;gap:0.75rem;margin-top:0.85rem;flex-wrap:wrap">
<button type="submit" id="iso-upload-btn" class="btn btn-sm" style="background:#2ea043;color:#fff;font-size:0.85rem;padding:0.5rem 1.2rem">
<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="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M17 8l-5-5-5 5"/><path d="M12 3v12"/></svg> <span id="iso-upload-btn-lbl">{{ t('iso_upload_btn') }}</span>
</button>
<span id="iso-file-info" style="font-size:0.75rem;color:var(--muted)"></span>
</div>
<div id="iso-progress" class="iso-prog" style="display:none">
<div class="iso-prog-head"><span id="iso-prog-label">Wysyłanie pliku…</span><b id="iso-prog-pct">0%</b></div>
<div class="iso-prog-track"><div class="iso-prog-fill" id="iso-prog-fill"></div></div>
<div class="iso-prog-sub" id="iso-prog-sub"></div>
</div>
<p style="font-size:0.78rem;color:var(--muted);margin:0.7rem 0 0">
Duży plik (1–2 GB) – upload może potrwać kilka minut. Nie zamykaj karty podczas przesyłania.
Plik trafia do <code>{{ iso_dir }}</code>. Wgranie wersji, która już istnieje, <strong>nadpisze</strong> poprzedni plik
(opis i zmiany zostaną zachowane, jeśli pozostawisz pola puste).
</p>
</form>
</div>
<div class="card">
<div class="card-header"><h2>{{ t('iso_list') }} ({{ isos|length }})</h2></div>
{% if isos %}
<div style="overflow-x:auto">
<table style="width:100%">
<thead><tr><th>{{ t('iso_version') }}</th><th>Opis</th><th>Rozmiar</th><th>SHA-256</th><th>Link</th><th>{{ t('admin_actions') }}</th></tr></thead>
<tbody>
{% for i in isos %}
<tr>
<td><strong>{{ i.version }}</strong>
{% if i.date %}<div style="font-size:0.68rem;color:var(--muted)">{{ i.date }}</div>{% endif %}
</td>
<td>
{% if i.description %}<div class="iso-clamp" title="{{ i.description }}{% if i.changes %} ZMIANY: {{ i.changes }}{% endif %}">{{ i.description }}</div>
{% else %}<span style="color:var(--muted);font-size:0.75rem">—</span>{% endif %}
</td>
<td style="font-size:0.8rem;color:var(--muted)">{{ (i.size / 1048576)|round(0)|int }} MB</td>
<td style="font-size:0.65rem;color:var(--muted)"><code>{{ i.sha256[:16] }}…</code></td>
<td style="font-size:0.75rem;word-break:break-all">
<a href="{{ i.link }}" target="_blank">{{ i.link }}</a>
<div style="font-size:0.65rem;margin-top:0.15rem"><a href="{{ i.link }}.sha256">suma (.sha256)</a></div>
</td>
<td>
<div style="display:flex;gap:0.3rem;flex-wrap:wrap">
<button type="button" class="btn btn-sm" data-edit-toggle="edit-row-{{ i.id }}" style="font-size:0.7rem;padding:0.15rem 0.4rem" title="Edytuj opis / zmiany"><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="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg></button>
<form method="POST" action="/admin/iso/{{ i.id }}/delete" style="margin:0" onsubmit="return confirm('Usunąć ISO {{ i.version }}? Plik zniknie z serwera.')">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn-sm" style="font-size:0.7rem;padding:0.15rem 0.4rem;background:#da3633;color:#fff" title="Usuń ISO"><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>
</form>
</div>
</td>
</tr>
<tr class="iso-edit-row" id="edit-row-{{ i.id }}" style="display:none">
<td colspan="6">
<form method="POST" action="/admin/iso/{{ i.id }}/update">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="iso-edit-box">
<div>
<label style="font-size:0.75rem">Opis</label>
<textarea name="description" placeholder="Krótka informacja o wersji">{{ i.description }}</textarea>
</div>
<div>
<label style="font-size:0.75rem">Zmiany / changelog (linia = wpis)</label>
<textarea name="changes" placeholder="Co nowego w tej wersji?">{{ i.changes }}</textarea>
</div>
</div>
<div class="iso-edit-actions">
<button class="btn btn-sm" style="background:#2ea043;color:#fff">Zapisz opis / zmiany</button>
<button type="button" class="btn btn-sm" data-edit-toggle="edit-row-{{ i.id }}">Zamknij</button>
</div>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p style="text-align:center;color:var(--muted);padding:2rem">{{ t('iso_empty') }}</p>
{% endif %}
</div>
<script>
(function () {
var form = document.getElementById('iso-upload-form');
var fileIn = document.getElementById('iso-file');
var info = document.getElementById('iso-file-info');
var btn = document.getElementById('iso-upload-btn');
var btnLbl = document.getElementById('iso-upload-btn-lbl');
var prog = document.getElementById('iso-progress');
var progFill = document.getElementById('iso-prog-fill');
var progPct = document.getElementById('iso-prog-pct');
var progLbl = document.getElementById('iso-prog-label');
var progSub = document.getElementById('iso-prog-sub');
function fmt(n) { return (n / 1048576).toFixed(1) + ' MB'; }
if (fileIn) fileIn.addEventListener('change', function () {
var f = fileIn.files && fileIn.files[0];
info.textContent = f ? (f.name + ' · ' + fmt(f.size)) : '';
});
document.querySelectorAll('[data-edit-toggle]').forEach(function (b) {
b.addEventListener('click', function () {
var row = document.getElementById(b.getAttribute('data-edit-toggle'));
if (row) row.style.display = (row.style.display === 'none') ? '' : 'none';
});
});
if (!form) return;
form.addEventListener('submit', function (e) {
var f = fileIn.files && fileIn.files[0];
var version = (document.getElementById('iso-version').value || '').trim();
if (!f) { alert('Wybierz plik ISO.'); e.preventDefault(); return; }
if (!version) { alert('Podaj wersję.'); e.preventDefault(); return; }
// Upload przez XHR z paskiem postępu
e.preventDefault();
var fd = new FormData(form);
var xhr = new XMLHttpRequest();
prog.style.display = '';
prog.classList.remove('err', 'done');
progFill.style.width = '0%';
progPct.textContent = '0%';
progLbl.textContent = 'Wysyłanie pliku…';
progSub.textContent = '';
btn.disabled = true;
btnLbl.textContent = 'Przesyłanie…';
xhr.upload.addEventListener('progress', function (ev) {
if (!ev.lengthComputable) return;
var pct = Math.round(ev.loaded / ev.total * 100);
progFill.style.width = pct + '%';
progPct.textContent = pct + '%';
progLbl.textContent = 'Wysyłanie pliku…';
progSub.textContent = fmt(ev.loaded) + ' / ' + fmt(ev.total);
});
xhr.upload.addEventListener('load', function () {
progLbl.textContent = 'Zapis na serwerze i obliczanie sumy SHA-256…';
progFill.style.width = '100%';
progSub.textContent = 'Duży plik – to może potrwać chwilę.';
});
xhr.addEventListener('load', function () {
btn.disabled = false;
btnLbl.textContent = 'Wgraj ISO';
var msg = '', ok = false, redir = '/admin/iso';
try {
var j = JSON.parse(xhr.responseText);
ok = !!j.ok; msg = j.message || ''; redir = j.redirect || redir;
} catch (e) { ok = xhr.status >= 200 && xhr.status < 300; }
if (ok) {
prog.classList.add('done');
progLbl.textContent = msg || 'Gotowe – przeładowuję listę…';
progSub.textContent = '';
setTimeout(function () { window.location.href = redir; }, 900);
} else {
prog.classList.add('err');
progFill.style.width = '100%';
progLbl.textContent = msg || 'Błąd – spróbuj ponownie.';
progSub.textContent = '';
}
});
xhr.addEventListener('error', function () {
prog.classList.add('err');
progLbl.textContent = 'Błąd połączenia – spróbuj ponownie.';
progSub.textContent = '';
btn.disabled = false;
btnLbl.textContent = 'Wgraj ISO';
});
xhr.addEventListener('abort', function () {
prog.classList.add('err');
progLbl.textContent = 'Przerwano.';
btn.disabled = false;
btnLbl.textContent = 'Wgraj ISO';
});
xhr.open('POST', form.getAttribute('action'));
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.send(fd);
});
})();
</script>
{% endblock %}