From 2bc46325a527203198c408f6acf7e8b3c733c9fc Mon Sep 17 00:00:00 2001 From: SundayEnglish Date: Wed, 19 Feb 2025 14:16:59 +0700 Subject: [PATCH] update --- app.py | 79 ++++++++++++++++++++++++++++++++++-------- data/services.json | 20 +++++++++-- static/script.js | 81 ++++++++++++++++++++++++++++++-------------- templates/index.html | 43 +++++++++++++++-------- templates/login.html | 30 ++++++++++++++++ 5 files changed, 196 insertions(+), 57 deletions(-) create mode 100644 templates/login.html diff --git a/app.py b/app.py index f5a5a96..b2b7fa4 100644 --- a/app.py +++ b/app.py @@ -1,39 +1,88 @@ -from flask import Flask, render_template, request, jsonify import subprocess import json import os +import time +from flask import Flask, render_template, request, Response, jsonify, redirect, url_for, session +from datetime import timedelta app = Flask(__name__) +app.secret_key = "your_secret_key" +app.permanent_session_lifetime = timedelta(days=1) + +# Danh sách tài khoản & mật khẩu +USERS = { + "admin": "123456", + "user1": "password1", + "user2": "password2" +} # Định nghĩa đường dẫn file JSON chứa danh sách service SERVICES_FILE = os.path.join(os.path.dirname(__file__), "data", "services.json") -# Load danh sách dịch vụ từ file JSON def load_services(): with open(SERVICES_FILE, "r", encoding="utf-8") as file: return json.load(file) @app.route("/") def home(): - listService = load_services() # Đọc danh sách service từ file + if "user" not in session: + return redirect(url_for("login")) + + listService = load_services() return render_template("index.html", listService=listService) -@app.route("/run-command", methods=["POST"]) +@app.route("/login", methods=["GET", "POST"]) +def login(): + if request.method == "POST": + username = request.form.get("username") + password = request.form.get("password") + + if username in USERS and USERS[username] == password: + session["user"] = username + session.permanent = True + return redirect(url_for("home")) + else: + return render_template("login.html", error="Invalid username or password") + + return render_template("login.html") + +@app.route("/logout") +def logout(): + session.pop("user", None) + return redirect(url_for("login")) + +# Streaming real-time output của lệnh +def generate_output(command_list): + # Chuyển danh sách thành chuỗi để chạy trong shell + command_str = " ".join(command_list) + + process = subprocess.Popen(command_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, executable="/bin/bash") + + for line in iter(process.stdout.readline, ''): + yield f"data: {line.strip()}\n\n" + time.sleep(0.1) + + for line in iter(process.stderr.readline, ''): + yield f"data: {line.strip()}\n\n" + time.sleep(0.1) + + process.wait() + if process.returncode == 0: + yield f"data: SUCCESS\n\n" + else: + yield f"data: ERROR\n\n" + +@app.route("/run-command", methods=["GET"]) def run_command(): - data = request.json - command = data.get("command") + if "user" not in session: + return jsonify({"error": "Unauthorized"}), 401 - if not command: - return jsonify({"error": "No command provided"}), 400 + command_list = request.args.getlist("command[]") # Nhận danh sách lệnh từ frontend - try: - # Chạy lệnh trong shell và lấy kết quả đầu ra - result = subprocess.run(command, shell=True, capture_output=True, text=True, executable="/bin/bash") - output = result.stdout.strip() if result.returncode == 0 else result.stderr.strip() + if not command_list: + return jsonify({"error": "No command provided"}), 400 - return jsonify({"status": "success" if result.returncode == 0 else "error", "output": output}) - except Exception as e: - return jsonify({"status": "error", "output": str(e)}), 500 + return Response(generate_output(command_list), mimetype="text/event-stream") if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/data/services.json b/data/services.json index 36fc576..bf95c8f 100644 --- a/data/services.json +++ b/data/services.json @@ -1,4 +1,20 @@ [ - {"name": "service.sundayenglish.com", "command": "cd /project/service-se && ls"}, - {"name": "exercise.sundayenglish.com", "command": "cd /project/exercise-se && ls"} + { + "name": "Service", + "url": "http://service.sundayenglish.com", + "description": "Sunday English Service", + "command": ["cd", "/projects/service-se", "&&", "ls"] + }, + { + "name": "Exercise", + "url": "http://exercise.sundayenglish.com", + "description": "Sunday English Exercise", + "command": ["cd", "/projects/exercise-se", "&&", "ls"] + }, + { + "name": "Julius", + "url": null, + "description": "Julius", + "command": ["cd", "/projects/julius", "&&", "docker", "compose", "down", "&&", "docker", "compose", "up", "-d"] + } ] diff --git a/static/script.js b/static/script.js index c130e4d..9794efb 100644 --- a/static/script.js +++ b/static/script.js @@ -7,47 +7,76 @@ document.addEventListener("DOMContentLoaded", function () { }); }); -function runCommand(button, command) { +function runCommand(button, commandStr) { const originalText = button.innerHTML; - button.innerHTML = - ' Running...'; + button.innerHTML = ' Running...'; button.disabled = true; - fetch("/run-command", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ command: command }), - }) - .then((response) => response.json()) - .then((data) => { - // Kiểm tra xem lệnh có chạy thành công không - if (data.output) { - showToast("Success", "success"); // Hiển thị thông báo "Success" - } else { - showToast("Error", "danger"); // Hiển thị thông báo "Error" - } - }) - .catch(() => { - showToast("Error", "danger"); // Hiển thị "Error" nếu request thất bại - }) - .finally(() => { + const outputContainer = document.getElementById("commandOutput"); + outputContainer.innerHTML = ""; // Xóa output cũ + + // Chuyển đổi chuỗi JSON thành mảng lệnh + let command; + try { + command = JSON.parse(commandStr); + } catch (error) { + showToast("Invalid command format", "danger"); + button.innerHTML = originalText; + button.disabled = false; + return; + } + + // Kiểm tra nếu command không phải là mảng + if (!Array.isArray(command)) { + showToast("Command must be an array", "danger"); + button.innerHTML = originalText; + button.disabled = false; + return; + } + + // Gửi danh sách command dưới dạng URL parameter + const url = new URL("/run-command", window.location.origin); + command.forEach(cmd => url.searchParams.append("command[]", cmd)); + + const eventSource = new EventSource(url); + + eventSource.onmessage = function (event) { + if (event.data === "SUCCESS") { + showToast("Success", "success"); + eventSource.close(); button.innerHTML = originalText; button.disabled = false; - }); + } else if (event.data === "ERROR") { + showToast("Error", "danger"); + eventSource.close(); + button.innerHTML = originalText; + button.disabled = false; + } else { + outputContainer.innerHTML += event.data + "\n"; + outputContainer.scrollTop = outputContainer.scrollHeight; + } + }; + + eventSource.onerror = function () { + showToast("Error", "danger"); + eventSource.close(); + button.innerHTML = originalText; + button.disabled = false; + }; } -// Hàm hiển thị Toast chỉ với "Success" hoặc "Error" -function showToast(status, type) { + +// Hiển thị Toast thông báo +function showToast(message, type) { const toastContainer = document.getElementById("toastContainer"); const toast = document.createElement("div"); toast.className = `toast align-items-center text-bg-${type} border-0 show`; toast.role = "alert"; - toast.innerHTML = `
${status}
`; + toast.innerHTML = `
${message}
`; toastContainer.appendChild(toast); - // Tự động ẩn sau 5 giây setTimeout(() => { toast.remove(); }, 5000); diff --git a/templates/index.html b/templates/index.html index 3705a56..93fa2e2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,23 +6,30 @@ Service List - - - - + +
-

🚀 Service List 🚀

+
+

🚀 Service List 🚀

+
+ 👤 {{ session['user'] }} + Logout +
+
+
+ + @@ -31,12 +38,21 @@ {% for service in listService %} + + + {% endfor %} @@ -45,17 +61,16 @@
-
Command Output:
-

+         
Command Output (Real-time Logs):
+

       
+
- - - - + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..4020e3a --- /dev/null +++ b/templates/login.html @@ -0,0 +1,30 @@ + + + + + + Login + + + + +
+

🔐 Login

+ {% if error %} +
{{ error }}
+ {% endif %} +
+
+ + +
+
+ + +
+ + +
+ + +
Service NameDescriptionURL Command Action
{{ service.name }}{{ service.description }} + {% if service.url %} + 🔗 Visit + {% else %} + N/A + {% endif %} + {{ service.command }} - -