🔒 Repository is read-only – file editing is disabled.

PaganLinux/pagan-web-v2/templates/admin/terminal.html main

92 linii Raw ← Powrót
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
{% extends "admin/layout.html" %}
{% block admin_title %}Terminal{% endblock %}
{% block admin_h1 %} Terminal{% endblock %}
{% block admin_sub %}Wykonywanie komend na VPS (wyjście na żywo przez SSE){% endblock %}
{% block admin_head %}
<style>
.term { background:#0a0a0a; border:1px solid #1a1a2e; border-radius:var(--radius);
 font-family:'JetBrains Mono','Fira Code',monospace; font-size:0.8rem; line-height:1.55;
 height:60vh; overflow-y:auto; padding:0.8rem; }
.term.line { white-space:pre-wrap; word-break:break-all; }
.term.prompt { color:#58a6ff; }
.term.cwd { color:#3fb950; }
.term.cmd { color:#e6edf3; }
.term.err { color:#f85149; }
.term-input-row { display:flex; gap:0.4rem; margin-top:0.5rem; align-items:center; }
.term-input-row input { flex:1; font-family:'JetBrains Mono',monospace; font-size:0.82rem;
 background:#0d1117; color:var(--fg); border:1px solid var(--border); border-radius:var(--radius-sm);
 padding:0.5rem 0.7rem; }
.term-input-row input:focus { outline:none; border-color:var(--accent); }
.term-status { font-size:0.75rem; color:var(--muted); margin-top:0.4rem; }
.term-btns { display:flex; gap:0.4rem; margin-top:0.5rem; }
</style>
{% endblock %}
{% block admin_content %}
<div class="term" id="term" onclick="document.getElementById('term-in').focus()">
 <div class="line dim">PaganOS VPS — terminal (komendy przez /bin/bash -c). Ctrl+C przerywa.</div>
</div>
<div class="term-input-row">
 <span class="prompt" id="term-prompt">root@vps:~$</span>
 <input id="term-in" autocomplete="off" autocapitalize="off" spellcheck="false"
 placeholder="wpisz komendę i naciśnij Enter..." onkeydown="onEnter(event)">
</div>
<div class="term-btns">
 <button class="btn btn-sm" onclick="stop()">⏹ Przerwij (Ctrl+C)</button>
 <button class="btn btn-sm" onclick="clearTerm()"> Wyczyść</button>
 <span class="term-status" id="term-status"></span>
</div>
<script>
const term=document.getElementById('term');
const input=document.getElementById('term-in');
let curJob=null, hist=[], hi=0;

function addLine(html){ const d=document.createElement('div'); d.className='line'; d.innerHTML=html;
 term.appendChild(d); term.scrollTop=term.scrollHeight; }
function addText(t){ 
 const d=document.createElement('div'); d.className='line';
 d.textContent=t; term.appendChild(d); term.scrollTop=term.scrollHeight; 
 // ogranicz liczbę linii w DOM
 while(term.childElementCount>600) term.removeChild(term.firstElementChild);
}

function onEnter(e){
 if(e.key==='Enter'){
 const cmd=input.value.trim();
 if(!cmd) return;
 hist.push(cmd); hi=hist.length;
 addText(`root@vps:~$ ${cmd}`);
 input.value=''; run(cmd);
 } else if(e.key==='ArrowUp'){ e.preventDefault(); if(hi>0){hi--; input.value=hist[hi]||'';} }
 else if(e.key==='ArrowDown'){ e.preventDefault(); if(hi<hist.length-1){hi++; input.value=hist[hi]||'';} else {hi=hist.length; input.value='';} }
}

async function run(cmd){
 document.getElementById('term-status').textContent='⏳ uruchamianie...';
 try{
 const r=await fetch('/admin/terminal/exec',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':CSRF_TOKEN},
 body:JSON.stringify({cmd:cmd})});
 const d=await r.json();
 if(!d.ok){ addText('❌ '+(d.error||'błąd')); status(''); return; }
 curJob=d.job;
 const es=new EventSource('/admin/terminal/stream/'+d.job);
 es.onmessage=e=>{
 let m; try{ m=JSON.parse(e.data); }catch(_){ return; }
 if(m.type==='out'){ addText(m.data.replace(/\n$/,'')); }
 else if(m.type==='end'){
 es.close(); curJob=null;
 status('✅ zakończono (rc='+(m.rc===null?'?':m.rc)+')');
 addText('');
 }
 };
 es.onerror=()=>{ es.close(); curJob=null; status('⚠ strumień przerwany'); };
 }catch(e){ addText('❌ '+e); status(''); }
}
function status(t){ document.getElementById('term-status').textContent=t; }
async function stop(){
 if(curJob){ try{ await fetch('/admin/terminal/kill/'+curJob,{method:'POST',headers:{'X-CSRF-Token':CSRF_TOKEN}}); }catch(_){}
 addText('⏹ przerwano (SIGTERM)'); curJob=null; status(''); }
}
function clearTerm(){ while(term.childElementCount>1) term.removeChild(term.lastElementChild); }
input.focus();
</script>
{% endblock %}