更新前端ui
This commit is contained in:
30
strategy_manager/frontend/dist/index.html
vendored
30
strategy_manager/frontend/dist/index.html
vendored
@@ -42,7 +42,12 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<h2 style="margin:0; color: #333;">📈 量化策略控制台</h2>
|
<h2 style="margin:0; color: #333;">📈 量化策略控制台</h2>
|
||||||
<n-space align="center">
|
<n-space align="center">
|
||||||
<!-- 1. 刷新频率选择器 -->
|
<!-- [核心修改] 1. 显示 Git 版本信息 -->
|
||||||
|
<n-tag :bordered="false" type="default" size="small">
|
||||||
|
📦 Version: {{ gitInfo }}
|
||||||
|
</n-tag>
|
||||||
|
|
||||||
|
<!-- 刷新频率选择器 -->
|
||||||
<n-select
|
<n-select
|
||||||
v-model:value="refreshInterval"
|
v-model:value="refreshInterval"
|
||||||
:options="intervalOptions"
|
:options="intervalOptions"
|
||||||
@@ -50,7 +55,7 @@
|
|||||||
style="width: 130px"
|
style="width: 130px"
|
||||||
></n-select>
|
></n-select>
|
||||||
|
|
||||||
<!-- 2. 手动刷新按钮 -->
|
<!-- 手动刷新按钮 -->
|
||||||
<n-button type="primary" size="small" @click="fetchStatus" :loading="loading">
|
<n-button type="primary" size="small" @click="fetchStatus" :loading="loading">
|
||||||
刷新状态
|
刷新状态
|
||||||
</n-button>
|
</n-button>
|
||||||
@@ -120,18 +125,18 @@
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const lastUpdated = ref('-');
|
const lastUpdated = ref('-');
|
||||||
|
|
||||||
// --- 修改点:默认值为 0 (仅手动) ---
|
// [核心修改] 2. 为 Git 信息创建一个 ref
|
||||||
const refreshInterval = ref(0);
|
const gitInfo = ref('Loading...');
|
||||||
|
|
||||||
|
const refreshInterval = ref(0);
|
||||||
const intervalOptions = [
|
const intervalOptions = [
|
||||||
{ label: '✋ 仅手动', value: 0 }, // 建议将手动选项放在第一个
|
{ label: '✋ 仅手动', value: 0 },
|
||||||
{ label: '⚡ 3秒自动', value: 3000 },
|
{ label: '⚡ 3秒自动', value: 3000 },
|
||||||
{ label: '⏱ 5秒自动', value: 5000 },
|
{ label: '⏱ 5秒自动', value: 5000 },
|
||||||
{ label: '🐢 10秒自动', value: 10000 }
|
{ label: '🐢 10秒自动', value: 10000 }
|
||||||
];
|
];
|
||||||
let timer = null;
|
let timer = null;
|
||||||
|
|
||||||
// --- 核心数据获取 ---
|
|
||||||
const fetchStatus = async () => {
|
const fetchStatus = async () => {
|
||||||
if (loading.value) return;
|
if (loading.value) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -140,6 +145,10 @@
|
|||||||
if (!res.ok) throw new Error("Error");
|
if (!res.ok) throw new Error("Error");
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
strategies.value = data.strategies;
|
strategies.value = data.strategies;
|
||||||
|
|
||||||
|
// [核心修改] 3. 更新 Git 信息
|
||||||
|
gitInfo.value = data.git_info || 'N/A';
|
||||||
|
|
||||||
lastUpdated.value = new Date().toLocaleTimeString();
|
lastUpdated.value = new Date().toLocaleTimeString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
message.error("连接服务器失败");
|
message.error("连接服务器失败");
|
||||||
@@ -148,16 +157,13 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- 定时器管理 ---
|
|
||||||
const resetTimer = () => {
|
const resetTimer = () => {
|
||||||
if (timer) clearInterval(timer);
|
if (timer) clearInterval(timer);
|
||||||
// 只有大于 0 才启动定时器
|
|
||||||
if (refreshInterval.value > 0) {
|
if (refreshInterval.value > 0) {
|
||||||
timer = setInterval(fetchStatus, refreshInterval.value);
|
timer = setInterval(fetchStatus, refreshInterval.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听下拉框变化
|
|
||||||
watch(refreshInterval, () => {
|
watch(refreshInterval, () => {
|
||||||
resetTimer();
|
resetTimer();
|
||||||
if (refreshInterval.value > 0) {
|
if (refreshInterval.value > 0) {
|
||||||
@@ -168,7 +174,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 其他逻辑 ---
|
|
||||||
const handleAction = (name, action) => {
|
const handleAction = (name, action) => {
|
||||||
const map = { start: '启动', stop: '停止', restart: '重启' };
|
const map = { start: '启动', stop: '停止', restart: '重启' };
|
||||||
dialog.warning({
|
dialog.warning({
|
||||||
@@ -216,8 +221,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchStatus(); // 页面加载时请求一次
|
fetchStatus();
|
||||||
resetTimer(); // 初始化定时器(当前为0,所以不启动)
|
resetTimer();
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -226,6 +231,7 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
strategies, loading, lastUpdated,
|
strategies, loading, lastUpdated,
|
||||||
|
gitInfo, // [核心修改] 4. 将 gitInfo 暴露给模板
|
||||||
refreshInterval, intervalOptions,
|
refreshInterval, intervalOptions,
|
||||||
showLogModal, currentLogKey, logLines, logLoading,
|
showLogModal, currentLogKey, logLines, logLoading,
|
||||||
fetchStatus, handleAction, viewLogs, fetchLogs
|
fetchStatus, handleAction, viewLogs, fetchLogs
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
# filename: main.py
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from collections import deque
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Query
|
from fastapi import FastAPI, HTTPException, Query
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from pathlib import Path
|
|
||||||
import logging
|
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
# 引入调度器
|
# 引入调度器
|
||||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
@@ -24,7 +29,41 @@ logging.basicConfig(level=logging.INFO)
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# ================== 定时任务逻辑 ==================
|
# ================== 新增:获取 Git 版本信息 ==================
|
||||||
|
def get_git_commit_info():
|
||||||
|
"""
|
||||||
|
[智能版] 获取 Git 仓库的最新提交信息。
|
||||||
|
此函数会自动从当前文件位置向上查找项目根目录(.git 文件夹所在位置)。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 1. 获取 main.py 所在的目录
|
||||||
|
script_dir = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
# 2. 推断出项目根目录 (即 main.py 所在目录的上一级)
|
||||||
|
project_root = script_dir.parent
|
||||||
|
|
||||||
|
# 3. 检查项目根目录下是否存在 .git 文件夹
|
||||||
|
git_dir = project_root / ".git"
|
||||||
|
if not git_dir.is_dir():
|
||||||
|
return "Git repo not found in parent directory"
|
||||||
|
|
||||||
|
# 4. 在推断出的项目根目录下执行 git 命令
|
||||||
|
# 使用 cwd (current working directory) 参数是关键
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'log', '-1', '--pretty=format:%h - %s (%cr)'],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
encoding='utf-8',
|
||||||
|
cwd=project_root # <--- 在这里指定命令的执行目录
|
||||||
|
)
|
||||||
|
return result.stdout.strip()
|
||||||
|
except (FileNotFoundError, subprocess.CalledProcessError) as e:
|
||||||
|
logger.warning(f"无法获取 Git 提交信息: {e}")
|
||||||
|
return "获取 Git 信息失败"
|
||||||
|
|
||||||
|
# ================== 定时任务逻辑 (保持不变) ==================
|
||||||
|
|
||||||
def scheduled_restart_task():
|
def scheduled_restart_task():
|
||||||
"""
|
"""
|
||||||
@@ -47,7 +86,6 @@ def scheduled_restart_task():
|
|||||||
|
|
||||||
for name in running_strategies:
|
for name in running_strategies:
|
||||||
try:
|
try:
|
||||||
# 调用 manager 的重启逻辑 (包含 stop -> sleep -> start)
|
|
||||||
manager.restart_strategy(name)
|
manager.restart_strategy(name)
|
||||||
logger.info(f"✅ [定时任务] {name} 重启成功")
|
logger.info(f"✅ [定时任务] {name} 重启成功")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -56,42 +94,39 @@ def scheduled_restart_task():
|
|||||||
logger.info("⏰ [定时任务] 自动重启流程结束")
|
logger.info("⏰ [定时任务] 自动重启流程结束")
|
||||||
|
|
||||||
|
|
||||||
# ================== FastAPI 事件钩子 ==================
|
# ================== FastAPI 事件钩子 (保持不变) ==================
|
||||||
|
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def start_scheduler():
|
async def start_scheduler():
|
||||||
"""服务启动时,加载定时任务"""
|
|
||||||
# 任务 1: 每天 08:55
|
|
||||||
scheduler.add_job(
|
scheduler.add_job(
|
||||||
scheduled_restart_task,
|
scheduled_restart_task,
|
||||||
CronTrigger(hour=8, minute=58),
|
CronTrigger(hour=8, minute=58),
|
||||||
id="restart_morning",
|
id="restart_morning",
|
||||||
replace_existing=True
|
replace_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# 任务 2: 每天 20:55
|
|
||||||
scheduler.add_job(
|
scheduler.add_job(
|
||||||
scheduled_restart_task,
|
scheduled_restart_task,
|
||||||
CronTrigger(hour=20, minute=58),
|
CronTrigger(hour=20, minute=58),
|
||||||
id="restart_evening",
|
id="restart_evening",
|
||||||
replace_existing=True
|
replace_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
logger.info("📅 定时任务调度器已启动 (计划时间: 08:55, 20:55)")
|
logger.info("📅 定时任务调度器已启动 (计划时间: 08:58, 20:58)")
|
||||||
|
|
||||||
|
|
||||||
@app.on_event("shutdown")
|
@app.on_event("shutdown")
|
||||||
async def stop_scheduler():
|
async def stop_scheduler():
|
||||||
"""服务关闭时停止调度器"""
|
|
||||||
scheduler.shutdown()
|
scheduler.shutdown()
|
||||||
|
|
||||||
|
|
||||||
# ================== 原有 REST API (保持不变) ==================
|
# ================== API 路由 ==================
|
||||||
|
|
||||||
@app.get("/api/status")
|
@app.get("/api/status")
|
||||||
def get_status():
|
def get_status():
|
||||||
return manager.get_status()
|
# [核心修改] 在返回状态的同时,注入 Git 版本信息
|
||||||
|
status_data = manager.get_status()
|
||||||
|
status_data['git_info'] = get_git_commit_info()
|
||||||
|
return status_data
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/strategy/{name}/start")
|
@app.post("/api/strategy/{name}/start")
|
||||||
@@ -110,7 +145,6 @@ def stop_strategy(name: str):
|
|||||||
|
|
||||||
@app.post("/api/strategy/{name}/restart")
|
@app.post("/api/strategy/{name}/restart")
|
||||||
def restart_strategy(name: str):
|
def restart_strategy(name: str):
|
||||||
# 修复了之前提到的返回值问题
|
|
||||||
if manager.restart_strategy(name):
|
if manager.restart_strategy(name):
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
raise HTTPException(400, "重启失败,请检查日志")
|
raise HTTPException(400, "重启失败,请检查日志")
|
||||||
@@ -127,7 +161,6 @@ def get_logs(name: str, lines: int = Query(50, le=500)):
|
|||||||
if not log_file.exists():
|
if not log_file.exists():
|
||||||
return {"lines": ["日志文件尚未生成"]}
|
return {"lines": ["日志文件尚未生成"]}
|
||||||
|
|
||||||
# 修复编码和读取
|
|
||||||
with open(log_file, 'r', encoding='utf-8', errors='replace') as f:
|
with open(log_file, 'r', encoding='utf-8', errors='replace') as f:
|
||||||
last_lines = deque(f, maxlen=lines)
|
last_lines = deque(f, maxlen=lines)
|
||||||
return {"lines": [line.rstrip() for line in last_lines]}
|
return {"lines": [line.rstrip() for line in last_lines]}
|
||||||
@@ -135,10 +168,11 @@ def get_logs(name: str, lines: int = Query(50, le=500)):
|
|||||||
raise HTTPException(500, f"读取日志失败: {e}")
|
raise HTTPException(500, f"读取日志失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
# ================== 静态文件挂载 ==================
|
# ================== 静态文件挂载 (保持不变) ==================
|
||||||
|
# 注意: 这里的路径是示例,请确保它与你的项目结构匹配
|
||||||
|
# 假设你的HTML文件在 "frontend/" 目录下
|
||||||
app.mount("/static", StaticFiles(directory="frontend/dist"), name="static")
|
app.mount("/static", StaticFiles(directory="frontend/dist"), name="static")
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def serve_frontend():
|
def serve_frontend():
|
||||||
return FileResponse("frontend/dist/index.html")
|
return FileResponse("frontend/dist/index.html")
|
||||||
Reference in New Issue
Block a user