🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
{% extends "admin/layout.html" %}
{% block admin_title %}Logi{% endblock %}
{% block admin_h1 %} Logi{% endblock %}
{% block admin_sub %}Logi buildów i systemowe{% endblock %}
{% block admin_head %}
<style>
.log-tabs { display:flex; gap:0.4rem; margin-bottom:0.8rem; flex-wrap:wrap; }
.log-tab { padding:0.35rem 0.9rem; border-radius:var(--radius-sm); background:#0d1117; border:1px solid var(--border);
color:var(--muted); cursor:pointer; font-size:0.82rem; }
.log-tab.active { background:var(--accent); color:#000; font-weight:600; }
.log-list { max-height:60vh; overflow:auto; border:1px solid var(--border); border-radius:var(--radius);
background:#0d1117; }
.log-file { display:flex; justify-content:space-between; gap:0.5rem; padding:0.45rem 0.7rem;
border-bottom:1px solid #111; cursor:pointer; font-size:0.8rem; }
.log-file:hover { background:#161b22; }
.log-file.nm { font-family:'JetBrains Mono',monospace; }
.log-file.meta { color:var(--muted); white-space:nowrap; }
.log-view { white-space:pre-wrap; font-family:'JetBrains Mono',monospace; font-size:0.74rem; line-height:1.5;
background:#0a0a0a; border:1px solid #1a1a2e; border-radius:var(--radius-sm); padding:0.8rem;
max-height:60vh; overflow:auto; display:none; }
</style>
{% endblock %}
{% block admin_content %}
<div class="log-tabs">
<div class="log-tab active" id="tab-build" onclick="switchSrc('build')">Buildy</div>
<div class="log-tab" id="tab-sys" onclick="switchSrc('sys')">Systemowe</div>
<select id="log-tail" style="margin-left:auto" onchange="loadList()">
<option value="100">100 linii</option>
<option value="200" selected>200 linii</option>
<option value="500">500 linii</option>
<option value="2000">2000 linii</option>
</select>
<button class="log-tab" onclick="downloadLog()" title="Pobierz wybrany log (albo najnowszy)">Pobierz</button>
<button class="log-tab" style="color:var(--red)" onclick="clearLogs()" title="Wyczyść wybrany log; w zakładce Buildy – wszystkie">Wyczyść</button>
</div>
<div class="log-list" id="log-list"></div>
<div class="log-view" id="log-view"></div>
<script>
let SRC='build';
let CURRENT=null;
async function switchSrc(s){
SRC=s;
document.getElementById('tab-build').classList.toggle('active', s==='build');
document.getElementById('tab-sys').classList.toggle('active', s==='sys');
document.getElementById('log-view').style.display='none';
loadList();
}
async function loadList(){
const el=document.getElementById('log-list');
try{
const r=await fetch('/admin/logs/api?src='+SRC); const d=await r.json();
let html='';
d.files.forEach(f=>{
html+=`<div class="log-file" onclick="viewLog('${f.name}')">
<span class="nm">${f.name}</span><span class="meta">${f.size} · ${f.mtime}</span></div>`;
});
el.innerHTML=html||'<div class="log-file">brak logów</div>';
}catch(e){ el.innerHTML='<div class="log-file">błąd</div>'; }
}
async function viewLog(name){
CURRENT=name;
const tail=document.getElementById('log-tail').value;
const view=document.getElementById('log-view');
try{
const r=await fetch(`/admin/logs/view?src=${SRC}&name=${encodeURIComponent(name)}&tail=${tail}`);
const d=await r.json();
view.style.display='block'; view.textContent = d.ok? d.content: ('błąd: '+(d.error||'?'));
document.getElementById('log-list').style.display='none';
}catch(e){ view.style.display='block'; view.textContent='błąd: '+e; }
}
function csrfToken(){
const m=document.querySelector('meta[name="csrf-token"]');
return m? m.getAttribute('content') : '';
}
function downloadLog(){
const q='src='+SRC+(CURRENT? '&name='+encodeURIComponent(CURRENT): '');
window.location='/admin/logs/download?'+q;
}
async function clearLogs(){
const scope = CURRENT ? ('plik „'+CURRENT+'”') : (SRC==='build' ? 'WSZYSTKIE logi buildów' : null);
if(!scope){ alert('Logi systemowe: najpierw kliknij konkretny plik, który chcesz wyczyścić.'); return; }
if(!confirm('Wyczyścić '+scope+'? Pliki zostaną OPRÓŻNIONE (nie usunięte).')) return;
try{
const fd=new URLSearchParams();
if(CURRENT) fd.append('name', CURRENT);
const r=await fetch('/admin/logs/clear?src='+SRC,{method:'POST',
headers:{'X-CSRF-Token': csrfToken(),'Content-Type':'application/x-www-form-urlencoded'},
body:fd.toString()});
const d=await r.json();
if(d.ok){ CURRENT=null; document.getElementById('log-view').style.display='none';
document.getElementById('log-list').style.display=''; loadList(); }
else alert(d.error||'błąd czyszczenia');
}catch(e){ alert('Błąd: '+e); }
}
// przycisk "wróć" po podglądzie
document.getElementById('log-view').addEventListener('dblclick', ()=>{
document.getElementById('log-view').style.display='none';
document.getElementById('log-list').style.display='';
});
loadList();
</script>
{% endblock %}