Распаковал архив Proxmox-VPS-Panel.rar и добавил содержимое в репозиторий
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from .. import models, schemas, proxmox_client as pve
|
||||
from ..config import settings
|
||||
from ..database import get_db
|
||||
from ..deps import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/instances", tags=["instances"])
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.InstanceOut])
|
||||
def list_my_instances(db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
|
||||
query = db.query(models.Instance).options(joinedload(models.Instance.template))
|
||||
query = query.filter(models.Instance.status != models.InstanceStatus.deleted)
|
||||
if user.role != models.Role.admin:
|
||||
query = query.filter(models.Instance.owner_id == user.id)
|
||||
return query.order_by(models.Instance.created_at.desc()).all()
|
||||
|
||||
|
||||
def _provision(instance_id: int, template_id: int, node: str):
|
||||
"""Фоновое создание VPS через Proxmox API."""
|
||||
from ..database import SessionLocal
|
||||
db = SessionLocal()
|
||||
try:
|
||||
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
|
||||
if not instance:
|
||||
return
|
||||
template = db.query(models.Template).filter(models.Template.id == template_id).first()
|
||||
if not template:
|
||||
instance.status = models.InstanceStatus.error
|
||||
instance.root_password = "ERROR: шаблон не найден"
|
||||
db.commit()
|
||||
return
|
||||
try:
|
||||
if template.guest_type == models.GuestType.vm:
|
||||
clean_name = ''.join(c for c in instance.name.strip() if c.isalnum() or c == '-').lower() or 'vm'
|
||||
pve.clone_vm(template.source_vmid, instance.vmid, clean_name, node)
|
||||
pve.resize_vm(instance.vmid, template.cores, template.memory_mb, node)
|
||||
pve.resize_disk(instance.vmid, template.disk_gb, node)
|
||||
if instance.ciuser and instance.root_password:
|
||||
pve.configure_cloud_init(instance.vmid, instance.ciuser, instance.root_password, node)
|
||||
pve.guest_action("vm", instance.vmid, "start", node)
|
||||
else:
|
||||
clean_name = ''.join(c for c in instance.name.strip() if c.isalnum() or c == '-').lower() or 'lxc'
|
||||
password = pve.create_lxc(
|
||||
new_vmid=instance.vmid, name=clean_name,
|
||||
template_volid=template.source_template,
|
||||
cores=template.cores, memory_mb=template.memory_mb,
|
||||
disk_gb=template.disk_gb, node=node,
|
||||
)
|
||||
instance.root_password = password
|
||||
pve.guest_action("lxc", instance.vmid, "start", node)
|
||||
instance.status = models.InstanceStatus.running
|
||||
except Exception as exc:
|
||||
instance.status = models.InstanceStatus.error
|
||||
instance.root_password = f"ERROR: {exc}"
|
||||
db.commit()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("", response_model=schemas.InstanceOut)
|
||||
def create_instance(
|
||||
payload: schemas.InstanceCreate,
|
||||
background_tasks: BackgroundTasks,
|
||||
db: Session = Depends(get_db),
|
||||
user: models.User = Depends(get_current_user),
|
||||
):
|
||||
template = db.query(models.Template).filter(
|
||||
models.Template.id == payload.template_id, models.Template.is_active == True
|
||||
).first()
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="Шаблон не найден")
|
||||
|
||||
for _ in range(100):
|
||||
try:
|
||||
vmid = pve.get_next_vmid()
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=f"Proxmox недоступен: {exc}")
|
||||
exists = db.query(models.Instance).filter(
|
||||
models.Instance.vmid == vmid,
|
||||
models.Instance.status != models.InstanceStatus.deleted,
|
||||
).first()
|
||||
if not exists:
|
||||
break
|
||||
else:
|
||||
raise HTTPException(status_code=409, detail="Не удалось найти свободный VMID")
|
||||
|
||||
clean_name = ''.join(c for c in payload.name.strip() if c.isalnum() or c == '-').lower() or 'vps'
|
||||
|
||||
# Proxmox переиспользует освободившиеся vmid — вычищаем мёртвые записи,
|
||||
# иначе INSERT упадёт с duplicate key по ix_instances_vmid
|
||||
stale = db.query(models.Instance).filter(
|
||||
models.Instance.vmid == vmid,
|
||||
models.Instance.status.in_([
|
||||
models.InstanceStatus.error,
|
||||
models.InstanceStatus.deleted,
|
||||
models.InstanceStatus.deleting,
|
||||
]),
|
||||
).all()
|
||||
for s in stale:
|
||||
db.delete(s)
|
||||
if stale:
|
||||
db.commit()
|
||||
instance = models.Instance(
|
||||
name=clean_name, vmid=vmid, node=settings.pve_node,
|
||||
guest_type=template.guest_type, status=models.InstanceStatus.creating,
|
||||
owner_id=user.id, template_id=template.id,
|
||||
ciuser=payload.ciuser or None,
|
||||
root_password=payload.cipassword or None,
|
||||
)
|
||||
db.add(instance)
|
||||
db.commit()
|
||||
db.refresh(instance)
|
||||
background_tasks.add_task(_provision, instance.id, template.id, settings.pve_node)
|
||||
return instance
|
||||
|
||||
|
||||
@router.get("/{instance_id}", response_model=schemas.InstanceOut)
|
||||
def get_instance(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
|
||||
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="VPS не найден")
|
||||
return instance
|
||||
|
||||
|
||||
@router.get("/{instance_id}/status")
|
||||
def get_instance_status(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
|
||||
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="VPS не найден")
|
||||
try:
|
||||
return pve.get_status(instance.guest_type.value, instance.vmid, instance.node)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=f"Proxmox недоступен: {exc}")
|
||||
|
||||
|
||||
@router.get("/{instance_id}/ip")
|
||||
def get_instance_ip(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
|
||||
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="VPS не найден")
|
||||
try:
|
||||
ip = pve.get_instance_ip(instance.vmid, instance.node)
|
||||
return {"ip": ip}
|
||||
except Exception as exc:
|
||||
return {"ip": f"ошибка: {exc}"}
|
||||
|
||||
|
||||
@router.post("/{instance_id}/action")
|
||||
def instance_action(instance_id: int, payload: schemas.InstanceAction, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
|
||||
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="VPS не найден")
|
||||
try:
|
||||
pve.guest_action(instance.guest_type.value, instance.vmid, payload.action, instance.node)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=f"Ошибка: {exc}")
|
||||
if payload.action == "start":
|
||||
instance.status = models.InstanceStatus.running
|
||||
elif payload.action in ("stop", "shutdown"):
|
||||
instance.status = models.InstanceStatus.stopped
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.get("/{instance_id}/ip")
|
||||
def get_instance_ip(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
|
||||
"""Возвращает IP-адрес инстанса."""
|
||||
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="VPS не найден")
|
||||
if user.role != models.Role.admin and instance.owner_id != user.id:
|
||||
raise HTTPException(status_code=403, detail="Нет доступа")
|
||||
try:
|
||||
ip = pve.get_instance_ip(instance.vmid, instance.node)
|
||||
return {"ip": ip}
|
||||
except Exception as exc:
|
||||
return {"ip": f"ошибка: {exc}"}
|
||||
|
||||
@router.get("/{instance_id}/ip")
|
||||
def get_instance_ip(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
|
||||
"""Возвращает IP-адрес инстанса."""
|
||||
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="VPS не найден")
|
||||
if user.role != models.Role.admin and instance.owner_id != user.id:
|
||||
raise HTTPException(status_code=403, detail="Нет доступа")
|
||||
try:
|
||||
ip = pve.get_instance_ip(instance.vmid, instance.node)
|
||||
return {"ip": ip}
|
||||
except Exception as exc:
|
||||
return {"ip": f"ошибка: {exc}"}
|
||||
|
||||
@router.get("/{instance_id}/live")
|
||||
def get_instance_live(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
|
||||
"""Live CPU/RAM/Memory показатели."""
|
||||
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="VPS не найден")
|
||||
try:
|
||||
return pve.get_live_stats(instance.vmid, instance.node)
|
||||
except Exception as exc:
|
||||
return {"cpu": 0, "mem_used": 0, "mem_total": 0, "uptime": 0, "status": "error", "error": str(exc)}
|
||||
|
||||
@router.delete("/{instance_id}")
|
||||
def delete_instance(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
|
||||
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="VPS не найден")
|
||||
try:
|
||||
pve.delete_guest(instance.guest_type.value, instance.vmid, instance.node)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=f"Ошибка удаления: {exc}")
|
||||
instance.status = models.InstanceStatus.deleted
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.get("/{instance_id}/console")
|
||||
def instance_console(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
|
||||
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="VPS не найден")
|
||||
try:
|
||||
ticket = pve.get_vnc_ticket(instance.guest_type.value, instance.vmid, instance.node)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=f"Консоль недоступна: {exc}")
|
||||
ticket["instance_id"] = instance.id
|
||||
return ticket
|
||||
Reference in New Issue
Block a user