6 Commits

6 changed files with 801 additions and 692 deletions

View File

@@ -1,41 +1,53 @@
function show_books(courses_data, user_selected_courses){
function show_books(courses_data, user_selected_courses) {
console.log(courses_data)
console.log(user_selected_courses)
}
function show_course_details(course_id){
window.location.href = '/course/' + course_id
function show_course_details(course_id) {
window.location.href = '/course/' + course_id
}
function on_click_select_course(event){
function on_click_select_course(event) {
event.stopPropagation(); // 阻止事件冒泡
const courseId = event.currentTarget.getAttribute('data-course-id');
if (event.currentTarget.classList.contains("selected-button")) {alert('课程已选择'); return;}
const button = event.currentTarget;
const courseId = button.getAttribute('data-course-id');
if (!courseId) { alert('缺少课程ID'); return; }
if (button.classList.contains("selected-button")) { alert('课程已选择'); return; }
if (button.disabled) { return; }
button.disabled = true;
fetch(`/select_course`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}' // 如果使用 CSRF 保护
},
body: JSON.stringify({ course_id: courseId })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 更新按钮样式或显示消息
this.classList.remove('select-button');
this.classList.add('selected-button');
this.textContent = '已选择';
.then(async (response) => {
let data = null;
try {
data = await response.json();
} catch (_) {
}
if (!response.ok) {
throw new Error(data?.message || `请求失败(${response.status})`);
}
return data;
})
.then((data) => {
if (!data?.success) {
throw new Error(data?.message || '选择课程失败');
}
button.classList.remove('select-button');
button.classList.add('selected-button');
button.textContent = '已选课';
alert('选择课程成功');
} else {
alert('选择课程失败');
}
})
.catch(error => {
console.error('Error:', error);
alert('选择课程失败');
});
})
.catch(error => {
console.error('Error:', error);
alert(error?.message || '选择课程失败');
button.disabled = false;
});
}
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,9 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>课程选择主页</title>
<meta charset="UTF-8">
<title>课程选择主页</title>
<script src="/static/cdnback/jquery.min.js"></script>
<link href="/static/cdnback/bootstrap.min.css" rel="stylesheet">
@@ -12,6 +13,7 @@
<script src="/static/cdnback/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="/static/css/index.css">
</head>
<body>
{% include 'navbar.html' %}
@@ -38,7 +40,7 @@
<h2>课程列表</h2>
<div class="course-cards" id="course-cards">
{% for course in courses_data %}
<!-- <div class="course-card" onclick="show_course_details('{{ course._id }}')">
<!-- <div class="course-card" onclick="show_course_details('{{ course._id }}')">
<img src="{{ course.image_url }}" alt="{{ course.course_name }}">
<h3>{{ course.name }}</h3>
<p>{{ course.description }}</p>
@@ -49,10 +51,9 @@
{% endif %}
</div> -->
<div class="col-md-6 col-lg-4">
<div class="col-md-6 col-lg-4">
<div class="course-card card h-100" onclick="openCourseProgress('{{course._id}}')">
<img src="{{course.image_url}}"
class="course-img" alt="课程封面">
<img src="{{course.image_url}}" class="course-img" alt="课程封面">
<div class="card-body d-flex flex-column">
<span class="course-category">算法设计</span>
<h5 class="course-title">{{course.name}}</h5>
@@ -62,11 +63,15 @@
<span class="course-rating"><i class="fas fa-star"></i> 4.7</span>
</div>
<div class="mt-auto d-grid gap-2 d-md-flex">
<button class="btn btn-view flex-fill" onclick="btn_view(event,'{{course._id}}')"><i class="far fa-eye me-1"></i> 课程</button>
<button class="btn btn-view flex-fill" onclick="btn_view(event,'{{course._id}}')"><i
class="far fa-eye me-1"></i> 课程</button>
{% if course._id in selected_courses %}
<button class="btn btn-process flex-fill selected-button" data-course-id="{{course._id}}">已选课</button>
<button class="btn btn-process flex-fill selected-button"
data-course-id="{{course._id}}">已选课</button>
{% else %}
<button class="btn btn-process flex-fill select-button" onclick="on_click_select_course(event)" data-course-id="{{course._id}}">选择课程</button>
<button class="btn btn-process flex-fill select-button"
onclick="on_click_select_course(event)"
data-course-id="{{course._id}}">选择课程</button>
{% endif %}
</div>
@@ -74,13 +79,13 @@
</div>
</div>
{% endfor %}
</div>
</div>
</main>
{% include 'footer-brief.html' %}
{% include 'footer-brief.html' %}
</body>
<script>
window.appData = {
@@ -88,5 +93,6 @@
}
</script>
<script src="/static/js/index.js"></script>
</html>
<script src="/static/js/dashboard.js"></script>
</html>

View File

@@ -1,5 +1,32 @@
import eventlet
eventlet.monkey_patch() # 进行猴子补丁操作
import logging
# 配置 eventlet 日志,抑制 IOClosed 等错误日志
logging.getLogger('eventlet.hubs').setLevel(logging.CRITICAL)
logging.getLogger('eventlet.greenio').setLevel(logging.CRITICAL)
# 进行猴子补丁操作
eventlet.monkey_patch()
# 配置 eventlet hub 以更优雅地处理 IOClosed 错误
try:
from eventlet.hubs import hub
# 保存原始的 handle_error 方法
original_handle_error = hub.Hub.handle_error
def custom_handle_error(self, context, type, value, tb):
# 忽略 IOClosed 错误
if type.__name__ == 'IOClosed' and 'Operation on closed file' in str(value):
return
# 其他错误调用原始方法
original_handle_error(self, context, type, value, tb)
# 替换原始方法
hub.Hub.handle_error = custom_handle_error
except Exception as e:
# 如果修改失败,忽略错误
pass
from flask import Flask
from .views.routes import main_bp
from .views.file import file_bp

View File

@@ -29,8 +29,12 @@ terminal_bp = Blueprint('terminal', __name__, url_prefix='/vsc-like')
def set_winsize(fd, row, col, xpix=0, ypix=0):
winsize = struct.pack("HHHH", row, col, xpix, ypix)
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
try:
winsize = struct.pack("HHHH", row, col, xpix, ypix)
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
except (OSError, IOError):
# File descriptor closed or invalid, do nothing
pass
def read_and_forward_pty_output(fd=None, pid=None, room_id=None,namespace=None):
@@ -67,21 +71,35 @@ def read_and_forward_pty_output(fd=None, pid=None, room_id=None,namespace=None):
return
if fd:
timeout_sec = 0
(data_ready, _, _) = select.select([fd], [], [], timeout_sec)
if data_ready:
# output = os.read(fd, max_read_bytes).decode('ascii')
timeout=0.1
try:
output = os.read(fd, max_read_bytes).decode()
except Exception as err:
output = """
***AQUI WEB TERM ERR***
{}
***********************
""".format(err)
# the key for different visitor to get different terminal (instead of mixing up)
# is to let the background task push pty response to each one's own (default) ROOM!
namespace.emit("pty_output", {"output": output}, room=room_id)
try:
# Check if file descriptor is still valid by trying to select on it
(data_ready, _, _) = select.select([fd], [], [], timeout_sec)
if data_ready:
timeout=0.1
try:
output = os.read(fd, max_read_bytes).decode()
except (OSError, IOError, EOFError) as err:
# File descriptor closed or other IO error, exit gracefully
return
except UnicodeDecodeError as err:
output = """
***AQUI WEB TERM ERR***
Unicode decode error: {}
***********************
""".format(err)
# the key for different visitor to get different terminal (instead of mixing up)
# is to let the background task push pty response to each one's own (default) ROOM!
try:
namespace.emit("pty_output", {"output": output}, room=room_id)
except Exception:
# If emit fails, the client is probably disconnected, exit
return
except (OSError, IOError) as err:
# File descriptor closed or invalid, exit gracefully
return
except Exception as e:
# Catch any other unexpected exceptions to prevent server crash
pass
finally:
# Clean up file descriptor if it's open
if fd:
@@ -158,7 +176,13 @@ class VSCLikeNameSpace(Namespace):
if fd:
# print("writing to ptd: %s" % data["input"])
# os.write(fd, data["input"].encode('ascii'))
os.write(fd, data["input"].encode())
try:
os.write(fd, data["input"].encode())
except (OSError, IOError):
# File descriptor closed or invalid, clean up
disconnect()
session['terminal_config'] = TERM_INIT_CONFIG
return
def on_resize(self, data):
@@ -174,10 +198,29 @@ class VSCLikeNameSpace(Namespace):
return
fd = session.get('terminal_config').get('fd')
if fd:
set_winsize(fd, data["rows"], data["cols"])
# 检查文件描述符是否有效
try:
# 尝试一个简单的操作来检查文件描述符是否有效
os.fstat(fd)
set_winsize(fd, data["rows"], data["cols"])
except (OSError, IOError):
# 文件描述符无效,清理资源
disconnect()
session['terminal_config'] = TERM_INIT_CONFIG
return
def on_disconnect(self):
child_pid = session.get('terminal_config', {}).get('child_pid')
terminal_config = session.get('terminal_config', {})
child_pid = terminal_config.get('child_pid')
fd = terminal_config.get('fd')
# 先关闭文件描述符,避免其他操作尝试使用无效的文件描述符
if fd:
try:
os.close(fd)
except Exception:
pass
if child_pid:
try:
child_process = psutil.Process(child_pid)
@@ -196,21 +239,14 @@ class VSCLikeNameSpace(Namespace):
pass
except psutil.TimeoutExpired:
# If process didn't terminate in time, kill it forcefully
child_process.kill()
try:
child_process.kill()
child_process.wait(timeout=1)
except Exception:
pass
except Exception as err:
current_app.logger.error(f'Error terminating process: {err}')
finally:
# Close the file descriptor if it's open
fd = session.get('terminal_config', {}).get('fd')
if fd:
try:
os.close(fd)
except Exception:
pass
# Reset session config
session['terminal_config'] = TERM_INIT_CONFIG
# Reset session config
session['terminal_config'] = TERM_INIT_CONFIG
current_app.logger.debug('Client disconnected')

View File

@@ -1,12 +1,32 @@
from app import create_app
from app.extensions import socketio
import logging
# 配置日志减少eventlet的调试日志
logging.getLogger('eventlet').setLevel(logging.ERROR)
logging.getLogger('socketio').setLevel(logging.ERROR)
logging.getLogger('werkzeug').setLevel(logging.ERROR)
app = create_app()
if __name__ == '__main__':
socketio.run(
app,
host="0.0.0.0",
port=5200,
debug=False, # 开发期打开
allow_unsafe_werkzeug=True, # 避免 dev server 的安全限制提示
try:
socketio.run(
app,
host="0.0.0.0",
port=5200,
debug=False, # 开发期打开
allow_unsafe_werkzeug=True, # 避免 dev server 的安全限制提示
log_output=False # 禁用详细输出
)
except KeyboardInterrupt:
print("Server stopped by user")
except Exception as e:
# 捕获所有异常,包括 eventlet 内部的 IOClosed 错误
if "IOClosed" in str(type(e).__name__) or "Operation on closed file" in str(e):
# 优雅处理 eventlet 关闭文件的错误
print("Eventlet IOClosed error handled gracefully")
else:
# 其他异常仍然打印
import traceback
traceback.print_exc()