Upload files to "/"
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
import http.server, socketserver, os
|
||||
|
||||
PORT = 8080
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args):
|
||||
pass
|
||||
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
with socketserver.TCPServer(('', PORT), Handler) as httpd:
|
||||
print(f'Serving on http://localhost:{PORT}')
|
||||
httpd.serve_forever()
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>XAMPP Control Panel</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300..700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root,[data-theme="light"]{
|
||||
--color-bg:#f7f6f2;--color-surface:#f9f8f5;--color-surface-2:#fbfbf9;
|
||||
--color-surface-offset:#f0ede8;--color-border:#d4d1ca;--color-divider:#dcd9d5;
|
||||
--color-text:#28251d;--color-text-muted:#7a7974;--color-text-faint:#bab9b4;
|
||||
--color-primary:#01696f;--color-primary-highlight:#cedcd8;
|
||||
--color-success:#437a22;--color-success-highlight:#d4dfcc;
|
||||
--color-warning:#964219;--color-warning-highlight:#ddcfc6;
|
||||
--color-error:#a12c7b;--color-error-highlight:#e0ced7;
|
||||
--color-notification:#a13544;--color-orange:#da7101;
|
||||
--shadow-sm:0 1px 2px oklch(0.2 0.01 80/.06);--shadow-md:0 4px 12px oklch(0.2 0.01 80/.08);
|
||||
--radius-sm:.375rem;--radius-md:.5rem;--radius-lg:.75rem;--radius-xl:1rem;--radius-full:9999px;
|
||||
--transition:180ms cubic-bezier(0.16,1,0.3,1);
|
||||
--font-body:'Inter',sans-serif;--font-mono:'JetBrains Mono',monospace;
|
||||
--text-xs:clamp(.75rem,.7rem + .25vw,.875rem);--text-sm:clamp(.875rem,.8rem + .35vw,1rem);
|
||||
--text-base:clamp(1rem,.95rem + .25vw,1.125rem);--text-lg:clamp(1.125rem,1rem + .75vw,1.5rem);
|
||||
--sp1:.25rem;--sp2:.5rem;--sp3:.75rem;--sp4:1rem;--sp5:1.25rem;--sp6:1.5rem;--sp8:2rem;
|
||||
}
|
||||
[data-theme="dark"]{
|
||||
--color-bg:#111110;--color-surface:#1a1917;--color-surface-2:#1f1e1c;
|
||||
--color-surface-offset:#252321;--color-border:#2e2c2a;--color-divider:#242220;
|
||||
--color-text:#cdccca;--color-text-muted:#797876;--color-text-faint:#5a5957;
|
||||
--color-primary:#4f98a3;--color-primary-highlight:#1e3032;
|
||||
--color-success:#6daa45;--color-success-highlight:#1e3018;
|
||||
--color-warning:#bb653b;--color-warning-highlight:#2e1e10;
|
||||
--color-error:#d163a7;--color-error-highlight:#2a1420;
|
||||
--color-notification:#dd6974;--color-orange:#fdab43;
|
||||
--shadow-sm:0 1px 2px oklch(0 0 0/.3);--shadow-md:0 4px 12px oklch(0 0 0/.4);
|
||||
}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
html{-webkit-font-smoothing:antialiased}
|
||||
body{min-height:100dvh;font-family:var(--font-body);font-size:var(--text-base);color:var(--color-text);background:var(--color-bg);display:flex;flex-direction:column}
|
||||
header{position:sticky;top:0;z-index:100;background:var(--color-surface);border-bottom:1px solid var(--color-border);padding:var(--sp4) var(--sp6);display:flex;align-items:center;justify-content:space-between;box-shadow:var(--shadow-sm)}
|
||||
.logo{display:flex;align-items:center;gap:var(--sp3)}
|
||||
.logo svg{color:var(--color-primary)}
|
||||
.logo-text{font-size:var(--text-lg);font-weight:700;letter-spacing:-.02em}
|
||||
.logo-sub{font-size:var(--text-xs);color:var(--color-text-muted)}
|
||||
.theme-btn{width:36px;height:36px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-surface-2);display:flex;align-items:center;justify-content:center;color:var(--color-text-muted);cursor:pointer;transition:background var(--transition),color var(--transition)}
|
||||
.theme-btn:hover{background:var(--color-surface-offset);color:var(--color-text)}
|
||||
main{flex:1;max-width:860px;width:100%;margin:0 auto;padding:var(--sp8) var(--sp6);display:flex;flex-direction:column;gap:var(--sp6)}
|
||||
.status-card{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-xl);padding:var(--sp5) var(--sp6);display:flex;align-items:center;justify-content:space-between;gap:var(--sp4);box-shadow:var(--shadow-sm)}
|
||||
.status-info{display:flex;flex-direction:column;gap:var(--sp1)}
|
||||
.status-label{font-size:var(--text-xs);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.06em}
|
||||
.status-value{font-size:var(--text-base);font-weight:600;display:flex;align-items:center;gap:var(--sp2)}
|
||||
.dot{width:8px;height:8px;border-radius:var(--radius-full);background:var(--color-text-faint);transition:background var(--transition)}
|
||||
.dot.running{background:var(--color-success);box-shadow:0 0 6px color-mix(in oklch,var(--color-success) 60%,transparent)}
|
||||
.dot.stopped{background:var(--color-notification)}
|
||||
.dot.loading{background:var(--color-orange);animation:pulse 1s ease-in-out infinite}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.sec-title{font-size:var(--text-xs);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.06em;font-weight:600}
|
||||
.btn-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(180px,100%),1fr));gap:var(--sp3)}
|
||||
.btn{display:flex;align-items:center;justify-content:center;gap:var(--sp2);padding:var(--sp4) var(--sp5);border-radius:var(--radius-lg);border:1px solid transparent;font-size:var(--text-sm);font-weight:600;cursor:pointer;transition:all var(--transition)}
|
||||
.btn:active{transform:scale(.97)}
|
||||
.btn:disabled{opacity:.45;cursor:not-allowed;transform:none}
|
||||
.btn-start{background:var(--color-success-highlight);color:var(--color-success);border-color:color-mix(in oklch,var(--color-success) 25%,transparent)}
|
||||
.btn-start:hover:not(:disabled){background:var(--color-success);color:#fff;border-color:var(--color-success);box-shadow:var(--shadow-md)}
|
||||
.btn-stop{background:var(--color-error-highlight);color:var(--color-error);border-color:color-mix(in oklch,var(--color-error) 25%,transparent)}
|
||||
.btn-stop:hover:not(:disabled){background:var(--color-error);color:#fff;border-color:var(--color-error);box-shadow:var(--shadow-md)}
|
||||
.btn-restart{background:var(--color-warning-highlight);color:var(--color-warning);border-color:color-mix(in oklch,var(--color-warning) 25%,transparent)}
|
||||
.btn-restart:hover:not(:disabled){background:var(--color-warning);color:#fff;border-color:var(--color-warning);box-shadow:var(--shadow-md)}
|
||||
.btn-reload{background:var(--color-primary-highlight);color:var(--color-primary);border-color:color-mix(in oklch,var(--color-primary) 25%,transparent)}
|
||||
.btn-reload:hover:not(:disabled){background:var(--color-primary);color:#fff;border-color:var(--color-primary);box-shadow:var(--shadow-md)}
|
||||
.btn-stat{background:var(--color-surface-2);color:var(--color-text-muted);border-color:var(--color-border)}
|
||||
.btn-stat:hover:not(:disabled){background:var(--color-surface-offset);color:var(--color-text)}
|
||||
.svc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(260px,100%),1fr));gap:var(--sp4)}
|
||||
.svc-card{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-xl);padding:var(--sp5);box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:var(--sp4)}
|
||||
.svc-head{display:flex;align-items:center;justify-content:space-between}
|
||||
.svc-name{font-weight:700;font-size:var(--text-sm)}
|
||||
.badge{font-size:var(--text-xs);font-weight:600;padding:2px 8px;border-radius:var(--radius-full);background:var(--color-surface-offset);color:var(--color-text-muted);transition:all var(--transition)}
|
||||
.badge.running{background:var(--color-success-highlight);color:var(--color-success)}
|
||||
.badge.stopped{background:var(--color-error-highlight);color:var(--color-error)}
|
||||
.svc-btns{display:flex;gap:var(--sp2)}
|
||||
.svc-btn{flex:1;padding:var(--sp2) var(--sp3);border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-surface-2);color:var(--color-text-muted);font-size:var(--text-xs);font-weight:600;cursor:pointer;transition:all var(--transition)}
|
||||
.svc-btn:disabled{opacity:.4;cursor:not-allowed}
|
||||
.svc-btn.s:hover:not(:disabled){background:var(--color-success);color:#fff;border-color:var(--color-success)}
|
||||
.svc-btn.x:hover:not(:disabled){background:var(--color-error);color:#fff;border-color:var(--color-error)}
|
||||
.log-wrap{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-sm)}
|
||||
.log-head{padding:var(--sp3) var(--sp5);border-bottom:1px solid var(--color-divider);display:flex;align-items:center;justify-content:space-between;background:var(--color-surface-2)}
|
||||
.log-title{font-size:var(--text-sm);font-weight:600}
|
||||
.log-clr{font-size:var(--text-xs);color:var(--color-text-muted);background:none;border:none;cursor:pointer;padding:var(--sp1) var(--sp2);border-radius:var(--radius-sm);transition:all var(--transition)}
|
||||
.log-clr:hover{color:var(--color-text);background:var(--color-surface-offset)}
|
||||
#log{font-family:var(--font-mono);font-size:.8rem;line-height:1.7;padding:var(--sp4) var(--sp5);height:220px;overflow-y:auto;white-space:pre-wrap;word-break:break-all}
|
||||
.ll{margin-bottom:2px;color:var(--color-text-faint)}
|
||||
.ll.ok{color:var(--color-success)}.ll.er{color:var(--color-error)}
|
||||
.ll.in{color:var(--color-primary)}.ll.wa{color:var(--color-orange)}
|
||||
footer{padding:var(--sp4) var(--sp6);border-top:1px solid var(--color-divider);text-align:center;font-size:var(--text-xs);color:var(--color-text-faint)}
|
||||
footer code{font-family:var(--font-mono)}
|
||||
@media(max-width:480px){main,header{padding-inline:var(--sp4)}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="logo">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
||||
<rect width="32" height="32" rx="8" fill="currentColor" fill-opacity="0.1"/>
|
||||
<path d="M8 10L14 16L8 22" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M17 10L24 16L17 22" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<div><div class="logo-text">XAMPP Control</div><div class="logo-sub">/opt/lampp</div></div>
|
||||
</div>
|
||||
<button class="theme-btn" id="themeBtn" aria-label="Toggle theme"></button>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="status-card">
|
||||
<div class="status-info">
|
||||
<div class="status-label">XAMPP Status</div>
|
||||
<div class="status-value"><span class="dot" id="mainDot"></span><span id="mainTxt">Unknown</span></div>
|
||||
</div>
|
||||
<div class="status-info" style="text-align:right">
|
||||
<div class="status-label">Backend</div>
|
||||
<div id="backendTxt" style="font-size:var(--text-xs);font-weight:600;color:var(--color-text-muted)">Connecting...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:var(--sp3)">
|
||||
<div class="sec-title">All Services</div>
|
||||
<div class="btn-grid">
|
||||
<button class="btn btn-start" onclick="run('start')">▶ Start</button>
|
||||
<button class="btn btn-stop" onclick="run('stop')">■ Stop</button>
|
||||
<button class="btn btn-restart" onclick="run('restart')">↺ Restart</button>
|
||||
<button class="btn btn-reload" onclick="run('reload')">⟳ Reload</button>
|
||||
<button class="btn btn-stat" onclick="run('status')">ⓘ Status</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:var(--sp3)">
|
||||
<div class="sec-title">Individual Services</div>
|
||||
<div class="svc-grid">
|
||||
<div class="svc-card">
|
||||
<div class="svc-head"><div class="svc-name">🌐 Apache</div><span class="badge" id="b-apache">—</span></div>
|
||||
<div class="svc-btns">
|
||||
<button class="svc-btn s" onclick="run('startapache')">▶ Start</button>
|
||||
<button class="svc-btn x" onclick="run('stopapache')">■ Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="svc-card">
|
||||
<div class="svc-head"><div class="svc-name">🐬 MySQL</div><span class="badge" id="b-mysql">—</span></div>
|
||||
<div class="svc-btns">
|
||||
<button class="svc-btn s" onclick="run('startmysql')">▶ Start</button>
|
||||
<button class="svc-btn x" onclick="run('stopmysql')">■ Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="svc-card">
|
||||
<div class="svc-head"><div class="svc-name">📁 FTP</div><span class="badge" id="b-ftp">—</span></div>
|
||||
<div class="svc-btns">
|
||||
<button class="svc-btn s" onclick="run('startftp')">▶ Start</button>
|
||||
<button class="svc-btn x" onclick="run('stopftp')">■ Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="log-wrap">
|
||||
<div class="log-head">
|
||||
<div class="log-title">📄 Output</div>
|
||||
<button class="log-clr" onclick="clearLog()">Clear</button>
|
||||
</div>
|
||||
<div id="log"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Backend: <code>python3 ~/xampp-gui/xampp-backend.py</code> ·
|
||||
HTML: <code>cd ~/xampp-gui && python3 -m http.server 8080</code>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const B='http://localhost:5050';
|
||||
let ok=false,busy=false;
|
||||
|
||||
(()=>{
|
||||
const btn=document.getElementById('themeBtn'),h=document.documentElement;
|
||||
let d=h.getAttribute('data-theme')||'dark';
|
||||
const sun='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>';
|
||||
const moon='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
|
||||
btn.innerHTML=d==='dark'?moon:sun;
|
||||
btn.onclick=()=>{d=d==='dark'?'light':'dark';h.setAttribute('data-theme',d);btn.innerHTML=d==='dark'?moon:sun;};
|
||||
})();
|
||||
|
||||
const logEl=document.getElementById('log');
|
||||
function log(msg,t=''){
|
||||
const d=document.createElement('div');
|
||||
d.className='ll'+(t?' '+t:'');
|
||||
d.textContent='['+new Date().toLocaleTimeString('en-GB')+'] '+msg;
|
||||
logEl.appendChild(d);logEl.scrollTop=logEl.scrollHeight;
|
||||
}
|
||||
function clearLog(){logEl.innerHTML='';log('Log cleared.');}
|
||||
|
||||
function setMain(state){
|
||||
document.getElementById('mainDot').className='dot '+state;
|
||||
document.getElementById('mainTxt').textContent=
|
||||
state==='running'?'Running':state==='stopped'?'Stopped':state==='loading'?'Executing...':'Unknown';
|
||||
}
|
||||
|
||||
function badge(id,state){
|
||||
const el=document.getElementById('b-'+id);
|
||||
if(!el)return;
|
||||
el.className='badge'+(state?' '+state:'');
|
||||
el.textContent=state==='running'?'Running':state==='stopped'?'Stopped':'—';
|
||||
}
|
||||
|
||||
function parseStatus(output){
|
||||
const lines=output.split('\n');
|
||||
const svcs=[
|
||||
{keys:['apache'],id:'apache'},
|
||||
{keys:['mysql'],id:'mysql'},
|
||||
{keys:['proftpd','ftp'],id:'ftp'},
|
||||
];
|
||||
const states={};
|
||||
for(const line of lines){
|
||||
const low=line.toLowerCase().trim();
|
||||
if(!low)continue;
|
||||
for(const {keys,id} of svcs){
|
||||
if(!keys.some(k=>low.includes(k)))continue;
|
||||
if(/is running/.test(low)){states[id]='running';break;}
|
||||
if(/is not running/.test(low)){states[id]='stopped';break;}
|
||||
if(/starting/.test(low)){states[id]=/ok\.|already running/.test(low)?'running':'stopped';break;}
|
||||
if(/stopping/.test(low)){states[id]='stopped';break;}
|
||||
if(/started|ok\./.test(low)&&!/not running/.test(low)){states[id]='running';break;}
|
||||
if(/not running|stopped/.test(low)){states[id]='stopped';break;}
|
||||
}
|
||||
}
|
||||
for(const [id,state] of Object.entries(states))badge(id,state);
|
||||
if('apache' in states)setMain(states['apache']);
|
||||
}
|
||||
|
||||
function lockBtns(v){document.querySelectorAll('.btn,.svc-btn').forEach(b=>b.disabled=v);}
|
||||
|
||||
async function run(cmd){
|
||||
if(!ok){log('Backend not reachable! Run: python3 xampp-backend.py','er');return;}
|
||||
if(busy){log('Please wait, a command is already running...','wa');return;}
|
||||
busy=true;lockBtns(true);setMain('loading');
|
||||
log('Running: ./xampp '+cmd+' ...','in');
|
||||
try{
|
||||
const res=await fetch(B+'/run',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({cmd}),signal:AbortSignal.timeout(180000)
|
||||
});
|
||||
const data=await res.json();
|
||||
const out=data.output||'';
|
||||
out.split('\n').filter(l=>l.trim()).forEach(l=>{
|
||||
const low=l.toLowerCase();
|
||||
const t=low.includes('error')||low.includes('fail')?'er'
|
||||
:/ok\.|starting|stopping|running/.test(low)?'ok':'';
|
||||
log(l,t);
|
||||
});
|
||||
parseStatus(out);
|
||||
if(['start','stop','restart','reload'].includes(cmd))setTimeout(fetchStatus,1500);
|
||||
}catch(e){
|
||||
log(e.name==='TimeoutError'||e.name==='AbortError'?'Timeout — command took too long':'Error: '+e.message,'er');
|
||||
setTimeout(fetchStatus,1000);
|
||||
}finally{busy=false;lockBtns(false);}
|
||||
}
|
||||
|
||||
async function fetchStatus(){
|
||||
if(!ok||busy)return;
|
||||
try{
|
||||
const res=await fetch(B+'/run',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({cmd:'status'}),signal:AbortSignal.timeout(30000)
|
||||
});
|
||||
parseStatus((await res.json()).output||'');
|
||||
}catch(_){}
|
||||
}
|
||||
|
||||
async function ping(){
|
||||
if(busy)return;
|
||||
try{
|
||||
await fetch(B+'/ping',{signal:AbortSignal.timeout(5000)});
|
||||
if(!ok){
|
||||
ok=true;
|
||||
document.getElementById('backendTxt').textContent='Connected';
|
||||
document.getElementById('backendTxt').style.color='var(--color-success)';
|
||||
log('Backend connected (port 5050)','ok');
|
||||
fetchStatus();
|
||||
}
|
||||
}catch(_){
|
||||
ok=false;
|
||||
document.getElementById('backendTxt').textContent='Not connected';
|
||||
document.getElementById('backendTxt').style.color='var(--color-error)';
|
||||
}
|
||||
}
|
||||
|
||||
log('XAMPP Control Panel ready.','in');
|
||||
log('Connecting to backend...','');
|
||||
ping();
|
||||
setInterval(ping,30000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import subprocess
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
XAMPP = '/opt/lampp/xampp'
|
||||
ALLOWED = {'start','stop','restart','reload','status','startapache','stopapache','startmysql','stopmysql','startftp','stopftp'}
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args):
|
||||
pass
|
||||
|
||||
def _cors(self):
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||
|
||||
def _send(self, code, body):
|
||||
data = json.dumps(body).encode()
|
||||
try:
|
||||
self.send_response(code)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self._cors()
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(204)
|
||||
self._cors()
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/ping':
|
||||
self._send(200, {'ok': True})
|
||||
else:
|
||||
self._send(404, {'error': 'Not found'})
|
||||
|
||||
def do_POST(self):
|
||||
if self.path != '/run':
|
||||
self._send(404, {'error': 'Not found'})
|
||||
return
|
||||
length = int(self.headers.get('Content-Length', 0))
|
||||
body = json.loads(self.rfile.read(length))
|
||||
cmd = body.get('cmd', '').strip()
|
||||
if cmd not in ALLOWED:
|
||||
self._send(400, {'error': f'Command not allowed: {cmd}'})
|
||||
return
|
||||
try:
|
||||
result = subprocess.run(['sudo', XAMPP, cmd], capture_output=True, text=True, timeout=180)
|
||||
output = (result.stdout + result.stderr).strip()
|
||||
self._send(200, {'output': output, 'code': result.returncode})
|
||||
except subprocess.TimeoutExpired:
|
||||
self._send(200, {'output': 'Timeout after 180 seconds.', 'code': -1})
|
||||
except Exception as e:
|
||||
self._send(500, {'error': str(e)})
|
||||
|
||||
if __name__ == '__main__':
|
||||
server = ThreadingHTTPServer(('127.0.0.1', 5050), Handler)
|
||||
print('XAMPP GUI Backend - Port 5050')
|
||||
print('Open: http://localhost:8080/')
|
||||
print('Stop: Ctrl+C')
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
pkill -f xampp-backend 2>/dev/null
|
||||
pkill -f http-server 2>/dev/null
|
||||
pkill -f 'python3 -m http.server' 2>/dev/null
|
||||
sleep 1
|
||||
|
||||
sudo -v
|
||||
|
||||
echo "Starting XAMPP GUI Backend..."
|
||||
python3 xampp-backend.py &
|
||||
python3 http-server.py &
|
||||
|
||||
sleep 1
|
||||
|
||||
xdg-open "http://localhost:8080/" 2>/dev/null || \
|
||||
firefox "http://localhost:8080/" 2>/dev/null || \
|
||||
echo "Open manually: http://localhost:8080/"
|
||||
|
||||
echo "GUI running at: http://localhost:8080/"
|
||||
wait
|
||||
Reference in New Issue
Block a user