From c673394fa84086019e141d6b01d32ed6fae2e474 Mon Sep 17 00:00:00 2001 From: Stefan Stammberger Date: Sun, 5 Sep 2021 20:23:59 +0200 Subject: [PATCH] refactor: system API refs #11 --- app/models/system.py | 108 +++++++++++++++++- .../{hardware_info.py => system.py} | 7 ++ app/routers/system.py | 15 ++- main.py | 2 +- 4 files changed, 127 insertions(+), 5 deletions(-) rename app/repositories/{hardware_info.py => system.py} (93%) diff --git a/app/models/system.py b/app/models/system.py index ca873ae..3bba59d 100644 --- a/app/models/system.py +++ b/app/models/system.py @@ -1,7 +1,11 @@ -from typing import Optional +from enum import Enum +from typing import List, Optional +from app.models.lightning import LnInfo +from fastapi import Query +from fastapi.param_functions import Query from pydantic import BaseModel -from pydantic.types import conint, constr +from pydantic.types import constr class LoginInput(BaseModel): @@ -9,3 +13,103 @@ class LoginInput(BaseModel): one_time_password: Optional[ constr(min_length=6, max_length=6, regex="^[0-9]+$") ] = None + + +class HealthMessagePriority(str, Enum): + INFO = "info" # FYI, can normally be ignored. + WARNING = ( + "warning" # Potential problem might occur, user interaction possibly required. + ) + ERROR = "error" # Something bad happened. User interaction deinitely required. + + +class HealthMessage(BaseModel): + id: int = Query( + None, + description="""ID of the message. +Idea behind the ID is that messages can be replacable on the client. +To prevent spamming the user with multiple messages, message with ID 25 will be replaced with never data of ID 25 + """, + example=""" +```{ + id: 25, + level: "warning", + message: "HDD is 89.3% full" +}``` + """, + ) + + level: HealthMessagePriority = Query( + HealthMessagePriority.INFO, + description="""Priority level of the message. For more info see `message`. + +`INFO`: FYI, can normally be ignored.\n +`WARNING`: Potential problem might occur, user interaction possibly required.\n +`ERROR`: Something bad happened. User interaction definitely required. + +If there are multiple messages with different priorities, the most severe level will be shown. + """, + ) + message: str = Query(..., description="Detailed message description") + + +class HealthState(str, Enum): + # All systems work nominally + GOOD = "good" + # Some event requires users attention (Software update, HDD nearly full) + ATTENTION_REQUIRED = "attention_required" + # An error happened which prevents the node from working properly + # (Hardware failure, DB corruption, Internet connection not available, ...) + STOPPED = "stopped" + + +class SystemInfo(BaseModel): + alias: str = Query("", description="Name of the node (same as Lightning alias)") + color: str = Query( + ..., description="The color of the current node in hex code format" + ) + version: str = Query(..., description="The software version of this RaspiBlitz") + health: HealthState = Query( + ..., description="General health state of the Raspiblitz" + ) + health_messages: List[HealthMessage] = Query( + [], description="List of all messages regarding node health." + ) + tor_web_ui: str = Query("", description="WebUI TOR address") + tor_api: str = Query("", description="API TOR address") + lan_web_ui: str = Query("", description="WebUI LAN address") + lan_api: str = Query("", description="API LAN address") + ssh_address: str = Query( + ..., + description="Address to ssh into on local LAN (e.g. `ssh admin@192.168.1.28`", + ) + # This is here to avoid having duplicated entries in BtcStatus and LnStatus + # The chain status will always be the same between Bitcoin Core + # and the Lightning Implementation + chain: str = Query( + ..., + description="The current chain this node is connected to (mainnet, testnet or signet)", + ) + + @classmethod + def from_rpc(cls, lninfo: LnInfo): + # TODO: implement rest of the calls + return cls( + alias=lninfo.alias, + color=lninfo.color, + version="v1.8.0", + health=HealthState.ATTENTION_REQUIRED, + health_messages=[ + HealthMessage( + id=25, level=HealthMessagePriority.WARNING, message="HDD 85% full" + ) + ], + tor_web_ui="arg6ybal4b7dszmsncsrudcpdfkxadzfdi24ktceodah7tgmdopgpyfd.onion", + tor_api="arg6ybal4b7dszmsncsrudcpdfkxadzfdi24ktceodah7tgmdopgpyfd.onion/api", + lan_web_ui="http://192.168.1.12/", + lan_api="http://192.168.1.12/api", + ssh_address="http://192.168.1.12/", + chain=lninfo.chains[ + 0 + ].network, # for now, assume we are only on bitcoin chain + ) diff --git a/app/repositories/hardware_info.py b/app/repositories/system.py similarity index 93% rename from app/repositories/hardware_info.py rename to app/repositories/system.py index ba184f9..2fd59e5 100644 --- a/app/repositories/hardware_info.py +++ b/app/repositories/system.py @@ -1,6 +1,8 @@ import asyncio import psutil +from app.models.system import SystemInfo +from app.repositories.lightning import get_ln_info from app.utils import SSE, send_sse_message from decouple import config from fastapi import Request @@ -10,6 +12,11 @@ CPU_AVG_PERIOD = config("cpu_usage_averaging_period", default=0.5, cast=float) HW_INFO_YIELD_TIME = SLEEP_TIME + CPU_AVG_PERIOD +async def get_system_info() -> SystemInfo: + lninfo = await get_ln_info() + return SystemInfo.from_rpc(lninfo) + + async def subscribe_hardware_info(request: Request): while True: if await request.is_disconnected(): diff --git a/app/routers/system.py b/app/routers/system.py index b4b878c..84738b4 100644 --- a/app/routers/system.py +++ b/app/routers/system.py @@ -1,9 +1,10 @@ from app.auth.auth_bearer import JWTBearer from app.auth.auth_handler import signJWT -from app.models.system import LoginInput -from app.repositories.hardware_info import ( +from app.models.system import LoginInput, SystemInfo +from app.repositories.system import ( HW_INFO_YIELD_TIME, get_hardware_info, + get_system_info, subscribe_hardware_info, ) from app.routers.system_docs import get_hw_info_json @@ -28,6 +29,16 @@ def login(i: LoginInput): raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="Password is wrong") +@router.get( + "/get_system_info", + summary="Get system status information", + dependencies=[Depends(JWTBearer())], + response_model=SystemInfo, +) +async def get_system_info_path(): + return await get_system_info() + + @router.get( "/hardware_info", summary="Get hardware status information.", diff --git a/main.py b/main.py index 4aab711..cd75a68 100644 --- a/main.py +++ b/main.py @@ -17,7 +17,7 @@ from app.repositories.bitcoin import ( register_bitcoin_status_gatherer, register_bitcoin_zmq_sub, ) -from app.repositories.hardware_info import register_hardware_info_gatherer +from app.repositories.system import register_hardware_info_gatherer from app.repositories.lightning import register_lightning_listener from app.routers import apps, bitcoin, lightning, setup, system from app.sse_starlette import EventSourceResponse