Pathl.Stona/app.py
2026-01-30 21:41:10 +01:00

657 lines
21 KiB
Python

from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify, send_from_directory
from markupsafe import Markup
import os, json, time, re
import markdown
from datetime import datetime
from ql_interpreter import run_ql_code # Upewnij się że to jest
app = Flask(__name__, static_folder='static')
app.secret_key = "super_secret_key_12345"
USERS_FILE = "users.json"
BASE_DIR = "projects"
os.makedirs(BASE_DIR, exist_ok=True)
# ------------------------------
# Funkcje pomocnicze dla templatki
# ------------------------------
@app.context_processor
def utility_processor():
def file_size(filename):
"""Zwraca rozmiar pliku w czytelnej formie"""
try:
username = session.get('username', '')
project = 'Projekt1' # Domyślny projekt
if 'project' in request.view_args:
project = request.view_args['project']
file_path = os.path.join(BASE_DIR, username, project, filename)
if os.path.exists(file_path):
size = os.path.getsize(file_path)
if size < 1024:
return f"{size} B"
elif size < 1024 * 1024:
return f"{size / 1024:.1f} KB"
else:
return f"{size / (1024 * 1024):.1f} MB"
except:
pass
return "0 B"
def get_file_icon(filename):
"""Zwraca ikonę dla typu pliku"""
if filename.endswith('.ql'):
return 'bi-file-earmark-code'
elif filename.endswith('.md'):
return 'bi-file-earmark-text'
elif filename.endswith('.py'):
return 'bi-file-earmark-code'
elif filename.endswith('.js'):
return 'bi-file-earmark-code'
elif filename.endswith('.html'):
return 'bi-file-earmark-code'
elif filename.endswith('.css'):
return 'bi-file-earmark-code'
elif filename.endswith('.json'):
return 'bi-file-earmark-data'
else:
return 'bi-file-earmark'
def format_date(timestamp):
"""Formatuje datę"""
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M')
return dict(
file_size=file_size,
get_file_icon=get_file_icon,
format_date=format_date
)
# ------------------------------
# Pomocnicze funkcje
# ------------------------------
def load_users():
if not os.path.exists(USERS_FILE):
return {}
try:
with open(USERS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return {}
def save_users(users):
with open(USERS_FILE, "w", encoding='utf-8') as f:
json.dump(users, f, indent=4, ensure_ascii=False)
def create_user_dir(username):
user_path = os.path.join(BASE_DIR, username)
os.makedirs(user_path, exist_ok=True)
return user_path
# ------------------------------
# Routes
# ------------------------------
@app.route("/")
def index():
return redirect(url_for('login'))
@app.route("/login", methods=["GET", "POST"])
def login():
if 'username' in session:
return redirect(url_for('dashboard'))
if request.method == "POST":
username = request.form.get('username', '').strip()
password = request.form.get('password', '').strip()
if not username or not password:
flash("Wypełnij wszystkie pola!")
return render_template("login.html")
users = load_users()
if username in users and users[username]['password'] == password:
session['username'] = username
session['cwd'] = os.path.join(BASE_DIR, username)
return redirect(url_for('dashboard'))
flash("Nieprawidłowy login lub hasło")
return render_template("login.html")
@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = request.form.get('username', '').strip()
password = request.form.get('password', '').strip()
if not username or not password:
flash("Wypełnij wszystkie pola!")
return render_template("register.html")
if len(username) < 3:
flash("Nazwa użytkownika musi mieć co najmniej 3 znaki")
return render_template("register.html")
if len(password) < 4:
flash("Hasło musi mieć co najmniej 4 znaki")
return render_template("register.html")
users = load_users()
if username in users:
flash("Użytkownik już istnieje!")
else:
users[username] = {"password": password}
save_users(users)
user_path = create_user_dir(username)
default_project = os.path.join(user_path, "Projekt1")
os.makedirs(default_project, exist_ok=True)
with open(os.path.join(default_project, "main.ql"), "w", encoding='utf-8') as f:
f.write("# Mój pierwszy program QL\nprint('Witaj w QL!');")
with open(os.path.join(default_project, "README.md"), "w", encoding='utf-8') as f:
f.write(
"# Projekt1\n\nTo jest domyślny projekt.\n\n## Funkcje:\n- Edycja kodu QL\n- Konsola systemowa\n- Zarządzanie plikami")
flash("Rejestracja udana! Możesz się teraz zalogować.")
return redirect(url_for('login'))
return render_template("register.html")
@app.route("/logout")
def logout():
session.clear()
return redirect(url_for("login"))
@app.route("/dashboard")
def dashboard():
if 'username' not in session:
return redirect(url_for('login'))
username = session['username']
user_dir = os.path.join(BASE_DIR, username)
if not os.path.exists(user_dir):
os.makedirs(user_dir, exist_ok=True)
projects = []
if os.path.exists(user_dir):
for item in os.listdir(user_dir):
item_path = os.path.join(user_dir, item)
if os.path.isdir(item_path):
projects.append(item)
if not projects:
default_project = os.path.join(user_dir, "Projekt1")
os.makedirs(default_project, exist_ok=True)
with open(os.path.join(default_project, "main.ql"), "w", encoding='utf-8') as f:
f.write("# Mój pierwszy program QL\nprint('Witaj w QL!');")
projects.append("Projekt1")
last_project = projects[-1] if projects else "Projekt1"
# Zaktualizowane kafelki z kolorami i ikonami
tiles = [
{
"title": "🔄 Modele",
"desc": "Wybierz model i funkcję",
"link": url_for('models_page'),
"icon": "bi-diagram-3",
"bg_color": "#007bff",
"bg_color2": "#6610f2"
},
{
"title": "📁 Projekty",
"desc": "Zarządzaj swoimi projektami",
"link": url_for('user_projects', username=username),
"icon": "bi-folder",
"bg_color": "#28a745",
"bg_color2": "#20c997"
},
{
"title": "✏️ Edytor",
"desc": "Otwórz edytor kodu",
"link": url_for('ql_editor', username=username, project=last_project),
"icon": "bi-code-slash",
"bg_color": "#343a40",
"bg_color2": "#6c757d"
},
{
"title": "🛒 Marketplace",
"desc": "Przeglądaj moduły",
"link": "#",
"icon": "bi-shop",
"bg_color": "#ffc107",
"bg_color2": "#fd7e14"
},
{
"title": "💬 Chat",
"desc": "Czat z użytkownikami",
"link": "#",
"icon": "bi-chat",
"bg_color": "#dc3545",
"bg_color2": "#e83e8c"
},
{
"title": "⚙️ Ustawienia",
"desc": "Konfiguracja platformy",
"link": "#",
"icon": "bi-gear",
"bg_color": "#17a2b8",
"bg_color2": "#20c997"
},
{
"title": "📚 Dokumentacja",
"desc": "Przeczytaj dokumentację",
"link": "#",
"icon": "bi-book",
"bg_color": "#6f42c1",
"bg_color2": "#e83e8c"
},
{
"title": "🚪 Wyloguj",
"desc": "Zakończ sesję",
"link": url_for('logout'),
"icon": "bi-box-arrow-right",
"bg_color": "#6c757d",
"bg_color2": "#495057"
},
]
return render_template("dashboard.html",
username=username,
tiles=tiles,
projects=projects)
@app.route("/models")
def models_page():
if 'username' not in session:
return redirect(url_for('login'))
return render_template("models.html", username=session['username'])
# ------------------------------
# Edytor QL
# ------------------------------
@app.route("/editor/<username>/<project>", methods=["GET"])
def ql_editor(username, project):
if 'username' not in session or session['username'] != username:
return redirect(url_for('login'))
project_path = os.path.join(BASE_DIR, username, project)
if not os.path.exists(project_path):
os.makedirs(project_path, exist_ok=True)
with open(os.path.join(project_path, "main.ql"), "w", encoding='utf-8') as f:
f.write("# Mój program QL\nprint('Witaj!');")
# Pobierz wszystkie pliki (nie tylko .ql)
files = []
if os.path.exists(project_path):
for f in os.listdir(project_path):
if os.path.isfile(os.path.join(project_path, f)):
files.append(f)
selected_file = request.args.get("file")
if not selected_file:
if files:
selected_file = files[0]
else:
selected_file = "main.ql"
with open(os.path.join(project_path, selected_file), "w", encoding='utf-8') as f:
f.write("# Nowy program QL\nprint('Hello World!');")
files.append(selected_file)
content = ""
file_path = os.path.join(project_path, selected_file)
if os.path.exists(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except:
content = f"# Błąd odczytu pliku: {selected_file}"
return render_template("editor.html",
username=username,
project=project,
files=files,
selected_file=selected_file,
content=content)
@app.route("/editor/<username>/<project>/save", methods=["POST"])
def save_editor_file(username, project):
if 'username' not in session or session['username'] != username:
return jsonify({"error": "Nie zalogowano"}), 403
data = request.get_json()
if not data:
return jsonify({"error": "Brak danych"}), 400
filename = data.get("filename", "").strip()
content = data.get("content", "")
if not filename:
return jsonify({"error": "Brak nazwy pliku"}), 400
project_path = os.path.join(BASE_DIR, username, project)
os.makedirs(project_path, exist_ok=True)
file_path = os.path.join(project_path, filename)
with open(file_path, "w", encoding='utf-8') as f:
f.write(content)
return jsonify({"status": "ok", "filename": filename})
# ------------------------------
# Tworzenie pliku
# ------------------------------
@app.route("/editor/<username>/<project>/create", methods=["POST"])
def create_file(username, project):
if 'username' not in session or session['username'] != username:
return jsonify({"error": "Nie zalogowano"}), 403
data = request.get_json()
if not data:
return jsonify({"error": "Brak danych"}), 400
filename = data.get("filename", "").strip()
content = data.get("content", "")
if not filename:
return jsonify({"error": "Brak nazwy pliku"}), 400
project_path = os.path.join(BASE_DIR, username, project)
os.makedirs(project_path, exist_ok=True)
file_path = os.path.join(project_path, filename)
# Sprawdź czy plik już istnieje
if os.path.exists(file_path):
return jsonify({"error": "Plik już istnieje"}), 400
# Utwórz plik
with open(file_path, "w", encoding='utf-8') as f:
f.write(content if content else "")
return jsonify({"status": "ok", "filename": filename})
# ------------------------------
# Usuwanie pliku
# ------------------------------
@app.route("/editor/<username>/<project>/delete", methods=["POST"])
def delete_file(username, project):
if 'username' not in session or session['username'] != username:
return jsonify({"error": "Nie zalogowano"}), 403
data = request.get_json()
filename = data.get("filename", "").strip()
if not filename:
return jsonify({"error": "Brak nazwy pliku"}), 400
file_path = os.path.join(BASE_DIR, username, project, filename)
if not os.path.exists(file_path):
return jsonify({"error": "Plik nie istnieje"}), 404
try:
os.remove(file_path)
return jsonify({"status": "ok"})
except Exception as e:
return jsonify({"error": f"Błąd usuwania: {str(e)}"}), 500
@app.route("/editor/<username>/<project>/execute", methods=["POST"])
def execute_ql_code(username, project):
if 'username' not in session or session['username'] != username:
return jsonify({"error": "Nie zalogowano"}), 403
data = request.get_json()
if not data:
return jsonify({"error": "Brak kodu"}), 400
code = data.get("code", "")
try:
# Użyj NOWEGO interpretera QL (bez verbose)
result = run_ql_code(code)
# Zwróć TYLKO output - bez dodatkowych komunikatów
return jsonify({
"status": "success",
"output": result['output'] # TYLKO to co w print()
})
except Exception as e:
return jsonify({
"status": "error",
"output": [f"{str(e)}"] # TYLKO błąd
})
# ------------------------------
# Konsola systemowa
# ------------------------------
@app.route("/editor/<username>/<project>/console", methods=["POST"])
def console_command(username, project):
if 'username' not in session or session['username'] != username:
return jsonify({"output": "Nie zalogowano"}), 403
data = request.get_json()
if not data:
return jsonify({"output": "Brak komendy"}), 400
cmd = data.get("command", "").strip()
if 'cwd' not in session:
session['cwd'] = os.path.join(BASE_DIR, username, project)
cwd = session.get('cwd', os.path.join(BASE_DIR, username, project))
user_root = os.path.join(BASE_DIR, username)
if not os.path.exists(cwd):
cwd = user_root
session['cwd'] = cwd
output = execute_console_command(cmd, cwd, user_root)
if 'cwd' in locals():
session['cwd'] = cwd
return jsonify({"output": output})
def execute_console_command(cmd, cwd, user_root):
parts = cmd.strip().split()
if not parts:
return ""
command = parts[0].lower()
try:
if command == "help":
return """Dostępne komendy:
help - wyświetla pomoc
ls - lista plików
cd <dir> - zmień katalog
pwd - pokaż aktualny katalog
mkdir <dir> - utwórz katalog
touch <file> - utwórz plik
cat <file> - pokaż zawartość pliku
rm <file> - usuń plik
clear - wyczyść konsolę"""
elif command == "ls":
items = os.listdir(cwd)
if not items:
return "Katalog jest pusty"
result = []
for item in items:
item_path = os.path.join(cwd, item)
if os.path.isdir(item_path):
result.append(f"[DIR] {item}/")
else:
size = os.path.getsize(item_path)
result.append(f"[FILE] {item} ({size} bytes)")
return "\n".join(result)
elif command == "pwd":
rel_path = os.path.relpath(cwd, user_root)
return f"{rel_path}\nAbsolutna ścieżka: {cwd}"
elif command == "cd":
if len(parts) < 2:
return "Użycie: cd <katalog>"
target = os.path.normpath(os.path.join(cwd, parts[1]))
if not os.path.exists(target):
return f"Katalog nie istnieje: {parts[1]}"
if not target.startswith(user_root):
return "Błąd: Próba dostępu poza dozwolony obszar"
if not os.path.isdir(target):
return f"To nie jest katalog: {parts[1]}"
# Aktualizacja cwd
cwd = target
return f"Zmieniono katalog na: {os.path.relpath(target, user_root)}"
elif command == "mkdir":
if len(parts) < 2:
return "Użycie: mkdir <nazwa_katalogu>"
new_dir = os.path.join(cwd, parts[1])
os.makedirs(new_dir, exist_ok=True)
return f"Utworzono katalog: {parts[1]}"
elif command == "touch":
if len(parts) < 2:
return "Użycie: touch <nazwa_pliku>"
file_path = os.path.join(cwd, parts[1])
with open(file_path, 'a', encoding='utf-8'):
pass
return f"Utworzono plik: {parts[1]}"
elif command == "cat":
if len(parts) < 2:
return "Użycie: cat <nazwa_pliku>"
file_path = os.path.join(cwd, parts[1])
if not os.path.exists(file_path):
return f"Plik nie istnieje: {parts[1]}"
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
return f"Zawartość pliku {parts[1]}:\n{'-' * 40}\n{content}"
elif command == "rm":
if len(parts) < 2:
return "Użycie: rm <nazwa_pliku>"
file_path = os.path.join(cwd, parts[1])
if not os.path.exists(file_path):
return f"Plik nie istnieje: {parts[1]}"
if os.path.isdir(file_path):
return "Użyj 'rmdir' do usuwania katalogów"
os.remove(file_path)
return f"Usunięto plik: {parts[1]}"
elif command == "clear":
return "CLEAR_CONSOLE"
else:
return f"Nieznana komenda: {command}\nWpisz 'help' aby zobaczyć dostępne komendy."
except PermissionError:
return "Błąd: Brak uprawnień"
except Exception as e:
return f"Błąd wykonania: {str(e)}"
# ------------------------------
# Projekty użytkowników
# ------------------------------
@app.route("/user_projects/<username>")
def user_projects(username):
if 'username' not in session or session['username'] != username:
return redirect(url_for('login'))
user_dir = os.path.join(BASE_DIR, username)
if not os.path.exists(user_dir):
projects = []
else:
projects = [d for d in os.listdir(user_dir)
if os.path.isdir(os.path.join(user_dir, d))]
project_info = []
for project in projects:
info = {
'name': project,
'path': os.path.join(user_dir, project),
'created': os.path.getctime(os.path.join(user_dir, project)),
'files': []
}
# Liczba plików
project_path = os.path.join(user_dir, project)
for root, dirs, files in os.walk(project_path):
for file in files:
if file.endswith('.ql'):
info['files'].append(file)
# README
md_path = os.path.join(user_dir, project, "README.md")
if os.path.exists(md_path):
try:
with open(md_path, 'r', encoding='utf-8') as f:
html_content = markdown.markdown(f.read())
info['readme'] = Markup(html_content)
except:
info['readme'] = Markup("<p>Błąd odczytu README</p>")
else:
info['readme'] = None
project_info.append(info)
return render_template("user_projects.html",
username=username,
projects=project_info)
# ------------------------------
# Static files
# ------------------------------
@app.route('/static/<path:filename>')
def serve_static(filename):
return send_from_directory(app.static_folder, filename)
# ------------------------------
# Uruchomienie
# ------------------------------
if __name__ == "__main__":
# Utwórz niezbędne katalogi
os.makedirs('static/css', exist_ok=True)
os.makedirs('static/js', exist_ok=True)
os.makedirs('templates', exist_ok=True)
os.makedirs('libs', exist_ok=True) # Dodaj katalog libs
os.makedirs(BASE_DIR, exist_ok=True)
print("Serwer uruchamia się na http://localhost:5000")
print("Katalog libs:", os.path.abspath('libs'))
app.run(debug=True, host='0.0.0.0', port=5000)