xiaoyukkkk commited on
Commit
f0bf113
·
verified ·
1 Parent(s): 8fb52da

Upload 9 files

Browse files
Files changed (2) hide show
  1. Dockerfile +4 -2
  2. main.py +151 -139
Dockerfile CHANGED
@@ -8,12 +8,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
8
  && apt-get autoremove -y \
9
  && rm -rf /var/lib/apt/lists/*
10
  COPY main.py .
11
- # 复制 uptime_tracker 模块
12
- COPY uptime_tracker.py .
13
  # 复制 core 模块
14
  COPY core ./core
15
  # 复制 util 目录
16
  COPY util ./util
 
 
 
 
17
  # 创建数据目录
18
  RUN mkdir -p ./data/images
19
  # 声明数据卷(运行时需要 -v 挂载才能持久化)
 
8
  && apt-get autoremove -y \
9
  && rm -rf /var/lib/apt/lists/*
10
  COPY main.py .
 
 
11
  # 复制 core 模块
12
  COPY core ./core
13
  # 复制 util 目录
14
  COPY util ./util
15
+ # 复制 templates 目录
16
+ COPY templates ./templates
17
+ # 复制 static 目录
18
+ COPY static ./static
19
  # 创建数据目录
20
  RUN mkdir -p ./data/images
21
  # 声明数据卷(运行时需要 -v 挂载才能持久化)
main.py CHANGED
@@ -1,4 +1,4 @@
1
- import json, time, os, asyncio, uuid, ssl, re, yaml
2
  from datetime import datetime, timezone, timedelta
3
  from typing import List, Optional, Union, Dict, Any
4
  from pathlib import Path
@@ -15,6 +15,27 @@ from util.streaming_parser import parse_json_array_stream_async
15
  from collections import deque
16
  from threading import Lock
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  # 导入认证模块
19
  from core.auth import verify_api_key
20
  from core.session_auth import is_logged_in, login_user, logout_user, require_login, generate_session_secret
@@ -45,7 +66,12 @@ from core.account import (
45
  )
46
 
47
  # 导入 Uptime 追踪器
48
- import uptime_tracker
 
 
 
 
 
49
 
50
  # ---------- 日志配置 ----------
51
 
@@ -54,7 +80,6 @@ log_buffer = deque(maxlen=3000)
54
  log_lock = Lock()
55
 
56
  # 统计数据持久化
57
- STATS_FILE = "data/stats.json"
58
  stats_lock = asyncio.Lock() # 改为异步锁
59
 
60
  async def load_stats():
@@ -118,102 +143,32 @@ memory_handler = MemoryLogHandler()
118
  memory_handler.setFormatter(logging.Formatter("%(asctime)s | %(levelname)s | %(message)s", datefmt="%H:%M:%S"))
119
  logger.addHandler(memory_handler)
120
 
121
- load_dotenv()
122
-
123
- # ---------- YAML 配置系统 ----------
124
- SETTINGS_FILE = "data/settings.yaml"
125
-
126
- # 默认配置
127
- DEFAULT_SETTINGS = {
128
- "basic": {
129
- "api_key": "",
130
- "base_url": "",
131
- "proxy": ""
132
- },
133
- "image_generation": {
134
- "enabled": True,
135
- "supported_models": ["gemini-3-pro-preview"],
136
- "last_updated": None
137
- },
138
- "retry": {
139
- "max_new_session_tries": 5,
140
- "max_request_retries": 3,
141
- "max_account_switch_tries": 5,
142
- "account_failure_threshold": 3,
143
- "rate_limit_cooldown_seconds": 600,
144
- "session_cache_ttl_seconds": 3600
145
- },
146
- "public_display": {
147
- "logo_url": "",
148
- "chat_url": ""
149
- },
150
- "session": {
151
- "expire_hours": 24
152
- }
153
- }
154
-
155
- def load_settings() -> dict:
156
- """加载 YAML 配置"""
157
- Path("data").mkdir(exist_ok=True)
158
- if Path(SETTINGS_FILE).exists():
159
- try:
160
- with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
161
- settings = yaml.safe_load(f) or {}
162
- # 合并默认配置(确保新增配置项有默认值)
163
- for key, value in DEFAULT_SETTINGS.items():
164
- if key not in settings:
165
- settings[key] = value
166
- elif isinstance(value, dict):
167
- for k, v in value.items():
168
- if k not in settings[key]:
169
- settings[key][k] = v
170
- return settings
171
- except Exception as e:
172
- print(f"[WARN] 加载配置文件失败: {e},使用默认配置")
173
- # 创建默认配置文件
174
- save_settings(DEFAULT_SETTINGS)
175
- return DEFAULT_SETTINGS.copy()
176
-
177
- def save_settings(settings: dict):
178
- """保存 YAML 配置"""
179
- Path("data").mkdir(exist_ok=True)
180
- with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
181
- yaml.dump(settings, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
182
-
183
- # 加载配置
184
- settings = load_settings()
185
-
186
- # ---------- 配置(从 settings.yaml 读取)----------
187
- PROXY = settings["basic"]["proxy"]
188
  TIMEOUT_SECONDS = 600
189
- API_KEY = os.getenv("API_KEY", settings["basic"]["api_key"]) # 优先使用环境变量
190
- PATH_PREFIX = os.getenv("PATH_PREFIX", "") # 路径前缀保留环境变量(影响路由注册)
191
- ADMIN_KEY = os.getenv("ADMIN_KEY", "") # 管理员密钥保留环境变量(安全相关)
192
- BASE_URL = settings["basic"]["base_url"]
193
- SESSION_SECRET_KEY = os.getenv("SESSION_SECRET_KEY", generate_session_secret()) # 自动生成
194
- SESSION_EXPIRE_HOURS = settings["session"]["expire_hours"]
 
195
 
196
  # ---------- 公开展示配置 ----------
197
- LOGO_URL = settings["public_display"]["logo_url"]
198
- CHAT_URL = settings["public_display"]["chat_url"]
199
 
200
  # ---------- 图片生成配置 ----------
201
- IMAGE_GENERATION_ENABLED = settings["image_generation"]["enabled"]
202
- IMAGE_GENERATION_MODELS = settings["image_generation"]["supported_models"]
203
 
204
- # ---------- 图片存储配置 ----------
205
- if os.path.exists("/data"):
206
- IMAGE_DIR = "/data/images" # HF Pro持久化存储
207
- else:
208
- IMAGE_DIR = "./data/images" # 本地持久化存储
209
-
210
- # ---------- 重试配置(从 settings.yaml 读取)----------
211
- MAX_NEW_SESSION_TRIES = settings["retry"]["max_new_session_tries"]
212
- MAX_REQUEST_RETRIES = settings["retry"]["max_request_retries"]
213
- MAX_ACCOUNT_SWITCH_TRIES = settings["retry"]["max_account_switch_tries"]
214
- ACCOUNT_FAILURE_THRESHOLD = settings["retry"]["account_failure_threshold"]
215
- RATE_LIMIT_COOLDOWN_SECONDS = settings["retry"]["rate_limit_cooldown_seconds"]
216
- SESSION_CACHE_TTL_SECONDS = settings["retry"]["session_cache_ttl_seconds"]
217
 
218
  # ---------- 模型映射配置 ----------
219
  MODEL_MAPPING = {
@@ -299,6 +254,17 @@ logger.info("[SYSTEM] 系统初始化完成")
299
  # ---------- OpenAI 兼容接口 ----------
300
  app = FastAPI(title="Gemini-Business OpenAI Gateway")
301
 
 
 
 
 
 
 
 
 
 
 
 
302
  # ---------- Session 中间件配置 ----------
303
  from starlette.middleware.sessions import SessionMiddleware
304
  app.add_middleware(
@@ -363,6 +329,15 @@ async def startup_event():
363
  """应用启动时初始化后台任务"""
364
  global global_stats
365
 
 
 
 
 
 
 
 
 
 
366
  # 加载统计数据
367
  global_stats = await load_stats()
368
  logger.info(f"[SYSTEM] 统计数据已加载: {global_stats['total_requests']} 次请求, {global_stats['total_visitors']} 位访客")
@@ -375,10 +350,6 @@ async def startup_event():
375
  asyncio.create_task(uptime_tracker.uptime_aggregation_task())
376
  logger.info("[SYSTEM] Uptime 数据聚合任务已启动(间隔: 240秒)")
377
 
378
- # ---------- 导入模板模块 ----------
379
- # 注意:必须在所有全局变量初始化之后导入,避免循环依赖
380
- from core import templates
381
-
382
  # ---------- 日志脱敏函数 ----------
383
  def get_sanitized_logs(limit: int = 100) -> list:
384
  """获取脱敏后的日志列表,按请求ID分组并提取关键事件"""
@@ -611,6 +582,24 @@ def create_chunk(id: str, created: int, model: str, delta: dict, finish_reason:
611
  }
612
  return json.dumps(chunk)
613
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
  @app.get("/")
615
  async def home(request: Request):
616
  """首页 - 根据PATH_PREFIX配置决定行为"""
@@ -620,7 +609,8 @@ async def home(request: Request):
620
  else:
621
  # 未设置PATH_PREFIX(公开模式),根据登录状态重定向
622
  if is_logged_in(request):
623
- return HTMLResponse(content=templates.generate_admin_html(request, multi_account_mgr))
 
624
  else:
625
  return RedirectResponse(url="/login", status_code=302)
626
 
@@ -630,7 +620,7 @@ async def home(request: Request):
630
  @app.get("/login")
631
  async def admin_login_get(request: Request, error: str = None):
632
  """登录页面"""
633
- return await templates.get_login_html(request, error)
634
 
635
  @app.post("/login")
636
  async def admin_login_post(request: Request, admin_key: str = Form(...)):
@@ -641,7 +631,7 @@ async def admin_login_post(request: Request, admin_key: str = Form(...)):
641
  return RedirectResponse(url="/", status_code=302)
642
  else:
643
  logger.warning(f"[AUTH] 登录失败 - 密钥错误")
644
- return await templates.get_login_html(request, error="密钥错误,请重试")
645
 
646
  @app.post("/logout")
647
  @require_login(redirect_to_login=False)
@@ -656,7 +646,7 @@ if PATH_PREFIX:
656
  @app.get(f"/{PATH_PREFIX}/login")
657
  async def admin_login_get_prefixed(request: Request, error: str = None):
658
  """登录页面(带前缀)"""
659
- return await templates.get_login_html(request, error)
660
 
661
  @app.post(f"/{PATH_PREFIX}/login")
662
  async def admin_login_post_prefixed(request: Request, admin_key: str = Form(...)):
@@ -667,7 +657,7 @@ if PATH_PREFIX:
667
  return RedirectResponse(url=f"/{PATH_PREFIX}", status_code=302)
668
  else:
669
  logger.warning(f"[AUTH] 登录失败 - 密钥错误")
670
- return await templates.get_login_html(request, error="密钥错误,请重试")
671
 
672
  @app.post(f"/{PATH_PREFIX}/logout")
673
  @require_login(redirect_to_login=False)
@@ -684,8 +674,8 @@ if PATH_PREFIX:
684
  @require_login()
685
  async def admin_home_no_prefix(request: Request):
686
  """管理首页"""
687
- html_content = templates.generate_admin_html(request, multi_account_mgr, show_hide_tip=False)
688
- return HTMLResponse(content=html_content)
689
 
690
  # 带PATH_PREFIX的管理端点(如果配置了PATH_PREFIX)
691
  if PATH_PREFIX:
@@ -809,33 +799,45 @@ async def admin_enable_account(request: Request, account_id: str):
809
  @require_login()
810
  async def admin_get_settings(request: Request):
811
  """获取系统设置"""
812
- return settings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
813
 
814
  @app.put("/admin/settings")
815
  @require_login()
816
  async def admin_update_settings(request: Request, new_settings: dict = Body(...)):
817
  """更新系统设置"""
818
- global settings, API_KEY, PROXY, BASE_URL, LOGO_URL, CHAT_URL
819
  global IMAGE_GENERATION_ENABLED, IMAGE_GENERATION_MODELS
820
  global MAX_NEW_SESSION_TRIES, MAX_REQUEST_RETRIES, MAX_ACCOUNT_SWITCH_TRIES
821
  global ACCOUNT_FAILURE_THRESHOLD, RATE_LIMIT_COOLDOWN_SECONDS, SESSION_CACHE_TTL_SECONDS
822
  global SESSION_EXPIRE_HOURS, multi_account_mgr, http_client
823
 
824
  try:
825
- # 合并设置(保留未修改的项)
826
- for key, value in new_settings.items():
827
- if key in settings and isinstance(value, dict):
828
- settings[key].update(value)
829
- else:
830
- settings[key] = value
831
-
832
- # 添加更新时间
833
- if "image_generation" in settings:
834
- settings["image_generation"]["last_updated"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
835
-
836
- # 保存到文件
837
- save_settings(settings)
838
-
839
  # 保存旧配置用于对比
840
  old_proxy = PROXY
841
  old_retry_config = {
@@ -844,21 +846,27 @@ async def admin_update_settings(request: Request, new_settings: dict = Body(...)
844
  "session_cache_ttl_seconds": SESSION_CACHE_TTL_SECONDS
845
  }
846
 
 
 
 
 
 
 
847
  # 更新全局变量(实时生效)
848
- API_KEY = settings["basic"]["api_key"]
849
- PROXY = settings["basic"]["proxy"]
850
- BASE_URL = settings["basic"]["base_url"]
851
- LOGO_URL = settings["public_display"]["logo_url"]
852
- CHAT_URL = settings["public_display"]["chat_url"]
853
- IMAGE_GENERATION_ENABLED = settings["image_generation"]["enabled"]
854
- IMAGE_GENERATION_MODELS = settings["image_generation"]["supported_models"]
855
- MAX_NEW_SESSION_TRIES = settings["retry"]["max_new_session_tries"]
856
- MAX_REQUEST_RETRIES = settings["retry"]["max_request_retries"]
857
- MAX_ACCOUNT_SWITCH_TRIES = settings["retry"]["max_account_switch_tries"]
858
- ACCOUNT_FAILURE_THRESHOLD = settings["retry"]["account_failure_threshold"]
859
- RATE_LIMIT_COOLDOWN_SECONDS = settings["retry"]["rate_limit_cooldown_seconds"]
860
- SESSION_CACHE_TTL_SECONDS = settings["retry"]["session_cache_ttl_seconds"]
861
- SESSION_EXPIRE_HOURS = settings["session"]["expire_hours"]
862
 
863
  # 检查是否需要重建 HTTP 客户端(代理变化)
864
  if old_proxy != PROXY:
@@ -960,7 +968,7 @@ async def admin_clear_logs(request: Request, confirm: str = None):
960
  @require_login()
961
  async def admin_logs_html_route(request: Request):
962
  """返回美化的 HTML 日志查看界面"""
963
- return await templates.admin_logs_html_no_auth(request)
964
 
965
  # 带PATH_PREFIX的管理API端点(如果配置了PATH_PREFIX)
966
  if PATH_PREFIX:
@@ -1590,9 +1598,9 @@ async def get_public_uptime(days: int = 90):
1590
  return await uptime_tracker.get_uptime_summary(days)
1591
 
1592
  @app.get("/public/uptime/html")
1593
- async def get_public_uptime_html():
1594
  """Uptime 监控页面(类似 status.openai.com)"""
1595
- return await templates.get_uptime_html()
1596
 
1597
  @app.get("/public/stats")
1598
  async def get_public_stats():
@@ -1677,9 +1685,13 @@ async def get_public_logs(request: Request, limit: int = 100):
1677
  return {"total": 0, "logs": [], "error": str(e)}
1678
 
1679
  @app.get("/public/log/html")
1680
- async def get_public_logs_html():
1681
  """公开的脱敏日志查看器"""
1682
- return await templates.get_public_logs_html()
 
 
 
 
1683
 
1684
  # ---------- 全局 404 处理(必须在最后) ----------
1685
 
 
1
+ import json, time, os, asyncio, uuid, ssl, re, yaml, shutil
2
  from datetime import datetime, timezone, timedelta
3
  from typing import List, Optional, Union, Dict, Any
4
  from pathlib import Path
 
15
  from collections import deque
16
  from threading import Lock
17
 
18
+ # ---------- 数据目录配置 ----------
19
+ # 自动检测环境:HF Spaces Pro 使用 /data,本地使用 ./data
20
+ if os.path.exists("/data"):
21
+ DATA_DIR = "/data" # HF Pro 持久化存储
22
+ logger_prefix = "[HF-PRO]"
23
+ else:
24
+ DATA_DIR = "./data" # 本地持久化存储
25
+ logger_prefix = "[LOCAL]"
26
+
27
+ # 确保数据目录存在
28
+ os.makedirs(DATA_DIR, exist_ok=True)
29
+
30
+ # 统一的数据文件路径
31
+ ACCOUNTS_FILE = os.path.join(DATA_DIR, "accounts.json")
32
+ SETTINGS_FILE = os.path.join(DATA_DIR, "settings.yaml")
33
+ STATS_FILE = os.path.join(DATA_DIR, "stats.json")
34
+ IMAGE_DIR = os.path.join(DATA_DIR, "images")
35
+
36
+ # 确保图片目录存在
37
+ os.makedirs(IMAGE_DIR, exist_ok=True)
38
+
39
  # 导入认证模块
40
  from core.auth import verify_api_key
41
  from core.session_auth import is_logged_in, login_user, logout_user, require_login, generate_session_secret
 
66
  )
67
 
68
  # 导入 Uptime 追踪器
69
+ from core import uptime as uptime_tracker
70
+
71
+ # 导入配置管理和模板系统
72
+ from fastapi.templating import Jinja2Templates
73
+ from core.config import config_manager, config
74
+ from util.template_helpers import prepare_admin_template_data
75
 
76
  # ---------- 日志配置 ----------
77
 
 
80
  log_lock = Lock()
81
 
82
  # 统计数据持久化
 
83
  stats_lock = asyncio.Lock() # 改为异步锁
84
 
85
  async def load_stats():
 
143
  memory_handler.setFormatter(logging.Formatter("%(asctime)s | %(levelname)s | %(message)s", datefmt="%H:%M:%S"))
144
  logger.addHandler(memory_handler)
145
 
146
+ # ---------- 配置管理(使用统一配置系统)----------
147
+ # 所有配置通过 config_manager 访问,优先级:环境变量 > YAML > 默认值
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  TIMEOUT_SECONDS = 600
149
+ API_KEY = config.basic.api_key
150
+ PATH_PREFIX = config.security.path_prefix
151
+ ADMIN_KEY = config.security.admin_key
152
+ PROXY = config.basic.proxy
153
+ BASE_URL = config.basic.base_url
154
+ SESSION_SECRET_KEY = config.security.session_secret_key
155
+ SESSION_EXPIRE_HOURS = config.session.expire_hours
156
 
157
  # ---------- 公开展示配置 ----------
158
+ LOGO_URL = config.public_display.logo_url
159
+ CHAT_URL = config.public_display.chat_url
160
 
161
  # ---------- 图片生成配置 ----------
162
+ IMAGE_GENERATION_ENABLED = config.image_generation.enabled
163
+ IMAGE_GENERATION_MODELS = config.image_generation.supported_models
164
 
165
+ # ---------- 重试配置 ----------
166
+ MAX_NEW_SESSION_TRIES = config.retry.max_new_session_tries
167
+ MAX_REQUEST_RETRIES = config.retry.max_request_retries
168
+ MAX_ACCOUNT_SWITCH_TRIES = config.retry.max_account_switch_tries
169
+ ACCOUNT_FAILURE_THRESHOLD = config.retry.account_failure_threshold
170
+ RATE_LIMIT_COOLDOWN_SECONDS = config.retry.rate_limit_cooldown_seconds
171
+ SESSION_CACHE_TTL_SECONDS = config.retry.session_cache_ttl_seconds
 
 
 
 
 
 
172
 
173
  # ---------- 模型映射配置 ----------
174
  MODEL_MAPPING = {
 
254
  # ---------- OpenAI 兼容接口 ----------
255
  app = FastAPI(title="Gemini-Business OpenAI Gateway")
256
 
257
+ # ---------- 模板系统配置 ----------
258
+ templates = Jinja2Templates(directory="templates")
259
+
260
+ # 开发模式:支持热更新
261
+ if os.getenv("ENV") == "development":
262
+ templates.env.auto_reload = True
263
+ logger.info("[SYSTEM] 模板热更新已启用(开发模式)")
264
+
265
+ # 挂载静态文件
266
+ app.mount("/static", StaticFiles(directory="static"), name="static")
267
+
268
  # ---------- Session 中间件配置 ----------
269
  from starlette.middleware.sessions import SessionMiddleware
270
  app.add_middleware(
 
329
  """应用启动时初始化后台任务"""
330
  global global_stats
331
 
332
+ # 文件迁移逻辑:将根目录的旧文件迁移到 data 目录
333
+ old_accounts = "accounts.json"
334
+ if os.path.exists(old_accounts) and not os.path.exists(ACCOUNTS_FILE):
335
+ try:
336
+ shutil.copy(old_accounts, ACCOUNTS_FILE)
337
+ logger.info(f"{logger_prefix} 已迁移 {old_accounts} -> {ACCOUNTS_FILE}")
338
+ except Exception as e:
339
+ logger.warning(f"{logger_prefix} 文件迁移失败: {e}")
340
+
341
  # 加载统计数据
342
  global_stats = await load_stats()
343
  logger.info(f"[SYSTEM] 统计数据已加载: {global_stats['total_requests']} 次请求, {global_stats['total_visitors']} 位访客")
 
350
  asyncio.create_task(uptime_tracker.uptime_aggregation_task())
351
  logger.info("[SYSTEM] Uptime 数据聚合任务已启动(间隔: 240秒)")
352
 
 
 
 
 
353
  # ---------- 日志脱敏函数 ----------
354
  def get_sanitized_logs(limit: int = 100) -> list:
355
  """获取脱敏后的日志列表,按请求ID分组并提取关键事件"""
 
582
  }
583
  return json.dumps(chunk)
584
 
585
+ # ---------- 辅助函数 ----------
586
+
587
+ def get_admin_template_data(request: Request):
588
+ """获取管理页面模板数据(避免重复代码)"""
589
+ return prepare_admin_template_data(
590
+ request, multi_account_mgr, log_buffer, log_lock,
591
+ api_key=API_KEY, base_url=BASE_URL, proxy=PROXY,
592
+ logo_url=LOGO_URL, chat_url=CHAT_URL, path_prefix=PATH_PREFIX,
593
+ max_new_session_tries=MAX_NEW_SESSION_TRIES,
594
+ max_request_retries=MAX_REQUEST_RETRIES,
595
+ max_account_switch_tries=MAX_ACCOUNT_SWITCH_TRIES,
596
+ account_failure_threshold=ACCOUNT_FAILURE_THRESHOLD,
597
+ rate_limit_cooldown_seconds=RATE_LIMIT_COOLDOWN_SECONDS,
598
+ session_cache_ttl_seconds=SESSION_CACHE_TTL_SECONDS
599
+ )
600
+
601
+ # ---------- 路由定义 ----------
602
+
603
  @app.get("/")
604
  async def home(request: Request):
605
  """首页 - 根据PATH_PREFIX配置决定行为"""
 
609
  else:
610
  # 未设置PATH_PREFIX(公开模式),根据登录状态重定向
611
  if is_logged_in(request):
612
+ template_data = get_admin_template_data(request)
613
+ return templates.TemplateResponse("admin/index.html", template_data)
614
  else:
615
  return RedirectResponse(url="/login", status_code=302)
616
 
 
620
  @app.get("/login")
621
  async def admin_login_get(request: Request, error: str = None):
622
  """登录页面"""
623
+ return templates.TemplateResponse("auth/login.html", {"request": request, "error": error})
624
 
625
  @app.post("/login")
626
  async def admin_login_post(request: Request, admin_key: str = Form(...)):
 
631
  return RedirectResponse(url="/", status_code=302)
632
  else:
633
  logger.warning(f"[AUTH] 登录失败 - 密钥错误")
634
+ return templates.TemplateResponse("auth/login.html", {"request": request, "error": "密钥错误,请重试"})
635
 
636
  @app.post("/logout")
637
  @require_login(redirect_to_login=False)
 
646
  @app.get(f"/{PATH_PREFIX}/login")
647
  async def admin_login_get_prefixed(request: Request, error: str = None):
648
  """登录页面(带前缀)"""
649
+ return templates.TemplateResponse("auth/login.html", {"request": request, "error": error})
650
 
651
  @app.post(f"/{PATH_PREFIX}/login")
652
  async def admin_login_post_prefixed(request: Request, admin_key: str = Form(...)):
 
657
  return RedirectResponse(url=f"/{PATH_PREFIX}", status_code=302)
658
  else:
659
  logger.warning(f"[AUTH] 登录失败 - 密钥错误")
660
+ return templates.TemplateResponse("auth/login.html", {"request": request, "error": "密钥错误,请重试"})
661
 
662
  @app.post(f"/{PATH_PREFIX}/logout")
663
  @require_login(redirect_to_login=False)
 
674
  @require_login()
675
  async def admin_home_no_prefix(request: Request):
676
  """管理首页"""
677
+ template_data = get_admin_template_data(request)
678
+ return templates.TemplateResponse("admin/index.html", template_data)
679
 
680
  # 带PATH_PREFIX的管理端点(如果配置了PATH_PREFIX)
681
  if PATH_PREFIX:
 
799
  @require_login()
800
  async def admin_get_settings(request: Request):
801
  """获取系统设置"""
802
+ # 返回当前配置(转换为字典格式)
803
+ return {
804
+ "basic": {
805
+ "api_key": config.basic.api_key,
806
+ "base_url": config.basic.base_url,
807
+ "proxy": config.basic.proxy
808
+ },
809
+ "image_generation": {
810
+ "enabled": config.image_generation.enabled,
811
+ "supported_models": config.image_generation.supported_models
812
+ },
813
+ "retry": {
814
+ "max_new_session_tries": config.retry.max_new_session_tries,
815
+ "max_request_retries": config.retry.max_request_retries,
816
+ "max_account_switch_tries": config.retry.max_account_switch_tries,
817
+ "account_failure_threshold": config.retry.account_failure_threshold,
818
+ "rate_limit_cooldown_seconds": config.retry.rate_limit_cooldown_seconds,
819
+ "session_cache_ttl_seconds": config.retry.session_cache_ttl_seconds
820
+ },
821
+ "public_display": {
822
+ "logo_url": config.public_display.logo_url,
823
+ "chat_url": config.public_display.chat_url
824
+ },
825
+ "session": {
826
+ "expire_hours": config.session.expire_hours
827
+ }
828
+ }
829
 
830
  @app.put("/admin/settings")
831
  @require_login()
832
  async def admin_update_settings(request: Request, new_settings: dict = Body(...)):
833
  """更新系统设置"""
834
+ global API_KEY, PROXY, BASE_URL, LOGO_URL, CHAT_URL
835
  global IMAGE_GENERATION_ENABLED, IMAGE_GENERATION_MODELS
836
  global MAX_NEW_SESSION_TRIES, MAX_REQUEST_RETRIES, MAX_ACCOUNT_SWITCH_TRIES
837
  global ACCOUNT_FAILURE_THRESHOLD, RATE_LIMIT_COOLDOWN_SECONDS, SESSION_CACHE_TTL_SECONDS
838
  global SESSION_EXPIRE_HOURS, multi_account_mgr, http_client
839
 
840
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
841
  # 保存旧配置用于对比
842
  old_proxy = PROXY
843
  old_retry_config = {
 
846
  "session_cache_ttl_seconds": SESSION_CACHE_TTL_SECONDS
847
  }
848
 
849
+ # 保存到 YAML
850
+ config_manager.save_yaml(new_settings)
851
+
852
+ # 热更新配置
853
+ config_manager.reload()
854
+
855
  # 更新全局变量(实时生效)
856
+ API_KEY = config.basic.api_key
857
+ PROXY = config.basic.proxy
858
+ BASE_URL = config.basic.base_url
859
+ LOGO_URL = config.public_display.logo_url
860
+ CHAT_URL = config.public_display.chat_url
861
+ IMAGE_GENERATION_ENABLED = config.image_generation.enabled
862
+ IMAGE_GENERATION_MODELS = config.image_generation.supported_models
863
+ MAX_NEW_SESSION_TRIES = config.retry.max_new_session_tries
864
+ MAX_REQUEST_RETRIES = config.retry.max_request_retries
865
+ MAX_ACCOUNT_SWITCH_TRIES = config.retry.max_account_switch_tries
866
+ ACCOUNT_FAILURE_THRESHOLD = config.retry.account_failure_threshold
867
+ RATE_LIMIT_COOLDOWN_SECONDS = config.retry.rate_limit_cooldown_seconds
868
+ SESSION_CACHE_TTL_SECONDS = config.retry.session_cache_ttl_seconds
869
+ SESSION_EXPIRE_HOURS = config.session.expire_hours
870
 
871
  # 检查是否需要重建 HTTP 客户端(代理变化)
872
  if old_proxy != PROXY:
 
968
  @require_login()
969
  async def admin_logs_html_route(request: Request):
970
  """返回美化的 HTML 日志查看界面"""
971
+ return templates.TemplateResponse("admin/logs.html", {"request": request})
972
 
973
  # 带PATH_PREFIX的管理API端点(如果配置了PATH_PREFIX)
974
  if PATH_PREFIX:
 
1598
  return await uptime_tracker.get_uptime_summary(days)
1599
 
1600
  @app.get("/public/uptime/html")
1601
+ async def get_public_uptime_html(request: Request):
1602
  """Uptime 监控页面(类似 status.openai.com)"""
1603
+ return templates.TemplateResponse("public/uptime.html", {"request": request})
1604
 
1605
  @app.get("/public/stats")
1606
  async def get_public_stats():
 
1685
  return {"total": 0, "logs": [], "error": str(e)}
1686
 
1687
  @app.get("/public/log/html")
1688
+ async def get_public_logs_html(request: Request):
1689
  """公开的脱敏日志查看器"""
1690
+ return templates.TemplateResponse("public/logs.html", {
1691
+ "request": request,
1692
+ "logo_url": LOGO_URL,
1693
+ "chat_url": CHAT_URL
1694
+ })
1695
 
1696
  # ---------- 全局 404 处理(必须在最后) ----------
1697