gitscope
← marzneshin / v0.2.1 / app/routes/node.py
https://dev.kronoz.win/r/marzneshin.git
app/routes/node.py · Python
Blame Raw
  1import json
  2import logging
  3from datetime import datetime
  4from typing import List, Annotated
  5
  6import sqlalchemy
  7from fastapi import APIRouter, Depends, Body
  8from fastapi import HTTPException, WebSocket
  9from starlette.requests import Request
 10from starlette.responses import PlainTextResponse
 11
 12from app import marznode
 13from app.db import crud, get_tls_certificate
 14from app.dependencies import DBDep, SudoAdminDep, sudo_admin, EndDateDep, StartDateDep, get_admin
 15from app.marznode import MarzNodeGRPCIO
 16from app.models.admin import Admin
 17from app.models.node import (NodeCreate, NodeModify, NodeResponse,
 18                             NodeSettings, NodeStatus, NodesUsageResponse)
 19
 20logger = logging.getLogger(__name__)
 21router = APIRouter(prefix="/nodes", tags=['Node'])
 22
 23
 24@router.get("/settings", response_model=NodeSettings)
 25def get_node_settings(db: DBDep,
 26                      admin: SudoAdminDep):
 27    tls = crud.get_tls_certificate(db)
 28
 29    return NodeSettings(
 30        certificate=tls.certificate
 31    )
 32
 33
 34@router.get("/usage", response_model=NodesUsageResponse)
 35def get_usage(db: DBDep,
 36              admin: SudoAdminDep,
 37              start_date: StartDateDep,
 38              end_date: EndDateDep):
 39    """
 40    Get nodes usage
 41    """
 42    usages = crud.get_nodes_usage(db, start_date, end_date)
 43
 44    return {"usages": usages}
 45
 46
 47@router.post("", response_model=NodeResponse)
 48async def add_node(new_node: NodeCreate,
 49                   db: DBDep,
 50                   admin: SudoAdminDep):
 51    try:
 52        db_node = crud.create_node(db, new_node)
 53    except sqlalchemy.exc.IntegrityError:
 54        db.rollback()
 55        raise HTTPException(status_code=409, detail=f"Node \"{new_node.name}\" already exists")
 56    certificate = get_tls_certificate(db)
 57
 58    await marznode.operations.add_node(db_node, certificate)
 59
 60    logger.info("New node `%s` added", db_node.name)
 61    return db_node
 62
 63
 64@router.get("/{node_id}", response_model=NodeResponse)
 65def get_node(node_id: int,
 66             db: DBDep,
 67             admin: SudoAdminDep):
 68    db_node = crud.get_node_by_id(db, node_id)
 69    if not db_node:
 70        raise HTTPException(status_code=404, detail="Node not found")
 71
 72    return db_node
 73
 74
 75@router.websocket("/{node_id}/logs")
 76async def node_logs(node_id: int,
 77                    websocket: WebSocket,
 78                    db: DBDep,
 79                    include_buffer: bool = True):
 80    token = (
 81        websocket.query_params.get('token')
 82        or
 83        websocket.headers.get('Authorization', '').removeprefix("Bearer ")
 84    )
 85    admin = get_admin(db, token)
 86
 87    if not admin or not admin.is_sudo:
 88        return await websocket.close(reason="You're not allowed", code=4403)
 89
 90    if not marznode.nodes.get(node_id):
 91        return await websocket.close(reason="Node not found", code=4404)
 92
 93    # if not await marznode.nodes[node_id].is_healthy:
 94    #    return await websocket.close(reason="Node is not connected", code=4400)
 95
 96    await websocket.accept()
 97    async for line in marznode.nodes[node_id].get_logs(include_buffer=include_buffer):
 98        await websocket.send_text(line)
 99
100
101@router.get("", response_model=List[NodeResponse])
102def get_nodes(db: DBDep,
103              admin: SudoAdminDep):
104    return crud.get_nodes(db)
105
106
107@router.put("/{node_id}", response_model=NodeResponse)
108async def modify_node(node_id: int,
109                      modified_node: NodeModify,
110                      db: DBDep,
111                      admin: SudoAdminDep):
112    db_node = crud.get_node_by_id(db, node_id)
113    if not db_node:
114        raise HTTPException(status_code=404, detail="Node not found")
115
116    db_node = crud.update_node(db, db_node, modified_node)
117
118    await marznode.operations.remove_node(db_node.id)
119    if db_node.status != NodeStatus.disabled:
120        certificate = get_tls_certificate(db)
121        await marznode.operations.add_node(db_node, certificate)
122
123    logger.info("Node `%s` modified", db_node.name)
124    return db_node
125
126
127@router.post("/{node_id}/resync")
128async def reconnect_node(node_id: int,
129                         db: DBDep,
130                         admin: SudoAdminDep):
131    db_node = crud.get_node_by_id(db, node_id)
132    if not db_node:
133        raise HTTPException(status_code=404, detail="Node not found")
134
135    return {}
136
137
138@router.get("/{node_id}/xray_config")
139async def get_node_xray_config(node_id: int,
140                               db: DBDep,
141                               admin: SudoAdminDep):
142    if not (node := marznode.nodes.get(node_id)):
143        raise HTTPException(status_code=404, detail="Node not found")
144    return json.loads(await node.get_xray_config())
145
146
147@router.put("/{node_id}/xray_config")
148async def alter_node_xray_config(node_id: int,
149                                 db: DBDep,
150                                 admin: SudoAdminDep,
151                                 body: Annotated[dict, Body()]):
152    if not (node := marznode.nodes.get(node_id)):
153        raise HTTPException(status_code=404, detail="Node not found")
154    xray_config = json.dumps(body)
155    await node.restart_xray(xray_config)
156    return {}
157
158
159@router.delete("/{node_id}")
160async def remove_node(node_id: int,
161                      db: DBDep,
162                      admin: SudoAdminDep):
163    db_node = crud.get_node_by_id(db, node_id)
164    if not db_node:
165        raise HTTPException(status_code=404, detail="Node not found")
166
167    crud.remove_node(db, db_node)
168    await marznode.operations.remove_node(db_node.id)
169
170    logger.info(f"Node `%s` deleted", db_node.name)
171    return {}