Route HTML5 AI structure CF/CFE through Windows Agent
This commit is contained in:
@@ -65,6 +65,7 @@ from api_server.html5_access_controller import (
|
||||
)
|
||||
from api_server.ai_structure_service import prepare_ai_structure as _prepare_ai_structure
|
||||
from api_server.html5_ai_structure_controller import (
|
||||
html5_ai_structure_job as _html5_ai_structure_job,
|
||||
html5_ai_structure_page as _html5_ai_structure_page,
|
||||
html5_ai_structure_run as _html5_ai_structure_run,
|
||||
)
|
||||
@@ -318,6 +319,51 @@ def _save_ai_structure_smb_credentials(project_id: str, credentials: dict[str, s
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _load_ai_structure_agent_run(job_id: str) -> dict[str, Any] | None:
|
||||
try:
|
||||
payload = _storage.read_document("ai_structure_agent_runs", job_id)
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
password_value = str(payload.get("password_b64") or "")
|
||||
try:
|
||||
password = base64.b64decode(password_value.encode("ascii")).decode("utf-8") if password_value else ""
|
||||
except (ValueError, UnicodeDecodeError):
|
||||
password = ""
|
||||
result = payload.get("result")
|
||||
return {
|
||||
"project_id": str(payload.get("project_id") or ""),
|
||||
"effective_project_id": str(payload.get("effective_project_id") or ""),
|
||||
"input_path": str(payload.get("input_path") or ""),
|
||||
"output_path": str(payload.get("output_path") or ""),
|
||||
"display_input_path": str(payload.get("display_input_path") or ""),
|
||||
"display_output_path": str(payload.get("display_output_path") or ""),
|
||||
"username": str(payload.get("username") or ""),
|
||||
"domain": str(payload.get("domain") or ""),
|
||||
"password": password,
|
||||
"result": result if isinstance(result, dict) else None,
|
||||
}
|
||||
|
||||
|
||||
def _save_ai_structure_agent_run(job_id: str, payload: dict[str, Any]) -> None:
|
||||
password = str(payload.get("password") or "")
|
||||
_storage.write_document(
|
||||
"ai_structure_agent_runs",
|
||||
job_id,
|
||||
{
|
||||
"project_id": str(payload.get("project_id") or ""),
|
||||
"effective_project_id": str(payload.get("effective_project_id") or ""),
|
||||
"input_path": str(payload.get("input_path") or ""),
|
||||
"output_path": str(payload.get("output_path") or ""),
|
||||
"display_input_path": str(payload.get("display_input_path") or ""),
|
||||
"display_output_path": str(payload.get("display_output_path") or ""),
|
||||
"username": str(payload.get("username") or ""),
|
||||
"domain": str(payload.get("domain") or ""),
|
||||
"password_b64": base64.b64encode(password.encode("utf-8")).decode("ascii") if password else "",
|
||||
"result": payload.get("result") if isinstance(payload.get("result"), dict) else None,
|
||||
},
|
||||
)
|
||||
|
||||
_ACCESS_TARGET_KINDS = {
|
||||
NodeKind.CATALOG,
|
||||
NodeKind.DOCUMENT,
|
||||
@@ -535,6 +581,78 @@ def _agent_id_for_source(settings: "ProjectSettingsRequest", source: "ImportSour
|
||||
return str(agent.get("agent_id") or "").strip()
|
||||
|
||||
|
||||
async def _start_ai_structure_agent_job(*, project_id: str, effective_project_id: str, input_path: Path) -> AgentImportJob:
|
||||
settings = _project_settings_or_404(project_id)
|
||||
binary_files = _ai_structure_binary_files(input_path)
|
||||
if not binary_files:
|
||||
raise HTTPException(status_code=400, detail="Во входном пути не найдены файлы .cf или .cfe.")
|
||||
|
||||
source = ImportSourceKind.CF_FILE if any(path.suffix.casefold() == ".cf" for path in binary_files) else ImportSourceKind.CFE_FILE
|
||||
agent_id = _agent_id_for_source(settings, ImportSourceKind.CF_FILE)
|
||||
if not agent_id:
|
||||
raise HTTPException(status_code=400, detail="В настройках проекта не выбран Windows Agent для CF/CFE.")
|
||||
agent_status = _agent_status_with_liveness(_agent_statuses.get(agent_id, AgentStatus(agent_id=agent_id)))
|
||||
if agent_status.status != "online":
|
||||
raise HTTPException(status_code=409, detail=f"Windows Agent {agent_id} сейчас офлайн. Запустите агент и повторите.")
|
||||
|
||||
agent = settings.agent if isinstance(settings.agent, dict) else {}
|
||||
metadata: dict[str, Any] = {
|
||||
"platform_version": settings.platform_version or None,
|
||||
"compatibility_mode": settings.compatibility_mode or None,
|
||||
}
|
||||
local_path: str | None = None
|
||||
|
||||
if source == ImportSourceKind.CF_FILE:
|
||||
one_c_server = _agent_string_value(agent, "one_c_server") or _agent_string_value(agent, "published_1c_server") or _agent_string_value(agent, "published_server_url")
|
||||
one_c_infobase = _agent_string_value(agent, "one_c_infobase") or _agent_string_value(agent, "published_infobase")
|
||||
if one_c_server.startswith(("http://", "https://")):
|
||||
one_c_server = urlsplit(one_c_server).hostname or one_c_server
|
||||
if not one_c_server or not one_c_infobase:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Для разбора .cf нужен сервер 1С и имя информационной базы в настройках проекта. Сейчас они не заполнены.",
|
||||
)
|
||||
metadata.update(
|
||||
{
|
||||
"one_c_server": one_c_server,
|
||||
"one_c_infobase": one_c_infobase,
|
||||
"one_c_user": _agent_string_value(agent, "one_c_user") or None,
|
||||
"one_c_password": _agent_string_value(agent, "one_c_password") or None,
|
||||
"include_extensions": True,
|
||||
}
|
||||
)
|
||||
else:
|
||||
cfe_files = [path for path in binary_files if path.suffix.casefold() == ".cfe"]
|
||||
if len(cfe_files) != 1:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Для прямого разбора расширения укажите один конкретный файл .cfe, а не папку с несколькими расширениями.",
|
||||
)
|
||||
cfe_file = cfe_files[0]
|
||||
local_path = str(cfe_file)
|
||||
metadata["one_c_extension"] = cfe_file.stem
|
||||
|
||||
return await create_agent_import_job(
|
||||
effective_project_id,
|
||||
source,
|
||||
AgentImportJobRequest(
|
||||
agent_id=agent_id,
|
||||
source=source,
|
||||
local_path=local_path,
|
||||
mode=ImportMode.FULL_REPLACE,
|
||||
metadata=metadata,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _ai_structure_binary_files(input_path: Path) -> list[Path]:
|
||||
if input_path.is_file() and input_path.suffix.casefold() in {".cf", ".cfe"}:
|
||||
return [input_path]
|
||||
if not input_path.exists() or not input_path.is_dir():
|
||||
return []
|
||||
return sorted(path for path in input_path.rglob("*") if path.is_file() and path.suffix.casefold() in {".cf", ".cfe"})
|
||||
|
||||
|
||||
def _cancel_stale_extension_install_jobs(project_id: str, selected_agent_id: str) -> None:
|
||||
now = _current_timestamp()
|
||||
for job in list(_agent_import_jobs.values()):
|
||||
@@ -1631,17 +1749,35 @@ async def html5_project_ai_structure(project_id: str) -> Response:
|
||||
async def html5_project_ai_structure_run(project_id: str, request: Request) -> Response:
|
||||
form = await _html5_form_data(request)
|
||||
return _html5_response(
|
||||
_html5_ai_structure_run(
|
||||
await _html5_ai_structure_run(
|
||||
project_id=project_id,
|
||||
form=form,
|
||||
prepare=_prepare_ai_structure,
|
||||
work_root=_storage.root / "ai_structure_work",
|
||||
start_binary_job=_start_ai_structure_agent_job,
|
||||
save_run_state=_save_ai_structure_agent_run,
|
||||
load_credentials=_load_ai_structure_smb_credentials,
|
||||
save_credentials=_save_ai_structure_smb_credentials,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@app.get("/html5/projects/{project_id}/ai-structure/jobs/{job_id}")
|
||||
async def html5_project_ai_structure_job(project_id: str, job_id: str) -> Response:
|
||||
return _html5_response(
|
||||
_html5_ai_structure_job(
|
||||
project_id=project_id,
|
||||
job_id=job_id,
|
||||
prepare=_prepare_ai_structure,
|
||||
work_root=_storage.root / "ai_structure_work",
|
||||
load_run_state=_load_ai_structure_agent_run,
|
||||
save_run_state=_save_ai_structure_agent_run,
|
||||
load_job=lambda current_job_id: _agent_import_jobs.get(current_job_id),
|
||||
current_project_source_root=_current_project_source_root,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@app.get("/html5/projects/{project_id}/access/profiles/{profile_name}/plan")
|
||||
async def html5_project_access_profile_plan(project_id: str, profile_name: str) -> Response:
|
||||
return _html5_response(
|
||||
|
||||
Reference in New Issue
Block a user