code-server-like

This commit is contained in:
CakeCN
2025-09-15 14:15:20 +08:00
parent 306bf48754
commit ec45771376
19 changed files with 962 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
from flask import Flask
from .views.routes import main_bp
from .sockets.terminal_service import terminal_bp
from .views.file import file_bp
from .config import Config
from .extensions import init_extensions
def create_app():
app = Flask(__name__, template_folder="../templates", static_folder="../static",static_url_path="/vsc-like/static")
app.config.from_object(Config)
@app.route('/')
def hello():
return 'Hello, World!'
# 注册蓝图
app.register_blueprint(main_bp)
app.register_blueprint(terminal_bp)
app.register_blueprint(file_bp)
# 初始化SocketIO
init_extensions(app)
return app

View File

@@ -0,0 +1,16 @@
import os
import configparser
GLOBAL_CONFIG = configparser.ConfigParser()
GLOBAL_CONFIG.read('config.ini')
class Config:
SECRET_KEY = GLOBAL_CONFIG['Global']['SECRET_KEY']
SOCKETIO_MESSAGE_QUEUE = GLOBAL_CONFIG['Global'].get('SOCKETIO_MESSAGE_QUEUE', 'redis://localhost:6379/0')
CORS_HEADERS = GLOBAL_CONFIG['Global'].get('CORS_HEADERS', 'Content-Type')
# 自定义其他全局配置
ROOT_WORKSPACE_PATH = GLOBAL_CONFIG['Global']['ROOT_WORKSPACE_PATH']

View File

@@ -0,0 +1,11 @@
from flask_cors import CORS
from flask_socketio import SocketIO
from .config import Config
# 初始化扩展
cors = CORS()
socketio = SocketIO()
session_paths = {}
def init_extensions(app):
cors.init_app(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
socketio.init_app(app, cors_allowed_origins="*", max_payload_size=52428800, async_mode="threading")
app.extensions["session_paths"] = session_paths

View File

@@ -0,0 +1,24 @@
import os
import json
# 递归获取目录文件树
def get_file_tree(path):
file_tree = []
for item in os.listdir(path):
full_path = os.path.join(path, item)
if os.path.isdir(full_path):
file_tree.append({"name": item, "type": "directory", "children": get_file_tree(full_path)})
else:
file_tree.append({"name": item, "type": "file"})
return file_tree
def load_config(path):
config_file = os.path.join(path, '.config')
if not os.path.exists(config_file):
return None
try:
with open(config_file, 'r') as f:
config = json.load(f)
return config
except Exception as e:
print(e)
return None

View File

@@ -0,0 +1,64 @@
import os
import subprocess
from flask import Blueprint, request, current_app, session
from flask_socketio import SocketIO, emit, join_room
from threading import Thread
import sys
from ..extensions import socketio
terminal_bp = Blueprint('terminal', __name__, url_prefix='/vsc-like')
# 存储终端进程
terminals = {}
def start_terminal_process(socket_id):
# 启动一个 bash 终端
process = subprocess.Popen(
['bash'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# 监听进程的输出
while True:
output = process.stdout.readline()
if output:
emit('terminal_output', {'data': output}, room=socket_id)
if process.poll() is not None:
break
@socketio.on('connect', namespace='/vsc-like/terminal')
def connect_terminal():
socket_id = session.get('uuid')
path = session.get('path')
if not path:
emit('error', {'message': 'Authentication failed, no path found for this session'})
return False # 拒绝连接
terminals[socket_id] = subprocess.Popen(
['bash'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
emit('connected', {'message': 'Terminal connected'})
join_room(path, namespace='/vsc-like/terminal')
# 启动监听终端输出的线程
thread = Thread(target=start_terminal_process, args=(socket_id,))
thread.daemon = True
thread.start()
@socketio.on('send_input', namespace='/vsc-like/terminal')
def send_input(data):
socket_id = session.get('uuid')
path = session.get('path')
input_data = data.get('input')
if socket_id in terminals:
terminals[socket_id].stdin.write(input_data + '\n')
terminals[socket_id].stdin.flush()
@socketio.on('disconnect', namespace='/vsc-like/terminal')
def disconnect_terminal():
socket_id = session.get('uuid')
path = session.get('path')
if socket_id in terminals:
terminals[socket_id].terminate()
del terminals[socket_id]

View File

@@ -0,0 +1,164 @@
from hmac import new
import shutil
from flask import Blueprint, request, jsonify, current_app, render_template, session
import os
from ..services.file_service import get_file_tree
file_bp = Blueprint('file', __name__, url_prefix='/vsc-like')
@file_bp.route('/create-folder', methods=['POST'])
def create_folder():
try:
filepath = request.json.get('path', '')
path = session.get('path')
parent = request.json.get('parent', '')
root_workspace_path = current_app.config['ROOT_WORKSPACE_PATH']
base_path = f'{root_workspace_path}/{path}'
filepath = f'{base_path}/{parent}/{filepath}'
try:
os.makedirs(filepath)
return jsonify({"message": "Folder created successfully"})
except Exception as e:
return jsonify({"error": str(e)}), 500
except Exception as e:
return jsonify({"error": str(e)}), 500
@file_bp.route('/create-file', methods=['POST'])
def create_file():
filepath = request.json.get('path', '')
path = session.get('path')
parent = request.json.get('parent', '')
root_workspace_path = current_app.config['ROOT_WORKSPACE_PATH']
base_path = f'{root_workspace_path}/{path}'
filepath = f'{base_path}/{parent}/{filepath}'
try:
open(filepath, 'w').close()
return jsonify({"message": "File created successfully"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@file_bp.route('/rename-file', methods=['POST'])
def rename_file():
user_root = session.get('path')
path = request.json.get('path', '')
new_name = request.json.get('newName', '')
root_workspace_path = current_app.config['ROOT_WORKSPACE_PATH']
base_path = f'{root_workspace_path}/{user_root}'
filepath = f'{base_path}/{path}'
try:
path_list = filepath.split('/')
path_list[-1] = new_name
new_filepath = '/'.join(path_list)
os.rename(filepath, new_filepath)
return jsonify({"message": "File renamed successfully"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@file_bp.route('/rename-folder', methods=['POST'])
def rename_folder():
user_root = session.get('path')
path = request.json.get('path', '')
new_name = request.json.get('newName', '')
root_workspace_path = current_app.config['ROOT_WORKSPACE_PATH']
base_path = f'{root_workspace_path}/{user_root}'
folderpath = f'{base_path}/{path}'
try:
path_list = folderpath.split('/')
path_list[-1] = new_name
new_filepath = '/'.join(path_list)
os.rename(folderpath, new_filepath)
return jsonify({"message": "Folder renamed successfully"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@file_bp.route('/copy-files', methods=['POST'])
def copy_files():
old_path = request.json.get('oldPath', '')
new_path = request.json.get('newPath', '')
root_workspace_path = current_app.config['ROOT_WORKSPACE_PATH']
user_root = session.get('path')
base_path = f'{root_workspace_path}/{user_root}'
old_path = f'{base_path}/{old_path}'
new_path = f'{base_path}/{new_path}'
# oldpath的父目录就是newpath的话,对oldpath进行重命名
# 获取 old_path 的文件/文件夹名
old_name = os.path.basename(old_path)
# 确保目标路径是父目录(即 new_path 是一个目录)
new_path_parent = os.path.abspath(new_path)
# 如果目标路径下已经存在同名文件/文件夹,进行重命名
final_new_path = os.path.join(new_path_parent, old_name)
counter = 1
while os.path.exists(final_new_path):
# 如果文件夹或文件已经存在,则为新的文件名添加计数后缀
final_new_path = os.path.join(new_path_parent, f"{old_name}_{counter}")
counter += 1
# 判断 old_path 是文件夹还是文件,并进行相应的复制操作
if os.path.isdir(old_path):
shutil.copytree(old_path, final_new_path, dirs_exist_ok=True)
else:
shutil.copy(old_path, final_new_path)
return jsonify({"message": "Files copied successfully"})
@file_bp.route('/delete-file', methods=['POST'])
def delete_file():
path = request.json.get('path', '')
root_workspace_path = current_app.config['ROOT_WORKSPACE_PATH']
user_root = session.get('path')
base_path = f'{root_workspace_path}/{user_root}'
filepath = f'{base_path}/{path}'
if os.path.isdir(filepath):
shutil.rmtree(filepath)
else:
os.remove(filepath)
return jsonify({"message": "File deleted successfully"})
@file_bp.route('/file-content', methods=['POST'])
def file_content():
path = session.get('path')
root_workspace_path = current_app.config['ROOT_WORKSPACE_PATH']
base_path = f'{root_workspace_path}/{path}'
filepath = request.json.get('path', '')
filepath = f'{base_path}/{filepath}'
if not os.path.exists(filepath):
return jsonify({"error": "File not found"}), 404
try:
with open(filepath, 'r') as file:
content = file.read()
return jsonify({"content": content})
except Exception as e:
return jsonify({"error": str(e)}), 500
@file_bp.route('/save-file', methods=['POST'])
def save_file():
path = session.get('path')
root_workspace_path = current_app.config['ROOT_WORKSPACE_PATH']
base_path = f'{root_workspace_path}/{path}'
filepath = request.json.get('path', '')
filepath = f'{base_path}/{filepath}'
content = request.json.get('content', '')
if not os.path.exists(filepath):
return jsonify({"error": "File not found"}), 404
try:
with open(filepath, 'w') as file:
file.write(content)
return jsonify({"message": "File saved successfully"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@file_bp.route('/file-tree', methods=['GET'])
def file_tree():
path = session.get('path')
if not path:
return jsonify({"error": "Missing path parameter"}), 400
root_workspace_path = current_app.config['ROOT_WORKSPACE_PATH']
base_path = f'{root_workspace_path}/{path}'
if not os.path.exists(base_path):
return jsonify({"error": "Workspace not found"}), 404
tree = get_file_tree(base_path)
return jsonify(tree)

View File

@@ -0,0 +1,24 @@
import os
from flask import Blueprint, request, jsonify, current_app, render_template, session
from ..services.file_service import get_file_tree, load_config
main_bp = Blueprint('main', __name__, url_prefix='/vsc-like')
@main_bp.route('/', methods=['GET'])
def vsc_like():
uuid = request.args.get('uuid')
path = request.args.get('path')
session['uuid'] = uuid
session['path'] = path
# 加载 .config 文件并验证 uuid
base_path = current_app.config['ROOT_WORKSPACE_PATH']
path = f'{base_path}/{path}'
config = load_config(path)
if not config:
return jsonify({"error": "Configuration file not found or invalid"}), 404
if config.get('uuid') != uuid:
return jsonify({"error": "UUID does not match"}), 403
return render_template('index.html')