parent
31f7ee98c0
commit
2bc46325a5
5 changed files with 196 additions and 57 deletions
@ -1,39 +1,88 @@ |
|||||||
from flask import Flask, render_template, request, jsonify |
|
||||||
import subprocess |
import subprocess |
||||||
import json |
import json |
||||||
import os |
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 = 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 |
# Đị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") |
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(): |
def load_services(): |
||||||
with open(SERVICES_FILE, "r", encoding="utf-8") as file: |
with open(SERVICES_FILE, "r", encoding="utf-8") as file: |
||||||
return json.load(file) |
return json.load(file) |
||||||
|
|
||||||
@app.route("/") |
@app.route("/") |
||||||
def home(): |
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) |
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(): |
def run_command(): |
||||||
data = request.json |
if "user" not in session: |
||||||
command = data.get("command") |
return jsonify({"error": "Unauthorized"}), 401 |
||||||
|
|
||||||
if not command: |
command_list = request.args.getlist("command[]") # Nhận danh sách lệnh từ frontend |
||||||
return jsonify({"error": "No command provided"}), 400 |
|
||||||
|
|
||||||
try: |
if not command_list: |
||||||
# Chạy lệnh trong shell và lấy kết quả đầu ra |
return jsonify({"error": "No command provided"}), 400 |
||||||
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() |
|
||||||
|
|
||||||
return jsonify({"status": "success" if result.returncode == 0 else "error", "output": output}) |
return Response(generate_output(command_list), mimetype="text/event-stream") |
||||||
except Exception as e: |
|
||||||
return jsonify({"status": "error", "output": str(e)}), 500 |
|
||||||
|
|
||||||
if __name__ == "__main__": |
if __name__ == "__main__": |
||||||
app.run(host="0.0.0.0", port=5000, debug=True) |
app.run(host="0.0.0.0", port=5000, debug=True) |
||||||
|
@ -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"] |
||||||
|
} |
||||||
] |
] |
||||||
|
@ -0,0 +1,30 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>Login</title> |
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css"> |
||||||
|
</head> |
||||||
|
<body class="d-flex align-items-center justify-content-center vh-100 bg-dark text-white"> |
||||||
|
|
||||||
|
<div class="card p-4 shadow-lg" style="width: 350px;"> |
||||||
|
<h3 class="text-center">🔐 Login</h3> |
||||||
|
{% if error %} |
||||||
|
<div class="alert alert-danger text-center">{{ error }}</div> |
||||||
|
{% endif %} |
||||||
|
<form method="POST"> |
||||||
|
<div class="mb-3"> |
||||||
|
<label class="form-label">Username</label> |
||||||
|
<input type="text" class="form-control" name="username" required autofocus> |
||||||
|
</div> |
||||||
|
<div class="mb-3"> |
||||||
|
<label class="form-label">Password</label> |
||||||
|
<input type="password" class="form-control" name="password" required> |
||||||
|
</div> |
||||||
|
<button type="submit" class="btn btn-primary w-100">Login</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
|
||||||
|
</body> |
||||||
|
</html> |
Loading…
Reference in new issue