diff --git a/app/templates/admin_tools.html b/app/templates/admin_tools.html index d1a090e..91552b3 100644 --- a/app/templates/admin_tools.html +++ b/app/templates/admin_tools.html @@ -476,38 +476,6 @@ {% endfor %}{% endfor %}{% endfor %} - -
-
-

📋 Cập nhật Checklist & Rule Config

-
-

- Chọn hệ điều hành và tải lên checklist XLSX, rule JSON hoặc cả hai. File cũ được sao lưu với đuôi .bak. -

-
-
- - -
-
-
- - -
-
- - -
-
- -
-
-
diff --git a/app/web_app_fastapi.py b/app/web_app_fastapi.py index db5c628..297299b 100644 --- a/app/web_app_fastapi.py +++ b/app/web_app_fastapi.py @@ -26,7 +26,7 @@ import ipaddress import re import requests as http_requests from authenticator.radius_login import check_radius_login, check_radius_login_extended -from config_upload import install_config_files, normalize_json, validate_xlsx +from config_upload import normalize_json, validate_xlsx from registry_service import ( RegistryError, RegistryNotFoundError, RegistryService, RegistryValidationError ) @@ -3072,78 +3072,6 @@ async def admin_delete_tool( params = urlencode({"msg": f"Lỗi khi xóa: {str(e)}", "msg_type": "error"}) return RedirectResponse(url=f"{request.url_for('admin_tools')}?{params}", status_code=303) -@app.post("/admin/checklists/upload", name="admin_checklists_upload") -async def admin_upload_checklist_config( - request: Request, - os_type: str = Form(...), - checklist_file: Optional[UploadFile] = File(None), - config_file: Optional[UploadFile] = File(None), -): - """Upload/replace the checklist XLSX and rule JSON for an OS. Admin only.""" - user = require_admin_session(request) - if not user: - return RedirectResponse(url=request.url_for('login'), status_code=303) - - from urllib.parse import urlencode - - os_info = OS_CONFIG.get(os_type) - if not os_info: - params = urlencode({"msg": "Hệ điều hành không hợp lệ", "msg_type": "error"}) - return RedirectResponse(url=f"{request.url_for('admin_tools')}?{params}", status_code=303) - - has_checklist = bool(checklist_file and checklist_file.filename) - has_config = bool(config_file and config_file.filename) - if not has_checklist and not has_config: - params = urlencode({"msg": "Cần chọn ít nhất một file checklist hoặc rule config", "msg_type": "error"}) - return RedirectResponse(url=f"{request.url_for('admin_tools')}?{params}", status_code=303) - - pending_files = [] - try: - if has_checklist: - if not checklist_file.filename.lower().endswith('.xlsx'): - raise ValueError("Checklist phải là file .xlsx") - checklist_content = validate_xlsx(await checklist_file.read()) - pending_files.append((os_info['checklist'], checklist_content)) - - if has_config: - if not config_file.filename.lower().endswith('.json'): - raise ValueError("Rule config phải là file .json") - normalized_config = normalize_json(await config_file.read()) - pending_files.append((os_info['config'], normalized_config)) - - # Compatibility endpoint: translate the fixed OS form into a new immutable - # profile version. ``install_config_files`` remains available for old - # deployments, but registry artifacts are now the authoritative source. - resolved = resolve_audit_profile(os_type=os_type) - with open(resolved['checklist_path'], 'rb') as stream: - active_checklist = stream.read() - with open(resolved['rules_path'], 'rb') as stream: - active_rules = stream.read() - checklist_content = next((content for name, content in pending_files if name == os_info['checklist']), - active_checklist) - normalized_config = next((content for name, content in pending_files if name == os_info['config']), - active_rules) - version_id = datetime.now(timezone.utc).strftime("upload-%Y%m%dT%H%M%S%fZ") - registry_service.create_version(resolved['profile']['id'], version_id, checklist_content, - normalized_config, activate=True, - created_by=user.get('username', 'admin')) - - print(Fore.GREEN + f"[ADMIN] Checklist/config updated for {os_type} by {user.get('username')}" + Fore.RESET) - changed = ', '.join(name for name, _ in pending_files) - params = urlencode({"msg": f"Đã cập nhật: {changed}", "msg_type": "success"}) - except (ValueError, UnicodeDecodeError, json.JSONDecodeError) as exc: - for target_name, _ in pending_files: - temp_path = os.path.join(CONFIG_FOLDER, target_name) + '.tmp' - if os.path.exists(temp_path): - os.remove(temp_path) - params = urlencode({"msg": str(exc), "msg_type": "error"}) - except Exception as exc: - print(Fore.RED + f"[ADMIN] Checklist/config upload error: {exc}" + Fore.RESET) - params = urlencode({"msg": f"Lỗi cập nhật checklist/config: {exc}", "msg_type": "error"}) - - return RedirectResponse(url=f"{request.url_for('admin_tools')}?{params}", status_code=303) - - @app.post("/admin/docs/upload", name="admin_docs_upload") async def admin_upload_doc( request: Request, diff --git a/tests/test_checklist_config_upload.py b/tests/test_checklist_config_upload.py index d4ce9a1..e88641e 100644 --- a/tests/test_checklist_config_upload.py +++ b/tests/test_checklist_config_upload.py @@ -6,27 +6,21 @@ TEMPLATE = (ROOT / "app" / "templates" / "admin_tools.html").read_text(encoding= COMPOSE = (ROOT / "docker-compose.yml").read_text(encoding="utf-8") -def test_admin_checklist_upload_route_exists_and_is_admin_only(): - assert '@app.post("/admin/checklists/upload", name="admin_checklists_upload")' in APP - assert "async def admin_upload_checklist_config(" in APP - function = APP.split("async def admin_upload_checklist_config(", 1)[1].split("@app.", 1)[0] - assert "require_admin_session(request)" in function +def test_legacy_checklist_upload_ui_and_route_are_removed(): + assert "admin_checklists_upload" not in APP + assert "admin_checklists_upload" not in TEMPLATE + assert "Cập nhật Checklist & Rule Config" not in TEMPLATE -def test_upload_uses_server_side_os_mapping_and_validates_json(): - function = APP.split("async def admin_upload_checklist_config(", 1)[1].split("@app.", 1)[0] - assert "OS_CONFIG.get(os_type)" in function - assert "normalize_json" in function - assert "validate_xlsx" in function - assert "install_config_files" in function - - -def test_admin_ui_contains_checklist_configuration_form(): - assert "admin_checklists_upload" in TEMPLATE +def test_registry_is_the_single_checklist_rule_management_flow(): + assert "registry_upload_version_v2" in APP + assert "registry_upload_version_v2" in TEMPLATE assert 'name="checklist_file"' in TEMPLATE assert 'name="config_file"' in TEMPLATE + assert "registry_activate_v2" in TEMPLATE + assert "registry_rollback_v2" in TEMPLATE -def test_config_mount_is_writable_for_persistent_uploads(): - assert "./config:/app/config:ro" not in COMPOSE - assert "./config:/app/config" in COMPOSE +def test_registry_mount_is_writable_and_persistent(): + assert "./registry:/app/registry" in COMPOSE + assert "AUDIT_REGISTRY_ROOT=/app/registry" in COMPOSE