657 lines
21 KiB
Python
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) |