feat(api): Add full node configuration to web panel API

This commit is contained in:
ReturnFI
2025-10-16 20:33:22 +00:00
parent dd9f2a3e57
commit 70fab7169e
2 changed files with 54 additions and 4 deletions

View File

@ -93,7 +93,14 @@ async def add_node(body: AddNodeBody):
body: Request body containing the name and IP of the node.
"""
try:
cli_api.add_node(body.name, body.ip)
cli_api.add_node(
name=body.name,
ip=body.ip,
port=body.port,
sni=body.sni,
pinSHA256=body.pinSHA256,
obfs=body.obfs
)
return DetailResponse(detail=f"Node '{body.name}' added successfully.")
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@ -102,7 +109,6 @@ async def add_node(body: AddNodeBody):
@router.post('/nodes/delete', response_model=DetailResponse, summary='Delete External Node')
async def delete_node(body: DeleteNodeBody):
"""
Deletes an external node from the configuration by its name.
Args:

View File

@ -1,6 +1,7 @@
from pydantic import BaseModel, field_validator, ValidationInfo
from ipaddress import IPv4Address, IPv6Address, ip_address
from pydantic import BaseModel, field_validator, Field
from ipaddress import ip_address
import re
from typing import Optional
def validate_ip_or_domain(v: str) -> str | None:
if v is None or v.strip() in ['', 'None']:
@ -34,6 +35,10 @@ class EditInputBody(StatusResponse):
class Node(BaseModel):
name: str
ip: str
port: Optional[int] = Field(default=None, ge=1, le=65535)
sni: Optional[str] = None
pinSHA256: Optional[str] = None
obfs: Optional[str] = None
@field_validator('ip', mode='before')
def check_node_ip(cls, v: str | None):
@ -41,6 +46,45 @@ class Node(BaseModel):
raise ValueError("IP or Domain field cannot be empty.")
return validate_ip_or_domain(v)
@field_validator('sni', mode='before')
def validate_sni_format(cls, v: str | None):
if v is None or not v.strip():
return None
v_stripped = v.strip()
if "://" in v_stripped:
raise ValueError("SNI must not contain a protocol (e.g., http://).")
try:
ip_address(v_stripped)
raise ValueError("SNI cannot be an IP address.")
except ValueError as e:
if "SNI cannot be an IP address" in str(e):
raise e
domain_regex = re.compile(
r'^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$',
re.IGNORECASE
)
if not domain_regex.match(v_stripped):
raise ValueError(f"'{v_stripped}' is not a valid domain name for SNI.")
return v_stripped
@field_validator('pinSHA256', mode='before')
def validate_pin_format(cls, v: str | None):
if v is None or not v.strip():
return None
v_stripped = v.strip().upper()
pin_regex = re.compile(r'^([0-9A-F]{2}:){31}[0-9A-F]{2}$')
if not pin_regex.match(v_stripped):
raise ValueError("Invalid SHA256 pin format.")
return v_stripped
class AddNodeBody(Node):
pass