feat(node): pair onboard computers with the Core fleet through UI

This commit is contained in:
DCCONSTRUCTIONS
2026-09-05 21:16:13 +03:00
parent fc545f8440
commit e82d012907
29 changed files with 2442 additions and 19 deletions
+85
View File
@@ -0,0 +1,85 @@
"""Operator-only fleet admission; the separate private mTLS listener is in fleet."""
from __future__ import annotations
import ipaddress
from typing import Annotated
from urllib.parse import urlsplit
from fastapi import APIRouter, Depends, HTTPException, Request, Response
from pydantic import BaseModel, ConfigDict, Field
from k1link.fleet.registry import FleetRegistry
from k1link.fleet.trust import PairingError
def local_operator(request: Request) -> FleetRegistry:
try:
peer = ipaddress.ip_address(request.client.host)
host = urlsplit(f"http://{request.headers.get('host', '')}")
if not peer.is_loopback or host.hostname not in ("127.0.0.1", "localhost", "::1"):
raise ValueError
origin = request.headers.get("origin")
if origin and origin != f"http://{request.headers['host']}":
raise ValueError
if request.headers.get("sec-fetch-site") == "cross-site":
raise ValueError
except (ValueError, AttributeError, KeyError):
raise HTTPException(403, "Откройте Mission Core на компьютере оператора.") from None
registry = getattr(request.app.state, "fleet", None)
if registry is None:
raise HTTPException(503, "Реестр аппаратов недоступен. Повторите подключение.")
return registry
class PreviewRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
code: str = Field(min_length=1, max_length=4096)
class AddRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
preview_id: str = Field(min_length=43, max_length=43)
name: str = Field(min_length=1, max_length=80)
platform: str = Field(pattern="^(ugv|uav|stationary|other)$")
router = APIRouter(prefix="/api/v1/fleet", tags=["fleet"])
@router.get("")
def fleet_list(response: Response, fleet: Annotated[FleetRegistry, Depends(local_operator)]):
response.headers["Cache-Control"] = "no-store"
return fleet.listing()
@router.post("/preview")
def fleet_preview(
body: PreviewRequest,
response: Response,
fleet: Annotated[FleetRegistry, Depends(local_operator)],
):
response.headers["Cache-Control"] = "no-store"
try:
return fleet.preview(body.code)
except PairingError as error:
raise HTTPException(409, str(error)) from None
@router.post("")
def fleet_add(
body: AddRequest, response: Response, fleet: Annotated[FleetRegistry, Depends(local_operator)]
):
response.headers["Cache-Control"] = "no-store"
try:
return fleet.add(body.preview_id, body.name, body.platform)
except PairingError as error:
raise HTTPException(409, str(error)) from None
@router.delete("/{vehicle_id}")
def fleet_revoke(vehicle_id: str, fleet: Annotated[FleetRegistry, Depends(local_operator)]):
try:
return fleet.revoke(vehicle_id)
except PairingError as error:
raise HTTPException(404, str(error)) from None