fix: make auto-import synchronous, return templates list
The /api/auto-import/{server_id} endpoint ran as a background task
and returned {status: "queued"} with no templates field, while the
frontend's executeAutoImport() immediately called data.templates.forEach()
— causing TypeError: can't access property "forEach", data.templates
is undefined.
Replaced the background task with a synchronous handler that imports
templates in-request and returns {templates: [...], imported_count: N},
matching what the frontend expects.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ API endpoints for OS Templates management
|
|||||||
Allows creating VM templates, groups, and deploying VMs from templates
|
Allows creating VM templates, groups, and deploying VMs from templates
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends, Request, HTTPException, status
|
from fastapi import APIRouter, Depends, Request, HTTPException, status
|
||||||
from fastapi.responses import HTMLResponse, JSONResponse
|
from fastapi.responses import HTMLResponse, JSONResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -533,29 +533,133 @@ def _do_auto_import(server_id: int, username: str):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/api/auto-import/{server_id}")
|
@router.post("/api/auto-import/{server_id}")
|
||||||
async def auto_import_templates(
|
def auto_import_templates(
|
||||||
server_id: int,
|
server_id: int,
|
||||||
background_tasks: BackgroundTasks,
|
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user: User = Depends(PermissionChecker("templates.manage"))
|
current_user: User = Depends(PermissionChecker("templates.manage"))
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Автоматический импорт шаблонов с Proxmox сервера (в фоне).
|
Синхронный импорт шаблонов с Proxmox сервера.
|
||||||
Возвращает сразу, импорт выполняется асинхронно.
|
Возвращает список импортированных шаблонов и их количество.
|
||||||
"""
|
"""
|
||||||
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
|
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
|
||||||
if not server:
|
if not server:
|
||||||
raise HTTPException(status_code=404, detail="Proxmox сервер не найден")
|
raise HTTPException(status_code=404, detail="Proxmox сервер не найден")
|
||||||
|
|
||||||
background_tasks.add_task(_do_auto_import, server_id, current_user.username)
|
if server.use_password:
|
||||||
logger.info(f"User {current_user.username} queued auto-import for server {server.name}")
|
client = ProxmoxClient(
|
||||||
|
host=server.ip_address,
|
||||||
|
user=server.api_user,
|
||||||
|
password=server.password,
|
||||||
|
verify_ssl=server.verify_ssl
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
client = ProxmoxClient(
|
||||||
|
host=server.ip_address,
|
||||||
|
user=server.api_user,
|
||||||
|
token_name=server.api_token_name,
|
||||||
|
token_value=server.api_token_value,
|
||||||
|
verify_ssl=server.verify_ssl
|
||||||
|
)
|
||||||
|
|
||||||
|
if not client.is_connected():
|
||||||
|
raise HTTPException(status_code=503, detail="Не удалось подключиться к Proxmox серверу")
|
||||||
|
|
||||||
|
node_filter = None
|
||||||
|
if server.hostname and server.hostname != server.ip_address:
|
||||||
|
node_filter = server.hostname
|
||||||
|
|
||||||
|
proxmox_templates = client.get_templates(node=node_filter)
|
||||||
|
|
||||||
|
existing_vmids = set(
|
||||||
|
t.vmid for t in db.query(OSTemplate).filter(OSTemplate.server_id == server_id).all()
|
||||||
|
)
|
||||||
|
groups_cache = {g.name: g for g in db.query(OSTemplateGroup).all()}
|
||||||
|
|
||||||
|
templates_by_group = {}
|
||||||
|
for tpl in proxmox_templates:
|
||||||
|
vmid = tpl.get('vmid')
|
||||||
|
if vmid in existing_vmids:
|
||||||
|
continue
|
||||||
|
template_name = tpl.get('name', f"Template-{vmid}")
|
||||||
|
group_info = detect_os_group(template_name)
|
||||||
|
group_name = group_info['name']
|
||||||
|
if group_name not in templates_by_group:
|
||||||
|
templates_by_group[group_name] = {'info': group_info, 'templates': []}
|
||||||
|
templates_by_group[group_name]['templates'].append({
|
||||||
|
'vmid': vmid,
|
||||||
|
'name': template_name,
|
||||||
|
'node': tpl.get('node'),
|
||||||
|
'maxmem': tpl.get('maxmem', 0),
|
||||||
|
'maxdisk': tpl.get('maxdisk', 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
imported = []
|
||||||
|
global_sort_order = 0
|
||||||
|
sorted_groups = sorted(templates_by_group.items(), key=lambda x: x[1]['info']['order'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
for group_name, group_data in sorted_groups:
|
||||||
|
group_info = group_data['info']
|
||||||
|
group_templates = group_data['templates']
|
||||||
|
|
||||||
|
if group_name not in groups_cache:
|
||||||
|
new_group = OSTemplateGroup(
|
||||||
|
name=group_name,
|
||||||
|
icon=group_info['icon'],
|
||||||
|
description=f"Шаблоны {group_name}",
|
||||||
|
sort_order=group_info['order'],
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
db.add(new_group)
|
||||||
|
db.flush()
|
||||||
|
groups_cache[group_name] = new_group
|
||||||
|
|
||||||
|
group = groups_cache[group_name]
|
||||||
|
group_templates.sort(key=lambda x: x['name'])
|
||||||
|
|
||||||
|
for tpl in group_templates:
|
||||||
|
default_memory = max(1024, (tpl.get('maxmem', 0) // (1024 * 1024)) or 1024)
|
||||||
|
default_disk = max(10, (tpl.get('maxdisk', 0) // (1024 * 1024 * 1024)) or 10)
|
||||||
|
template = OSTemplate(
|
||||||
|
group_id=group.id,
|
||||||
|
server_id=server_id,
|
||||||
|
name=tpl['name'],
|
||||||
|
vmid=tpl['vmid'],
|
||||||
|
node=tpl.get('node'),
|
||||||
|
source_node=tpl.get('node'),
|
||||||
|
default_cores=2,
|
||||||
|
default_memory=default_memory,
|
||||||
|
default_disk=default_disk,
|
||||||
|
min_cores=1,
|
||||||
|
min_memory=512,
|
||||||
|
min_disk=5,
|
||||||
|
is_active=True,
|
||||||
|
sort_order=global_sort_order
|
||||||
|
)
|
||||||
|
db.add(template)
|
||||||
|
imported.append({
|
||||||
|
'vmid': tpl['vmid'],
|
||||||
|
'name': tpl['name'],
|
||||||
|
'group': group_name,
|
||||||
|
'sort_order': global_sort_order
|
||||||
|
})
|
||||||
|
global_sort_order += 1
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
logger.error(f"Auto-import failed for server {server_id}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=f"Ошибка импорта: {str(e)}")
|
||||||
|
|
||||||
|
logger.info(f"User {current_user.username} imported {len(imported)} templates from {server.name}")
|
||||||
|
|
||||||
return JSONResponse(content={
|
return JSONResponse(content={
|
||||||
'success': True,
|
'success': True,
|
||||||
'status': 'queued',
|
|
||||||
'server_id': server_id,
|
'server_id': server_id,
|
||||||
'server_name': server.name,
|
'server_name': server.name,
|
||||||
'message': 'Импорт шаблонов запущен в фоне'
|
'imported_count': len(imported),
|
||||||
|
'templates': imported
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user