diff --git a/logs/app.log b/logs/app.log index 21fe9dd..a1aad73 100644 --- a/logs/app.log +++ b/logs/app.log @@ -13,3 +13,4 @@ WARNING: This is a development server. Do not use it in a production deployment. * Running on http://127.0.0.1:16041 * Running on http://192.168.0.101:16041 Press CTRL+C to quit +127.0.0.1 - - [11/Jul/2026 00:34:38] "GET /api/models/export HTTP/1.1" 200 - diff --git a/modules/routes/__pycache__/api_cpus.cpython-310.pyc b/modules/routes/__pycache__/api_cpus.cpython-310.pyc index 0fdea06..e568bf6 100644 Binary files a/modules/routes/__pycache__/api_cpus.cpython-310.pyc and b/modules/routes/__pycache__/api_cpus.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_gpus.cpython-310.pyc b/modules/routes/__pycache__/api_gpus.cpython-310.pyc index 511d132..2a95b63 100644 Binary files a/modules/routes/__pycache__/api_gpus.cpython-310.pyc and b/modules/routes/__pycache__/api_gpus.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_items.cpython-310.pyc b/modules/routes/__pycache__/api_items.cpython-310.pyc index 24097df..16e60a4 100644 Binary files a/modules/routes/__pycache__/api_items.cpython-310.pyc and b/modules/routes/__pycache__/api_items.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_models.cpython-310.pyc b/modules/routes/__pycache__/api_models.cpython-310.pyc index 9a3af04..1250244 100644 Binary files a/modules/routes/__pycache__/api_models.cpython-310.pyc and b/modules/routes/__pycache__/api_models.cpython-310.pyc differ diff --git a/modules/routes/api_cpus.py b/modules/routes/api_cpus.py index 2e91fcb..614253a 100644 --- a/modules/routes/api_cpus.py +++ b/modules/routes/api_cpus.py @@ -2,8 +2,9 @@ CPU CRUD API """ import uuid +import json from datetime import datetime -from flask import Blueprint, request, jsonify +from flask import Blueprint, request, jsonify, Response from config import CPUS_FILE from utils import load_data, save_data, parse_date_to_timestamp @@ -95,3 +96,63 @@ def api_toggle_cpu_visible(cpu_id): cpu['visible'] = not cpu.get('visible', True) save_data(CPUS_FILE, cpus) return jsonify({'success': True, 'visible': cpu['visible']}) + + +# ─── 导出导入 ─────────────────────────────────────────────────────── + +@cpus_bp.route('/api/cpus/export', methods=['GET']) +def api_export_cpus(): + """导出所有CPU数据""" + try: + cpus = load_data(CPUS_FILE) + export_data = { + 'type': 'cpus', + 'items': cpus, + 'count': len(cpus), + 'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'version': '1.0' + } + json_str = json.dumps(export_data, ensure_ascii=False, indent=2) + response = Response( + json_str, + mimetype='application/json', + headers={'Content-Disposition': f'attachment; filename=cpus-export-{datetime.now().strftime("%Y%m%d%H%M%S")}.json'} + ) + return response + except Exception as e: + return jsonify({'error': str(e)}), 500 + + +@cpus_bp.route('/api/cpus/import', methods=['GET', 'POST']) +def api_import_cpus(): + """导入CPU数据""" + try: + if request.method == 'GET': + return jsonify({'endpoint': '/api/cpus/import', 'method': 'POST', 'params': {'mode': 'merge 或 replace'}}) + + import_data = request.get_json() + if not import_data or 'items' not in import_data: + return jsonify({'error': '缺少 items 字段'}), 400 + + mode = request.args.get('mode', 'merge') + cpus = load_data(CPUS_FILE) + imported_items = import_data['items'] + + result = {'success': True, 'imported': 0, 'updated': 0, 'skipped': []} + + for item in imported_items: + existing = next((c for c in cpus if c['id'] == item['id']), None) + if existing: + if mode == 'replace': + existing.update(item) + result['updated'] += 1 + else: + result['skipped'].append(item['id']) + else: + cpus.append(item) + result['imported'] += 1 + + save_data(CPUS_FILE, cpus) + return jsonify(result) + except Exception as e: + return jsonify({'error': str(e)}), 500 diff --git a/modules/routes/api_gpus.py b/modules/routes/api_gpus.py index d39e70c..c9deb4e 100644 --- a/modules/routes/api_gpus.py +++ b/modules/routes/api_gpus.py @@ -2,8 +2,9 @@ GPU CRUD API """ import uuid +import json from datetime import datetime -from flask import Blueprint, request, jsonify +from flask import Blueprint, request, jsonify, Response from config import GPUS_FILE from utils import load_data, save_data, parse_date_to_timestamp @@ -95,3 +96,63 @@ def api_toggle_gpu_visible(gpu_id): gpu['visible'] = not gpu.get('visible', True) save_data(GPUS_FILE, gpus) return jsonify({'success': True, 'visible': gpu['visible']}) + + +# ─── 导出导入 ─────────────────────────────────────────────────────── + +@gpus_bp.route('/api/gpus/export', methods=['GET']) +def api_export_gpus(): + """导出所有GPU数据""" + try: + gpus = load_data(GPUS_FILE) + export_data = { + 'type': 'gpus', + 'items': gpus, + 'count': len(gpus), + 'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'version': '1.0' + } + json_str = json.dumps(export_data, ensure_ascii=False, indent=2) + response = Response( + json_str, + mimetype='application/json', + headers={'Content-Disposition': f'attachment; filename=gpus-export-{datetime.now().strftime("%Y%m%d%H%M%S")}.json'} + ) + return response + except Exception as e: + return jsonify({'error': str(e)}), 500 + + +@gpus_bp.route('/api/gpus/import', methods=['GET', 'POST']) +def api_import_gpus(): + """导入GPU数据""" + try: + if request.method == 'GET': + return jsonify({'endpoint': '/api/gpus/import', 'method': 'POST', 'params': {'mode': 'merge 或 replace'}}) + + import_data = request.get_json() + if not import_data or 'items' not in import_data: + return jsonify({'error': '缺少 items 字段'}), 400 + + mode = request.args.get('mode', 'merge') + gpus = load_data(GPUS_FILE) + imported_items = import_data['items'] + + result = {'success': True, 'imported': 0, 'updated': 0, 'skipped': []} + + for item in imported_items: + existing = next((g for g in gpus if g['id'] == item['id']), None) + if existing: + if mode == 'replace': + existing.update(item) + result['updated'] += 1 + else: + result['skipped'].append(item['id']) + else: + gpus.append(item) + result['imported'] += 1 + + save_data(GPUS_FILE, gpus) + return jsonify(result) + except Exception as e: + return jsonify({'error': str(e)}), 500 diff --git a/modules/routes/api_items.py b/modules/routes/api_items.py index 8f67693..f9a0650 100644 --- a/modules/routes/api_items.py +++ b/modules/routes/api_items.py @@ -2,8 +2,9 @@ 动态分类数据 API """ import uuid +import json from datetime import datetime -from flask import Blueprint, request, jsonify +from flask import Blueprint, request, jsonify, Response from config import DATA_DIR from utils import load_data, save_data, parse_date_to_timestamp @@ -95,3 +96,66 @@ def api_toggle_item_visible(category_id, item_id): item['visible'] = not item.get('visible', True) save_data(items_file, items) return jsonify({'success': True, 'visible': item['visible']}) + + +# ─── 导出导入 ─────────────────────────────────────────────────────── + +@items_bp.route('/api/items//export', methods=['GET']) +def api_export_items(category_id): + """导出指定分类的所有数据""" + try: + items_file = DATA_DIR / f'items_{category_id}.json' + items = load_data(items_file) + export_data = { + 'type': 'items', + 'category_id': category_id, + 'items': items, + 'count': len(items), + 'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'version': '1.0' + } + json_str = json.dumps(export_data, ensure_ascii=False, indent=2) + response = Response( + json_str, + mimetype='application/json', + headers={'Content-Disposition': f'attachment; filename={category_id}-export-{datetime.now().strftime("%Y%m%d%H%M%S")}.json'} + ) + return response + except Exception as e: + return jsonify({'error': str(e)}), 500 + + +@items_bp.route('/api/items//import', methods=['GET', 'POST']) +def api_import_items(category_id): + """导入指定分类的数据""" + try: + if request.method == 'GET': + return jsonify({'endpoint': f'/api/items/{category_id}/import', 'method': 'POST', 'params': {'mode': 'merge 或 replace'}}) + + import_data = request.get_json() + if not import_data or 'items' not in import_data: + return jsonify({'error': '缺少 items 字段'}), 400 + + mode = request.args.get('mode', 'merge') + items_file = DATA_DIR / f'items_{category_id}.json' + items = load_data(items_file) + imported_items = import_data['items'] + + result = {'success': True, 'imported': 0, 'updated': 0, 'skipped': []} + + for item in imported_items: + existing = next((i for i in items if i['id'] == item['id']), None) + if existing: + if mode == 'replace': + existing.update(item) + result['updated'] += 1 + else: + result['skipped'].append(item['id']) + else: + items.append(item) + result['imported'] += 1 + + save_data(items_file, items) + return jsonify(result) + except Exception as e: + return jsonify({'error': str(e)}), 500 diff --git a/modules/routes/api_models.py b/modules/routes/api_models.py index c58f017..6287dcd 100644 --- a/modules/routes/api_models.py +++ b/modules/routes/api_models.py @@ -2,8 +2,9 @@ AI模型 CRUD API """ import uuid +import json from datetime import datetime -from flask import Blueprint, request, jsonify +from flask import Blueprint, request, jsonify, Response from config import MODELS_FILE from utils import load_data, save_data, parse_date_to_timestamp, safe_sort_key @@ -88,3 +89,63 @@ def api_toggle_model_visible(model_id): model['visible'] = not model.get('visible', True) save_data(MODELS_FILE, models) return jsonify({'success': True, 'visible': model['visible']}) + + +# ─── 导出导入 ─────────────────────────────────────────────────────── + +@models_bp.route('/api/models/export', methods=['GET']) +def api_export_models(): + """导出所有模型数据""" + try: + models = load_data(MODELS_FILE) + export_data = { + 'type': 'models', + 'items': models, + 'count': len(models), + 'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'version': '1.0' + } + json_str = json.dumps(export_data, ensure_ascii=False, indent=2) + response = Response( + json_str, + mimetype='application/json', + headers={'Content-Disposition': f'attachment; filename=models-export-{datetime.now().strftime("%Y%m%d%H%M%S")}.json'} + ) + return response + except Exception as e: + return jsonify({'error': str(e)}), 500 + + +@models_bp.route('/api/models/import', methods=['GET', 'POST']) +def api_import_models(): + """导入模型数据""" + try: + if request.method == 'GET': + return jsonify({'endpoint': '/api/models/import', 'method': 'POST', 'params': {'mode': 'merge 或 replace'}}) + + import_data = request.get_json() + if not import_data or 'items' not in import_data: + return jsonify({'error': '缺少 items 字段'}), 400 + + mode = request.args.get('mode', 'merge') + models = load_data(MODELS_FILE) + imported_items = import_data['items'] + + result = {'success': True, 'imported': 0, 'updated': 0, 'skipped': []} + + for item in imported_items: + existing = next((m for m in models if m['id'] == item['id']), None) + if existing: + if mode == 'replace': + existing.update(item) + result['updated'] += 1 + else: + result['skipped'].append(item['id']) + else: + models.append(item) + result['imported'] += 1 + + save_data(MODELS_FILE, models) + return jsonify(result) + except Exception as e: + return jsonify({'error': str(e)}), 500 diff --git a/templates/admin.html b/templates/admin.html index e1104d6..3f5c98b 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -160,6 +160,8 @@

数据管理

+ +
@@ -179,6 +181,8 @@

大模型管理

+ +
@@ -200,6 +204,8 @@

GPU管理

+ +
@@ -221,6 +227,8 @@

CPU管理

+ +
@@ -501,20 +509,18 @@