OrbitMC commited on
Commit
44c040e
·
verified ·
1 Parent(s): 0de7f14

Update panel.py

Browse files
Files changed (1) hide show
  1. panel.py +58 -34
panel.py CHANGED
@@ -1,11 +1,11 @@
1
  import os
2
  import asyncio
3
  import collections
4
- import shutil
5
  from fastapi import FastAPI, WebSocket, Request, Response, Form, UploadFile, File, HTTPException
6
- from fastapi.responses import HTMLResponse, FileResponse
7
  from fastapi.middleware.cors import CORSMiddleware
8
  import uvicorn
 
9
 
10
  app = FastAPI()
11
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
@@ -13,8 +13,12 @@ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
13
  mc_process = None
14
  output_history = collections.deque(maxlen=300)
15
  connected_clients = set()
16
- BASE_DIR = os.path.abspath("/app")
 
17
 
 
 
 
18
  HTML_CONTENT = """<!DOCTYPE html>
19
  <html lang="en">
20
  <head>
@@ -466,31 +470,39 @@ function escHtml(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(
466
  loadDir('');
467
  </script>
468
  </body>
469
- </html>"""
 
470
 
 
 
 
471
  def get_safe_path(subpath: str):
472
  subpath = (subpath or "").strip("/")
473
  target = os.path.abspath(os.path.join(BASE_DIR, subpath))
474
  if not target.startswith(BASE_DIR):
475
- raise HTTPException(status_code=403, detail="Access denied")
476
  return target
477
 
478
  async def broadcast(message: str):
479
  output_history.append(message)
480
- dead = set()
481
  for client in connected_clients:
482
  try:
483
  await client.send_text(message)
484
  except:
485
- dead.add(client)
486
- connected_clients.difference_update(dead)
487
 
488
- async def read_stream(stream):
 
 
 
489
  while True:
490
  try:
491
  line = await stream.readline()
492
  if not line: break
493
- await broadcast(line.decode('utf-8', errors='replace').rstrip('\r\n'))
 
494
  except Exception:
495
  break
496
 
@@ -510,8 +522,10 @@ async def start_minecraft():
510
  "-jar", "purpur.jar", "--nogui"
511
  ]
512
  mc_process = await asyncio.create_subprocess_exec(
513
- *java_args, stdin=asyncio.subprocess.PIPE,
514
- stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT,
 
 
515
  cwd=BASE_DIR
516
  )
517
  asyncio.create_task(read_stream(mc_process.stdout))
@@ -520,12 +534,15 @@ async def start_minecraft():
520
  async def startup_event():
521
  asyncio.create_task(start_minecraft())
522
 
 
 
 
523
  @app.get("/")
524
  def get_panel():
525
  return HTMLResponse(content=HTML_CONTENT)
526
 
527
  @app.websocket("/ws")
528
- async def ws_endpoint(websocket: WebSocket):
529
  await websocket.accept()
530
  connected_clients.add(websocket)
531
  for line in output_history:
@@ -537,7 +554,7 @@ async def ws_endpoint(websocket: WebSocket):
537
  mc_process.stdin.write((cmd + "\n").encode('utf-8'))
538
  await mc_process.stdin.drain()
539
  except:
540
- connected_clients.discard(websocket)
541
 
542
  @app.get("/api/fs/list")
543
  def fs_list(path: str = ""):
@@ -554,8 +571,11 @@ def fs_read(path: str):
554
  target = get_safe_path(path)
555
  if not os.path.isfile(target): raise HTTPException(400, "Not a file")
556
  try:
557
- with open(target, 'r', encoding='utf-8') as f: return Response(content=f.read(), media_type="text/plain")
558
- except: raise HTTPException(400, "File is binary")
 
 
 
559
 
560
  @app.get("/api/fs/download")
561
  def fs_download(path: str):
@@ -565,37 +585,41 @@ def fs_download(path: str):
565
 
566
  @app.post("/api/fs/write")
567
  def fs_write(path: str = Form(...), content: str = Form(...)):
568
- with open(get_safe_path(path), 'w', encoding='utf-8') as f: f.write(content)
 
 
569
  return {"status": "ok"}
570
 
571
  @app.post("/api/fs/upload")
572
  async def fs_upload(path: str = Form(""), file: UploadFile = File(...)):
573
- with open(os.path.join(get_safe_path(path), file.filename), "wb") as buffer:
 
 
574
  shutil.copyfileobj(file.file, buffer)
575
  return {"status": "ok"}
576
 
577
- @app.post("/api/fs/delete")
578
- def fs_delete(path: str = Form(...)):
579
- t = get_safe_path(path)
580
- if os.path.isdir(t): shutil.rmtree(t)
581
- else: os.remove(t)
 
 
 
 
582
  return {"status": "ok"}
583
 
584
  @app.post("/api/fs/mkdir")
585
  def fs_mkdir(path: str = Form(...)):
586
- os.makedirs(get_safe_path(path), exist_ok=True)
587
- return {"status": "ok"}
588
-
589
- @app.post("/api/fs/rename")
590
- def fs_rename(path: str = Form(...), new_name: str = Form(...)):
591
- src = get_safe_path(path)
592
- dst = os.path.join(os.path.dirname(src), new_name)
593
- os.rename(src, dst)
594
  return {"status": "ok"}
595
 
596
- @app.post("/api/fs/move")
597
- def fs_move(src_path: str = Form(...), dst_path: str = Form(...)):
598
- shutil.move(get_safe_path(src_path), get_safe_path(dst_path))
 
 
599
  return {"status": "ok"}
600
 
601
  if __name__ == "__main__":
 
1
  import os
2
  import asyncio
3
  import collections
 
4
  from fastapi import FastAPI, WebSocket, Request, Response, Form, UploadFile, File, HTTPException
5
+ from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
6
  from fastapi.middleware.cors import CORSMiddleware
7
  import uvicorn
8
+ import shutil
9
 
10
  app = FastAPI()
11
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
 
13
  mc_process = None
14
  output_history = collections.deque(maxlen=300)
15
  connected_clients = set()
16
+ # Automatically adapts to Hugging Face Docker environments (/app, /home/user/app, etc.)
17
+ BASE_DIR = os.path.abspath(os.getcwd())
18
 
19
+ # -----------------
20
+ # HTML FRONTEND (Ultra-Modern UI)
21
+ # -----------------
22
  HTML_CONTENT = """<!DOCTYPE html>
23
  <html lang="en">
24
  <head>
 
470
  loadDir('');
471
  </script>
472
  </body>
473
+ </html>
474
+ """
475
 
476
+ # -----------------
477
+ # UTILITIES
478
+ # -----------------
479
  def get_safe_path(subpath: str):
480
  subpath = (subpath or "").strip("/")
481
  target = os.path.abspath(os.path.join(BASE_DIR, subpath))
482
  if not target.startswith(BASE_DIR):
483
+ raise HTTPException(status_code=403, detail="Access denied outside server directory")
484
  return target
485
 
486
  async def broadcast(message: str):
487
  output_history.append(message)
488
+ dead_clients = set()
489
  for client in connected_clients:
490
  try:
491
  await client.send_text(message)
492
  except:
493
+ dead_clients.add(client)
494
+ connected_clients.difference_update(dead_clients)
495
 
496
+ # -----------------
497
+ # SERVER PROCESSES
498
+ # -----------------
499
+ async def read_stream(stream, prefix=""):
500
  while True:
501
  try:
502
  line = await stream.readline()
503
  if not line: break
504
+ line_str = line.decode('utf-8', errors='replace').rstrip('\r\n')
505
+ await broadcast(prefix + line_str)
506
  except Exception:
507
  break
508
 
 
522
  "-jar", "purpur.jar", "--nogui"
523
  ]
524
  mc_process = await asyncio.create_subprocess_exec(
525
+ *java_args,
526
+ stdin=asyncio.subprocess.PIPE,
527
+ stdout=asyncio.subprocess.PIPE,
528
+ stderr=asyncio.subprocess.STDOUT,
529
  cwd=BASE_DIR
530
  )
531
  asyncio.create_task(read_stream(mc_process.stdout))
 
534
  async def startup_event():
535
  asyncio.create_task(start_minecraft())
536
 
537
+ # -----------------
538
+ # API ROUTING
539
+ # -----------------
540
  @app.get("/")
541
  def get_panel():
542
  return HTMLResponse(content=HTML_CONTENT)
543
 
544
  @app.websocket("/ws")
545
+ async def websocket_endpoint(websocket: WebSocket):
546
  await websocket.accept()
547
  connected_clients.add(websocket)
548
  for line in output_history:
 
554
  mc_process.stdin.write((cmd + "\n").encode('utf-8'))
555
  await mc_process.stdin.drain()
556
  except:
557
+ connected_clients.remove(websocket)
558
 
559
  @app.get("/api/fs/list")
560
  def fs_list(path: str = ""):
 
571
  target = get_safe_path(path)
572
  if not os.path.isfile(target): raise HTTPException(400, "Not a file")
573
  try:
574
+ with open(target, 'r', encoding='utf-8') as f:
575
+ return Response(content=f.read(), media_type="text/plain")
576
+ except UnicodeDecodeError:
577
+ # Prevent binary files (like .jar or .world) from crashing the API/Frontend
578
+ raise HTTPException(400, "File is binary or unsupported encoding")
579
 
580
  @app.get("/api/fs/download")
581
  def fs_download(path: str):
 
585
 
586
  @app.post("/api/fs/write")
587
  def fs_write(path: str = Form(...), content: str = Form(...)):
588
+ target = get_safe_path(path)
589
+ with open(target, 'w', encoding='utf-8') as f:
590
+ f.write(content)
591
  return {"status": "ok"}
592
 
593
  @app.post("/api/fs/upload")
594
  async def fs_upload(path: str = Form(""), file: UploadFile = File(...)):
595
+ target_dir = get_safe_path(path)
596
+ target_file = os.path.join(target_dir, file.filename)
597
+ with open(target_file, "wb") as buffer:
598
  shutil.copyfileobj(file.file, buffer)
599
  return {"status": "ok"}
600
 
601
+ @app.post("/api/fs/rename")
602
+ def fs_rename(path: str = Form(...), new_name: str = Form(...)):
603
+ target = get_safe_path(path)
604
+ if not os.path.exists(target):
605
+ raise HTTPException(404, "File not found")
606
+ if "/" in new_name or "\\" in new_name:
607
+ raise HTTPException(400, "Invalid new name")
608
+ new_target = get_safe_path(os.path.join(os.path.dirname(path), new_name))
609
+ os.rename(target, new_target)
610
  return {"status": "ok"}
611
 
612
  @app.post("/api/fs/mkdir")
613
  def fs_mkdir(path: str = Form(...)):
614
+ target = get_safe_path(path)
615
+ os.makedirs(target, exist_ok=True)
 
 
 
 
 
 
616
  return {"status": "ok"}
617
 
618
+ @app.post("/api/fs/delete")
619
+ def fs_delete(path: str = Form(...)):
620
+ target = get_safe_path(path)
621
+ if os.path.isdir(target): shutil.rmtree(target)
622
+ else: os.remove(target)
623
  return {"status": "ok"}
624
 
625
  if __name__ == "__main__":