v2.0.1 done
This commit is contained in:
+33
-14
@@ -30,7 +30,7 @@ from app.history import HistoryManager
|
||||
app = FastAPI(
|
||||
title="CapacityReport",
|
||||
description="容量报表数据处理系统",
|
||||
version="2.0.0"
|
||||
version="2.0.1"
|
||||
)
|
||||
|
||||
# CORS 配置
|
||||
@@ -103,7 +103,7 @@ async def health_check():
|
||||
return {
|
||||
"status": "healthy" if is_healthy else "unhealthy",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"uptime_pid": os.getpid(),
|
||||
"checks": checks
|
||||
}
|
||||
@@ -396,13 +396,18 @@ async def start_processing(task_id: str = Body(..., embed=True)):
|
||||
logs: List[str] = []
|
||||
def log_callback(msg: str):
|
||||
logs.append(msg)
|
||||
processing_tasks[task_id] = {"logs": logs, "status": "processing"}
|
||||
# 确保每次日志更新都同步到 processing_tasks
|
||||
if task_id in processing_tasks:
|
||||
processing_tasks[task_id]["logs"] = logs.copy()
|
||||
processing_tasks[task_id]["status"] = "processing"
|
||||
else:
|
||||
processing_tasks[task_id] = {"logs": logs.copy(), "status": "processing"}
|
||||
|
||||
logger = ProcessLogger(log_file=log_file, callback=log_callback)
|
||||
|
||||
# 更新状态
|
||||
history_manager.update(task_id, status="processing")
|
||||
processing_tasks[task_id] = {"logs": logs, "status": "processing"}
|
||||
processing_tasks[task_id] = {"logs": [], "status": "processing"}
|
||||
|
||||
# 更新全局锁定状态为处理中
|
||||
global_task_lock["locked"] = True
|
||||
@@ -453,22 +458,31 @@ async def start_processing(task_id: str = Body(..., embed=True)):
|
||||
@app.post("/api/process/status")
|
||||
async def get_processing_status(task_id: str = Body(..., embed=True)):
|
||||
"""获取处理任务状态和日志(task_id 放在 POST body 中)"""
|
||||
# 优先从文件读取日志(实时写入,保证最新)
|
||||
logs = history_manager.get_logs(task_id)
|
||||
|
||||
# 检查内存中的实时状态(用于获取状态)
|
||||
# 检查内存中的实时状态(优先使用内存中的日志,实时更新)
|
||||
if task_id in processing_tasks:
|
||||
task_info = processing_tasks[task_id]
|
||||
# 如果内存中有日志,优先使用内存中的(实时更新)
|
||||
if "logs" in task_info and task_info["logs"]:
|
||||
return {
|
||||
"task_id": task_id,
|
||||
"status": task_info["status"],
|
||||
"logs": task_info["logs"] # 使用内存中的实时日志
|
||||
}
|
||||
# 如果内存中没有日志,尝试从文件读取
|
||||
logs = history_manager.get_logs(task_id)
|
||||
return {
|
||||
"task_id": task_id,
|
||||
"status": processing_tasks[task_id]["status"],
|
||||
"status": task_info["status"],
|
||||
"logs": logs # 使用文件中的日志
|
||||
}
|
||||
|
||||
# 从历史记录获取
|
||||
# 从历史记录获取(任务已完成)
|
||||
record = history_manager.get(task_id)
|
||||
if not record:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
|
||||
# 从文件读取日志
|
||||
logs = history_manager.get_logs(task_id)
|
||||
return {
|
||||
"task_id": task_id,
|
||||
"status": record.status,
|
||||
@@ -1118,7 +1132,7 @@ async def get_service_status():
|
||||
"""获取服务运行状态"""
|
||||
return {
|
||||
"status": "running",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"platform": platform.system(),
|
||||
"supervisor": is_supervisor_running(),
|
||||
"pid": os.getpid(),
|
||||
@@ -1179,12 +1193,17 @@ async def execute_script():
|
||||
logs: List[str] = []
|
||||
def log_callback(msg: str):
|
||||
logs.append(msg)
|
||||
processing_tasks[task_id] = {"logs": logs, "status": "processing"}
|
||||
# 确保每次日志更新都同步到 processing_tasks
|
||||
if task_id in processing_tasks:
|
||||
processing_tasks[task_id]["logs"] = logs.copy()
|
||||
processing_tasks[task_id]["status"] = "processing"
|
||||
else:
|
||||
processing_tasks[task_id] = {"logs": logs.copy(), "status": "processing"}
|
||||
|
||||
logger = ProcessLogger(log_file=None, callback=log_callback)
|
||||
|
||||
# 初始化任务状态
|
||||
processing_tasks[task_id] = {"logs": logs, "status": "processing"}
|
||||
processing_tasks[task_id] = {"logs": [], "status": "processing"}
|
||||
|
||||
# 在后台线程执行脚本
|
||||
def run_script():
|
||||
@@ -1256,6 +1275,6 @@ async def save_script_content(content: str = Body(..., embed=True)):
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
print(f"CapacityReport v2.0.0")
|
||||
print(f"CapacityReport v2.0.1")
|
||||
print(f"配置更新时间: {config.update}")
|
||||
uvicorn.run("app.main:app", host="0.0.0.0", port=9081, reload=False)
|
||||
|
||||
+28
-9
@@ -687,27 +687,46 @@ class DataProcessor:
|
||||
with open(SQL_SCRIPT, 'r', encoding='utf-8') as f:
|
||||
sql_text = f.read()
|
||||
|
||||
sqls = sqlparse.split(sql_text)
|
||||
total = len(sqls)
|
||||
if not sql_text or not sql_text.strip():
|
||||
self.logger.warning("SQL 脚本文件为空,跳过执行")
|
||||
return
|
||||
|
||||
sqls = sqlparse.split(sql_text)
|
||||
# 过滤掉空语句和注释
|
||||
valid_sqls = []
|
||||
for sql in sqls:
|
||||
sql = sql.strip()
|
||||
if sql and not sql.startswith('#'):
|
||||
valid_sqls.append(sql)
|
||||
|
||||
if not valid_sqls:
|
||||
self.logger.warning("SQL 脚本中没有有效的 SQL 语句(可能全是注释或空行)")
|
||||
return
|
||||
|
||||
total = len(valid_sqls)
|
||||
self.logger.info(f"共找到 {total} 条有效的 SQL 语句")
|
||||
|
||||
executed_count = 0
|
||||
with self.db.get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
for i, sql in enumerate(sqls, 1):
|
||||
sql = sql.strip()
|
||||
if not sql or sql.startswith('#'):
|
||||
continue
|
||||
|
||||
for i, sql in enumerate(valid_sqls, 1):
|
||||
start_time = time.time()
|
||||
preview = sql[:80].replace('\n', ' ')
|
||||
self.logger.info(f"执行 SQL ({i}/{total}): {preview}...")
|
||||
|
||||
try:
|
||||
cursor.execute(sql)
|
||||
executed_count += 1
|
||||
elapsed = round(time.time() - start_time, 2)
|
||||
self.logger.info(f"完成,耗时 {elapsed} 秒")
|
||||
affected_rows = cursor.rowcount if cursor.rowcount >= 0 else 0
|
||||
if affected_rows > 0:
|
||||
self.logger.info(f"完成,耗时 {elapsed} 秒,影响 {affected_rows} 行")
|
||||
else:
|
||||
self.logger.info(f"完成,耗时 {elapsed} 秒")
|
||||
except Exception as e:
|
||||
self.logger.error(f"SQL 执行失败: {e}")
|
||||
# 继续执行下一条 SQL,不中断
|
||||
|
||||
conn.commit()
|
||||
|
||||
self.logger.success("SQL 脚本执行完成")
|
||||
self.logger.success(f"SQL 脚本执行完成,共执行 {executed_count}/{total} 条语句")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
title CapacityReport v2.0.0 [自动重启模式]
|
||||
title CapacityReport v2.0.1 [自动重启模式]
|
||||
|
||||
echo ========================================
|
||||
echo CapacityReport - 容量报表处理程序
|
||||
|
||||
+2
-6
@@ -41,9 +41,9 @@
|
||||
<span>系统设置</span>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="sidebar-footer" data-version="v2.0.0">
|
||||
<div class="sidebar-footer" data-version="v2.0.1">
|
||||
<div class="sidebar-footer-info">
|
||||
<span class="version">版本:v2.0.0</span>
|
||||
<span class="version">版本:v2.0.1</span>
|
||||
<span class="version">Power by:NIXEVOL</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,10 +116,6 @@
|
||||
<span>总计:</span>
|
||||
<span class="count" id="statsTotal">0</span>
|
||||
</div>
|
||||
<div class="upload-stats-item uploading">
|
||||
<span>上传中:</span>
|
||||
<span class="count" id="statsUploading">0</span>
|
||||
</div>
|
||||
<div class="upload-stats-item success">
|
||||
<span>已完成:</span>
|
||||
<span class="count" id="statsSuccess">0</span>
|
||||
|
||||
+126
-28
@@ -111,13 +111,24 @@ function showConfirm(title, message) {
|
||||
|
||||
// API 调用
|
||||
async function api(endpoint, options = {}) {
|
||||
const response = await fetch(`/api${endpoint}`, {
|
||||
// 如果没有指定 method 且没有 body,默认使用 GET
|
||||
const method = options.method || (options.body ? 'POST' : 'GET');
|
||||
|
||||
const fetchOptions = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
// GET 请求不应该有 body
|
||||
if (method === 'GET' && fetchOptions.body) {
|
||||
delete fetchOptions.body;
|
||||
}
|
||||
|
||||
const response = await fetch(`/api${endpoint}`, fetchOptions);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ detail: response.statusText }));
|
||||
@@ -460,13 +471,11 @@ class FileUploader {
|
||||
updateUploadStats() {
|
||||
const stats = {
|
||||
total: this.files.length,
|
||||
uploading: this.files.filter(f => f.status === 'uploading').length,
|
||||
success: this.files.filter(f => f.status === 'uploaded').length,
|
||||
error: this.files.filter(f => f.status === 'error').length
|
||||
};
|
||||
|
||||
$('#statsTotal').textContent = stats.total;
|
||||
$('#statsUploading').textContent = stats.uploading;
|
||||
$('#statsSuccess').textContent = stats.success;
|
||||
$('#statsError').textContent = stats.error;
|
||||
|
||||
@@ -536,15 +545,23 @@ class FileUploader {
|
||||
$('#totalProgress').style.display = 'none';
|
||||
$('#uploadStats').style.display = 'none';
|
||||
|
||||
// 显示处理区域(上传中状态)
|
||||
$('#processSection').style.display = 'block';
|
||||
$('#processActions').style.display = 'none';
|
||||
$('#logContent').innerHTML = '<div class="log-line info">正在上传文件...</div>';
|
||||
$('#processStatus').className = 'process-status processing';
|
||||
$('#processStatus').textContent = '上传中...';
|
||||
// 隐藏处理区域(上传完成前不显示)
|
||||
$('#processSection').style.display = 'none';
|
||||
|
||||
// 强制浏览器重绘
|
||||
$('#processSection').offsetHeight;
|
||||
// 显示文件列表和进度信息
|
||||
$('#fileList').style.display = 'block';
|
||||
$('#totalProgress').style.display = 'block';
|
||||
$('#uploadStats').style.display = 'flex';
|
||||
|
||||
// 初始化进度显示
|
||||
$('#totalPercent').textContent = '0%';
|
||||
$('#totalProgressBar').style.width = '0%';
|
||||
|
||||
// 保持所有文件为等待上传状态(不立即改为上传中)
|
||||
this.files.forEach((fileData, index) => {
|
||||
this.updateFileStatus(index, 'pending', 0);
|
||||
});
|
||||
this.updateUploadStats();
|
||||
|
||||
this.isUploading = true;
|
||||
$('#startUpload').disabled = true;
|
||||
@@ -566,10 +583,40 @@ class FileUploader {
|
||||
// 上传进度事件
|
||||
xhr.upload.addEventListener('progress', (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const progress = Math.round((e.loaded / e.total) * 100);
|
||||
// 更新日志显示进度
|
||||
$('#logContent').innerHTML = `<div class="log-line info">正在上传文件... ${progress}%</div>`;
|
||||
$('#processStatus').textContent = `上传中 ${progress}%`;
|
||||
const totalProgress = Math.round((e.loaded / e.total) * 100);
|
||||
const fileCount = this.files.length;
|
||||
|
||||
// 更新总进度条
|
||||
$('#totalPercent').textContent = `${totalProgress}%`;
|
||||
$('#totalProgressBar').style.width = `${totalProgress}%`;
|
||||
|
||||
// 根据总进度计算当前应该处理到第几个文件
|
||||
// 每个文件占 100/fileCount 的进度
|
||||
const progressPerFile = 100 / fileCount;
|
||||
const currentFileIndex = Math.floor(totalProgress / progressPerFile);
|
||||
const currentFileProgress = (totalProgress % progressPerFile) / progressPerFile * 100;
|
||||
|
||||
// 更新每个文件的状态
|
||||
this.files.forEach((fileData, index) => {
|
||||
if (index < currentFileIndex) {
|
||||
// 已完成的文件
|
||||
this.updateFileStatus(index, 'uploaded', 100);
|
||||
} else if (index === currentFileIndex) {
|
||||
// 当前正在上传的文件
|
||||
const fileProgress = Math.min(100, Math.max(0, Math.round(currentFileProgress)));
|
||||
this.updateFileStatus(index, 'uploading', fileProgress);
|
||||
|
||||
// 滚动到当前正在上传的文件
|
||||
const fileItem = $(`#file-item-${index}`);
|
||||
if (fileItem) {
|
||||
fileItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
} else {
|
||||
// 还未开始的文件
|
||||
this.updateFileStatus(index, 'pending', 0);
|
||||
}
|
||||
});
|
||||
this.updateUploadStats();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -606,10 +653,25 @@ class FileUploader {
|
||||
this.taskId = taskId;
|
||||
console.log('上传完成,任务ID:', taskId);
|
||||
|
||||
// 将所有文件状态改为已完成
|
||||
this.files.forEach((fileData, index) => {
|
||||
this.updateFileStatus(index, 'uploaded', 100);
|
||||
});
|
||||
this.updateUploadStats();
|
||||
|
||||
// 注意:锁定已在后端上传接口中完成,这里不需要再次锁定
|
||||
|
||||
// 更新UI显示上传完成
|
||||
// 隐藏上传进度,显示处理区域
|
||||
$('#totalProgress').style.display = 'none';
|
||||
$('#uploadStats').style.display = 'none';
|
||||
$('#fileList').style.display = 'none';
|
||||
|
||||
// 显示处理区域(上传完成后才显示)
|
||||
$('#processSection').style.display = 'block';
|
||||
const processActions = $('#processActions');
|
||||
if (processActions) processActions.style.display = 'none';
|
||||
$('#logContent').innerHTML = `<div class="log-line success">上传完成: ${result.file_count || this.files.length} 个文件</div>`;
|
||||
$('#processStatus').className = 'process-status processing';
|
||||
$('#processStatus').textContent = '准备开始处理...';
|
||||
showToast(`上传完成: ${result.file_count || this.files.length} 个文件`, 'success');
|
||||
|
||||
@@ -626,6 +688,13 @@ class FileUploader {
|
||||
} catch (error) {
|
||||
showToast(`上传失败: ${error.message}`, 'error');
|
||||
console.error('上传失败:', error);
|
||||
|
||||
// 将所有文件状态改为失败
|
||||
this.files.forEach((fileData, index) => {
|
||||
this.updateFileStatus(index, 'error', 0);
|
||||
});
|
||||
this.updateUploadStats();
|
||||
|
||||
// 上传失败,解锁全局任务(后端会自动处理,但这里也尝试解锁)
|
||||
if (this.taskId) {
|
||||
try {
|
||||
@@ -669,7 +738,8 @@ class FileUploader {
|
||||
|
||||
// 显示处理区域
|
||||
$('#processSection').style.display = 'block';
|
||||
$('#processActions').style.display = 'none';
|
||||
const processActions = $('#processActions');
|
||||
if (processActions) processActions.style.display = 'none';
|
||||
$('#processStatus').className = 'process-status processing';
|
||||
$('#processStatus').textContent = '处理中...';
|
||||
$('#logContent').innerHTML = '<div class="log-line info">处理任务已启动,等待日志...</div>';
|
||||
@@ -771,6 +841,11 @@ class FileUploader {
|
||||
else if (log.includes('[WARN]')) level = 'warn';
|
||||
return `<div class="log-line ${level}">${log}</div>`;
|
||||
}).join('');
|
||||
} else if (status.status === 'processing') {
|
||||
// 处理中但还没有日志,显示等待信息
|
||||
if (!logContent.innerHTML || logContent.innerHTML.trim() === '') {
|
||||
logContent.innerHTML = '<div class="log-line info">等待处理开始...</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// 自动滚动到底部(仅在自动刷新开启时)
|
||||
@@ -813,13 +888,20 @@ class FileUploader {
|
||||
|
||||
} catch (error) {
|
||||
console.error('轮询状态失败:', error);
|
||||
// 如果请求失败(如 404),可能是任务ID错误
|
||||
// 如果请求失败(如 404),可能是任务ID错误或任务已完成
|
||||
if (error.message && error.message.includes('404')) {
|
||||
clearInterval(this.pollInterval);
|
||||
showToast('任务状态获取失败', 'error');
|
||||
$('#processStatus').textContent = '状态获取失败';
|
||||
// 任务不存在,停止轮询并恢复界面
|
||||
if (this.pollInterval) {
|
||||
clearInterval(this.pollInterval);
|
||||
this.pollInterval = null;
|
||||
}
|
||||
showToast('任务不存在或已完成', 'warning');
|
||||
$('#processStatus').textContent = '任务不存在';
|
||||
$('#processStatus').className = 'process-status failed';
|
||||
this.restoreUploadUI();
|
||||
} else {
|
||||
// 其他错误(如网络错误),继续轮询但显示错误提示
|
||||
console.warn('轮询状态失败,继续重试:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -847,6 +929,16 @@ class FileUploader {
|
||||
else if (log.includes('[WARN]')) level = 'warn';
|
||||
return `<div class="log-line ${level}">${log}</div>`;
|
||||
}).join('');
|
||||
// 滚动到底部
|
||||
const container = $('#logContainer');
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
} else if (status.status === 'processing') {
|
||||
// 处理中但还没有日志,显示等待信息
|
||||
if (!logContent.innerHTML || logContent.innerHTML.trim() === '') {
|
||||
logContent.innerHTML = '<div class="log-line info">等待处理开始...</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// 不自动滚动,让用户自己控制
|
||||
@@ -881,11 +973,17 @@ class FileUploader {
|
||||
|
||||
this.updateFileList();
|
||||
// 隐藏处理区域
|
||||
$('#processSection').style.display = 'none';
|
||||
$('#processActions').style.display = 'none';
|
||||
$('#processStatus').className = 'process-status processing';
|
||||
$('#processStatus').textContent = '处理中...';
|
||||
$('#logContent').innerHTML = '';
|
||||
const processSection = $('#processSection');
|
||||
if (processSection) processSection.style.display = 'none';
|
||||
const processActions = $('#processActions');
|
||||
if (processActions) processActions.style.display = 'none';
|
||||
const processStatus = $('#processStatus');
|
||||
if (processStatus) {
|
||||
processStatus.className = 'process-status processing';
|
||||
processStatus.textContent = '处理中...';
|
||||
}
|
||||
const logContent = $('#logContent');
|
||||
if (logContent) logContent.innerHTML = '';
|
||||
// 恢复显示上传区域
|
||||
this.restoreUploadUI();
|
||||
console.log('任务已重置,UI 已恢复');
|
||||
@@ -2096,7 +2194,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// 历史数据大小已在处理历史页面显示,不再在侧边栏显示
|
||||
|
||||
console.log('CapacityReport v2.0.0 已加载');
|
||||
console.log('CapacityReport v2.0.1 已加载');
|
||||
|
||||
// 重启服务按钮事件(使用事件委托,支持所有页面的重启按钮)
|
||||
document.addEventListener('click', async (e) => {
|
||||
|
||||
Reference in New Issue
Block a user