Compare commits
6 Commits
042f6ee99e
...
test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aa567cdf2 | ||
|
|
a3b3018de7 | ||
|
|
761095d61d | ||
| 643c16fdf7 | |||
| 5082436289 | |||
| 7193b9a000 |
@@ -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
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user