feat(monitor): 添加国际化支持(中文/英文/德语)

- 新增i18n框架和3个语言包(405个翻译条目/语言)
- 修改监控模块3个HTML页面和JS添加国际化支持
- 支持浏览器语言自动检测、localStorage持久化、热切换
- 修复定时任务显示模板字符串语法错误

影响范围:
- static/lib/i18n/: 新增i18n框架和语言包
- static/monitor/: 监控模块全面国际化
- 11 files changed, +2481/-502 lines
This commit is contained in:
2026-05-23 22:48:56 +08:00
parent bc4d2f9475
commit 441966a9c6
11 changed files with 2481 additions and 502 deletions
+3 -1
View File
@@ -102,4 +102,6 @@ offline_packages/
offline_packages/**/
docker_images/
docker_images/**/
docker_images/**/
entrypoint.sh
+246 -81
View File
@@ -1,31 +1,37 @@
#!/bin/bash
# =====================================================
# 开发环境服务启停脚本
# 开发环境服务启停脚本(智能版)
# 用法:
# ./dev_server.sh start - 启动服务
# ./dev_server.sh start - 智能启动(自动检测并启动所需服务
# ./dev_server.sh stop - 停止服务
# ./dev_server.sh restart - 重启服务
# ./dev_server.sh status - 查看状态
# ./dev_server.sh logs - 查看日志
# =====================================================
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 项目根目录
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$PROJECT_DIR"
# 查找Python解释器
find_python() {
# 优先使用虚拟环境
if [ -f "$PROJECT_DIR/venv/bin/python" ]; then
echo "$PROJECT_DIR/venv/bin/python"
return
fi
# 尝试python3
if command -v python3 &> /dev/null; then
echo "python3"
return
fi
# 尝试python
if command -v python &> /dev/null; then
echo "python"
return
@@ -42,20 +48,33 @@ LOG_FILE="$PROJECT_DIR/logs/dev_server.log"
HOST="0.0.0.0"
PORT="8001"
# 创建日志目录
# 创建必要目录
mkdir -p "$PROJECT_DIR/logs"
mkdir -p "$PROJECT_DIR/storage"
# 获取进程ID
get_pid() {
if [ -f "$PID_FILE" ]; then
cat "$PID_FILE"
else
pgrep -f "python.*main\.py" | head -1 || pgrep -f "python3.*main\.py" | head -1
# =====================================================
# 智能服务检测与管理
# =====================================================
check_redis() {
if pgrep -x "redis-server" > /dev/null 2>&1; then
if redis-cli ping > /dev/null 2>&1; then
return 0
fi
fi
return 1
}
# 检查服务是否运行
is_running() {
check_postgresql() {
if pgrep -x "postgres" > /dev/null 2>&1; then
if pg_isready > /dev/null 2>&1; then
return 0
fi
fi
return 1
}
check_app() {
local pid=$(get_pid)
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
return 0
@@ -63,152 +82,298 @@ is_running() {
return 1
}
# 启动服务
start() {
if is_running; then
echo "服务已在运行中 (PID: $(get_pid))"
start_redis() {
echo -e "${BLUE}[Redis]${NC} 检查服务状态..."
if check_redis; then
echo -e "${GREEN}[Redis]${NC} ✓ 已在运行"
return 0
fi
echo -e "${YELLOW}[Redis]${NC} 服务未运行,正在启动..."
if ! command -v redis-server &> /dev/null; then
echo -e "${RED}[Redis]${NC} ✗ 未安装 redis-server"
echo -e "${YELLOW}[Redis]${NC} 请执行: sudo apt install redis-server"
return 1
fi
echo "正在启动服务..."
echo "项目目录: $PROJECT_DIR"
echo "Python解释器: $PYTHON_CMD"
echo "访问地址: http://localhost:$PORT"
echo "API文档: http://localhost:$PORT/docs"
redis-server --daemonize yes 2>/dev/null
local count=0
while ! check_redis && [ $count -lt 10 ]; do
sleep 0.5
count=$((count + 1))
done
if check_redis; then
echo -e "${GREEN}[Redis]${NC} ✓ 启动成功"
return 0
else
echo -e "${RED}[Redis]${NC} ✗ 启动失败"
return 1
fi
}
start_postgresql() {
echo -e "${BLUE}[PostgreSQL]${NC} 检查服务状态..."
if check_postgresql; then
echo -e "${GREEN}[PostgreSQL]${NC} ✓ 已在运行"
return 0
fi
echo -e "${YELLOW}[PostgreSQL]${NC} 服务未运行,正在启动..."
if ! command -v pg_isready &> /dev/null; then
echo -e "${RED}[PostgreSQL]${NC} ✗ 未安装 PostgreSQL"
echo -e "${YELLOW}[PostgreSQL]${NC} 请执行: sudo apt install postgresql postgresql-contrib"
return 1
fi
sudo service postgresql start 2>/dev/null || true
local count=0
while ! check_postgresql && [ $count -lt 15 ]; do
sleep 1
count=$((count + 1))
done
if check_postgresql; then
echo -e "${GREEN}[PostgreSQL]${NC} ✓ 启动成功"
return 0
else
echo -e "${RED}[PostgreSQL]${NC} ✗ 启动失败"
return 1
fi
}
ensure_services() {
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}检查基础服务...${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
local redis_ok=true
local pg_ok=true
start_redis || redis_ok=false
start_postgresql || pg_ok=false
echo ""
if [ "$redis_ok" = false ] || [ "$pg_ok" = false ]; then
echo -e "${RED}部分基础服务启动失败,应用可能无法正常工作${NC}"
echo -e "${YELLOW}是否继续启动应用?[y/N]${NC}"
read -r response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
echo "已取消启动"
exit 1
fi
fi
}
# =====================================================
# 应用服务管理
# =====================================================
get_pid() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
echo "$pid"
return
fi
fi
pgrep -f "python.*main\.py" | head -1 || true
}
start_app() {
if check_app; then
echo -e "${GREEN}[应用]${NC} ✓ 已在运行 (PID: $(get_pid))"
echo -e "${GREEN}[应用]${NC} 访问地址: http://localhost:$PORT"
echo -e "${GREEN}[应用]${NC} API文档: http://localhost:$PORT/docs"
return 0
fi
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}启动应用服务...${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e " 项目目录: $PROJECT_DIR"
echo -e " Python: $PYTHON_CMD"
echo -e " 端口: $PORT"
echo ""
# 启动服务
nohup env PORT=$PORT $PYTHON_CMD main.py > "$LOG_FILE" 2>&1 &
local pid=$!
echo $pid > "$PID_FILE"
sleep 2
if is_running; then
echo "✓ 服务启动成功 (PID: $pid)"
echo "日志文件: $LOG_FILE"
if check_app; then
echo -e "${GREEN}[应用]${NC}启动成功 (PID: $pid)"
echo -e "${GREEN}[应用]${NC} 访问地址: http://localhost:$PORT"
echo -e "${GREEN}[应用]${NC} API文档: http://localhost:$PORT/docs"
echo -e "${GREEN}[应用]${NC} 日志文件: $LOG_FILE"
return 0
else
echo "✗ 服务启动失败,请查看日志:"
echo -e "${RED}[应用]${NC} ✗ 启动失败"
echo -e "${YELLOW}[应用]${NC} 最近日志:"
tail -20 "$LOG_FILE"
rm -f "$PID_FILE"
return 1
fi
}
# 停止服务
stop() {
if ! is_running; then
echo "服务未运行"
stop_app() {
if ! check_app; then
echo -e "${YELLOW}[应用]${NC} 服务未运行"
rm -f "$PID_FILE"
return 0
fi
local pid=$(get_pid)
echo "正在停止服务 (PID: $pid)..."
echo -e "${YELLOW}[应用]${NC} 正在停止 (PID: $pid)..."
# 发送SIGTERM信号
kill "$pid" 2>/dev/null
# 等待进程结束
local count=0
while kill -0 "$pid" 2>/dev/null && [ $count -lt 10 ]; do
sleep 1
count=$((count + 1))
done
# 如果进程还在运行,强制结束
if kill -0 "$pid" 2>/dev/null; then
echo "强制结束进程..."
echo -e "${YELLOW}[应用]${NC} 强制结束..."
kill -9 "$pid" 2>/dev/null
fi
rm -f "$PID_FILE"
echo "✓ 服务已停止"
echo -e "${GREEN}[应用]${NC}已停止"
}
# 清除Python缓存
clear_cache() {
echo "清除Python缓存..."
find "$PROJECT_DIR" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null
find "$PROJECT_DIR" -name "*.pyc" -delete 2>/dev/null
echo "✓ 缓存已清除"
echo -e "${YELLOW}清除Python缓存...${NC}"
find "$PROJECT_DIR" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find "$PROJECT_DIR" -name "*.pyc" -delete 2>/dev/null || true
echo -e "${GREEN}✓ 缓存已清除${NC}"
}
# 重启服务
restart() {
stop
# =====================================================
# 主命令
# =====================================================
cmd_start() {
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}MyAPS API 开发服务器${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
ensure_services
echo ""
start_app
}
cmd_stop() {
echo -e "${BLUE}停止服务...${NC}"
stop_app
}
cmd_restart() {
cmd_stop
clear_cache
sleep 1
start
cmd_start
}
# 查看状态
status() {
if is_running; then
cmd_status() {
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}服务状态${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e "${BLUE}[Redis]${NC}"
if check_redis; then
echo -e " 状态: ${GREEN}运行中${NC}"
redis-cli INFO server 2>/dev/null | grep "redis_version" | sed 's/^/ /' || true
else
echo -e " 状态: ${RED}未运行${NC}"
fi
echo ""
echo -e "${BLUE}[PostgreSQL]${NC}"
if check_postgresql; then
echo -e " 状态: ${GREEN}运行中${NC}"
pg_isready 2>/dev/null | sed 's/^/ /' || true
else
echo -e " 状态: ${RED}未运行${NC}"
fi
echo ""
echo -e "${BLUE}[应用服务]${NC}"
if check_app; then
local pid=$(get_pid)
echo "✓ 服务运行中"
echo " PID: $pid"
echo " 访问地址: http://localhost:$PORT"
echo " API文档: http://localhost:$PORT/docs"
# 显示进程信息
echo -e " 状态: ${GREEN}运行中${NC}"
echo -e " PID: $pid"
echo -e " 访问地址: http://localhost:$PORT"
echo -e " API文档: http://localhost:$PORT/docs"
if command -v ps &> /dev/null; then
ps -p "$pid" -o pid,ppid,%cpu,%mem,etime,cmd 2>/dev/null || true
ps -p "$pid" -o pid,ppid,%cpu,%mem,etime 2>/dev/null || true
fi
else
echo "✗ 服务未运行"
echo -e " 状态: ${RED}未运行${NC}"
fi
echo ""
}
# 查看日志
logs() {
cmd_logs() {
if [ ! -f "$LOG_FILE" ]; then
echo "日志文件不存在: $LOG_FILE"
echo -e "${YELLOW}日志文件不存在: $LOG_FILE${NC}"
return 1
fi
if [ "$1" = "-f" ] || [ "$1" = "--follow" ]; then
echo "实时查看日志 (Ctrl+C 退出)..."
echo -e "${BLUE}实时查看日志 (Ctrl+C 退出)...${NC}"
tail -f "$LOG_FILE"
else
echo "最近50行日志:"
echo -e "${BLUE}最近50行日志:${NC}"
tail -50 "$LOG_FILE"
fi
}
# 帮助信息
help() {
echo "用法: $0 {start|stop|restart|status|logs|clear_cache}"
echo -e "${BLUE}用法:${NC} $0 {start|stop|restart|status|logs|clear_cache}"
echo ""
echo "命令:"
echo " start - 启动服务"
echo " stop - 停止服务"
echo " restart - 重启服务(自动清除缓存)"
echo " status - 查看服务状态"
echo -e "${BLUE}命令:${NC}"
echo " start - 智能启动(自动检测并启动 Redis/PostgreSQL/应用)"
echo " stop - 停止应用服务"
echo " restart - 重启应用服务(自动清除缓存)"
echo " status - 查看所有服务状态"
echo " logs - 查看日志 (添加 -f 参数实时查看)"
echo " clear_cache - 清除Python缓存"
echo ""
echo "示例:"
echo " $0 start"
echo " $0 restart"
echo " $0 logs -f"
echo -e "${BLUE}示例:${NC}"
echo " $0 start # 一键启动所有服务"
echo " $0 restart # 重启应用"
echo " $0 logs -f # 实时查看日志"
echo " $0 status # 查看所有服务状态"
}
# 主入口
case "$1" in
start)
start
cmd_start
;;
stop)
stop
cmd_stop
;;
restart)
restart
cmd_restart
;;
status)
status
cmd_status
;;
logs)
logs "$2"
cmd_logs "$2"
;;
clear_cache)
clear_cache
@@ -217,7 +382,7 @@ case "$1" in
help
;;
*)
echo "错误: 未知命令 '$1'"
echo -e "${RED}错误: 未知命令 '$1'${NC}"
help
exit 1
;;
+488
View File
@@ -0,0 +1,488 @@
/**
* Deutsches Sprachpaket (Deutsch)
* German Language Pack (German)
*/
window.__i18n_de_DE__ = {
// ============ Seitentitel ============
'page.title': 'MyAPI Systemüberwachung',
'page.live_logs': 'Echtzeit-Logs - Systemüberwachung',
'page.history_logs': 'Protokollverlauf - Systemüberwachung',
// ============ Navigation ============
'nav.overview': '📊 Übersicht',
'nav.database': '🗃️ Datenbank',
'nav.events': '☎️ Ereignisse',
'nav.scheduler': '⏰ Planer',
'nav.http_requests': '📥 HTTP-Anfragen',
'nav.outbound_requests': '📤 Ausgehend',
'nav.logs': '📋 Protokolle',
// ============ Tabs ============
'tab.overview': 'Übersicht',
'tab.database': 'Datenbank',
'tab.events': 'Ereignisse',
'tab.scheduler': 'Planer',
'tab.http': 'HTTP-Anfragen',
'tab.outbound': 'Ausgehend',
'tab.logs': 'Systemprotokolle',
'tab.timeline': 'Zeitachse',
'tab.chart': 'Diagramme',
// ============ Karten ============
'card.resource': 'Ressourcennutzung',
'card.db_status': 'Datenbankstatus',
'card.db_connections': 'Datenbankverbindungen',
'card.event_helpers': 'Ereignis-Helfer',
'card.scheduler': 'Aufgabenplaner',
'card.api_requests': 'HTTP-Anfragen',
'card.outbound_requests': 'Ausgehende Anfragen',
'card.redis': 'Redis-Status',
'card.event_listener': 'Ereignis-Listener',
'card.recent_alerts': 'Aktuelle Warnungen',
'card.mysql': 'MySQL',
'card.http_requests_log': 'HTTP-Anfragedatensätze',
'card.scheduler_detail': 'Planer-Details',
'card.outbound_requests_log': 'Ausgehende Anfragedatensätze',
'card.callback_tracker': 'Callback-Tracker',
'card.event_deduplicator': 'Ereignis-Deduplikator',
// ============ Kennzahlen ============
'metric.cpu': 'CPU-Auslastung',
'metric.memory': 'Speichernutzung',
'metric.threads': 'Threads',
'metric.uptime': 'Laufzeit',
'metric.total_connections': 'Verbindungen',
'metric.healthy': 'Gesund',
'metric.unhealthy': 'Fehlerhaft',
'metric.degraded': 'Eingeschränkt',
'metric.total': 'Gesamt',
'metric.success': 'Erfolg',
'metric.failed': 'Fehler',
'metric.pending': 'Ausstehend',
'metric.active': 'Aktiv',
'metric.idle': 'Inaktiv',
'metric.unknown': 'Unbekannt',
'metric.warning': 'Warnung',
'metric.events_received': 'Empfangene Ereignisse',
'metric.events_processed': 'Verarbeitete Ereignisse',
'metric.events_failed': 'Fehlgeschlagene Ereignisse',
'metric.jobs_running': 'Laufende Aufgaben',
'metric.jobs_pending': 'Ausstehende Aufgaben',
'metric.requests_total': 'Anfragen Gesamt',
'metric.requests_slow': 'Langsame Anfragen',
'metric.requests_error': 'Fehlerhafte Anfragen',
'metric.error_rate': 'Fehlerquote',
'metric.avg_time': 'Durchschn. Antwort',
'metric.connection_status': 'Verbindungsstatus',
'metric.host': 'Host',
'metric.port': 'Port',
'metric.database': 'Datenbank',
'metric.used_connections': 'Verwendete Verbindungen',
'metric.max_connections': 'Max. Verbindungen',
'metric.connection_usage': 'Verbindungsnutzung',
'metric.buffer_size': 'Puffergröße',
'metric.buffer_threshold': 'Pufferschwellwert',
'metric.buffer_usage': 'Puffernutzung',
'metric.events_interrupted': 'Unterbrochen',
'metric.overall_success_rate': 'Gesamterfolgsquote',
'metric.active_event_types': 'Aktive Ereignistypen',
'metric.backpressure_status': 'Rückstau-Status',
'metric.backpressure_pending': 'Ausstehende Ereignisse',
'metric.backpressure_usage': 'Rückstau-Nutzung',
'metric.event_loop_status': 'Ereignisschleifen-Status',
'metric.pending_callbacks': 'Ausstehende Callbacks',
'metric.max_retries': 'Max. Wiederholungen',
'metric.pending_retries': 'Ausstehende Wiederholungen',
'metric.total_entries': 'Gesamteinträge',
'metric.active_items': 'Aktive Elemente',
'metric.ttl_seconds': 'TTL (Sekunden)',
'metric.max_entries': 'Max. Einträge',
'metric.dl_total': 'DL-Gesamt',
'metric.recent_dl': 'Aktuelle DL',
'metric.process_success_rate': 'Verarbeitungserfolgsquote',
'metric.scheduler_status': 'Planer-Status',
'metric.job_count': 'Aufgabenanzahl',
'metric.pending_events': 'Ausstehende Ereignisse',
'metric.total_events': 'Gesamtereignisse',
'metric.file_size': 'Dateigröße',
'metric.running_status': 'Betriebsstatus',
// ============ Status ============
'status.healthy': '● System gesund',
'status.unhealthy': '● System fehlerhaft',
'status.degraded': '● System eingeschränkt',
'status.running': 'Läuft',
'status.checking': 'Prüfe',
'status.stopped': 'Gestoppt',
'status.loading': 'Laden...',
'status.querying': 'Abfrage...',
'status.exporting': 'Exportiere...',
'status.no_data': 'Keine Daten',
'status.connected': 'Verbunden',
'status.connecting': 'Verbinde...',
'status.disconnected': 'Getrennt',
'status.reconnecting': 'Verbinde neu...',
'status.paused': 'Pausiert',
'status.enabled': 'Aktiviert',
'status.disabled': 'Deaktiviert',
'status.no_db_connections': 'Keine Datenbankverbindungen',
'status.no_scheduler': 'Keine geplanten Aufgaben',
'status.no_http_requests': 'Keine HTTP-Anfragen',
'status.no_alerts': 'Keine Warnungen',
'status.no_logs': 'Keine Protokolle',
'status.no_events': 'Keine Ereignisstatistiken',
'status.no_api_requests': 'Keine API-Anfragedatensätze',
'status.no_outbound_requests': 'Keine ausgehenden Anfragedatensätze',
'status.no_dead_letters': 'Keine DL',
'status.partial_warnings': '● Teilweise Warnungen',
'status.monitoring': 'Überwachung',
// ============ Schaltflächen ============
'btn.query': 'Abfrage',
'btn.reset': 'Zurücksetzen',
'btn.refresh': 'Aktualisieren',
'btn.export': 'Exportieren',
'btn.export_csv': 'CSV Export',
'btn.export_json': 'JSON Export',
'btn.detail': 'Details',
'btn.pause': 'Pause',
'btn.resume': 'Fortsetzen',
'btn.clear': 'Löschen',
'btn.save': 'Speichern',
'btn.delete': 'Löschen',
'btn.close': 'Schließen',
'btn.confirm': 'Bestätigen',
'btn.cancel': 'Abbrechen',
'btn.test': 'Testen',
'btn.apply': 'Anwenden',
'btn.filter': 'Filtern',
'btn.clear_all': 'Alle löschen',
'btn.realtime_on': 'Echtzeit: AN',
'btn.realtime_off': 'Echtzeit: AUS',
'btn.precise_locate': '± 60s',
'btn.precise_cancel': '± 60s - Klick zum Abbrechen',
'btn.reset_stats': 'Statistik zurücksetzen',
'btn.flush_all': 'Alle leeren',
'btn.new_window': 'Neues Fenster',
'btn.auto_scroll': 'Auto-Scroll',
'btn.live_logs': 'Echtzeit-Protokolle',
'btn.history_query': 'Verlaufsanfrage',
'btn.mark_all_read': 'Alle als gelesen markieren',
'btn.clear_read_status': 'Lesestatus löschen',
// ============ Tabellenspalten ============
'col.index': '#',
'col.time': 'Zeit',
'col.level': 'Stufe',
'col.module': 'Modul',
'col.message': 'Nachricht',
'col.method': 'Methode',
'col.path': 'Pfad',
'col.url': 'URL',
'col.status': 'Status',
'col.duration': 'Dauer',
'col.client_ip': 'Client-IP',
'col.source': 'Quelle',
'col.function': 'Funktion',
'col.line': 'Zeile',
'col.operation': 'Aktion',
'col.count': 'Anzahl',
'col.avg_time': 'Durchschn. Zeit',
'col.max_time': 'Max. Zeit',
'col.db_name': 'DB-Name',
'col.last_check': 'Letzte Prüfung',
'col.current_connections': 'Aktuell',
'col.max_connections': 'Max',
'col.min_connections': 'Min',
'col.idle_connections': 'Inaktiv',
'col.used_connections': 'Verwendet',
'col.usage': 'Nutzung',
'col.processed_records': 'Verarbeitet',
'col.timestamp': 'Zeitstempel',
'col.query_params': 'Abfrageparameter',
'col.error_message': 'Fehlermeldung',
'col.url': 'URL',
'col.is_read': 'Gelesen',
'col.description': 'Beschreibung',
'col.total_received': 'Gesamt empfangen',
'col.pending': 'Ausstehend',
'col.processed': 'Verarbeitet',
'col.interrupted': 'Unterbrochen',
'col.completion_rate': 'Abschlussrate',
'col.avg_latency': 'Durchschn. Latenz',
'col.last_action': 'Letzte Aktion',
'col.id': 'ID',
'col.event_type': 'Ereignistyp',
'col.table': 'Tabelle',
// ============ Schnellzeit ============
'time.start_time': 'Startzeit',
'time.end_time': 'Endzeit',
'time.last_10m': 'Letzte 10 Min',
'time.last_30m': 'Letzte 30 Min',
'time.last_1h': 'Letzte Stunde',
'time.last_6h': 'Letzte 6 Stunden',
'time.last_24h': 'Letzte 24 Stunden',
'time.range': 'Zeitbereich',
// ============ Filter ============
'filter.level': 'Alle Stufen',
'filter.type': 'Alle Typen',
'filter.module': 'Modul',
'filter.keyword': 'Schlüsselwort',
'filter.method': 'Methode',
'filter.client_ip': 'Client-IP',
'filter.status_range': 'Statusbereich',
'filter.duration_range': 'Dauerbereich',
'filter.advanced': 'Erweiterte Filter',
'filter.collapse': 'Einklappen',
'filter.expand': 'Ausklappen',
'filter.clear': 'Löschen',
'filter.active': 'Aktive Filter',
'filter.module_placeholder': 'Modulname eingeben',
'filter.keyword_placeholder': 'Schlüsselwort eingeben',
'filter.ip_placeholder': 'Client-IP eingeben',
'filter.status_placeholder': 'z.B.: 400-499',
'filter.duration_placeholder': 'z.B.: >1000',
'filter.all_logs': 'Alle Protokolle',
// ============ Diagramme ============
'chart.request_trend': '📊 Anfragentrend',
'chart.level_distribution': '📊 Protokollstufen-Verteilung',
'chart.status_distribution': '📈 Statuscode-Verteilung',
'chart.slow_requests': '⏱️ Langsame Anfragen TOP10',
'chart.total_requests': 'Anfragen Gesamt',
'chart.error_count': 'Fehler',
'chart.slow_count': 'Langsame Anfragen',
'chart.log_count': 'Protokolle Gesamt',
'chart.no_slow': '✅ Keine langsamen Anfragen',
'chart.query_required': 'Bitte zuerst Abfrage ausführen',
'chart.load_failed': '❌ Diagramm-Daten laden fehlgeschlagen, bitte erneut versuchen',
'chart.redis_connections': 'Redis Verbindungspool-Nutzung',
'chart.redis_buffer': 'Redis Puffergrößen-Änderungen',
'chart.cpu': 'CPU',
'chart.memory': 'Speicher',
'chart.upload': 'Upload',
'chart.download': 'Download',
'chart.cpu_memory_axis': 'CPU / Speicher (%)',
'chart.network_axis': 'Netzwerk-Upload / Download (KB/s)',
'chart.used_connections': 'Verwendete Verbindungen',
'chart.buffer_size_mb': 'Puffergröße (MB)',
// ============ Fehler ============
'error.time_range_invalid': 'Startzeit kann nicht größer als Endzeit sein',
'error.time_range_required': 'Bitte Startzeit und Endzeit auswählen',
'error.query_failed': 'Abfrage fehlgeschlagen, bitte erneut versuchen',
'error.export_failed': 'Export fehlgeschlagen, bitte erneut versuchen',
'error.connection_failed': 'Verbindung fehlgeschlagen, bitte Seite aktualisieren',
'error.max_templates': 'Maximal 10 Vorlagen erlaubt, bitte zuerst einige löschen',
'error.auto_pause': 'Echtzeit-Verfolgung automatisch pausiert (über 10 Minuten)',
'error.load_failed': 'Laden fehlgeschlagen, bitte erneut versuchen',
'error.invalid_params': 'Ungültige Parameter',
'error.clear_dl_failed': 'DeadLetter-Warteschlange löschen fehlgeschlagen',
// ============ Erfolg ============
'success.query_complete': 'Abfrage abgeschlossen',
'success.export_complete': 'Export abgeschlossen',
'success.template_saved': 'Vorlage "{name}" gespeichert',
'success.logs_cleared': 'Protokolle gelöscht',
'success.operation_success': 'Operation erfolgreich',
'success.dl_cleared': 'DeadLetter-Warteschlange gelöscht',
// ============ Zeitachse ============
'timeline.title': 'Zeitachse',
'timeline.no_data': 'Keine Daten, bitte zuerst Abfrage ausführen',
'timeline.anomaly_detected': '⚠️ {count} Anomalien erkannt',
'timeline.error_burst': '{count} aufeinanderfolgende FEHLER-Protokolle',
'timeline.slow_anomaly': 'Langsame Anfrage Anomalie ({duration}ms > durchschn. {avg}ms×3)',
'timeline.duplicate_error': 'Duplizierter Fehler "{msg}" {count} mal aufgetreten',
'timeline.http_request': 'HTTP-Anfrage',
'timeline.outbound_request': 'Ausgehende Anfrage',
'timeline.system_log': 'Systemprotokoll',
// ============ Paginierung ============
'pagination.page': 'Seite',
'pagination.of': 'von',
'pagination.items': 'Einträge',
'pagination.showing': 'Anzeigen',
'pagination.per_page': 'Pro Seite',
'pagination.first': 'Erste',
'pagination.last': 'Letzte',
'pagination.prev': 'Vorherige',
'pagination.next': 'Nächste',
'pagination.go_to': 'Gehe zu',
// ============ Export ============
'export.current_page': 'Aktuelle Seite exportieren',
'export.all_data': 'Alle Daten exportieren',
'export.format_csv': 'CSV-Format',
'export.format_json': 'JSON-Format',
'export.select_format': 'Format auswählen',
// ============ Vorlagen ============
'template.saved_queries': 'Gespeicherte Abfragen...',
'template.save': 'Abfrage speichern',
'template.manage': 'Vorlagen verwalten',
'template.name': 'Vorlagenname',
'template.description': 'Beschreibung',
'template.delete_confirm': 'Diese Vorlage löschen?',
'template.empty': 'Keine gespeicherten Vorlagen',
// ============ Statistiken ============
'stats.time_range': 'Abfragezeitbereich',
'stats.http_requests': 'HTTP-Anfragen',
'stats.outbound_requests': 'Ausgehende Anfragen',
'stats.system_logs': 'Systemprotokolle',
'stats.level_distribution': 'Stufenverteilung',
'stats.all_time': 'Gesamte Zeit',
// ============ Sonstiges ============
'other.last_update': 'Letzte Aktualisierung',
'other.auto_reconnect': 'Neuverbindung in 5s',
'other.waiting_logs': 'Warte auf Protokolldaten...',
'other.no_matching_logs': 'Keine passenden Protokolle',
'other.precise_mode': 'Präziser Modus',
'other.saved_queries': 'Gespeicherte Abfragen...',
'other.all_time': 'Gesamte Zeit',
'other.linked_query': 'Verknüpfte Abfrage',
'other.seconds': 'Sekunden',
'other.minutes': 'Minuten',
'other.hours': 'Stunden',
'other.days': 'Tage',
'other.ms': 'ms',
'other.times': 'mal',
'other.view_detail': 'Details anzeigen',
'other.copy': 'Kopieren',
'other.expand': 'Ausklappen',
'other.collapse': 'Einklappen',
'other.show_internal': 'Intern anzeigen',
'other.show_read': 'Gelesen anzeigen',
// ============ Datum ============
'date.today': 'Heute',
'date.yesterday': 'Gestern',
// ============ Sprache ============
'lang.select': 'Sprache auswählen',
'lang.zh': '中文',
'lang.en': 'English',
'lang.de': 'Deutsch',
// ============ HTTP-Methoden ============
'method.get': 'GET',
'method.post': 'POST',
'method.put': 'PUT',
'method.delete': 'DELETE',
'method.patch': 'PATCH',
// ============ Protokollstufen ============
'level.debug': 'DEBUG',
'level.info': 'INFO',
'level.warning': 'WARNING',
'level.error': 'ERROR',
'level.critical': 'CRITICAL',
// ============ Planer ============
'scheduler.rule': 'Planungsregel',
'scheduler.last_run': 'Letzte Ausführung',
'scheduler.max_time': 'Max. Zeit',
'scheduler.never_run': 'Nie ausgeführt',
'scheduler.default': 'Standard',
'scheduler.running': 'Läuft',
'scheduler.not_scheduled': 'Nicht geplant',
// ============ Relative Zeit ============
'time.just_now': 'Gerade eben',
'time.minutes_ago': 'vor {n} Min.',
'time.hours_ago': 'vor {n} Stunden',
'time.days_ago': 'vor {n} Tagen',
'time.day_before_yesterday': 'Vorgestern',
// ============ Warnungstypen ============
'alert.warning': 'Warnung',
'alert.error': 'Fehler',
'alert.critical': 'Kritisch',
'alert.normal': 'Normal',
// ============ Verbindungsstatus ============
'connection.failed': 'Verbindung fehlgeschlagen',
'connection.reconnecting': 'Verbinde neu',
'connection.connected': 'Verbunden',
'connection.disconnected': 'Getrennt',
// ============ Überwachungsstatus ============
'monitor.paused': '● Überwachung pausiert (inaktiv)',
'monitor.panel': 'Überwachungstafel',
'monitor.last_update': 'Letzte Aktualisierung',
'monitor.reset_stats_confirm': 'Alle Ereignisstatistiken zurücksetzen?',
// ============ Hervorhebung ============
'highlight.on': 'Hervorgehoben',
'highlight.off': 'Hervorheben',
// ============ Kopieren ============
'copy.success': 'Kopiert',
'copy.failed': 'Kopieren fehlgeschlagen, bitte manuell kopieren',
'copy.error': 'Kopieren fehlgeschlagen',
// ============ Eingabeaufforderungen ============
'prompt.select_date': 'Bitte Datum auswählen',
'prompt.fetch_failed': 'Anfragedatensätze abrufen fehlgeschlagen, bitte erneut versuchen',
'prompt.fetch_outbound_failed': 'Ausgehende Anfragedatensätze abrufen fehlgeschlagen, bitte erneut versuchen',
'prompt.reset_failed': 'Zurücksetzen fehlgeschlagen, bitte erneut versuchen',
// ============ Seitentitel ============
'page.http_requests_log': 'HTTP-Anfragedatensätze',
'page.outbound_requests_log': 'Ausgehende Anfragedatensätze',
// ============ Zeit ============
'time.tomorrow': 'Morgen',
'time.day_after_tomorrow': 'Übermorgen',
'time.over_24h': 'Über 24 Stunden',
'time.expired': 'Abgelaufen',
'time.days_hours_later': 'In {days}T {hours}Std.',
'time.unknown': 'Unbekannt',
'time.month_day': '{day}.{month}.',
// ============ Aufgabentypen ============
'task.system': 'System',
'task.project': 'Projekt',
'task.avg_time': 'Durchschn. Ausführungszeit',
// ============ Detail-Modal ============
'modal.outbound_detail': 'Ausgehende Anfrage-Details',
'modal.timestamp': 'Zeitstempel:',
'modal.method': 'Methode:',
'modal.status_code': 'Statuscode:',
'modal.response_time': 'Antwortzeit:',
'modal.module': 'Modul:',
'modal.error_msg': 'Fehlermeldung:',
// ============ Anfrage/Antwort-Bereiche ============
'section.request_headers': 'Anfrage-Header',
'section.request_body': 'Anfrage-Body',
'section.response_headers': 'Antwort-Header',
'section.response_body': 'Antwort-Body',
'section.no_response': 'Keine Antwort-Body-Daten',
'section.response_failed': 'Antwort-Body-Verarbeitung fehlgeschlagen: ',
'section.truncated': '[Inhalt gekürzt, exportieren für vollständigen Inhalt]',
'section.large_data_warning': '⚠️ Große Datenmenge ({count} Zeichen), kann Leistung beeinträchtigen',
'section.showing_chars': 'Zeige erste {count} Zeichen',
'section.basic_info': 'Grundinformationen',
'section.no_data': 'Keine Daten',
// ============ Ratenbegrenzung ============
'status.rate_limited': 'Ratenbegrenzt',
// ============ Protokollstufen-Auswahl ============
'log_level.all_with_debug': 'Alle Stufen (mit DEBUG)',
'log_level.error': 'Fehlerprotokolle',
'log_level.warning': 'Warnungsprotokolle',
'log_level.info': 'Infoprotokolle',
'log_level.debug': 'Debugprotokolle',
// ============ DeadLetter ============
'dl.reprocess': 'Neu verarbeiten',
'dl.reprocess_success': 'DeadLetter erfolgreich neu verarbeitet',
'dl.reprocess_failed': 'DeadLetter Neuverarbeitung fehlgeschlagen'
};
+487
View File
@@ -0,0 +1,487 @@
/**
* English Language Pack (US English)
*/
window.__i18n_en_US__ = {
// ============ Page Titles ============
'page.title': 'MyAPI System Monitor',
'page.live_logs': 'Live Logs - System Monitor',
'page.history_logs': 'Log History Query - System Monitor',
// ============ Navigation ============
'nav.overview': '📊 Overview',
'nav.database': '🗃️ Database',
'nav.events': '☎️ Events',
'nav.scheduler': '⏰ Scheduler',
'nav.http_requests': '📥 HTTP Requests',
'nav.outbound_requests': '📤 Outbound',
'nav.logs': '📋 Logs',
// ============ Tabs ============
'tab.overview': 'Overview',
'tab.database': 'Database',
'tab.events': 'Events',
'tab.scheduler': 'Scheduler',
'tab.http': 'HTTP Requests',
'tab.outbound': 'Outbound',
'tab.logs': 'System Logs',
'tab.timeline': 'Timeline',
'tab.chart': 'Charts',
// ============ Cards ============
'card.resource': 'Resource Usage',
'card.db_status': 'Database Status',
'card.db_connections': 'Database Connections',
'card.event_helpers': 'Event Helpers',
'card.scheduler': 'Job Scheduler',
'card.api_requests': 'HTTP Requests',
'card.outbound_requests': 'Outbound Requests',
'card.redis': 'Redis Status',
'card.event_listener': 'Event Listener',
'card.recent_alerts': 'Recent Alerts',
'card.mysql': 'MySQL',
'card.http_requests_log': 'HTTP Request Records',
'card.scheduler_detail': 'Scheduler Details',
'card.outbound_requests_log': 'Outbound Request Records',
'card.callback_tracker': 'Callback Tracker',
'card.event_deduplicator': 'Event Deduplicator',
// ============ Metrics ============
'metric.cpu': 'CPU Usage',
'metric.memory': 'Memory Usage',
'metric.threads': 'Threads',
'metric.uptime': 'Uptime',
'metric.total_connections': 'Total Connections',
'metric.healthy': 'Healthy',
'metric.unhealthy': 'Unhealthy',
'metric.degraded': 'Degraded',
'metric.total': 'Total',
'metric.success': 'Success',
'metric.failed': 'Failed',
'metric.pending': 'Pending',
'metric.active': 'Active',
'metric.idle': 'Idle',
'metric.unknown': 'Unknown',
'metric.warning': 'Warning',
'metric.events_received': 'Events Received',
'metric.events_processed': 'Events Processed',
'metric.events_failed': 'Events Failed',
'metric.jobs_running': 'Running Jobs',
'metric.jobs_pending': 'Pending Jobs',
'metric.requests_total': 'Total Requests',
'metric.requests_slow': 'Slow Requests',
'metric.requests_error': 'Error Requests',
'metric.error_rate': 'Error Rate',
'metric.avg_time': 'Avg Response',
'metric.connection_status': 'Connection Status',
'metric.host': 'Host',
'metric.port': 'Port',
'metric.database': 'Database',
'metric.used_connections': 'Used Connections',
'metric.max_connections': 'Max Connections',
'metric.connection_usage': 'Connection Usage',
'metric.buffer_size': 'Buffer Size',
'metric.buffer_threshold': 'Buffer Threshold',
'metric.buffer_usage': 'Buffer Usage',
'metric.events_interrupted': 'Interrupted',
'metric.overall_success_rate': 'Overall Success Rate',
'metric.active_event_types': 'Active Event Types',
'metric.backpressure_status': 'Backpressure Status',
'metric.backpressure_pending': 'Pending Events',
'metric.backpressure_usage': 'Backpressure Usage',
'metric.event_loop_status': 'Event Loop Status',
'metric.pending_callbacks': 'Pending Callbacks',
'metric.max_retries': 'Max Retries',
'metric.pending_retries': 'Pending Retries',
'metric.total_entries': 'Total Entries',
'metric.active_items': 'Active Items',
'metric.ttl_seconds': 'TTL (seconds)',
'metric.max_entries': 'Max Entries',
'metric.dl_total': 'DL Total',
'metric.recent_dl': 'Recent DL',
'metric.process_success_rate': 'Process Success Rate',
'metric.scheduler_status': 'Scheduler Status',
'metric.job_count': 'Job Count',
'metric.pending_events': 'Pending Events',
'metric.total_events': 'Total Events',
'metric.file_size': 'File Size',
'metric.running_status': 'Running Status',
// ============ Status ============
'status.healthy': '● System Healthy',
'status.unhealthy': '● System Unhealthy',
'status.degraded': '● System Degraded',
'status.running': 'Running',
'status.checking': 'Checking',
'status.stopped': 'Stopped',
'status.loading': 'Loading...',
'status.querying': 'Querying...',
'status.exporting': 'Exporting...',
'status.no_data': 'No data',
'status.connected': 'Connected',
'status.connecting': 'Connecting...',
'status.disconnected': 'Disconnected',
'status.reconnecting': 'Reconnecting...',
'status.paused': 'Paused',
'status.enabled': 'Enabled',
'status.disabled': 'Disabled',
'status.no_db_connections': 'No database connections',
'status.no_scheduler': 'No scheduled tasks',
'status.no_http_requests': 'No HTTP requests',
'status.no_alerts': 'No alerts',
'status.no_logs': 'No logs',
'status.no_events': 'No event statistics',
'status.no_api_requests': 'No API request records',
'status.no_outbound_requests': 'No outbound request records',
'status.no_dead_letters': 'No DL',
'status.partial_warnings': '● Partial Warnings',
'status.monitoring': 'Monitoring',
// ============ Buttons ============
'btn.query': 'Query',
'btn.reset': 'Reset',
'btn.refresh': 'Refresh',
'btn.export': 'Export',
'btn.export_csv': 'Export CSV',
'btn.export_json': 'Export JSON',
'btn.detail': 'Detail',
'btn.pause': 'Pause',
'btn.resume': 'Resume',
'btn.clear': 'Clear',
'btn.save': 'Save',
'btn.delete': 'Delete',
'btn.close': 'Close',
'btn.confirm': 'Confirm',
'btn.cancel': 'Cancel',
'btn.test': 'Test',
'btn.apply': 'Apply',
'btn.filter': 'Filter',
'btn.clear_all': 'Clear All',
'btn.realtime_on': 'Realtime: ON',
'btn.realtime_off': 'Realtime: OFF',
'btn.precise_locate': '± 60s',
'btn.precise_cancel': '± 60s - Click to cancel',
'btn.reset_stats': 'Reset Stats',
'btn.flush_all': 'Flush All',
'btn.new_window': 'New Window',
'btn.auto_scroll': 'Auto Scroll',
'btn.live_logs': 'Live Logs',
'btn.history_query': 'History Query',
'btn.mark_all_read': 'Mark All Read',
'btn.clear_read_status': 'Clear Read Status',
// ============ Table Columns ============
'col.index': '#',
'col.time': 'Time',
'col.level': 'Level',
'col.module': 'Module',
'col.message': 'Message',
'col.method': 'Method',
'col.path': 'Path',
'col.url': 'URL',
'col.status': 'Status',
'col.duration': 'Duration',
'col.client_ip': 'Client IP',
'col.source': 'Source',
'col.function': 'Function',
'col.line': 'Line',
'col.operation': 'Action',
'col.count': 'Count',
'col.avg_time': 'Avg Time',
'col.max_time': 'Max Time',
'col.db_name': 'DB Name',
'col.last_check': 'Last Check',
'col.current_connections': 'Current',
'col.max_connections': 'Max',
'col.min_connections': 'Min',
'col.idle_connections': 'Idle',
'col.used_connections': 'Used',
'col.usage': 'Usage',
'col.processed_records': 'Processed',
'col.timestamp': 'Timestamp',
'col.query_params': 'Query Params',
'col.error_message': 'Error Message',
'col.url': 'URL',
'col.is_read': 'Read',
'col.description': 'Description',
'col.total_received': 'Total Received',
'col.pending': 'Pending',
'col.processed': 'Processed',
'col.interrupted': 'Interrupted',
'col.completion_rate': 'Completion Rate',
'col.avg_latency': 'Avg Latency',
'col.last_action': 'Last Action',
'col.id': 'ID',
'col.event_type': 'Event Type',
'col.table': 'Table',
// ============ Quick Time ============
'time.start_time': 'Start Time',
'time.end_time': 'End Time',
'time.last_10m': 'Last 10 min',
'time.last_30m': 'Last 30 min',
'time.last_1h': 'Last 1 hour',
'time.last_6h': 'Last 6 hours',
'time.last_24h': 'Last 24 hours',
'time.range': 'Time Range',
// ============ Filters ============
'filter.level': 'All Levels',
'filter.type': 'All Types',
'filter.module': 'Module',
'filter.keyword': 'Keyword',
'filter.method': 'Method',
'filter.client_ip': 'Client IP',
'filter.status_range': 'Status Range',
'filter.duration_range': 'Duration Range',
'filter.advanced': 'Advanced Filters',
'filter.collapse': 'Collapse',
'filter.expand': 'Expand',
'filter.clear': 'Clear',
'filter.active': 'Active Filters',
'filter.module_placeholder': 'Enter module name',
'filter.keyword_placeholder': 'Enter keyword',
'filter.ip_placeholder': 'Enter client IP',
'filter.status_placeholder': 'e.g.: 400-499',
'filter.duration_placeholder': 'e.g.: >1000',
'filter.all_logs': 'All Logs',
// ============ Charts ============
'chart.request_trend': '📊 Request Trend',
'chart.level_distribution': '📊 Log Level Distribution',
'chart.status_distribution': '📈 Status Code Distribution',
'chart.slow_requests': '⏱️ Slow Requests TOP10',
'chart.total_requests': 'Total Requests',
'chart.error_count': 'Errors',
'chart.slow_count': 'Slow Requests',
'chart.log_count': 'Total Logs',
'chart.no_slow': '✅ No slow requests',
'chart.query_required': 'Please execute query first',
'chart.load_failed': '❌ Chart data load failed, please retry',
'chart.redis_connections': 'Redis Connection Pool Usage',
'chart.redis_buffer': 'Redis Buffer Size Changes',
'chart.cpu': 'CPU',
'chart.memory': 'Memory',
'chart.upload': 'Upload',
'chart.download': 'Download',
'chart.cpu_memory_axis': 'CPU / Memory (%)',
'chart.network_axis': 'Network Upload / Download (KB/s)',
'chart.used_connections': 'Used Connections',
'chart.buffer_size_mb': 'Buffer Size (MB)',
// ============ Errors ============
'error.time_range_invalid': 'Start time cannot be greater than end time',
'error.time_range_required': 'Please select start time and end time',
'error.query_failed': 'Query failed, please try again',
'error.export_failed': 'Export failed, please retry',
'error.connection_failed': 'Connection failed, please refresh page',
'error.max_templates': 'Maximum 10 templates allowed, please delete some first',
'error.auto_pause': 'Realtime tracking auto-paused (over 10 minutes)',
'error.load_failed': 'Load failed, please retry',
'error.invalid_params': 'Invalid parameters',
'error.clear_dl_failed': 'Failed to clear DeadLetter queue',
// ============ Success ============
'success.query_complete': 'Query Complete',
'success.export_complete': 'Export Complete',
'success.template_saved': 'Template "{name}" saved',
'success.logs_cleared': 'Logs cleared',
'success.operation_success': 'Operation successful',
'success.dl_cleared': 'DeadLetter queue cleared',
// ============ Timeline ============
'timeline.title': 'Timeline',
'timeline.no_data': 'No data, please execute query first',
'timeline.anomaly_detected': '⚠️ {count} anomalies detected',
'timeline.error_burst': '{count} consecutive ERROR logs',
'timeline.slow_anomaly': 'Slow request anomaly ({duration}ms > avg {avg}ms×3)',
'timeline.duplicate_error': 'Duplicate error "{msg}" appeared {count} times',
'timeline.http_request': 'HTTP Request',
'timeline.outbound_request': 'Outbound Request',
'timeline.system_log': 'System Log',
// ============ Pagination ============
'pagination.page': 'Page',
'pagination.of': 'of',
'pagination.items': 'items',
'pagination.showing': 'Showing',
'pagination.per_page': 'Per Page',
'pagination.first': 'First',
'pagination.last': 'Last',
'pagination.prev': 'Previous',
'pagination.next': 'Next',
'pagination.go_to': 'Go to',
// ============ Export ============
'export.current_page': 'Export Current Page',
'export.all_data': 'Export All Data',
'export.format_csv': 'CSV Format',
'export.format_json': 'JSON Format',
'export.select_format': 'Select Format',
// ============ Templates ============
'template.saved_queries': 'Saved Queries...',
'template.save': 'Save Query',
'template.manage': 'Manage Templates',
'template.name': 'Template Name',
'template.description': 'Description',
'template.delete_confirm': 'Delete this template?',
'template.empty': 'No saved templates',
// ============ Statistics ============
'stats.time_range': 'Query Time Range',
'stats.http_requests': 'HTTP Requests',
'stats.outbound_requests': 'Outbound Requests',
'stats.system_logs': 'System Logs',
'stats.level_distribution': 'Level Distribution',
'stats.all_time': 'All Time',
// ============ Others ============
'other.last_update': 'Last Update',
'other.auto_reconnect': 'reconnect in 5s',
'other.waiting_logs': 'Waiting for log data...',
'other.no_matching_logs': 'No matching logs',
'other.precise_mode': 'Precise Mode',
'other.saved_queries': 'Saved Queries...',
'other.all_time': 'All Time',
'other.linked_query': 'Linked Query',
'other.seconds': 'seconds',
'other.minutes': 'minutes',
'other.hours': 'hours',
'other.days': 'days',
'other.ms': 'ms',
'other.times': 'times',
'other.view_detail': 'View Detail',
'other.copy': 'Copy',
'other.expand': 'Expand',
'other.collapse': 'Collapse',
'other.show_internal': 'Show Internal',
'other.show_read': 'Show Read',
// ============ Date ============
'date.today': 'Today',
'date.yesterday': 'Yesterday',
// ============ Language ============
'lang.select': 'Select Language',
'lang.zh': '中文',
'lang.en': 'English',
'lang.de': 'Deutsch',
// ============ HTTP Methods ============
'method.get': 'GET',
'method.post': 'POST',
'method.put': 'PUT',
'method.delete': 'DELETE',
'method.patch': 'PATCH',
// ============ Log Levels ============
'level.debug': 'DEBUG',
'level.info': 'INFO',
'level.warning': 'WARNING',
'level.error': 'ERROR',
'level.critical': 'CRITICAL',
// ============ Scheduler ============
'scheduler.rule': 'Schedule Rule',
'scheduler.last_run': 'Last Run',
'scheduler.max_time': 'Max Time',
'scheduler.never_run': 'Never Run',
'scheduler.default': 'Default',
'scheduler.running': 'Running',
'scheduler.not_scheduled': 'Not Scheduled',
// ============ Relative Time ============
'time.just_now': 'Just now',
'time.minutes_ago': '{n} min ago',
'time.hours_ago': '{n} hours ago',
'time.days_ago': '{n} days ago',
'time.day_before_yesterday': 'Day before yesterday',
// ============ Alert Types ============
'alert.warning': 'Warning',
'alert.error': 'Error',
'alert.critical': 'Critical',
'alert.normal': 'Normal',
// ============ Connection Status ============
'connection.failed': 'Connection Failed',
'connection.reconnecting': 'Reconnecting',
'connection.connected': 'Connected',
'connection.disconnected': 'Disconnected',
// ============ Monitor Status ============
'monitor.paused': '● Monitor paused (inactive)',
'monitor.panel': 'Monitor Panel',
'monitor.last_update': 'Last Update',
'monitor.reset_stats_confirm': 'Reset all event statistics?',
// ============ Highlight ============
'highlight.on': 'Highlighted',
'highlight.off': 'Highlight',
// ============ Copy ============
'copy.success': 'Copied',
'copy.failed': 'Copy failed, please copy manually',
'copy.error': 'Copy failed',
// ============ Prompts ============
'prompt.select_date': 'Please select a date',
'prompt.fetch_failed': 'Failed to fetch request records, please try again',
'prompt.fetch_outbound_failed': 'Failed to fetch outbound request records, please try again',
'prompt.reset_failed': 'Reset failed, please try again',
// ============ Page Titles ============
'page.http_requests_log': 'HTTP Request Records',
'page.outbound_requests_log': 'Outbound Request Records',
// ============ Time ============
'time.tomorrow': 'Tomorrow',
'time.day_after_tomorrow': 'Day after tomorrow',
'time.over_24h': 'Over 24 hours',
'time.expired': 'Expired',
'time.days_hours_later': '{days}d {hours}h later',
'time.unknown': 'Unknown',
'time.month_day': '{month}/{day}',
// ============ Task Types ============
'task.system': 'System',
'task.project': 'Project',
'task.avg_time': 'Avg Execution Time',
// ============ Detail Modal ============
'modal.outbound_detail': 'Outbound Request Detail',
'modal.timestamp': 'Timestamp:',
'modal.method': 'Method:',
'modal.status_code': 'Status Code:',
'modal.response_time': 'Response Time:',
'modal.module': 'Module:',
'modal.error_msg': 'Error Message:',
// ============ Request/Response Sections ============
'section.request_headers': 'Request Headers',
'section.request_body': 'Request Body',
'section.response_headers': 'Response Headers',
'section.response_body': 'Response Body',
'section.no_response': 'No response body data',
'section.response_failed': 'Response body processing failed: ',
'section.truncated': '[Content truncated, export to view full content]',
'section.large_data_warning': '⚠️ Large data ({count} chars), may affect performance',
'section.showing_chars': 'Showing first {count} chars',
'section.basic_info': 'Basic Information',
'section.no_data': 'No data',
// ============ Rate Limiting ============
'status.rate_limited': 'Rate Limited',
// ============ Log Level Selection ============
'log_level.all_with_debug': 'All Levels (with DEBUG)',
'log_level.error': 'Error Logs',
'log_level.warning': 'Warning Logs',
'log_level.info': 'Info Logs',
'log_level.debug': 'Debug Logs',
// ============ DeadLetter ============
'dl.reprocess': 'Reprocess',
'dl.reprocess_success': 'DeadLetter reprocessed successfully',
'dl.reprocess_failed': 'DeadLetter reprocess failed'
};
+268
View File
@@ -0,0 +1,268 @@
/**
* 轻量级国际化框架
* 支持中文(zh-CN)、英语(en-US)、德语(de-DE)
*
* 特性:
* - 无第三方依赖
* - 自动检测浏览器语言
* - 支持localStorage持久化
* - 支持插值参数
* - 支持热切换(无需刷新)
*/
class I18n {
constructor() {
this.currentLang = this.detectLanguage();
this.messages = {};
this.fallbackLang = 'zh-CN';
}
/**
* 检测用户语言
* 优先级:localStorage > 浏览器语言 > 默认中文
*/
detectLanguage() {
const supportedLangs = ['zh-CN', 'en-US', 'de-DE'];
const saved = localStorage.getItem('monitor-lang');
if (saved && supportedLangs.includes(saved)) {
return saved;
}
const browserLang = navigator.language || navigator.userLanguage || 'zh-CN';
const langMap = {
'zh': 'zh-CN',
'zh-CN': 'zh-CN',
'zh-Hans': 'zh-CN',
'zh-Hans-CN': 'zh-CN',
'zh-TW': 'zh-CN',
'zh-HK': 'zh-CN',
'zh-Hant': 'zh-CN',
'en': 'en-US',
'en-US': 'en-US',
'en-GB': 'en-US',
'en-AU': 'en-US',
'en-CA': 'en-US',
'de': 'de-DE',
'de-DE': 'de-DE',
'de-AT': 'de-DE',
'de-CH': 'de-DE',
'de-LI': 'de-DE'
};
if (langMap[browserLang]) {
return langMap[browserLang];
}
const baseLang = browserLang.split('-')[0];
if (langMap[baseLang]) {
return langMap[baseLang];
}
return 'zh-CN';
}
/**
* 初始化i18n
*/
async init() {
try {
await this.loadLanguage(this.currentLang);
this.applyTranslations();
this.updateLangSelector();
this.updateHtmlLang();
console.log(`[i18n] Initialized with language: ${this.currentLang}`);
return true;
} catch (error) {
console.error('[i18n] Initialization failed:', error);
if (this.currentLang !== this.fallbackLang) {
console.log('[i18n] Falling back to:', this.fallbackLang);
this.currentLang = this.fallbackLang;
return await this.init();
}
return false;
}
}
/**
* 加载语言包
*/
async loadLanguage(lang) {
const varName = `__i18n_${lang.replace('-', '_')}__`;
if (window[varName]) {
this.messages = window[varName];
return;
}
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = `/static/lib/i18n/${lang}.js`;
script.onload = () => {
if (window[varName]) {
this.messages = window[varName];
resolve();
} else {
reject(new Error(`Language pack variable not found: ${varName}`));
}
};
script.onerror = () => reject(new Error(`Failed to load language pack: ${lang}`));
document.head.appendChild(script);
});
}
/**
* 应用翻译到DOM
*/
applyTranslations() {
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
const text = this.t(key);
if (text !== key) {
el.textContent = text;
}
});
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.getAttribute('data-i18n-placeholder');
const text = this.t(key);
if (text !== key) {
el.placeholder = text;
}
});
document.querySelectorAll('[data-i18n-title]').forEach(el => {
const key = el.getAttribute('data-i18n-title');
const text = this.t(key);
if (text !== key) {
el.title = text;
}
});
document.querySelectorAll('[data-i18n-value]').forEach(el => {
const key = el.getAttribute('data-i18n-value');
const text = this.t(key);
if (text !== key) {
el.value = text;
}
});
const pageTitle = document.querySelector('[data-i18n-page-title]');
if (pageTitle) {
const key = pageTitle.getAttribute('data-i18n-page-title');
const text = this.t(key);
if (text !== key) {
document.title = text;
}
}
}
/**
* 获取翻译文本
* @param {string} key - 翻译键
* @param {object} params - 插值参数(可选)
*/
t(key, params = {}) {
let text = this.messages[key] || key;
Object.keys(params).forEach(k => {
text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), params[k]);
});
return text;
}
/**
* 切换语言
*/
async switchLanguage(lang) {
if (lang === this.currentLang) return;
const supportedLangs = ['zh-CN', 'en-US', 'de-DE'];
if (!supportedLangs.includes(lang)) {
console.error('[i18n] Unsupported language:', lang);
return;
}
try {
localStorage.setItem('monitor-lang', lang);
this.currentLang = lang;
await this.loadLanguage(lang);
this.applyTranslations();
this.updateLangSelector();
this.updateHtmlLang();
window.dispatchEvent(new CustomEvent('langchange', {
detail: { lang: lang }
}));
console.log(`[i18n] Language switched to: ${lang}`);
} catch (error) {
console.error('[i18n] Language switch failed:', error);
}
}
/**
* 更新语言选择器状态
*/
updateLangSelector() {
const selector = document.getElementById('lang-selector');
if (selector) {
selector.value = this.currentLang;
}
}
/**
* 更新HTML lang属性
*/
updateHtmlLang() {
const htmlEl = document.documentElement;
if (htmlEl) {
htmlEl.lang = this.currentLang;
}
}
/**
* 获取当前语言
*/
getCurrentLang() {
return this.currentLang;
}
/**
* 获取支持的语言列表
*/
getSupportedLanguages() {
return [
{ code: 'zh-CN', name: '中文', flag: '🇨🇳' },
{ code: 'en-US', name: 'English', flag: '🇺🇸' },
{ code: 'de-DE', name: 'Deutsch', flag: '🇩🇪' }
];
}
/**
* 检查是否有翻译
*/
has(key) {
return !!this.messages[key];
}
/**
* 获取所有翻译键
*/
getKeys() {
return Object.keys(this.messages);
}
}
const i18n = new I18n();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => i18n.init());
} else {
i18n.init();
}
+488
View File
@@ -0,0 +1,488 @@
/**
* 中文语言包(简体中文)
* Chinese Language Pack (Simplified Chinese)
*/
window.__i18n_zh_CN__ = {
// ============ 页面标题 ============
'page.title': 'MyAPI 系统监控面板',
'page.live_logs': '实时日志 - 系统监控',
'page.history_logs': '日志历史查询 - 系统监控',
// ============ 导航菜单 ============
'nav.overview': '📊 Overview',
'nav.database': '🗃️ 数据库',
'nav.events': '☎️ 事件处理',
'nav.scheduler': '⏰ 定时任务',
'nav.http_requests': '📥 接收请求',
'nav.outbound_requests': '📤 发送请求',
'nav.logs': '📋 日志',
// ============ 标签页 ============
'tab.overview': '概览',
'tab.database': '数据库',
'tab.events': '事件处理',
'tab.scheduler': '定时任务',
'tab.http': '接收请求',
'tab.outbound': '发送请求',
'tab.logs': '系统日志',
'tab.timeline': '时间线',
'tab.chart': '图表分析',
// ============ 卡片标题 ============
'card.resource': '资源使用',
'card.db_status': '账套状态',
'card.db_connections': '数据库连接状态',
'card.event_helpers': '事件辅助模块',
'card.scheduler': '定时任务调度器',
'card.api_requests': 'HTTP请求统计',
'card.outbound_requests': '对外HTTP请求',
'card.redis': 'Redis状态',
'card.event_listener': '事件监听',
'card.recent_alerts': '最近告警',
'card.mysql': 'MySQL',
'card.http_requests_log': '接收请求记录',
'card.scheduler_detail': '定时任务详情',
'card.outbound_requests_log': '发送请求记录',
'card.callback_tracker': '回调跟踪器',
'card.event_deduplicator': '事件去重器',
// ============ 指标标签 ============
'metric.cpu': 'CPU 使用率',
'metric.memory': '内存使用',
'metric.threads': '线程数',
'metric.uptime': '运行时间',
'metric.total_connections': '总连接数',
'metric.healthy': '健康',
'metric.unhealthy': '异常',
'metric.degraded': '降级',
'metric.total': '总数',
'metric.success': '成功',
'metric.failed': '失败',
'metric.pending': '待处理',
'metric.active': '活跃',
'metric.idle': '空闲',
'metric.unknown': '未知',
'metric.warning': '警告',
'metric.events_received': '接收事件',
'metric.events_processed': '处理事件',
'metric.events_failed': '失败事件',
'metric.jobs_running': '运行中任务',
'metric.jobs_pending': '待执行任务',
'metric.requests_total': '总请求数',
'metric.requests_slow': '慢请求',
'metric.requests_error': '错误请求',
'metric.error_rate': '错误率',
'metric.avg_time': '平均响应',
'metric.connection_status': '连接状态',
'metric.host': '主机',
'metric.port': '端口',
'metric.database': '数据库',
'metric.used_connections': '使用连接数',
'metric.max_connections': '最大连接数',
'metric.connection_usage': '连接使用率',
'metric.buffer_size': '缓冲大小',
'metric.buffer_threshold': '缓冲阈值',
'metric.buffer_usage': '缓冲使用率',
'metric.events_interrupted': '中断处理',
'metric.overall_success_rate': '整体成功率',
'metric.active_event_types': '活跃事件类型',
'metric.backpressure_status': '背压状态',
'metric.backpressure_pending': '待处理事件',
'metric.backpressure_usage': '背压使用率',
'metric.event_loop_status': '事件循环状态',
'metric.pending_callbacks': '待处理回调',
'metric.max_retries': '最大重试次数',
'metric.pending_retries': '待处理重试',
'metric.total_entries': '总存储条目',
'metric.active_items': '活跃项目',
'metric.ttl_seconds': 'TTL (秒)',
'metric.max_entries': '最大条目',
'metric.dl_total': 'DL总数',
'metric.recent_dl': '最近DL',
'metric.process_success_rate': '处理成功率',
'metric.scheduler_status': '调度器状态',
'metric.job_count': '任务数量',
'metric.pending_events': '待写入事件',
'metric.total_events': '总事件数',
'metric.file_size': '文件大小',
'metric.running_status': '运行状态',
// ============ 状态 ============
'status.healthy': '● 系统正常',
'status.unhealthy': '● 系统异常',
'status.degraded': '● 系统降级',
'status.running': '运行中',
'status.checking': '检查中',
'status.stopped': '已停止',
'status.loading': '加载中...',
'status.querying': '查询中...',
'status.exporting': '导出中...',
'status.no_data': '暂无数据',
'status.connected': '已连接',
'status.connecting': '连接中...',
'status.disconnected': '已断开',
'status.reconnecting': '重连中...',
'status.paused': '已暂停',
'status.enabled': '已启用',
'status.disabled': '已禁用',
'status.no_db_connections': '暂无数据库连接',
'status.no_scheduler': '暂无定时任务',
'status.no_http_requests': '暂无接收请求',
'status.no_alerts': '暂无告警',
'status.no_logs': '暂无日志',
'status.no_events': '暂无事件统计',
'status.no_api_requests': '暂无 API 请求记录',
'status.no_outbound_requests': '暂无发送请求记录',
'status.no_dead_letters': '暂无DL',
'status.partial_warnings': '● 部分警告',
'status.monitoring': '监控中',
// ============ 按钮 ============
'btn.query': '查询',
'btn.reset': '重置',
'btn.refresh': '刷新',
'btn.export': '导出',
'btn.export_csv': '导出 CSV',
'btn.export_json': '导出 JSON',
'btn.detail': '详情',
'btn.pause': '暂停',
'btn.resume': '继续',
'btn.clear': '清空',
'btn.save': '保存',
'btn.delete': '删除',
'btn.close': '关闭',
'btn.confirm': '确认',
'btn.cancel': '取消',
'btn.test': '测试',
'btn.apply': '应用',
'btn.filter': '过滤',
'btn.clear_all': '清空全部',
'btn.realtime_on': '实时追踪: 开',
'btn.realtime_off': '实时追踪: 关',
'btn.precise_locate': '± 60s',
'btn.precise_cancel': '± 60s - 点击取消',
'btn.reset_stats': '重置统计',
'btn.flush_all': '立即刷新所有',
'btn.new_window': '新窗口',
'btn.auto_scroll': '自动滚动',
'btn.live_logs': '实时日志',
'btn.history_query': '历史查询',
'btn.mark_all_read': '标记全部已读',
'btn.clear_read_status': '清空已读状态',
// ============ 表格列名 ============
'col.index': '序号',
'col.time': '时间',
'col.level': '级别',
'col.module': '模块',
'col.message': '消息',
'col.method': '方法',
'col.path': '端点',
'col.url': 'URL',
'col.status': '状态码',
'col.duration': '响应时间',
'col.client_ip': '客户端IP',
'col.source': '来源',
'col.function': '函数',
'col.line': '行号',
'col.operation': '操作',
'col.count': '数量',
'col.avg_time': '平均时间',
'col.max_time': '最大时间',
'col.db_name': '账套名称',
'col.last_check': '最后检查',
'col.current_connections': '当前连接',
'col.max_connections': '最大连接',
'col.min_connections': '最小连接',
'col.idle_connections': '空闲连接',
'col.used_connections': '使用中连接',
'col.usage': '使用率',
'col.processed_records': '处理记录',
'col.timestamp': '时间戳',
'col.query_params': '查询参数',
'col.error_message': '错误信息',
'col.url': 'URL',
'col.is_read': '已读',
'col.description': '描述',
'col.total_received': '总接收',
'col.pending': '待处理',
'col.processed': '已处理',
'col.interrupted': '中断',
'col.completion_rate': '完成率',
'col.avg_latency': '平均延迟',
'col.last_action': '最后动作',
'col.id': 'ID',
'col.event_type': '事件类型',
'col.table': '表',
// ============ 快捷时间 ============
'time.start_time': '开始时间',
'time.end_time': '结束时间',
'time.last_10m': '最近10分钟',
'time.last_30m': '最近30分钟',
'time.last_1h': '最近1小时',
'time.last_6h': '最近6小时',
'time.last_24h': '最近24小时',
'time.range': '时间范围',
// ============ 过滤条件 ============
'filter.level': '全部级别',
'filter.type': '全部数据',
'filter.module': '模块',
'filter.keyword': '关键词',
'filter.method': '请求方法',
'filter.client_ip': '客户端IP',
'filter.status_range': '状态码范围',
'filter.duration_range': '响应时间范围',
'filter.advanced': '高级过滤',
'filter.collapse': '收起',
'filter.expand': '展开',
'filter.clear': '清空',
'filter.active': '已激活过滤条件',
'filter.module_placeholder': '输入模块名',
'filter.keyword_placeholder': '输入关键词搜索',
'filter.ip_placeholder': '输入客户端IP',
'filter.status_placeholder': '如: 400-499',
'filter.duration_placeholder': '如: >1000',
'filter.all_logs': '全部日志',
// ============ 图表 ============
'chart.request_trend': '📊 请求量趋势',
'chart.level_distribution': '📊 日志级别分布',
'chart.status_distribution': '📈 状态码分布',
'chart.slow_requests': '⏱️ 慢请求TOP10',
'chart.total_requests': '总请求',
'chart.error_count': '错误',
'chart.slow_count': '慢请求',
'chart.log_count': '日志总数',
'chart.no_slow': '✅ 无慢请求',
'chart.query_required': '请先执行查询',
'chart.load_failed': '❌ 图表数据加载失败,请重试',
'chart.redis_connections': 'Redis 连接池使用情况',
'chart.redis_buffer': 'Redis 缓冲大小变化',
'chart.cpu': 'CPU',
'chart.memory': '内存',
'chart.upload': '上传',
'chart.download': '下载',
'chart.cpu_memory_axis': 'CPU / 内存 (%)',
'chart.network_axis': '网络上传 / 下载 (KB/s)',
'chart.used_connections': '使用连接数',
'chart.buffer_size_mb': '缓冲大小 (MB)',
// ============ 错误提示 ============
'error.time_range_invalid': '开始时间不能大于结束时间',
'error.time_range_required': '请选择开始时间和结束时间',
'error.query_failed': '查询失败,请稍后重试',
'error.export_failed': '导出失败,请重试',
'error.connection_failed': '连接失败,请刷新页面',
'error.max_templates': '最多保存10个模板,请先删除部分模板',
'error.auto_pause': '实时追踪已自动暂停(超过10分钟)',
'error.load_failed': '加载失败,请重试',
'error.invalid_params': '参数错误',
'error.clear_dl_failed': '清空DeadLetter队列失败',
// ============ 成功提示 ============
'success.query_complete': '查询完成',
'success.export_complete': '导出完成',
'success.template_saved': '模板"{name}"已保存',
'success.logs_cleared': '日志已清空',
'success.operation_success': '操作成功',
'success.dl_cleared': 'DeadLetter队列已清空',
// ============ 时间线 ============
'timeline.title': '时间线',
'timeline.no_data': '暂无数据,请先执行查询',
'timeline.anomaly_detected': '⚠️ 发现 {count} 处异常',
'timeline.error_burst': '连续{count}条ERROR日志',
'timeline.slow_anomaly': '异常慢请求({duration}ms > 平均{avg}ms×3)',
'timeline.duplicate_error': '重复错误"{msg}"出现{count}次',
'timeline.http_request': 'HTTP请求',
'timeline.outbound_request': '发送请求',
'timeline.system_log': '系统日志',
// ============ 分页 ============
'pagination.page': '页',
'pagination.of': '共',
'pagination.items': '条',
'pagination.showing': '显示',
'pagination.per_page': '每页',
'pagination.first': '首页',
'pagination.last': '末页',
'pagination.prev': '上一页',
'pagination.next': '下一页',
'pagination.go_to': '跳转',
// ============ 导出 ============
'export.current_page': '导出当前页',
'export.all_data': '导出全部数据',
'export.format_csv': 'CSV格式',
'export.format_json': 'JSON格式',
'export.select_format': '选择导出格式',
// ============ 查询模板 ============
'template.saved_queries': '已保存查询...',
'template.save': '保存查询条件',
'template.manage': '管理模板',
'template.name': '模板名称',
'template.description': '描述',
'template.delete_confirm': '确定删除该模板?',
'template.empty': '暂无保存的模板',
// ============ 统计信息 ============
'stats.time_range': '查询时间范围',
'stats.http_requests': '接收请求',
'stats.outbound_requests': '发送请求',
'stats.system_logs': '系统日志',
'stats.level_distribution': '级别分布',
'stats.all_time': '全部时间',
// ============ 其他 ============
'other.last_update': '最后更新',
'other.auto_reconnect': '5秒后重连',
'other.waiting_logs': '正在等待日志数据...',
'other.no_matching_logs': '没有匹配的日志',
'other.precise_mode': '精确定位',
'other.saved_queries': '已保存查询...',
'other.all_time': '全部时间',
'other.linked_query': '联动查询',
'other.seconds': '秒',
'other.minutes': '分钟',
'other.hours': '小时',
'other.days': '天',
'other.ms': '毫秒',
'other.times': '次',
'other.view_detail': '查看详情',
'other.copy': '复制',
'other.expand': '展开',
'other.collapse': '收起',
'other.show_internal': '显示内部请求',
'other.show_read': '显示已读',
// ============ 日期 ============
'date.today': '今天',
'date.yesterday': '昨天',
// ============ 语言选择 ============
'lang.select': '选择语言',
'lang.zh': '中文',
'lang.en': 'English',
'lang.de': 'Deutsch',
// ============ HTTP方法 ============
'method.get': 'GET',
'method.post': 'POST',
'method.put': 'PUT',
'method.delete': 'DELETE',
'method.patch': 'PATCH',
// ============ 日志级别 ============
'level.debug': 'DEBUG',
'level.info': 'INFO',
'level.warning': 'WARNING',
'level.error': 'ERROR',
'level.critical': 'CRITICAL',
// ============ 定时任务 ============
'scheduler.rule': '定时规则',
'scheduler.last_run': '最近执行',
'scheduler.max_time': '最大执行时间',
'scheduler.never_run': '从未执行',
'scheduler.default': '默认',
'scheduler.running': '执行中',
'scheduler.not_scheduled': '未计划',
// ============ 相对时间 ============
'time.just_now': '刚刚',
'time.minutes_ago': '{n}分钟前',
'time.hours_ago': '{n}小时前',
'time.days_ago': '{n}天前',
'time.day_before_yesterday': '前天',
// ============ 告警类型 ============
'alert.warning': '警告',
'alert.error': '错误',
'alert.critical': '严重',
'alert.normal': '正常',
// ============ 连接状态 ============
'connection.failed': '连接失败',
'connection.reconnecting': '重新连接',
'connection.connected': '已连接',
'connection.disconnected': '断开',
// ============ 监控状态 ============
'monitor.paused': '● 监控已暂停(长时间未活动)',
'monitor.panel': '监控面板',
'monitor.last_update': '最后更新',
'monitor.reset_stats_confirm': '确定要重置所有事件统计吗?',
// ============ 高亮操作 ============
'highlight.on': '已高亮',
'highlight.off': '高亮',
// ============ 复制操作 ============
'copy.success': '已复制',
'copy.failed': '复制失败,请手动复制',
'copy.error': '复制失败',
// ============ 选择提示 ============
'prompt.select_date': '请选择日期',
'prompt.fetch_failed': '获取请求记录失败,请稍后重试',
'prompt.fetch_outbound_failed': '获取对外请求记录失败,请稍后重试',
'prompt.reset_failed': '重置失败,请重试',
// ============ 页面标题 ============
'page.http_requests_log': '接收请求记录',
'page.outbound_requests_log': '发送请求记录',
// ============ 时间相关 ============
'time.tomorrow': '明天',
'time.day_after_tomorrow': '后天',
'time.over_24h': '超过24小时',
'time.expired': '已过期',
'time.days_hours_later': '{days}天{hours}小时后',
'time.unknown': '未知',
'time.month_day': '{month}月{day}日',
// ============ 任务类型 ============
'task.system': '系统',
'task.project': '项目',
'task.avg_time': '平均执行时间',
// ============ 详情模态框 ============
'modal.outbound_detail': '发送请求详情',
'modal.timestamp': '时间戳:',
'modal.method': '方法:',
'modal.status_code': '状态码:',
'modal.response_time': '响应时间:',
'modal.module': '模块:',
'modal.error_msg': '错误信息:',
// ============ 请求/响应部分 ============
'section.request_headers': '请求头',
'section.request_body': '请求体',
'section.response_headers': '响应头',
'section.response_body': '响应体',
'section.no_response': '无响应体数据',
'section.response_failed': '响应体处理失败: ',
'section.truncated': '[内容已截断,完整内容请导出查看]',
'section.large_data_warning': '⚠️ 数据量较大({count}字符),可能影响显示性能',
'section.showing_chars': '仅显示前{count}字符',
'section.basic_info': '基本信息',
'section.no_data': '无数据',
// ============ 限流状态 ============
'status.rate_limited': '限流',
// ============ 日志级别选择 ============
'log_level.all_with_debug': '全部级别 (含DEBUG)',
'log_level.error': '错误日志',
'log_level.warning': '警告日志',
'log_level.info': '信息日志',
'log_level.debug': '调试日志',
// ============ DeadLetter ============
'dl.reprocess': '重新处理',
'dl.reprocess_success': 'DeadLetter重新处理成功',
'dl.reprocess_failed': 'DeadLetter重新处理失败'
};
+28
View File
@@ -2995,4 +2995,32 @@ td {
color: #fff;
font-size: 12px;
margin-right: 8px;
}
/* 语言选择器 */
.lang-selector {
padding: 6px 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 13px;
cursor: pointer;
background: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
transition: all 0.2s;
}
.lang-selector:hover {
border-color: var(--primary-color);
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
}
.lang-selector:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.1);
}
.lang-selector option {
padding: 8px 12px;
font-size: 13px;
}
+62 -57
View File
@@ -3,9 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>日志历史查询 - 系统监控</title>
<title data-i18n-page-title="page.history_logs">日志历史查询 - 系统监控</title>
<link rel="icon" href="/static/swagger/favicon.png" type="image/png">
<link rel="stylesheet" href="/static/monitor/css/monitor.css">
<script src="/static/lib/i18n/i18n.js"></script>
<link rel="stylesheet" href="/static/mds/css/bootstrap-icons.css">
<style>
body {
@@ -841,19 +842,23 @@
<div class="history-logs-container">
<div class="history-logs-header">
<div class="history-logs-header-left">
<h1>📊 日志历史查询</h1>
<span class="badge" id="history-query-badge">联动查询</span>
<h1 data-i18n="page.history_logs">📊 日志历史查询</h1>
<span class="badge" id="history-query-badge" data-i18n="other.linked_query">联动查询</span>
</div>
<div class="history-logs-header-right">
<select id="lang-selector" class="lang-selector" onchange="i18n.switchLanguage(this.value)">
<option value="zh-CN">🇨🇳 中文</option>
<option value="en-US">🇺🇸 English</option>
<option value="de-DE">🇩🇪 Deutsch</option>
</select>
<div class="header-query-controls">
<div class="time-range-selector">
<!-- <label>起止时间:</label> -->
<input type="datetime-local" id="history-start-time" class="datetime-input" step="1">
<input type="datetime-local" id="history-end-time" class="datetime-input" step="1">
</div>
<div class="query-filters">
<select id="history-log-level" class="filter-select">
<option value="">全部级别</option>
<option value="" data-i18n="filter.level">全部级别</option>
<option value="DEBUG">≥ DEBUG</option>
<option value="INFO">≥ INFO</option>
<option value="WARNING">≥ WARNING</option>
@@ -861,19 +866,19 @@
<option value="CRITICAL">≥ CRITICAL</option>
</select>
<select id="history-query-type" class="filter-select">
<option value="all">全部数据</option>
<option value="http">接收请求</option>
<option value="outbound">发送请求</option>
<option value="logs">系统日志</option>
<option value="all" data-i18n="filter.type">全部数据</option>
<option value="http" data-i18n="tab.http">接收请求</option>
<option value="outbound" data-i18n="tab.outbound">发送请求</option>
<option value="logs" data-i18n="tab.logs">系统日志</option>
</select>
</div>
<button class="btn btn-secondary header-btn" onclick="quickTimeRange('10m')">10分钟</button>
<button class="btn btn-secondary header-btn" onclick="quickTimeRange('30m')">30分钟</button>
<button class="btn btn-secondary header-btn" onclick="quickTimeRange('1h')">1小时</button>
<button class="btn btn-secondary header-btn" onclick="quickTimeRange('6h')">6小时</button>
<button class="btn btn-secondary header-btn" onclick="quickTimeRange('24h')">24小时</button>
<button class="btn btn-secondary header-btn header-btn-reset" onclick="resetHistoryQuery()">重置</button>
<button class="btn btn-primary header-btn" id="query-btn" onclick="executeHistoryQuery()">查询</button>
<button class="btn btn-secondary header-btn" onclick="quickTimeRange('10m')" data-i18n="time.last_10m">10分钟</button>
<button class="btn btn-secondary header-btn" onclick="quickTimeRange('30m')" data-i18n="time.last_30m">30分钟</button>
<button class="btn btn-secondary header-btn" onclick="quickTimeRange('1h')" data-i18n="time.last_1h">1小时</button>
<button class="btn btn-secondary header-btn" onclick="quickTimeRange('6h')" data-i18n="time.last_6h">6小时</button>
<button class="btn btn-secondary header-btn" onclick="quickTimeRange('24h')" data-i18n="time.last_24h">24小时</button>
<button class="btn btn-secondary header-btn header-btn-reset" onclick="resetHistoryQuery()" data-i18n="btn.reset">重置</button>
<button class="btn btn-primary header-btn" id="query-btn" onclick="executeHistoryQuery()" data-i18n="btn.query">查询</button>
</div>
<div id="precise-mode-container"></div>
</div>
@@ -882,11 +887,11 @@
<div class="history-logs-body">
<div class="result-tabs" id="result-tabs" style="display: none;">
<div class="result-tabs-left">
<button class="tab-btn active" data-tab="logs" onclick="switchResultTab('logs')"><i class="bi bi-journal-text"></i> 系统日志</button>
<button class="tab-btn" data-tab="http" onclick="switchResultTab('http')"><i class="bi bi-box-arrow-in-right"></i> 接收请求</button>
<button class="tab-btn" data-tab="outbound" onclick="switchResultTab('outbound')"><i class="bi bi-box-arrow-right"></i> 发送请求</button>
<button class="tab-btn" data-tab="timeline" onclick="switchResultTab('timeline')"><i class="bi bi-clock-history"></i> 时间线</button>
<button class="tab-btn" data-tab="chart" onclick="switchResultTab('chart')"><i class="bi bi-bar-chart-line"></i> 图表分析</button>
<button class="tab-btn active" data-tab="logs" onclick="switchResultTab('logs')"><i class="bi bi-journal-text"></i> <span data-i18n="tab.logs">系统日志</span></button>
<button class="tab-btn" data-tab="http" onclick="switchResultTab('http')"><i class="bi bi-box-arrow-in-right"></i> <span data-i18n="tab.http">接收请求</span></button>
<button class="tab-btn" data-tab="outbound" onclick="switchResultTab('outbound')"><i class="bi bi-box-arrow-right"></i> <span data-i18n="tab.outbound">发送请求</span></button>
<button class="tab-btn" data-tab="timeline" onclick="switchResultTab('timeline')"><i class="bi bi-clock-history"></i> <span data-i18n="tab.timeline">时间线</span></button>
<button class="tab-btn" data-tab="chart" onclick="switchResultTab('chart')"><i class="bi bi-bar-chart-line"></i> <span data-i18n="tab.chart">图表分析</span></button>
</div>
<div class="result-tabs-right">
<div class="result-summary-inline">
@@ -1000,7 +1005,7 @@
</div>
<div class="filter-item">
<select id="saved-templates" class="filter-select" onchange="loadQueryTemplate()" style="max-width:120px;">
<option value="">已保存查询...</option>
<option value="" data-i18n="template.saved_queries">已保存查询...</option>
</select>
</div>
</div>
@@ -1018,7 +1023,7 @@
</tr>
</thead>
<tbody id="http-results-body">
<tr><td colspan="8" class="no-data">暂无数据</td></tr>
<tr><td colspan="8" class="no-data" data-i18n="status.no_data">暂无数据</td></tr>
</tbody>
</table>
</div>
@@ -1071,7 +1076,7 @@
</tr>
</thead>
<tbody id="outbound-results-body">
<tr><td colspan="8" class="no-data">暂无数据</td></tr>
<tr><td colspan="8" class="no-data" data-i18n="status.no_data">暂无数据</td></tr>
</tbody>
</table>
</div>
@@ -1099,7 +1104,7 @@
</tr>
</thead>
<tbody id="logs-results-body">
<tr><td colspan="6" class="no-data">暂无数据</td></tr>
<tr><td colspan="6" class="no-data" data-i18n="status.no_data">暂无数据</td></tr>
</tbody>
</table>
</div>
@@ -1107,7 +1112,7 @@
<!-- 时间线标签页 -->
<div class="result-tab-content" id="tab-timeline">
<div class="timeline-container" id="timeline-container">
<div class="timeline-empty">暂无数据,请先执行查询</div>
<div class="timeline-empty" data-i18n="timeline.no_data">暂无数据,请先执行查询</div>
</div>
</div>
@@ -1176,7 +1181,7 @@
const endDate = new Date(endTime);
if (startDate >= endDate) {
alert('开始时间不能大于结束时间');
alert(i18n.t('error.time_range_invalid'));
return;
}
}
@@ -1189,14 +1194,14 @@
try {
const badge = document.getElementById('history-query-badge');
if (badge) {
badge.textContent = '查询中...';
badge.textContent = i18n.t('status.querying');
badge.className = 'badge warning';
}
const queryBtn = document.getElementById('query-btn');
if (queryBtn) {
queryBtn.disabled = true;
queryBtn.textContent = '查询中...';
queryBtn.textContent = i18n.t('status.querying');
}
let url = `${API_BASE}/history/query?timezone_offset=${timezoneOffset}`;
@@ -1301,29 +1306,29 @@
switchResultTab(currentTab);
if (badge) {
badge.textContent = '查询完成';
badge.textContent = i18n.t('success.query_complete');
badge.className = 'badge healthy';
}
if (queryBtn) {
queryBtn.disabled = false;
queryBtn.textContent = '查询';
queryBtn.textContent = i18n.t('btn.query');
}
} catch (error) {
console.error('执行历史查询失败:', error);
alert('查询失败,请稍后重试');
alert(i18n.t('error.query_failed'));
const badge = document.getElementById('history-query-badge');
if (badge) {
badge.textContent = '查询失败';
badge.textContent = i18n.t('error.query_failed');
badge.className = 'badge error';
}
const queryBtn = document.getElementById('query-btn');
if (queryBtn) {
queryBtn.disabled = false;
queryBtn.textContent = '查询';
queryBtn.textContent = i18n.t('btn.query');
}
}
}
@@ -1348,9 +1353,9 @@
document.getElementById('result-tabs').style.display = 'none';
document.getElementById('http-results-body').innerHTML = '<tr><td colspan="8" class="no-data">暂无数据</td></tr>';
document.getElementById('outbound-results-body').innerHTML = '<tr><td colspan="8" class="no-data">暂无数据</td></tr>';
document.getElementById('logs-results-body').innerHTML = '<tr><td colspan="6" class="no-data">暂无数据</td></tr>';
document.getElementById('http-results-body').innerHTML = '<tr><td colspan="8" class="no-data">' + i18n.t('status.no_data') + '</td></tr>';
document.getElementById('outbound-results-body').innerHTML = '<tr><td colspan="8" class="no-data">' + i18n.t('status.no_data') + '</td></tr>';
document.getElementById('logs-results-body').innerHTML = '<tr><td colspan="6" class="no-data">' + i18n.t('status.no_data') + '</td></tr>';
historyQueryData = { http: [], outbound: [], logs: [] };
// 清理缓存的排序数据
@@ -1360,7 +1365,7 @@
const badge = document.getElementById('history-query-badge');
if (badge) {
badge.textContent = '联动查询';
badge.textContent = i18n.t('other.linked_query');
badge.className = '';
}
}
@@ -1548,7 +1553,7 @@
const endTime = document.getElementById('history-end-time').value;
if (!startTime || !endTime) {
alert('请先选择时间范围');
alert(i18n.t('error.time_range_required'));
return;
}
@@ -1651,7 +1656,7 @@
if (!container) return;
if (timelineData.length === 0) {
container.innerHTML = '<div class="timeline-empty">暂无数据</div>';
container.innerHTML = '<div class="timeline-empty">' + i18n.t('status.no_data') + '</div>';
return;
}
@@ -1764,7 +1769,7 @@
const endTime = document.getElementById('history-end-time').value;
if (!startTime || !endTime) {
document.getElementById('tab-chart').innerHTML = '<div style="padding:40px;text-align:center;color:#999;">请先执行查询</div>';
document.getElementById('tab-chart').innerHTML = '<div style="padding:40px;text-align:center;color:#999;">' + i18n.t('chart.query_required') + '</div>';
return;
}
@@ -1861,7 +1866,7 @@
console.error('渲染图表失败:', error);
const chartContainer = document.querySelector('.chart-container');
if (chartContainer) {
chartContainer.innerHTML = '<div style="padding:40px;text-align:center;color:#ef4444;">❌ 图表数据加载失败,请重试</div>';
chartContainer.innerHTML = '<div style="padding:40px;text-align:center;color:#ef4444;">' + i18n.t('chart.load_failed') + '</div>';
}
}
}
@@ -1877,11 +1882,11 @@
const btn = document.getElementById('realtime-btn');
if (realtimeTracking) {
btn.textContent = '实时追踪: 开';
btn.textContent = i18n.t('btn.realtime_on');
btn.className = 'btn btn-primary';
startRealtimeTracking();
} else {
btn.textContent = '实时追踪: 关';
btn.textContent = i18n.t('btn.realtime_off');
btn.className = 'btn btn-secondary';
stopRealtimeTracking();
}
@@ -1894,12 +1899,12 @@
realtimeInterval = setInterval(async () => {
trackingDuration += 5;
// 10分钟自动暂停
if (trackingDuration >= 600) {
stopRealtimeTracking();
alert('实时追踪已自动暂停(超过10分钟)');
return;
}
// 10分钟自动暂停
if (trackingDuration >= 600) {
stopRealtimeTracking();
alert(i18n.t('error.auto_pause'));
return;
}
try {
const url = `${API_BASE}/history/recent?since_timestamp=${encodeURIComponent(lastTimestamp)}&data_type=logs&limit=50`;
@@ -1933,7 +1938,7 @@
}
realtimeTracking = false;
const btn = document.getElementById('realtime-btn');
btn.textContent = '实时追踪: 关';
btn.textContent = i18n.t('btn.realtime_off');
btn.className = 'btn btn-secondary';
}
@@ -1959,14 +1964,14 @@
if (!select) return;
const templates = getTemplates();
select.innerHTML = '<option value="">已保存查询...</option>' +
select.innerHTML = '<option value="">' + i18n.t('template.saved_queries') + '</option>' +
templates.map((t, i) => `<option value="${i}">${t.name}</option>`).join('');
}
function saveQueryTemplate() {
const templates = getTemplates();
if (templates.length >= 10) {
alert('最多保存10个模板,请先删除部分模板');
alert(i18n.t('error.max_templates'));
return;
}
@@ -1992,7 +1997,7 @@
templates.push(template);
saveTemplates(templates);
alert(`模板"${name}"已保存`);
alert(i18n.t('success.template_saved', {name: name}));
}
function loadQueryTemplate() {
@@ -2128,7 +2133,7 @@
if (!tbody) return;
if (historyQueryData.http.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" class="no-data">暂无数据</td></tr>';
tbody.innerHTML = '<tr><td colspan="8" class="no-data">' + i18n.t('status.no_data') + '</td></tr>';
sortedHttpData = [];
return;
}
@@ -2165,7 +2170,7 @@
if (!tbody) return;
if (historyQueryData.outbound.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" class="no-data">暂无数据</td></tr>';
tbody.innerHTML = '<tr><td colspan="8" class="no-data">' + i18n.t('status.no_data') + '</td></tr>';
sortedOutboundData = [];
return;
}
@@ -2202,7 +2207,7 @@
if (!tbody) return;
if (historyQueryData.logs.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="no-data">暂无数据</td></tr>';
tbody.innerHTML = '<tr><td colspan="6" class="no-data">' + i18n.t('status.no_data') + '</td></tr>';
sortedLogsData = [];
return;
}
+164 -158
View File
@@ -3,30 +3,36 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MyAPI 系统监控面板</title>
<title data-i18n-page-title="page.title">MyAPI 系统监控面板</title>
<link rel="icon" href="/static/swagger/favicon.png" type="image/png">
<link rel="stylesheet" href="/static/monitor/css/monitor.css">
<link rel="stylesheet" href="/static/monitor/lib/prism.min.css">
<script src="/static/monitor/lib/chart.min.js"></script>
<script src="/static/monitor/lib/prism.min.js"></script>
<script src="/static/monitor/lib/prism-json.min.js"></script>
<script src="/static/lib/i18n/i18n.js"></script>
</head>
<body>
<div class="container">
<header class="header">
<nav class="nav-menu">
<a href="#" class="nav-item active" data-page="overview">📊 Overview</a>
<a href="#" class="nav-item" data-page="database">🗃️ 数据库<span id="database-badge" class="nav-badge" style="display: none;"></span></a>
<a href="#" class="nav-item" data-page="event-helpers">☎️ 事件处理</a>
<a href="#" class="nav-item" data-page="scheduler">⏰ 定时任务</a>
<a href="#" class="nav-item" data-page="api-requests">📥 接收请求<span id="api-requests-badge" class="nav-badge" style="display: none;"></span></a>
<a href="#" class="nav-item" data-page="outbound-requests">📤 发送请求<span id="outbound-requests-badge" class="nav-badge" style="display: none;"></span></a>
<a href="#" class="nav-item" data-page="logs">📋 日志<span id="logs-badge" class="nav-badge" style="display: none;"></span></a>
<a href="#" class="nav-item active" data-page="overview" data-i18n="nav.overview">📊 Overview</a>
<a href="#" class="nav-item" data-page="database" data-i18n="nav.database">🗃️ 数据库<span id="database-badge" class="nav-badge" style="display: none;"></span></a>
<a href="#" class="nav-item" data-page="event-helpers" data-i18n="nav.events">☎️ 事件处理</a>
<a href="#" class="nav-item" data-page="scheduler" data-i18n="nav.scheduler">⏰ 定时任务</a>
<a href="#" class="nav-item" data-page="api-requests" data-i18n="nav.http_requests">📥 接收请求<span id="api-requests-badge" class="nav-badge" style="display: none;"></span></a>
<a href="#" class="nav-item" data-page="outbound-requests" data-i18n="nav.outbound_requests">📤 发送请求<span id="outbound-requests-badge" class="nav-badge" style="display: none;"></span></a>
<a href="#" class="nav-item" data-page="logs" data-i18n="nav.logs">📋 日志<span id="logs-badge" class="nav-badge" style="display: none;"></span></a>
</nav>
<div class="header-info">
<span id="status-indicator" class="status healthy">● 系统正常</span>
<span id="last-update">最后更新&nbsp;&nbsp;--</span>
<button id="refresh-btn" class="btn btn-primary" onclick="refreshAll()">刷新</button>
<select id="lang-selector" class="lang-selector" onchange="i18n.switchLanguage(this.value)">
<option value="zh-CN">🇨🇳 中文</option>
<option value="en-US">🇺🇸 English</option>
<option value="de-DE">🇩🇪 Deutsch</option>
</select>
<span id="status-indicator" class="status healthy" data-i18n="status.healthy">● 系统正常</span>
<span id="last-update"><span data-i18n="other.last_update">最后更新</span>&nbsp;&nbsp;--</span>
<button id="refresh-btn" class="btn btn-primary" onclick="refreshAll()" data-i18n="btn.refresh">刷新</button>
</div>
</header>
@@ -35,20 +41,20 @@
<!-- 资源监控卡片 -->
<section class="card">
<div class="card-header">
<h2>资源使用</h2>
<span class="badge" id="resource-badge">运行中</span>
<h2 data-i18n="card.resource">资源使用</h2>
<span class="badge" id="resource-badge" data-i18n="status.running">运行中</span>
</div>
<div class="card-body">
<div class="metrics-grid">
<div class="metric-item">
<div class="metric-label">CPU 使用率</div>
<div class="metric-label" data-i18n="metric.cpu">CPU 使用率</div>
<div class="metric-value" id="cpu-value">--%</div>
<div class="progress-bar">
<div class="progress-fill" id="cpu-bar"></div>
</div>
</div>
<div class="metric-item">
<div class="metric-label">内存使用</div>
<div class="metric-label" data-i18n="metric.memory">内存使用</div>
<div class="metric-value" id="memory-value">
<span id="memory-usage">-- MB</span>
<span id="memory-percent" class="memory-percent">--%</span>
@@ -58,11 +64,11 @@
</div>
</div>
<div class="metric-item">
<div class="metric-label">线程数</div>
<div class="metric-label" data-i18n="metric.threads">线程数</div>
<div class="metric-value" id="threads-value">--</div>
</div>
<div class="metric-item">
<div class="metric-label">运行时间</div>
<div class="metric-label" data-i18n="metric.uptime">运行时间</div>
<div class="metric-value" id="uptime-value">--</div>
</div>
</div>
@@ -75,21 +81,21 @@
<!-- 数据库状态卡片 -->
<section class="card">
<div class="card-header">
<h2>账套状态</h2>
<span class="badge" id="db-badge">检查中</span>
<h2 data-i18n="card.db_status">账套状态</h2>
<span class="badge" id="db-badge" data-i18n="status.checking">检查中</span>
</div>
<div class="card-body">
<div class="db-summary" id="db-summary">
<div class="summary-item">
<span class="summary-label">总连接数</span>
<span class="summary-label" data-i18n="metric.total_connections">总连接数</span>
<span class="summary-value" id="db-total">--</span>
</div>
<div class="summary-item">
<span class="summary-label">健康</span>
<span class="summary-label" data-i18n="metric.healthy">健康</span>
<span class="summary-value healthy" id="db-healthy">--</span>
</div>
<div class="summary-item">
<span class="summary-label">异常</span>
<span class="summary-label" data-i18n="metric.unhealthy">异常</span>
<span class="summary-value error" id="db-unhealthy">--</span>
</div>
</div>
@@ -102,8 +108,8 @@
<!-- 提醒信息卡片 -->
<section class="card">
<div class="card-header">
<h2>最近告警</h2>
<button class="btn btn-small" onclick="clearAlerts()">清空</button>
<h2 data-i18n="card.recent_alerts">最近告警</h2>
<button class="btn btn-small" onclick="clearAlerts()" data-i18n="btn.clear">清空</button>
</div>
<div class="card-body">
<div class="alert-list" id="alert-list">
@@ -115,21 +121,21 @@
<!-- 接收请求卡片 -->
<section class="card">
<div class="card-header">
<h2>接收请求 <span style="font-size: 12px; color: #888; font-weight: normal;">(最近24小时)</span></h2>
<span class="badge" id="http-badge">监控中</span>
<h2 data-i18n="card.api_requests">接收请求</h2>
<span class="badge" id="http-badge" data-i18n="status.running">监控中</span>
</div>
<div class="card-body">
<div class="http-summary" id="http-summary">
<div class="summary-item">
<span class="summary-label">总请求数</span>
<span class="summary-label" data-i18n="metric.requests_total">总请求数</span>
<span class="summary-value" id="http-total">--</span>
</div>
<div class="summary-item">
<span class="summary-label">错误率</span>
<span class="summary-label" data-i18n="metric.error_rate">错误率</span>
<span class="summary-value" id="http-error-rate">--%</span>
</div>
<div class="summary-item">
<span class="summary-label">平均响应</span>
<span class="summary-label" data-i18n="metric.avg_time">平均响应</span>
<span class="summary-value" id="http-avg-time">--ms</span>
</div>
<div class="summary-item">
@@ -143,21 +149,21 @@
<!-- 发送请求卡片 -->
<section class="card">
<div class="card-header">
<h2>发送请求 <span style="font-size: 12px; color: #888; font-weight: normal;">(最近24小时)</span></h2>
<span class="badge" id="outbound-badge">监控中</span>
<h2 data-i18n="card.outbound_requests">发送请求</h2>
<span class="badge" id="outbound-badge" data-i18n="status.running">监控中</span>
</div>
<div class="card-body">
<div class="outbound-summary" id="outbound-summary">
<div class="summary-item">
<span class="summary-label">总请求数</span>
<span class="summary-label" data-i18n="metric.requests_total">总请求数</span>
<span class="summary-value" id="outbound-total">--</span>
</div>
<div class="summary-item">
<span class="summary-label">错误率</span>
<span class="summary-label" data-i18n="metric.error_rate">错误率</span>
<span class="summary-value" id="outbound-error-rate">--%</span>
</div>
<div class="summary-item">
<span class="summary-label">平均响应</span>
<span class="summary-label" data-i18n="metric.avg_time">平均响应</span>
<span class="summary-value" id="outbound-avg">--ms</span>
</div>
</div>
@@ -167,17 +173,17 @@
<!-- 定时任务监控卡片 -->
<section class="card">
<div class="card-header">
<h2>定时任务</h2>
<span class="badge" id="scheduler-badge">检查中</span>
<h2 data-i18n="card.scheduler">定时任务</h2>
<span class="badge" id="scheduler-badge" data-i18n="status.checking">检查中</span>
</div>
<div class="card-body">
<div class="scheduler-info" id="scheduler-info">
<div class="info-item">
<span class="info-label">调度器状态</span>
<span class="info-label" data-i18n="metric.scheduler_status">调度器状态</span>
<span class="info-value" id="scheduler-status">--</span>
</div>
<div class="info-item">
<span class="info-label">任务数量</span>
<span class="info-label" data-i18n="metric.job_count">任务数量</span>
<span class="info-value" id="job-count">--</span>
</div>
</div>
@@ -190,8 +196,8 @@
<!-- 数据库详细信息卡片 -->
<section class="card full-width">
<div class="card-header">
<h2>MySQL</h2>
<span class="badge" id="db-detail-badge">检查中</span>
<h2 data-i18n="card.mysql">MySQL</h2>
<span class="badge" id="db-detail-badge" data-i18n="status.checking">检查中</span>
</div>
<div class="card-body">
<!-- 数据库详细信息 -->
@@ -199,17 +205,17 @@
<table class="db-detail-table" id="db-detail-table">
<thead>
<tr>
<th>账套名称</th>
<th>连接状态</th>
<th>最后检查</th>
<th>当前连接</th>
<th>最大连接</th>
<th>最小连接</th>
<th>空闲连接</th>
<th>使用中连接</th>
<th>使用率</th>
<th>处理记录</th>
<th>次数</th>
<th data-i18n="col.db_name">账套名称</th>
<th data-i18n="metric.connection_status">连接状态</th>
<th data-i18n="col.last_check">最后检查</th>
<th data-i18n="col.current_connections">当前连接</th>
<th data-i18n="col.max_connections">最大连接</th>
<th data-i18n="col.min_connections">最小连接</th>
<th data-i18n="col.idle_connections">空闲连接</th>
<th data-i18n="col.used_connections">使用中连接</th>
<th data-i18n="col.usage">使用率</th>
<th data-i18n="col.processed_records">处理记录</th>
<th data-i18n="col.count">次数</th>
</tr>
</thead>
<tbody id="db-detail-tbody">
@@ -223,62 +229,62 @@
<!-- Redis 监控卡片 -->
<section class="card full-width">
<div class="card-header">
<h2>Redis</h2>
<span class="badge" id="redis-badge">检查中</span>
<h2 data-i18n="card.redis">Redis</h2>
<span class="badge" id="redis-badge" data-i18n="status.checking">检查中</span>
</div>
<div class="card-body">
<div class="redis-metrics-grid">
<div class="metric-item">
<div class="metric-label">连接状态</div>
<div class="metric-label" data-i18n="metric.connection_status">连接状态</div>
<div class="metric-value" id="redis-status">--</div>
</div>
<div class="metric-item">
<div class="metric-label">主机</div>
<div class="metric-label" data-i18n="metric.host">主机</div>
<div class="metric-value" id="redis-host">--</div>
</div>
<div class="metric-item">
<div class="metric-label">端口</div>
<div class="metric-label" data-i18n="metric.port">端口</div>
<div class="metric-value" id="redis-port">--</div>
</div>
<div class="metric-item">
<div class="metric-label">数据库</div>
<div class="metric-label" data-i18n="metric.database">数据库</div>
<div class="metric-value" id="redis-db">--</div>
</div>
</div>
<div class="redis-pool-metrics">
<div class="metric-item">
<div class="metric-label">使用连接数</div>
<div class="metric-label" data-i18n="metric.used_connections">使用连接数</div>
<div class="metric-value" id="redis-connections-used">0</div>
</div>
<div class="metric-item">
<div class="metric-label">最大连接数</div>
<div class="metric-label" data-i18n="metric.max_connections">最大连接数</div>
<div class="metric-value" id="redis-connections-max">0</div>
</div>
<div class="metric-item">
<div class="metric-label">连接使用率</div>
<div class="metric-label" data-i18n="metric.connection_usage">连接使用率</div>
<div class="metric-value" id="redis-connection-usage">0%</div>
</div>
</div>
<div class="redis-buffer-metrics">
<div class="metric-item">
<div class="metric-label">缓冲大小</div>
<div class="metric-label" data-i18n="metric.buffer_size">缓冲大小</div>
<div class="metric-value" id="redis-buffer-size">0</div>
</div>
<div class="metric-item">
<div class="metric-label">缓冲阈值</div>
<div class="metric-label" data-i18n="metric.buffer_threshold">缓冲阈值</div>
<div class="metric-value" id="redis-buffer-threshold">0</div>
</div>
<div class="metric-item">
<div class="metric-label">缓冲使用率</div>
<div class="metric-label" data-i18n="metric.buffer_usage">缓冲使用率</div>
<div class="metric-value" id="redis-buffer-usage">0%</div>
</div>
</div>
<div class="chart-container">
<h3>Redis 连接池使用情况</h3>
<h3 data-i18n="chart.redis_connections">Redis 连接池使用情况</h3>
<canvas id="redis-connections-chart"></canvas>
</div>
<div class="chart-container">
<h3>Redis 缓冲大小变化</h3>
<h3 data-i18n="chart.redis_buffer">Redis 缓冲大小变化</h3>
<canvas id="redis-buffer-chart"></canvas>
</div>
</div>
@@ -289,7 +295,7 @@
<main class="main-content page-content" id="page-api-requests">
<section class="card full-width">
<div class="card-header">
<h2>接收请求记录</h2>
<h2 data-i18n="card.http_requests_log">接收请求记录</h2>
<div class="card-actions">
<div class="date-selector" style="display: none;">
<label for="api-date-picker">选择日期:</label>
@@ -300,11 +306,11 @@
<label>
<input type="checkbox" id="show-api-internal-requests" onchange="toggleAPIIntternalRequests()">
<span class="toggle-slider"></span>
显示内部请求
<span data-i18n="other.show_internal">显示内部请求</span>
</label>
</div>
<button class="btn btn-sm" onclick="refreshAPIRequests()">刷新</button>
<button class="btn btn-sm btn-secondary" onclick="resetAPIStats()">重置统计</button>
<button class="btn btn-sm" onclick="refreshAPIRequests()" data-i18n="btn.refresh">刷新</button>
<button class="btn btn-sm btn-secondary" onclick="resetAPIStats()" data-i18n="btn.reset_stats">重置统计</button>
</div>
</div>
<div class="card-body">
@@ -312,15 +318,15 @@
<table class="api-requests-table" id="api-requests-table">
<thead>
<tr>
<th>序号</th>
<th>时间戳</th>
<th>方法</th>
<th>端点</th>
<th>查询参数</th>
<th>状态码</th>
<th>响应时间</th>
<th>客户端IP</th>
<th>错误信息</th>
<th data-i18n="col.index">序号</th>
<th data-i18n="col.timestamp">时间戳</th>
<th data-i18n="col.method">方法</th>
<th data-i18n="col.path">端点</th>
<th data-i18n="col.query_params">查询参数</th>
<th data-i18n="col.status">状态码</th>
<th data-i18n="col.duration">响应时间</th>
<th data-i18n="col.client_ip">客户端IP</th>
<th data-i18n="col.error_message">错误信息</th>
</tr>
</thead>
<tbody id="api-requests-tbody">
@@ -375,9 +381,9 @@
<main class="main-content page-content" id="page-scheduler">
<section class="card full-width">
<div class="card-header">
<h2>定时任务详情</h2>
<span class="badge" id="scheduler-detail-badge">检查中</span>
<button class="btn btn-small" onclick="refreshSchedulerPage()">刷新</button>
<h2 data-i18n="card.scheduler_detail">定时任务详情</h2>
<span class="badge" id="scheduler-detail-badge" data-i18n="status.checking">检查中</span>
<button class="btn btn-small" onclick="refreshSchedulerPage()" data-i18n="btn.refresh">刷新</button>
</div>
<div class="card-body">
<div class="scheduler-detail-grid" id="scheduler-detail-grid">
@@ -391,7 +397,7 @@
<main class="main-content page-content" id="page-outbound-requests">
<section class="card full-width">
<div class="card-header">
<h2>发送请求记录</h2>
<h2 data-i18n="card.outbound_requests_log">发送请求记录</h2>
<div class="card-actions">
<div class="date-selector" style="display: none;">
<label for="outbound-date-picker">选择日期:</label>
@@ -402,11 +408,11 @@
<label>
<input type="checkbox" id="show-internal-requests" onchange="toggleInternalRequests()">
<span class="toggle-slider"></span>
显示内部请求
<span data-i18n="other.show_internal">显示内部请求</span>
</label>
</div>
<button class="btn btn-sm" onclick="refreshOutboundRequests()">刷新</button>
<button class="btn btn-sm btn-secondary" onclick="resetOutboundStats()">重置统计</button>
<button class="btn btn-sm" onclick="refreshOutboundRequests()" data-i18n="btn.refresh">刷新</button>
<button class="btn btn-sm btn-secondary" onclick="resetOutboundStats()" data-i18n="btn.reset_stats">重置统计</button>
</div>
</div>
<div class="card-body">
@@ -415,14 +421,14 @@
<table class="outbound-requests-table">
<thead>
<tr>
<th>序号</th>
<th>时间戳</th>
<th>方法</th>
<th>URL</th>
<th>状态码</th>
<th>响应时间</th>
<th>模块</th>
<th>错误信息</th>
<th data-i18n="col.index">序号</th>
<th data-i18n="col.timestamp">时间戳</th>
<th data-i18n="col.method">方法</th>
<th data-i18n="col.url">URL</th>
<th data-i18n="col.status">状态码</th>
<th data-i18n="col.duration">响应时间</th>
<th data-i18n="col.module">模块</th>
<th data-i18n="col.error_message">错误信息</th>
</tr>
</thead>
<tbody id="outbound-requests-table">
@@ -441,23 +447,23 @@
<div class="card-header">
<h2>Warning & Error</h2>
<div class="log-page-controls">
<button class="btn btn-primary" onclick="window.open('/monitor/live-logs', '_blank')">实时日志</button>
<button class="btn btn-primary" onclick="window.open('/monitor/history-logs', '_blank')">历史查询</button>
<button class="btn btn-primary" onclick="window.open('/monitor/live-logs', '_blank')" data-i18n="btn.live_logs">实时日志</button>
<button class="btn btn-primary" onclick="window.open('/monitor/history-logs', '_blank')" data-i18n="btn.history_query">历史查询</button>
<div class="toggle-switch">
<label>
<input type="checkbox" id="show-read-logs" onchange="toggleReadLogs()">
<span class="toggle-slider"></span>
显示已读
<span data-i18n="other.show_read">显示已读</span>
</label>
</div>
<select id="log-page-level" onchange="fetchLogsPage()">
<option value="">全部日志</option>
<option value="" data-i18n="filter.all_logs">全部日志</option>
<option value="error">错误日志</option>
<option value="warning">警告日志</option>
</select>
<button class="btn btn-small" onclick="markAllLogsAsRead()">标记全部已读</button>
<button class="btn btn-small" onclick="clearReadStatus()">清空已读状态</button>
<button class="btn btn-small" onclick="refreshLogsPage()">刷新</button>
<button class="btn btn-small" onclick="markAllLogsAsRead()" data-i18n="btn.mark_all_read">标记全部已读</button>
<button class="btn btn-small" onclick="clearReadStatus()" data-i18n="btn.clear_read_status">清空已读状态</button>
<button class="btn btn-small" onclick="refreshLogsPage()" data-i18n="btn.refresh">刷新</button>
</div>
</div>
<div class="card-body">
@@ -465,12 +471,12 @@
<table class="logs-table" id="logs-table">
<thead>
<tr>
<th>序号</th>
<th>时间</th>
<th>级别</th>
<th>模块</th>
<th>消息</th>
<th>已读</th>
<th data-i18n="col.index">序号</th>
<th data-i18n="col.time">时间</th>
<th data-i18n="col.level">级别</th>
<th data-i18n="col.module">模块</th>
<th data-i18n="col.message">消息</th>
<th data-i18n="col.is_read">已读</th>
</tr>
</thead>
<tbody id="logs-tbody">
@@ -487,55 +493,55 @@
<!-- 事件监听卡片 -->
<section class="card full-width">
<div class="card-header">
<h2>事件监听</h2>
<h2 data-i18n="card.event_listener">事件监听</h2>
<div class="events-actions">
<button class="btn btn-small" onclick="refreshEventStats()">刷新</button>
<button class="btn btn-small" onclick="flushAllEvents()">立即刷新所有</button>
<button class="btn btn-small btn-secondary" onclick="resetEventStats()">重置统计</button>
<button class="btn btn-small" onclick="refreshEventStats()" data-i18n="btn.refresh">刷新</button>
<button class="btn btn-small" onclick="flushAllEvents()" data-i18n="btn.flush_all">立即刷新所有</button>
<button class="btn btn-small btn-secondary" onclick="resetEventStats()" data-i18n="btn.reset_stats">重置统计</button>
</div>
</div>
<div class="card-body">
<div class="events-summary" id="events-summary">
<div class="summary-item">
<span class="summary-label">总接收事件</span>
<span class="summary-label" data-i18n="metric.events_received">总接收事件</span>
<span class="summary-value" id="events-total-received">--</span>
</div>
<div class="summary-item">
<span class="summary-label">已处理事件</span>
<span class="summary-label" data-i18n="metric.events_processed">已处理事件</span>
<span class="summary-value healthy" id="events-total-processed">--</span>
</div>
<div class="summary-item">
<span class="summary-label">中断处理</span>
<span class="summary-label" data-i18n="metric.events_interrupted">中断处理</span>
<span class="summary-value error" id="events-total-failed">--</span>
</div>
<div class="summary-item">
<span class="summary-label">待处理</span>
<span class="summary-label" data-i18n="metric.pending">待处理</span>
<span class="summary-value" id="events-total-pending">--</span>
</div>
<div class="summary-item">
<span class="summary-label">整体成功率</span>
<span class="summary-label" data-i18n="metric.overall_success_rate">整体成功率</span>
<span class="summary-value" id="events-success-rate">--%</span>
</div>
<div class="summary-item">
<span class="summary-label">活跃事件类型</span>
<span class="summary-label" data-i18n="metric.active_event_types">活跃事件类型</span>
<span class="summary-value" id="events-active-types">--</span>
</div>
<!-- 背压监控 -->
<div class="summary-item" id="backpressure-summary" style="display: none;">
<span class="summary-label">背压状态</span>
<span class="summary-label" data-i18n="metric.backpressure_status">背压状态</span>
<span class="summary-value" id="backpressure-status">正常</span>
</div>
<div class="summary-item" id="backpressure-pending" style="display: none;">
<span class="summary-label">待处理事件</span>
<span class="summary-label" data-i18n="metric.backpressure_pending">待处理事件</span>
<span class="summary-value" id="backpressure-pending-count">--</span>
</div>
<div class="summary-item" id="backpressure-percent" style="display: none;">
<span class="summary-label">背压使用率</span>
<span class="summary-label" data-i18n="metric.backpressure_usage">背压使用率</span>
<span class="summary-value" id="backpressure-usage">--%</span>
</div>
<!-- 事件循环健康状态 -->
<div class="summary-item" id="event-loop-status" style="display: none;">
<span class="summary-label">事件循环状态</span>
<span class="summary-label" data-i18n="metric.event_loop_status">事件循环状态</span>
<span class="summary-value" id="event-loop-health">正常</span>
</div>
</div>
@@ -543,17 +549,17 @@
<table class="events-table" id="events-table">
<thead>
<tr>
<th>描述</th>
<th>总接收</th>
<th>待处理</th>
<th>已处理</th>
<th>中断</th>
<th>完成率</th>
<th>平均延迟</th>
<th>操作</th>
<th>最后动作</th>
<th>状态</th>
<th>操作</th>
<th data-i18n="col.description">描述</th>
<th data-i18n="col.total_received">总接收</th>
<th data-i18n="col.pending">待处理</th>
<th data-i18n="col.processed">已处理</th>
<th data-i18n="col.interrupted">中断</th>
<th data-i18n="col.completion_rate">完成率</th>
<th data-i18n="col.avg_latency">平均延迟</th>
<th data-i18n="col.operation">操作</th>
<th data-i18n="col.last_action">最后动作</th>
<th data-i18n="col.status">状态</th>
<th data-i18n="col.operation">操作</th>
</tr>
</thead>
<tbody id="events-tbody">
@@ -571,21 +577,21 @@
<!-- 回调跟踪器卡片 -->
<section class="card">
<div class="card-header">
<h2>回调跟踪器</h2>
<span class="badge" id="callback-badge">监控中</span>
<h2 data-i18n="card.callback_tracker">回调跟踪器</h2>
<span class="badge" id="callback-badge" data-i18n="status.monitoring">监控中</span>
</div>
<div class="card-body">
<div class="metrics-grid">
<div class="metric-item">
<div class="metric-label">待处理回调</div>
<div class="metric-label" data-i18n="metric.pending_callbacks">待处理回调</div>
<div class="metric-value" id="callback-pending">--</div>
</div>
<div class="metric-item">
<div class="metric-label">最大重试次数</div>
<div class="metric-label" data-i18n="metric.max_retries">最大重试次数</div>
<div class="metric-value" id="callback-max-retries">--</div>
</div>
<div class="metric-item">
<div class="metric-label">待处理重试</div>
<div class="metric-label" data-i18n="metric.pending_retries">待处理重试</div>
<div class="metric-value" id="callback-pending-retries">--</div>
</div>
</div>
@@ -595,25 +601,25 @@
<!-- 事件去重器卡片 -->
<section class="card">
<div class="card-header">
<h2>事件去重器</h2>
<span class="badge" id="deduplicator-badge">监控中</span>
<h2 data-i18n="card.event_deduplicator">事件去重器</h2>
<span class="badge" id="deduplicator-badge" data-i18n="status.monitoring">监控中</span>
</div>
<div class="card-body">
<div class="metrics-grid">
<div class="metric-item">
<div class="metric-label">总存储条目</div>
<div class="metric-label" data-i18n="metric.total_entries">总存储条目</div>
<div class="metric-value" id="deduplicator-total">--</div>
</div>
<div class="metric-item">
<div class="metric-label">活跃项目</div>
<div class="metric-label" data-i18n="metric.active_items">活跃项目</div>
<div class="metric-value" id="deduplicator-active">--</div>
</div>
<div class="metric-item">
<div class="metric-label">TTL (秒)</div>
<div class="metric-label" data-i18n="metric.ttl_seconds">TTL (秒)</div>
<div class="metric-value" id="deduplicator-ttl">--</div>
</div>
<div class="metric-item">
<div class="metric-label">最大条目</div>
<div class="metric-label" data-i18n="metric.max_entries">最大条目</div>
<div class="metric-value" id="deduplicator-max">--</div>
</div>
</div>
@@ -627,40 +633,40 @@
<div class="card-header">
<h2>Dead Letter Queue</h2>
<div class="dead-letter-actions">
<button class="btn btn-small" onclick="refreshDeadLetterStats()">刷新</button>
<button class="btn btn-small btn-secondary" onclick="clearDeadLetters()">清空</button>
<button class="btn btn-small" onclick="refreshDeadLetterStats()" data-i18n="btn.refresh">刷新</button>
<button class="btn btn-small btn-secondary" onclick="clearDeadLetters()" data-i18n="btn.clear">清空</button>
</div>
</div>
<div class="card-body">
<div class="metrics-grid">
<div class="metric-item">
<div class="metric-label">待写入事件</div>
<div class="metric-label" data-i18n="metric.pending_events">待写入事件</div>
<div class="metric-value" id="dlq-pending">--</div>
</div>
<div class="metric-item">
<div class="metric-label">总事件数</div>
<div class="metric-label" data-i18n="metric.total_events">总事件数</div>
<div class="metric-value" id="dlq-total">--</div>
</div>
<div class="metric-item">
<div class="metric-label">文件大小</div>
<div class="metric-label" data-i18n="metric.file_size">文件大小</div>
<div class="metric-value" id="dlq-file-size">--</div>
</div>
<div class="metric-item">
<div class="metric-label">运行状态</div>
<div class="metric-label" data-i18n="metric.running_status">运行状态</div>
<div class="metric-value" id="dlq-running">--</div>
</div>
</div>
<div class="dead-letter-summary" id="dead-letter-summary" style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e2e8f0;">
<div class="summary-item">
<span class="summary-label">DL总数</span>
<span class="summary-label" data-i18n="metric.dl_total">DL总数</span>
<span class="summary-value" id="dead-letter-total">--</span>
</div>
<div class="summary-item">
<span class="summary-label">最近DL</span>
<span class="summary-label" data-i18n="metric.recent_dl">最近DL</span>
<span class="summary-value" id="dead-letter-recent">--</span>
</div>
<div class="summary-item">
<span class="summary-label">处理成功率</span>
<span class="summary-label" data-i18n="metric.process_success_rate">处理成功率</span>
<span class="summary-value" id="dead-letter-success-rate">--%</span>
</div>
</div>
@@ -668,13 +674,13 @@
<table class="dead-letter-table" id="dead-letter-table">
<thead>
<tr>
<th>ID</th>
<th>事件类型</th>
<th>时间</th>
<th>数据库</th>
<th></th>
<th>错误信息</th>
<th>操作</th>
<th data-i18n="col.id">ID</th>
<th data-i18n="col.event_type">事件类型</th>
<th data-i18n="col.time">时间</th>
<th data-i18n="col.database">数据库</th>
<th data-i18n="col.table"></th>
<th data-i18n="col.error_message">错误信息</th>
<th data-i18n="col.operation">操作</th>
</tr>
</thead>
<tbody id="dead-letter-tbody">
File diff suppressed because it is too large Load Diff
+26 -21
View File
@@ -3,9 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实时日志 - 系统监控</title>
<title data-i18n-page-title="page.live_logs">实时日志 - 系统监控</title>
<link rel="icon" href="/static/swagger/favicon.png" type="image/png">
<link rel="stylesheet" href="/static/monitor/css/monitor.css">
<script src="/static/lib/i18n/i18n.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
@@ -330,19 +331,23 @@
<div class="live-logs-container">
<div class="live-logs-header">
<div class="live-logs-header-left">
<h3>实时日志流</h3>
<h3 data-i18n="page.live_logs">实时日志流</h3>
<div class="connection-status">
<div class="status-dot connecting" id="statusDot"></div>
<span id="statusText">连接中...</span>
<span id="statusText" data-i18n="status.connecting">连接中...</span>
</div>
</div>
<div class="header-controls">
<select id="lang-selector" class="lang-selector" onchange="i18n.switchLanguage(this.value)" style="margin-right: 12px;">
<option value="zh-CN">🇨🇳 中文</option>
<option value="en-US">🇺🇸 English</option>
<option value="de-DE">🇩🇪 Deutsch</option>
</select>
<div class="header-control-group">
<label>级别:</label>
<label data-i18n="filter.level">级别:</label>
<select id="levelFilter" onchange="applyFilters()">
<option value="">全部</option>
<!-- <option value="DEBUG">DEBUG</option> -->
<option value="" data-i18n="filter.level">全部</option>
<option value="INFO" selected>INFO</option>
<option value="WARNING">WARNING</option>
<option value="ERROR">ERROR</option>
@@ -351,11 +356,11 @@
</div>
<div class="header-control-group">
<input type="text" id="searchInput" placeholder="搜索日志..." onkeyup="handleSearch(event)">
<input type="text" id="searchInput" data-i18n-placeholder="filter.keyword_placeholder" placeholder="搜索日志..." onkeyup="handleSearch(event)">
</div>
<div class="header-control-group">
<label>自动滚动</label>
<label data-i18n="other.auto_scroll">自动滚动</label>
<label class="toggle-switch">
<input type="checkbox" id="autoScroll" checked onchange="toggleAutoScroll()">
<span class="toggle-slider"></span>
@@ -363,17 +368,17 @@
</div>
<button class="btn btn-secondary" onclick="togglePause()">
<span id="pauseBtnText">暂停</span>
<span id="pauseBtnText" data-i18n="btn.pause">暂停</span>
</button>
<button class="btn btn-danger" onclick="clearLogs()">清空</button>
<button class="btn btn-danger" onclick="clearLogs()" data-i18n="btn.clear">清空</button>
<button class="btn btn-primary" onclick="openInNewWindow()">新窗口</button>
<button class="btn btn-primary" onclick="openInNewWindow()" data-i18n="btn.new_window">新窗口</button>
</div>
</div>
<div class="log-stats">
<span>总日志: <strong id="totalCount">0</strong></span>
<span data-i18n="metric.total">总日志: <strong id="totalCount">0</strong></span>
<span>DEBUG: <span class="level-count DEBUG" id="debugCount">0</span></span>
<span>INFO: <span class="level-count INFO" id="infoCount">0</span></span>
<span>WARNING: <span class="level-count WARNING" id="warningCount">0</span></span>
@@ -382,7 +387,7 @@
</div>
<div class="logs-container" id="logsContainer">
<div class="no-logs" id="noLogs">
<div class="no-logs" id="noLogs" data-i18n="other.waiting_logs">
正在等待日志数据...
</div>
</div>
@@ -544,7 +549,7 @@
if (isDomReady) {
const statusText = document.getElementById('statusText');
if (statusText) {
statusText.textContent = '连接失败,请刷新页面';
statusText.textContent = i18n.t('error.connection_failed');
}
}
return;
@@ -600,14 +605,14 @@
switch (status) {
case 'connected':
dot.classList.add('connected');
text.textContent = '已连接';
text.textContent = i18n.t('status.connected');
break;
case 'connecting':
dot.classList.add('connecting');
text.textContent = '连接中...';
text.textContent = i18n.t('status.connecting');
break;
case 'disconnected':
text.textContent = '已断开 (5秒后重连)';
text.textContent = i18n.t('status.disconnected') + ' (' + i18n.t('other.auto_reconnect') + ')';
break;
}
}
@@ -722,7 +727,7 @@
}
if (logs.length === 0) {
container.innerHTML = '<div class="no-logs" id="noLogs">正在等待日志数据...</div>';
container.innerHTML = '<div class="no-logs" id="noLogs">' + i18n.t('other.waiting_logs') + '</div>';
lastRenderIndex = 0;
return;
}
@@ -738,7 +743,7 @@
});
if (filteredLogs.length === 0) {
container.innerHTML = '<div class="no-logs" id="noLogs">没有匹配的日志</div>';
container.innerHTML = '<div class="no-logs" id="noLogs">' + i18n.t('other.no_matching_logs') + '</div>';
lastRenderIndex = 0;
return;
}
@@ -811,7 +816,7 @@
const autoScrollIndicator = document.getElementById('autoScrollIndicator');
if (pauseBtnText) {
pauseBtnText.textContent = isPaused ? '继续' : '暂停';
pauseBtnText.textContent = isPaused ? i18n.t('btn.resume') : i18n.t('btn.pause');
}
if (autoScrollIndicator) {
autoScrollIndicator.classList.toggle('show', isPaused);
@@ -856,7 +861,7 @@
updateStats();
const logsContainer = document.getElementById('logsContainer');
if (logsContainer) {
logsContainer.innerHTML = '<div class="no-logs">日志已清空</div>';
logsContainer.innerHTML = '<div class="no-logs">' + i18n.t('success.logs_cleared') + '</div>';
}
}