MCPanel/app/api.py
2025-04-13 17:38:54 +02:00

345 lines
10 KiB
Python

import json
import os
import shutil
import subprocess
import docker
import requests
from flask import Blueprint, jsonify, request, send_from_directory, abort
from .auth import oidc
from .docker_utils import start_server, stop_server, restart_server, get_logs, delete_server, save_server_info, \
DEFAULT_CONFIG
api = Blueprint('api', __name__)
client = docker.from_env()
# Server Deployment
@api.route('/setup', methods=['POST'])
@oidc.require_login
def setup_server():
data = request.get_json()
username = oidc.user_getfield('preferred_username')
type_ = data['type']
version = data['version']
path = f"./servers/mc-{username}"
os.makedirs(path, exist_ok=True)
save_server_info(username, type_, version)
return jsonify({'success': True})
@api.route('/delete', methods=['POST'])
@oidc.require_login
def delete():
username = oidc.user_getfield('preferred_username')
if not username:
return jsonify({"error": "Brak nazwy użytkownika"}), 400
result = delete_server(username)
return jsonify({"message": result})
# Server Controls
@api.route('/start', methods=['POST'])
@oidc.require_login
def start():
username = oidc.user_getfield('preferred_username')
setup_file_path = f"./servers/mc-{username}/server_info.json"
if not os.path.exists(setup_file_path):
return jsonify({"error": "Server setup file not found."}), 400
with open(setup_file_path, 'r') as file:
server_info = json.load(file)
server_type = server_info.get('type')
server_version = server_info.get('version')
if not server_type or not server_version:
return jsonify({"error": "Invalid server info."}), 400
start_server(username)
return jsonify({"status": "started"})
@api.route('/stop', methods=['POST'])
@oidc.require_login
def stop():
username = oidc.user_getfield('preferred_username')
stop_server(username)
return jsonify({"status": "stopped"})
@api.route('/restart', methods=['POST'])
@oidc.require_login
def restart():
username = oidc.user_getfield('preferred_username')
restart_server(username)
return jsonify({"status": "restarted"})
@api.route('/logs', methods=['GET'])
@oidc.require_login
def logs():
username = oidc.user_getfield('preferred_username')
return jsonify({"logs": get_logs(username)})
@api.route('/command', methods=['POST'])
@oidc.require_login
def send_command():
data = request.get_json()
username = data['username']
command = data['command']
if username != oidc.user_getfield('preferred_username'):
return jsonify({"error": "Unauthorized request."}), 403
container_name = f"mc-{username}"
try:
subprocess.run(
["docker", "exec", container_name, "rcon-cli", command],
check=True,
capture_output=True
)
return jsonify(success=True)
except subprocess.CalledProcessError as e:
return jsonify(success=False, error=str(e)), 500
# Files APIs (Upload, download, delete)
@api.route('/files', methods=['GET'])
@oidc.require_login
def list_files():
username = oidc.user_getfield('preferred_username')
path = request.args.get('path', '')
base_path = os.path.abspath(f'./servers/mc-{username}')
requested_path = os.path.abspath(os.path.join(base_path, path))
if not requested_path.startswith(base_path):
return abort(403) # Prevent directory traversal
if not os.path.exists(requested_path):
return abort(404)
entries = []
for item in os.listdir(requested_path):
if item == "server_info.json": # Hiding panel-specific files
continue
full_path = os.path.join(requested_path, item)
entries.append({
'name': item,
'is_dir': os.path.isdir(full_path)
})
return jsonify(entries)
@api.route('/files/download', methods=['GET'])
@oidc.require_login
def download_file():
username = oidc.user_getfield('preferred_username')
path = request.args.get('path')
base_path = os.path.abspath(f'./servers/mc-{username}')
file_path = os.path.abspath(os.path.join(base_path, path))
if not file_path.startswith(base_path) or not os.path.isfile(file_path):
return abort(403)
directory = os.path.dirname(file_path)
filename = os.path.basename(file_path)
return send_from_directory(directory, filename, as_attachment=True)
@api.route('/files/upload', methods=['POST'])
@oidc.require_login
def upload_file():
username = oidc.user_getfield('preferred_username')
path = request.form.get('path', '')
file = request.files['files']
base_path = os.path.abspath(f'./servers/mc-{username}')
upload_path = os.path.abspath(os.path.join(base_path, path))
if not upload_path.startswith(base_path):
return abort(403)
os.makedirs(upload_path, exist_ok=True)
file.save(os.path.join(upload_path, file.filename))
return jsonify({'success': True})
@api.route('/files/delete', methods=['POST'])
@oidc.require_login
def delete_file_or_folder():
data = request.get_json()
username = data.get('username')
if username != oidc.user_getfield('preferred_username'):
return jsonify({"error": "Unauthorized request."}), 403
path = data.get('path')
base_path = os.path.abspath(f'./servers/mc-{username}')
target_path = os.path.abspath(os.path.join(base_path, path))
if not target_path.startswith(base_path):
return abort(403)
if not os.path.exists(target_path):
return jsonify({"error": "Nie znaleziono pliku lub folderu"}), 404
try:
if os.path.isdir(target_path):
shutil.rmtree(target_path)
else:
os.remove(target_path)
return jsonify({"success": True})
except Exception as e:
return jsonify({"error": str(e)}), 500
# Server config
@api.route('/config')
@oidc.require_login
def get_config():
username = oidc.user_getfield('preferred_username')
server_info_path = f'./servers/mc-{username}/server_info.json'
if not os.path.exists(server_info_path):
return jsonify({"success": False, "message": "Server config not found"})
with open(server_info_path, 'r') as f:
server_info = json.load(f)
return jsonify({"success": True, "config": server_info["config"], "version": server_info["version"],
"type": server_info["type"]})
@api.route('/config', methods=['POST'])
@oidc.require_login
def update_config():
data = request.json
username = data.get('username')
if username != oidc.user_getfield('preferred_username'):
return jsonify({"error": "Unauthorized request."}), 403
incoming_config = data.get('config', {})
server_info_path = f'./servers/mc-{username}/server_info.json'
if os.path.exists(server_info_path):
with open(server_info_path, 'r') as f:
server_info = json.load(f)
else:
server_info = DEFAULT_CONFIG.copy()
for key in ["type", "version"]:
if key in incoming_config:
server_info[key] = incoming_config.pop(key)
formatted_config = {key.replace('_', '-'): value for key, value in incoming_config.items()}
server_info["config"] = formatted_config
with open(server_info_path, 'w') as f:
json.dump(server_info, f, indent=4)
return jsonify({"success": True})
# Server stats
@api.route("/status")
def status():
username = request.args.get("username")
container_name = f"mc-{username}"
try:
container = client.containers.get(container_name)
return jsonify(running=container.status == "running")
except docker.errors.NotFound:
return jsonify(running=False)
@api.route('/stats', methods=['GET'])
@oidc.require_login
def stats():
username = oidc.user_getfield("preferred_username")
container_name = f"mc-{username}"
try:
container = client.containers.get(container_name)
stats = container.stats(stream=False)
# RAM (MB)
memory_usage = stats['memory_stats']['usage'] / (1024 * 1024)
# CPU %
cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - stats['precpu_stats']['cpu_usage']['total_usage']
system_delta = stats['cpu_stats']['system_cpu_usage'] - stats['precpu_stats']['system_cpu_usage']
percpu = stats['cpu_stats']['cpu_usage'].get('percpu_usage')
cpu_count = len(percpu) if percpu else 1
cpu_usage = (cpu_delta / system_delta) * cpu_count * 100 if system_delta > 0 else 0
# Disk Usage (GB)
server_path = f"./servers/{container_name}"
total_size = sum(
os.path.getsize(os.path.join(dp, f)) for dp, dn, filenames in os.walk(server_path) for f in filenames
) / (1024 * 1024 * 1024)
disk_usage = min(total_size, 15)
return jsonify({
"cpu": round(cpu_usage, 2),
"ram": round(memory_usage, 2),
"disk": round(disk_usage, 2),
"disk_max": 15
})
except docker.errors.NotFound:
return jsonify({"error": "Container not found"}), 404
# Modrinth
@api.route('/modrinth/search')
def modrinth_search():
query = request.args.get("query")
server_type = request.args.get("type") # 'fabric', 'paper', etc.
version = request.args.get("version")
categories = {
'fabric': 'mod',
'paper': 'plugin'
}
category = categories.get(server_type, 'mod') # fallback
response = requests.get(
f"https://api.modrinth.com/v2/search",
params={
"query": query,
"facets": f'[["project_type:{category}"],["versions:{version}"]]',
"limit": 10
}
)
return jsonify(response.json())
@api.route('/modrinth/download', methods=['POST'])
@oidc.require_login
def download_mod():
data = request.json
project_id = data['project_id']
username = data['username']
server_type = data['type']
version_response = requests.get(f"https://api.modrinth.com/v2/project/{project_id}/version")
version_data = version_response.json()
file_url = version_data[0]['files'][0]['url']
file_name = version_data[0]['files'][0]['filename']
folder = 'plugins' if server_type == 'paper' else 'mods'
file_path = f"./servers/mc-{username}/{folder}/{file_name}"
with requests.get(file_url, stream=True) as r:
with open(file_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
return jsonify({"success": True, "file": file_name})