Introduce the function of initially restoring the real-time output of console information.
This commit is contained in:
@@ -74,27 +74,69 @@ def read_log_from_file(app_name, tail_lines=None):
|
|||||||
|
|
||||||
def read_process_output(process, app_name):
|
def read_process_output(process, app_name):
|
||||||
"""读取进程输出并写入文件"""
|
"""读取进程输出并写入文件"""
|
||||||
|
import select
|
||||||
|
import sys
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
if process.poll() is not None:
|
if process.poll() is not None:
|
||||||
|
# 进程结束,读取剩余输出
|
||||||
|
remaining_output = process.stdout.read()
|
||||||
|
if remaining_output:
|
||||||
|
lines = remaining_output.decode('utf-8', errors='replace').split('\n')
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
timestamp = datetime.now().strftime('%H:%M:%S')
|
||||||
|
formatted_line = f"[{timestamp}] {line}"
|
||||||
|
write_log_to_file(app_name, formatted_line)
|
||||||
|
socketio.emit('console_output', {
|
||||||
|
'app': app_name,
|
||||||
|
'line': formatted_line
|
||||||
|
})
|
||||||
break
|
break
|
||||||
|
|
||||||
output = process.stdout.readline()
|
# 使用非阻塞读取
|
||||||
if output:
|
if sys.platform == 'win32':
|
||||||
# 使用UTF-8解码,忽略错误字符
|
# Windows下使用不同的方法
|
||||||
line = output.decode('utf-8', errors='replace').strip()
|
output = process.stdout.readline()
|
||||||
if line:
|
if output:
|
||||||
timestamp = datetime.now().strftime('%H:%M:%S')
|
line = output.decode('utf-8', errors='replace').strip()
|
||||||
formatted_line = f"[{timestamp}] {line}"
|
if line:
|
||||||
|
timestamp = datetime.now().strftime('%H:%M:%S')
|
||||||
# 写入日志文件
|
formatted_line = f"[{timestamp}] {line}"
|
||||||
write_log_to_file(app_name, formatted_line)
|
|
||||||
|
# 写入日志文件
|
||||||
# 发送到前端
|
write_log_to_file(app_name, formatted_line)
|
||||||
socketio.emit('console_output', {
|
|
||||||
'app': app_name,
|
# 发送到前端
|
||||||
'line': formatted_line
|
socketio.emit('console_output', {
|
||||||
})
|
'app': app_name,
|
||||||
|
'line': formatted_line
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# 没有输出时短暂休眠
|
||||||
|
time.sleep(0.1)
|
||||||
|
else:
|
||||||
|
# Unix系统使用select
|
||||||
|
ready, _, _ = select.select([process.stdout], [], [], 0.1)
|
||||||
|
if ready:
|
||||||
|
output = process.stdout.readline()
|
||||||
|
if output:
|
||||||
|
line = output.decode('utf-8', errors='replace').strip()
|
||||||
|
if line:
|
||||||
|
timestamp = datetime.now().strftime('%H:%M:%S')
|
||||||
|
formatted_line = f"[{timestamp}] {line}"
|
||||||
|
|
||||||
|
# 写入日志文件
|
||||||
|
write_log_to_file(app_name, formatted_line)
|
||||||
|
|
||||||
|
# 发送到前端
|
||||||
|
socketio.emit('console_output', {
|
||||||
|
'app': app_name,
|
||||||
|
'line': formatted_line
|
||||||
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Error reading output for {app_name}: {e}"
|
error_msg = f"Error reading output for {app_name}: {e}"
|
||||||
print(error_msg)
|
print(error_msg)
|
||||||
@@ -126,16 +168,19 @@ def start_streamlit_app(app_name, script_path, port):
|
|||||||
'--server.port', str(port),
|
'--server.port', str(port),
|
||||||
'--server.headless', 'true',
|
'--server.headless', 'true',
|
||||||
'--browser.gatherUsageStats', 'false',
|
'--browser.gatherUsageStats', 'false',
|
||||||
'--logger.level', 'info'
|
'--logger.level', 'debug', # 增加日志详细程度
|
||||||
|
'--server.enableCORS', 'false'
|
||||||
]
|
]
|
||||||
|
|
||||||
# 设置环境变量确保UTF-8编码
|
# 设置环境变量确保UTF-8编码和减少缓冲
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env.update({
|
env.update({
|
||||||
'PYTHONIOENCODING': 'utf-8',
|
'PYTHONIOENCODING': 'utf-8',
|
||||||
'PYTHONUTF8': '1',
|
'PYTHONUTF8': '1',
|
||||||
'LANG': 'en_US.UTF-8',
|
'LANG': 'en_US.UTF-8',
|
||||||
'LC_ALL': 'en_US.UTF-8'
|
'LC_ALL': 'en_US.UTF-8',
|
||||||
|
'PYTHONUNBUFFERED': '1', # 禁用Python缓冲
|
||||||
|
'STREAMLIT_BROWSER_GATHER_USAGE_STATS': 'false'
|
||||||
})
|
})
|
||||||
|
|
||||||
# 使用当前工作目录而不是脚本目录
|
# 使用当前工作目录而不是脚本目录
|
||||||
@@ -143,11 +188,12 @@ def start_streamlit_app(app_name, script_path, port):
|
|||||||
cmd,
|
cmd,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
bufsize=1,
|
bufsize=0, # 无缓冲
|
||||||
universal_newlines=False,
|
universal_newlines=False,
|
||||||
cwd=os.getcwd(),
|
cwd=os.getcwd(),
|
||||||
env=env,
|
env=env,
|
||||||
encoding=None # 让我们手动处理编码
|
encoding=None, # 让我们手动处理编码
|
||||||
|
creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
processes[app_name]['process'] = process
|
processes[app_name]['process'] = process
|
||||||
@@ -314,6 +360,27 @@ def get_output(app_name):
|
|||||||
'output': output_lines
|
'output': output_lines
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@app.route('/api/test_log/<app_name>')
|
||||||
|
def test_log(app_name):
|
||||||
|
"""测试日志写入功能"""
|
||||||
|
if app_name not in processes:
|
||||||
|
return jsonify({'success': False, 'message': '未知应用'})
|
||||||
|
|
||||||
|
# 写入测试消息
|
||||||
|
test_msg = f"[{datetime.now().strftime('%H:%M:%S')}] 测试日志消息 - {datetime.now()}"
|
||||||
|
write_log_to_file(app_name, test_msg)
|
||||||
|
|
||||||
|
# 通过Socket.IO发送
|
||||||
|
socketio.emit('console_output', {
|
||||||
|
'app': app_name,
|
||||||
|
'line': test_msg
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': f'测试消息已写入 {app_name} 日志'
|
||||||
|
})
|
||||||
|
|
||||||
@app.route('/api/search', methods=['POST'])
|
@app.route('/api/search', methods=['POST'])
|
||||||
def search():
|
def search():
|
||||||
"""统一搜索接口"""
|
"""统一搜索接口"""
|
||||||
|
|||||||
+49
-110
@@ -172,32 +172,16 @@
|
|||||||
/* 控制台输出 */
|
/* 控制台输出 */
|
||||||
.console-output {
|
.console-output {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
padding: 15px;
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
color: #00ff00;
|
color: #00ff00;
|
||||||
font-family: 'Courier New', monospace;
|
font-family: 'Courier New', monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
overflow: hidden; /* 隐藏滚动条,由内部面板控制 */
|
|
||||||
position: relative;
|
|
||||||
min-height: 0; /* 允许内容缩小 */
|
|
||||||
max-height: 100%; /* 限制最大高度 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 控制台面板 */
|
|
||||||
.console-panel {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 15px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
display: none; /* 默认隐藏 */
|
min-height: 0; /* 允许内容缩小 */
|
||||||
}
|
max-height: 100%; /* 限制最大高度 */
|
||||||
|
|
||||||
.console-panel.active {
|
|
||||||
display: block; /* 激活时显示 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.console-line {
|
.console-line {
|
||||||
@@ -320,20 +304,7 @@
|
|||||||
|
|
||||||
<!-- 控制台输出 -->
|
<!-- 控制台输出 -->
|
||||||
<div class="console-output" id="consoleOutput">
|
<div class="console-output" id="consoleOutput">
|
||||||
<!-- Insight Engine 控制台 -->
|
<div class="console-line">[系统] 等待连接...</div>
|
||||||
<div class="console-panel active" id="console-insight">
|
|
||||||
<div class="console-line">[系统] Insight Engine 控制台</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Media Engine 控制台 -->
|
|
||||||
<div class="console-panel" id="console-media">
|
|
||||||
<div class="console-line">[系统] Media Engine 控制台</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Query Engine 控制台 -->
|
|
||||||
<div class="console-panel" id="console-query">
|
|
||||||
<div class="console-line">[系统] Query Engine 控制台</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -367,13 +338,10 @@
|
|||||||
checkStatus();
|
checkStatus();
|
||||||
setInterval(checkStatus, 5000);
|
setInterval(checkStatus, 5000);
|
||||||
|
|
||||||
// 初始化行计数
|
|
||||||
lastLineCount = { insight: 1, media: 1, query: 1 }; // 跳过初始消息
|
|
||||||
|
|
||||||
// 定期刷新控制台输出
|
// 定期刷新控制台输出
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
refreshConsoleOutput();
|
refreshConsoleOutput();
|
||||||
}, 2000);
|
}, 1000);
|
||||||
|
|
||||||
// 延迟预加载iframe以确保应用启动完成
|
// 延迟预加载iframe以确保应用启动完成
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -395,16 +363,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('console_output', function(data) {
|
socket.on('console_output', function(data) {
|
||||||
// 直接添加到对应应用的控制台面板,不依赖currentApp
|
if (data.app === currentApp) {
|
||||||
const consolePanel = document.getElementById(`console-${data.app}`);
|
addConsoleOutput(data.line);
|
||||||
if (consolePanel) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'console-line';
|
|
||||||
div.textContent = data.line;
|
|
||||||
consolePanel.appendChild(div);
|
|
||||||
|
|
||||||
// 自动滚动到底部
|
|
||||||
consolePanel.scrollTop = consolePanel.scrollHeight;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -489,24 +449,12 @@
|
|||||||
|
|
||||||
currentApp = app;
|
currentApp = app;
|
||||||
|
|
||||||
// 切换控制台面板显示
|
// 清空并加载新的控制台输出
|
||||||
document.querySelectorAll('.console-panel').forEach(panel => {
|
document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到 ' + app + ' 应用</div>';
|
||||||
panel.classList.remove('active');
|
|
||||||
});
|
|
||||||
document.getElementById(`console-${app}`).classList.add('active');
|
|
||||||
|
|
||||||
// 只在面板为空时加载输出,不重置行计数
|
// 重置行计数
|
||||||
const consolePanel = document.getElementById(`console-${app}`);
|
lastLineCount[app] = 0;
|
||||||
if (consolePanel && consolePanel.children.length <= 1) { // 只有初始消息
|
loadConsoleOutput(app);
|
||||||
loadConsoleOutput(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换后滚动到底部
|
|
||||||
setTimeout(() => {
|
|
||||||
if (consolePanel) {
|
|
||||||
consolePanel.scrollTop = consolePanel.scrollHeight;
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
// 更新嵌入页面
|
// 更新嵌入页面
|
||||||
updateEmbeddedPage(app);
|
updateEmbeddedPage(app);
|
||||||
@@ -521,7 +469,7 @@
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success && data.output.length > 0) {
|
if (data.success && data.output.length > 0) {
|
||||||
const consolePanel = document.getElementById(`console-${app}`);
|
const consoleOutput = document.getElementById('consoleOutput');
|
||||||
|
|
||||||
// 只添加新的行
|
// 只添加新的行
|
||||||
const lastCount = lastLineCount[app] || 0;
|
const lastCount = lastLineCount[app] || 0;
|
||||||
@@ -531,11 +479,11 @@
|
|||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'console-line';
|
div.className = 'console-line';
|
||||||
div.textContent = line;
|
div.textContent = line;
|
||||||
consolePanel.appendChild(div);
|
consoleOutput.appendChild(div);
|
||||||
});
|
});
|
||||||
|
|
||||||
lastLineCount[app] = data.output.length;
|
lastLineCount[app] = data.output.length;
|
||||||
consolePanel.scrollTop = consolePanel.scrollHeight;
|
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -543,57 +491,48 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新所有应用的控制台输出
|
// 刷新当前应用的控制台输出
|
||||||
function refreshConsoleOutput() {
|
function refreshConsoleOutput() {
|
||||||
// 为每个应用刷新输出,不只是当前应用
|
if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') {
|
||||||
Object.keys(appStatus).forEach(app => {
|
fetch(`/api/output/${currentApp}`)
|
||||||
if (appStatus[app] === 'running' || appStatus[app] === 'starting') {
|
.then(response => response.json())
|
||||||
fetch(`/api/output/${app}`)
|
.then(data => {
|
||||||
.then(response => response.json())
|
if (data.success && data.output.length > 0) {
|
||||||
.then(data => {
|
const consoleOutput = document.getElementById('consoleOutput');
|
||||||
if (data.success && data.output.length > 0) {
|
|
||||||
const consolePanel = document.getElementById(`console-${app}`);
|
// 只添加新的行
|
||||||
|
const lastCount = lastLineCount[currentApp] || 0;
|
||||||
|
const newLines = data.output.slice(lastCount);
|
||||||
|
|
||||||
|
if (newLines.length > 0) {
|
||||||
|
newLines.forEach(line => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'console-line';
|
||||||
|
div.textContent = line;
|
||||||
|
consoleOutput.appendChild(div);
|
||||||
|
});
|
||||||
|
|
||||||
// 只添加新的行
|
lastLineCount[currentApp] = data.output.length;
|
||||||
const lastCount = lastLineCount[app] || 0;
|
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
||||||
const newLines = data.output.slice(lastCount);
|
|
||||||
|
|
||||||
if (newLines.length > 0) {
|
|
||||||
newLines.forEach(line => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'console-line';
|
|
||||||
div.textContent = line;
|
|
||||||
consolePanel.appendChild(div);
|
|
||||||
});
|
|
||||||
|
|
||||||
lastLineCount[app] = data.output.length;
|
|
||||||
// 只有当前显示的面板才自动滚动
|
|
||||||
if (app === currentApp) {
|
|
||||||
consolePanel.scrollTop = consolePanel.scrollHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.catch(error => {
|
})
|
||||||
console.error(`刷新${app}输出失败:`, error);
|
.catch(error => {
|
||||||
});
|
console.error('刷新输出失败:', error);
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加控制台输出
|
// 添加控制台输出
|
||||||
function addConsoleOutput(line) {
|
function addConsoleOutput(line) {
|
||||||
// 根据当前应用添加到对应的控制台面板
|
const consoleOutput = document.getElementById('consoleOutput');
|
||||||
const consolePanel = document.getElementById(`console-${currentApp}`);
|
const div = document.createElement('div');
|
||||||
if (consolePanel) {
|
div.className = 'console-line';
|
||||||
const div = document.createElement('div');
|
div.textContent = line;
|
||||||
div.className = 'console-line';
|
consoleOutput.appendChild(div);
|
||||||
div.textContent = line;
|
|
||||||
consolePanel.appendChild(div);
|
|
||||||
|
|
||||||
// 自动滚动到底部显示最新内容
|
// 自动滚动到底部显示最新内容
|
||||||
consolePanel.scrollTop = consolePanel.scrollHeight;
|
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预加载的iframe存储
|
// 预加载的iframe存储
|
||||||
|
|||||||
Reference in New Issue
Block a user