Spaces:
Paused
Paused
| from flask import Flask, render_template, request, redirect, url_for, session, jsonify | |
| from dotenv import load_dotenv | |
| from huggingface_hub import HfApi | |
| from functools import wraps | |
| from datetime import datetime, timedelta | |
| from concurrent.futures import ThreadPoolExecutor | |
| from config import USERNAME, PASSWORD, HF_TOKENS | |
| from api import api as api_blueprint | |
| import concurrent.futures | |
| import threading | |
| import logging | |
| import time | |
| import os | |
| # 配置日志 | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # 加载环境变量 | |
| load_dotenv() | |
| app = Flask(__name__) | |
| app.secret_key = os.urandom(24) | |
| app.register_blueprint(api_blueprint) | |
| # 缓存管理 | |
| class SpaceCache: | |
| def __init__(self): | |
| self.spaces = {} | |
| self.last_update = None | |
| self.lock = threading.Lock() | |
| def update_all(self, spaces_data): | |
| with self.lock: | |
| self.spaces = {space['repo_id']: space for space in spaces_data} | |
| self.last_update = datetime.now() | |
| def get_all(self): | |
| with self.lock: | |
| return list(self.spaces.values()) if self.spaces else [] | |
| def is_expired(self, expire_minutes=5): | |
| if not self.last_update: | |
| return True | |
| return datetime.now() - self.last_update > timedelta(minutes=expire_minutes) | |
| space_cache = SpaceCache() | |
| # 登录验证装饰器 | |
| def login_required(f): | |
| def decorated_function(*args, **kwargs): | |
| if 'authenticated' not in session or not session['authenticated']: | |
| return redirect(url_for('login')) | |
| return f(*args, **kwargs) | |
| return decorated_function | |
| def process_single_space(space, hf_api, username, token): | |
| try: | |
| space_info = hf_api.space_info(repo_id=space.id) | |
| space_runtime = space_info.runtime | |
| status = "未知状态" | |
| if space_runtime: | |
| status = space_runtime.stage if hasattr(space_runtime, 'stage') else "未知状态" | |
| return { | |
| "repo_id": space_info.id, | |
| "name": space_info.cardData.get('title') or space_info.id.split('/')[-1], | |
| "owner": space_info.author, | |
| "username": username, | |
| "token": token, | |
| "url": f"https://{space_info.author}-{space_info.id.split('/')[-1]}.hf.space", | |
| "status": status, | |
| "last_modified": space_info.lastModified.strftime("%Y-%m-%d %H:%M:%S") if space_info.lastModified else "未知", | |
| "created_at": space_info.created_at.strftime("%Y-%m-%d %H:%M:%S") if space_info.created_at else "未知", | |
| "sdk": space_info.sdk, | |
| "tags": space_info.tags, | |
| "private": space_info.private, | |
| "app_port": space_info.cardData.get('app_port', '未知') | |
| } | |
| except Exception as e: | |
| logger.error(f"处理 Space {space.id} 时出错: {e}") | |
| return None | |
| def get_all_user_spaces(): | |
| # 检查缓存是否有效 | |
| if not space_cache.is_expired(): | |
| logger.info("从缓存获取 Spaces 数据") | |
| return space_cache.get_all() | |
| all_spaces = [] | |
| with ThreadPoolExecutor(max_workers=10) as executor: | |
| for token in HF_TOKENS: | |
| try: | |
| hf_api = HfApi(token=token) | |
| user_info = hf_api.whoami() | |
| username = user_info["name"] | |
| logger.info(f"获取到用户信息: {username}") | |
| spaces = list(hf_api.list_spaces(author=username)) | |
| logger.info(f"获取到 {len(spaces)} 个 Spaces") | |
| # 并行处理每个space | |
| future_to_space = { | |
| executor.submit(process_single_space, space, hf_api, username, token): space | |
| for space in spaces | |
| } | |
| for future in concurrent.futures.as_completed(future_to_space): | |
| space_data = future.result() | |
| if space_data: | |
| all_spaces.append(space_data) | |
| except Exception as e: | |
| logger.error(f"获取 Spaces 列表失败 (token: {token[:5]}...): {e}") | |
| import traceback | |
| traceback.print_exc() | |
| # 按名称排序 | |
| all_spaces.sort(key=lambda x: x['name'].lower()) | |
| # 更新缓存 | |
| space_cache.update_all(all_spaces) | |
| logger.info(f"总共获取到 {len(all_spaces)} 个 Spaces") | |
| return all_spaces | |
| # 后台更新缓存的函数 | |
| def background_cache_update(): | |
| while True: | |
| try: | |
| _ = get_all_user_spaces() | |
| time.sleep(300) # 每5分钟更新一次 | |
| except Exception as e: | |
| logger.error(f"后台更新缓存失败: {e}") | |
| time.sleep(60) # 出错后等待1分钟再试 | |
| # 启动后台更新线程 | |
| update_thread = threading.Thread(target=background_cache_update, daemon=True) | |
| update_thread.start() | |
| def login(): | |
| if 'authenticated' in session and session['authenticated']: | |
| return redirect(url_for('dashboard')) | |
| if request.method == "POST": | |
| username = request.form.get("username") | |
| password = request.form.get("password") | |
| if username == USERNAME and password == PASSWORD: | |
| session['authenticated'] = True | |
| return redirect(url_for("dashboard")) | |
| else: | |
| return render_template("index.html", error="用户名或密码错误") | |
| return render_template("index.html", error=None) | |
| def logout(): | |
| session.clear() | |
| return redirect(url_for('login')) | |
| def dashboard(): | |
| spaces = get_all_user_spaces() | |
| logger.info(f"Dashboard 显示 {len(spaces)} 个 Spaces") | |
| return render_template("dashboard.html", spaces=spaces) | |
| def get_space_status(repo_id): | |
| spaces = get_all_user_spaces() | |
| space = next((s for s in spaces if s["repo_id"] == repo_id), None) | |
| if not space: | |
| return jsonify({"error": "Space not found"}), 404 | |
| return jsonify({ | |
| "id": repo_id, | |
| "status": space["status"] | |
| }) | |
| def restart_space(repo_id, token): | |
| try: | |
| hf_api = HfApi(token=token) | |
| hf_api.restart_space(repo_id=repo_id) | |
| return f"成功重启 Space: {repo_id}" | |
| except Exception as e: | |
| return f"重启 Space {repo_id} 失败: {e}" | |
| def rebuild_space(repo_id, token): | |
| try: | |
| hf_api = HfApi(token=token) | |
| hf_api.restart_space(repo_id=repo_id, factory_reboot=True) | |
| return f"成功重建 Space: {repo_id}" | |
| except Exception as e: | |
| return f"重建 Space {repo_id} 失败: {e}" | |
| def space_action(action_type, repo_id): | |
| spaces = get_all_user_spaces() | |
| space = next((s for s in spaces if s["repo_id"] == repo_id), None) | |
| if not space: | |
| return "Space not found", 404 | |
| if action_type == "restart": | |
| message = restart_space(repo_id, space["token"]) | |
| elif action_type == "rebuild": | |
| message = rebuild_space(repo_id, space["token"]) | |
| else: | |
| message = "未知操作" | |
| return render_template("action_result.html", message=message) | |
| if __name__ == "__main__": | |
| app.run(host='0.0.0.0', port=5000) | |