feat: 更新页面实时日志 — SSE轮询 + logs/updates_logs 文件记录
This commit is contained in:
@@ -76,6 +76,18 @@ class UpdateService:
|
|||||||
self.sm = service_manager
|
self.sm = service_manager
|
||||||
self._task: Optional[asyncio.Task] = None
|
self._task: Optional[asyncio.Task] = None
|
||||||
self._running = False
|
self._running = False
|
||||||
|
# 日志目录
|
||||||
|
import logging as _logging
|
||||||
|
self.update_logger = _logging.getLogger("senSu.update")
|
||||||
|
log_dir = _PROJECT_ROOT / "logs" / "updates_logs"
|
||||||
|
log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
fh = _logging.FileHandler(
|
||||||
|
log_dir / f"update_{time.strftime('%Y%m%d_%H%M%S')}.log",
|
||||||
|
encoding='utf-8'
|
||||||
|
)
|
||||||
|
fh.setFormatter(_logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
|
||||||
|
self.update_logger.addHandler(fh)
|
||||||
|
self.update_logger.setLevel(_logging.INFO)
|
||||||
self.status: dict = {"state": "idle", "progress": 0, "error": "",
|
self.status: dict = {"state": "idle", "progress": 0, "error": "",
|
||||||
"last_check": 0, "checked": False, "update_available": False,
|
"last_check": 0, "checked": False, "update_available": False,
|
||||||
"current_version": "", "latest_version": ""}
|
"current_version": "", "latest_version": ""}
|
||||||
@@ -179,8 +191,10 @@ class UpdateService:
|
|||||||
url = self.download_url
|
url = self.download_url
|
||||||
logger.info(f"📥 下载更新包: {url}")
|
logger.info(f"📥 下载更新包: {url}")
|
||||||
|
|
||||||
|
self._log("开始更新...")
|
||||||
# 0. 备份当前版本
|
# 0. 备份当前版本
|
||||||
self.status["progress"] = 2
|
self.status["progress"] = 2
|
||||||
|
self._log("创建备份...")
|
||||||
backup_name = f"SenSu_backup_{self._current_version()}_{time.strftime('%Y%m%d_%H%M%S')}.zip"
|
backup_name = f"SenSu_backup_{self._current_version()}_{time.strftime('%Y%m%d_%H%M%S')}.zip"
|
||||||
backup_dir = _PROJECT_ROOT / "data" / "backups"
|
backup_dir = _PROJECT_ROOT / "data" / "backups"
|
||||||
backup_dir.mkdir(parents=True, exist_ok=True)
|
backup_dir.mkdir(parents=True, exist_ok=True)
|
||||||
@@ -199,12 +213,14 @@ class UpdateService:
|
|||||||
with urllib.request.urlopen(req, timeout=300) as resp:
|
with urllib.request.urlopen(req, timeout=300) as resp:
|
||||||
shutil.copyfileobj(resp, tmp_zip)
|
shutil.copyfileobj(resp, tmp_zip)
|
||||||
tmp_zip.close()
|
tmp_zip.close()
|
||||||
|
self._log("下载完成")
|
||||||
self.status["progress"] = 40
|
self.status["progress"] = 40
|
||||||
|
|
||||||
# 2. 解压到临时目录
|
# 2. 解压到临时目录
|
||||||
tmp_dir = tempfile.mkdtemp()
|
tmp_dir = tempfile.mkdtemp()
|
||||||
with zipfile.ZipFile(tmp_zip.name, "r") as zf:
|
with zipfile.ZipFile(tmp_zip.name, "r") as zf:
|
||||||
zf.extractall(tmp_dir)
|
zf.extractall(tmp_dir)
|
||||||
|
self._log("解压更新包...")
|
||||||
self.status["progress"] = 60
|
self.status["progress"] = 60
|
||||||
|
|
||||||
# 3. Gitea zip 内层目录: 通常是 repo_name-branch/
|
# 3. Gitea zip 内层目录: 通常是 repo_name-branch/
|
||||||
@@ -229,6 +245,7 @@ class UpdateService:
|
|||||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||||
shutil.copy2(src, dst)
|
shutil.copy2(src, dst)
|
||||||
|
|
||||||
|
self._log("覆盖文件 (跳过用户配置)...")
|
||||||
self.status["progress"] = 80
|
self.status["progress"] = 80
|
||||||
|
|
||||||
# 4.5 同步版本号到旧配置文件
|
# 4.5 同步版本号到旧配置文件
|
||||||
@@ -246,10 +263,12 @@ class UpdateService:
|
|||||||
old_cfg.setdefault("framework", {})["version"] = new_ver
|
old_cfg.setdefault("framework", {})["version"] = new_ver
|
||||||
with open(old_cfg_path, "w") as f:
|
with open(old_cfg_path, "w") as f:
|
||||||
_yaml.dump(old_cfg, f, default_flow_style=False, allow_unicode=True)
|
_yaml.dump(old_cfg, f, default_flow_style=False, allow_unicode=True)
|
||||||
|
self._log(f"版本号已同步: {new_ver}")
|
||||||
logger.info(f"📝 版本号已同步: {new_ver}")
|
logger.info(f"📝 版本号已同步: {new_ver}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"版本号同步失败: {e}")
|
logger.warning(f"版本号同步失败: {e}")
|
||||||
|
|
||||||
|
self._log("安装依赖...")
|
||||||
# 5. 安装依赖 (子进程)
|
# 5. 安装依赖 (子进程)
|
||||||
req_file = str(_PROJECT_ROOT / "requirements.txt")
|
req_file = str(_PROJECT_ROOT / "requirements.txt")
|
||||||
if os.path.exists(req_file):
|
if os.path.exists(req_file):
|
||||||
@@ -259,12 +278,15 @@ class UpdateService:
|
|||||||
cwd=str(_PROJECT_ROOT),
|
cwd=str(_PROJECT_ROOT),
|
||||||
)
|
)
|
||||||
if r.returncode != 0:
|
if r.returncode != 0:
|
||||||
|
self._log(f"依赖安装警告: {r.stderr[-200:]}")
|
||||||
logger.warning(f"依赖安装警告: {r.stderr[-300:]}")
|
logger.warning(f"依赖安装警告: {r.stderr[-300:]}")
|
||||||
|
|
||||||
self.status["progress"] = 100
|
self.status["progress"] = 100
|
||||||
|
self._log("更新完成, 框架将重启")
|
||||||
return {"ok": True, "msg": "更新完成, 框架将重启"}
|
return {"ok": True, "msg": "更新完成, 框架将重启"}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self._log(f"更新失败: {e}")
|
||||||
logger.error(f"更新失败: {e}")
|
logger.error(f"更新失败: {e}")
|
||||||
return {"ok": False, "error": str(e)}
|
return {"ok": False, "error": str(e)}
|
||||||
finally:
|
finally:
|
||||||
@@ -273,6 +295,11 @@ class UpdateService:
|
|||||||
if tmp_dir and os.path.exists(tmp_dir):
|
if tmp_dir and os.path.exists(tmp_dir):
|
||||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
def _log(self, msg: str):
|
||||||
|
"""写入更新日志 + 更新SSE状态"""
|
||||||
|
self.update_logger.info(msg)
|
||||||
|
self.status["log"] = self.status.get("log", "") + msg + "\n"
|
||||||
|
|
||||||
async def check_now(self):
|
async def check_now(self):
|
||||||
await self._check_version()
|
await self._check_version()
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
<button class="btn btn-sm btn-filled" onclick="createBackup()" style="margin-left:8px">+ 创建备份</button>
|
<button class="btn btn-sm btn-filled" onclick="createBackup()" style="margin-left:8px">+ 创建备份</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="backup-list"><span style="color:var(--text-dim);font-size:.85rem">加载中...</span></div>
|
<div id="backup-list"><span style="color:var(--text-dim);font-size:.85rem">加载中...</span></div>
|
||||||
|
<div id="update-log-area" style="display:none;margin-top:12px">
|
||||||
|
<div style="font-weight:600;margin-bottom:4px">更新日志</div>
|
||||||
|
<pre id="update-log" style="background:var(--md-sys-color-surface-container-lowest);padding:8px;border-radius:var(--shape-xs);max-height:200px;overflow-y:auto;font-size:.8rem;margin:0;white-space:pre-wrap;word-break:break-all"></pre>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -134,6 +138,7 @@
|
|||||||
setTimeout(loadVersion, 1500);
|
setTimeout(loadVersion, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _updatePollTimer=null;
|
||||||
async function applyUpdate() {
|
async function applyUpdate() {
|
||||||
if (!confirm("确认更新 SenSu?更新前会自动备份当前版本。")) return;
|
if (!confirm("确认更新 SenSu?更新前会自动备份当前版本。")) return;
|
||||||
document.getElementById("btn-apply").disabled = true;
|
document.getElementById("btn-apply").disabled = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user