generated from Luis/nextjs-python-web-template
readmes added comments and some links to the openapi docs mds :)
This commit is contained in:
37
pkgs/clan-cli/clan_cli/README.md
Normal file
37
pkgs/clan-cli/clan_cli/README.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
**init\_**.py:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
usage: clan webui [-h] [--port PORT] [--host HOST] [--populate] [--emulate] [--no-open] [--dev]
|
||||||
|
[--dev-port DEV_PORT] [--dev-host DEV_HOST] [--reload]
|
||||||
|
[--log-level {critical,error,warning,info,debug,trace}]
|
||||||
|
[sub_url]
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
sub_url Sub URL to open in the browser
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--port PORT Port to listen on
|
||||||
|
--host HOST Host to listen on
|
||||||
|
--populate Populate the database with dummy data
|
||||||
|
--emulate Emulate two entities c1 and c2 + dlg and ap
|
||||||
|
--no-open Don't open the browser
|
||||||
|
--dev Run in development mode
|
||||||
|
--dev-port DEV_PORT Port to listen on for the dev server
|
||||||
|
--dev-host DEV_HOST Host to listen on
|
||||||
|
--reload Don't reload on changes
|
||||||
|
--log-level {critical,error,warning,info,debug,trace}
|
||||||
|
Log level
|
||||||
|
```
|
||||||
|
|
||||||
|
In this folder are some basic files:
|
||||||
|
|
||||||
|
- config.py
|
||||||
|
- to configer basic value for the server and the emulation
|
||||||
|
- ip/host
|
||||||
|
- ports
|
||||||
|
- emuplate_fast.py
|
||||||
|
- some api call that emulate the behavoir
|
||||||
|
- extra servers with api calls are emulated here
|
||||||
|
|
||||||
|
In the subfolder <webui> is the backend impplemented.
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
|
# Imports
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
# Custom imports
|
||||||
from . import webui
|
from . import webui
|
||||||
from .custom_logger import setup_logging
|
from .custom_logger import setup_logging
|
||||||
|
|
||||||
|
# Setting up the logger
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Trying to import argcomplete module, if not present, set it to None
|
||||||
argcomplete: Optional[ModuleType] = None
|
argcomplete: Optional[ModuleType] = None
|
||||||
try:
|
try:
|
||||||
import argcomplete # type: ignore[no-redef]
|
import argcomplete # type: ignore[no-redef]
|
||||||
@@ -16,44 +20,59 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Function to create the main argument parser
|
||||||
def create_parser(prog: Optional[str] = None) -> argparse.ArgumentParser:
|
def create_parser(prog: Optional[str] = None) -> argparse.ArgumentParser:
|
||||||
|
# Creating the main argument parser with a description
|
||||||
parser = argparse.ArgumentParser(prog=prog, description="cLAN tool")
|
parser = argparse.ArgumentParser(prog=prog, description="cLAN tool")
|
||||||
|
|
||||||
|
# Adding a debug argument to enable debug logging
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--debug",
|
"--debug",
|
||||||
help="Enable debug logging",
|
help="Enable debug logging",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Adding subparsers for different commands
|
||||||
subparsers = parser.add_subparsers()
|
subparsers = parser.add_subparsers()
|
||||||
|
|
||||||
|
# Adding a subparser for the "webui" command
|
||||||
parser_webui = subparsers.add_parser("webui", help="start webui")
|
parser_webui = subparsers.add_parser("webui", help="start webui")
|
||||||
|
# Registering additional arguments for the "webui" command
|
||||||
webui.register_parser(parser_webui)
|
webui.register_parser(parser_webui)
|
||||||
|
|
||||||
|
# Using argcomplete for shell autocompletion if available
|
||||||
if argcomplete:
|
if argcomplete:
|
||||||
argcomplete.autocomplete(parser)
|
argcomplete.autocomplete(parser)
|
||||||
|
|
||||||
|
# If no command-line arguments provided, print the help message
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
# this will be the entrypoint under /bin/clan (see pyproject.toml config)
|
# this will be the entrypoint under /bin/clan (see pyproject.toml config)
|
||||||
|
### Main entry point function
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
# Creating the main argument parser
|
||||||
parser = create_parser()
|
parser = create_parser()
|
||||||
|
# Parsing command-line arguments
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Setting up logging based on the debug flag
|
||||||
if args.debug:
|
if args.debug:
|
||||||
setup_logging(logging.DEBUG)
|
setup_logging(logging.DEBUG)
|
||||||
log.debug("Debug log activated")
|
log.debug("Debug log activated")
|
||||||
else:
|
else:
|
||||||
setup_logging(logging.INFO)
|
setup_logging(logging.INFO)
|
||||||
|
|
||||||
|
# If the parsed arguments do not have the "func" attribute, exit
|
||||||
if not hasattr(args, "func"):
|
if not hasattr(args, "func"):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Calling the function associated with the specified command
|
||||||
args.func(args)
|
args.func(args)
|
||||||
|
|
||||||
|
|
||||||
|
# Entry point for script execution
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
|
# CORS configuration
|
||||||
|
cors_url = [
|
||||||
|
"http://localhost",
|
||||||
|
"http://127.0.0.1",
|
||||||
|
"http://0.0.0.0",
|
||||||
|
"http://[::]",
|
||||||
|
]
|
||||||
|
cors_ports = [2979, 3000]
|
||||||
|
|
||||||
|
# host for the server, frontend, backend and emulators
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
|
# used for emmulation and population for testing
|
||||||
port_dlg = 7000
|
port_dlg = 7000
|
||||||
port_ap = 7500
|
port_ap = 7500
|
||||||
_port_client_base = 8000
|
_port_client_base = 8000
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
|
# Importing necessary modules and packages
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib
|
import urllib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Importing FastAPI and related components
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.responses import HTMLResponse, JSONResponse
|
from fastapi.responses import HTMLResponse, JSONResponse
|
||||||
|
|
||||||
|
# Importing configuration and schemas from the clan_cli package
|
||||||
import clan_cli.config as config
|
import clan_cli.config as config
|
||||||
from clan_cli.webui.schemas import Resolution
|
from clan_cli.webui.schemas import Resolution
|
||||||
|
|
||||||
|
# Creating FastAPI instances for different applications
|
||||||
app_dlg = FastAPI(swagger_ui_parameters={"tryItOutEnabled": True})
|
app_dlg = FastAPI(swagger_ui_parameters={"tryItOutEnabled": True})
|
||||||
app_ap = FastAPI(swagger_ui_parameters={"tryItOutEnabled": True})
|
app_ap = FastAPI(swagger_ui_parameters={"tryItOutEnabled": True})
|
||||||
app_c1 = FastAPI(swagger_ui_parameters={"tryItOutEnabled": True})
|
app_c1 = FastAPI(swagger_ui_parameters={"tryItOutEnabled": True})
|
||||||
app_c2 = FastAPI(swagger_ui_parameters={"tryItOutEnabled": True})
|
app_c2 = FastAPI(swagger_ui_parameters={"tryItOutEnabled": True})
|
||||||
|
|
||||||
|
# List of FastAPI instances and their associated ports
|
||||||
apps = [
|
apps = [
|
||||||
(app_dlg, config.port_dlg),
|
(app_dlg, config.port_dlg),
|
||||||
(app_ap, config.port_ap),
|
(app_ap, config.port_ap),
|
||||||
@@ -22,7 +27,7 @@ apps = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
#### HEALTHCHECK
|
# Healthcheck endpoints for different applications
|
||||||
@app_c1.get("/")
|
@app_c1.get("/")
|
||||||
async def root_c1() -> str:
|
async def root_c1() -> str:
|
||||||
return "C1 is alive"
|
return "C1 is alive"
|
||||||
@@ -63,6 +68,7 @@ async def healthcheck_ap() -> str:
|
|||||||
return "200 OK"
|
return "200 OK"
|
||||||
|
|
||||||
|
|
||||||
|
# Function for performing health checks on a given URL with retries
|
||||||
def get_health(*, url: str, max_retries: int = 20, delay: float = 0.2) -> str | None:
|
def get_health(*, url: str, max_retries: int = 20, delay: float = 0.2) -> str | None:
|
||||||
for attempt in range(max_retries):
|
for attempt in range(max_retries):
|
||||||
try:
|
try:
|
||||||
@@ -74,13 +80,10 @@ def get_health(*, url: str, max_retries: int = 20, delay: float = 0.2) -> str |
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
#### CONSUME SERVICE
|
# Service consumption emulation for c1 which returns a gif1
|
||||||
|
|
||||||
# TODO send_msg???
|
|
||||||
|
|
||||||
|
|
||||||
@app_c1.get("/v1/print_daemon1", response_class=HTMLResponse)
|
@app_c1.get("/v1/print_daemon1", response_class=HTMLResponse)
|
||||||
async def consume_service_from_other_entity_c1() -> HTMLResponse:
|
async def consume_service_from_other_entity_c1() -> HTMLResponse:
|
||||||
|
# HTML content for the response
|
||||||
html_content = """
|
html_content = """
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
@@ -104,6 +107,7 @@ async def deregister_c1() -> JSONResponse:
|
|||||||
|
|
||||||
@app_c2.get("/v1/print_daemon2", response_class=HTMLResponse)
|
@app_c2.get("/v1/print_daemon2", response_class=HTMLResponse)
|
||||||
async def consume_service_from_other_entity_c2() -> HTMLResponse:
|
async def consume_service_from_other_entity_c2() -> HTMLResponse:
|
||||||
|
# Similar HTML content for the response
|
||||||
html_content = """
|
html_content = """
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
@@ -127,7 +131,9 @@ async def deregister_c2() -> JSONResponse:
|
|||||||
|
|
||||||
@app_ap.get("/ap_list_of_services", response_class=JSONResponse)
|
@app_ap.get("/ap_list_of_services", response_class=JSONResponse)
|
||||||
async def ap_list_of_services() -> JSONResponse:
|
async def ap_list_of_services() -> JSONResponse:
|
||||||
|
# Sample list of services as a JSON response
|
||||||
res = [
|
res = [
|
||||||
|
# Service 1
|
||||||
{
|
{
|
||||||
"uuid": "bdd640fb-0667-1ad1-1c80-317fa3b1799d",
|
"uuid": "bdd640fb-0667-1ad1-1c80-317fa3b1799d",
|
||||||
"service_name": "Carlos Printing0",
|
"service_name": "Carlos Printing0",
|
||||||
@@ -150,6 +156,7 @@ async def ap_list_of_services() -> JSONResponse:
|
|||||||
},
|
},
|
||||||
"usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}],
|
"usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}],
|
||||||
},
|
},
|
||||||
|
# Service 2 (similar structure)
|
||||||
{
|
{
|
||||||
"uuid": "23b8c1e9-3924-56de-3eb1-3b9046685257",
|
"uuid": "23b8c1e9-3924-56de-3eb1-3b9046685257",
|
||||||
"service_name": "Carlos Printing1",
|
"service_name": "Carlos Printing1",
|
||||||
@@ -217,7 +224,6 @@ async def ap_list_of_services() -> JSONResponse:
|
|||||||
"usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}],
|
"usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
# resp = json.dumps(obj=res)
|
|
||||||
return JSONResponse(content=res, status_code=200)
|
return JSONResponse(content=res, status_code=200)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
6
pkgs/clan-cli/clan_cli/webui/README.md
Normal file
6
pkgs/clan-cli/clan_cli/webui/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Here are the files found for the backend of the service.
|
||||||
|
|
||||||
|
- the backed is using a sql light db with sqlachremy
|
||||||
|
- this is done in
|
||||||
|
- sql\_\*.py, schema.py, tags.py
|
||||||
|
- subfolder: routers which also contains the apicall defenitions
|
||||||
@@ -2,25 +2,55 @@ import argparse
|
|||||||
import logging
|
import logging
|
||||||
from typing import Callable, NoReturn, Optional
|
from typing import Callable, NoReturn, Optional
|
||||||
|
|
||||||
|
# Get the logger for this module
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Initialize variables for server startup and potential ImportError
|
||||||
start_server: Optional[Callable] = None
|
start_server: Optional[Callable] = None
|
||||||
ServerImportError: Optional[ImportError] = None
|
ServerImportError: Optional[ImportError] = None
|
||||||
|
|
||||||
|
# Try importing the start_server function from the server module
|
||||||
try:
|
try:
|
||||||
from .server import start_server
|
from .server import start_server
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
|
# If ImportError occurs, log the exception and store it in ServerImportError
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
ServerImportError = e
|
ServerImportError = e
|
||||||
|
|
||||||
|
|
||||||
|
# Function to be called when FastAPI is not installed
|
||||||
|
##########################################################################################
|
||||||
|
# usage: clan webui [-h] [--port PORT] [--host HOST] [--populate] [--emulate] [--no-open] [--dev]
|
||||||
|
# [--dev-port DEV_PORT] [--dev-host DEV_HOST] [--reload]
|
||||||
|
# [--log-level {critical,error,warning,info,debug,trace}]
|
||||||
|
# [sub_url]
|
||||||
|
#
|
||||||
|
# positional arguments:
|
||||||
|
# sub_url Sub URL to open in the browser
|
||||||
|
#
|
||||||
|
# options:
|
||||||
|
# -h, --help show this help message and exit
|
||||||
|
# --port PORT Port to listen on
|
||||||
|
# --host HOST Host to listen on
|
||||||
|
# --populate Populate the database with dummy data
|
||||||
|
# --emulate Emulate two entities c1 and c2 + dlg and ap
|
||||||
|
# --no-open Don't open the browser
|
||||||
|
# --dev Run in development mode
|
||||||
|
# --dev-port DEV_PORT Port to listen on for the dev server
|
||||||
|
# --dev-host DEV_HOST Host to listen on
|
||||||
|
# --reload Don't reload on changes
|
||||||
|
# --log-level {critical,error,warning,info,debug,trace}
|
||||||
|
# Log level
|
||||||
|
##########################################################################################
|
||||||
def fastapi_is_not_installed(_: argparse.Namespace) -> NoReturn:
|
def fastapi_is_not_installed(_: argparse.Namespace) -> NoReturn:
|
||||||
assert ServerImportError is not None
|
assert ServerImportError is not None
|
||||||
print(
|
print(
|
||||||
f"Dependencies for the webserver is not installed. The webui command has been disabled ({ServerImportError})"
|
f"Dependencies for the webserver are not installed. The webui command has been disabled ({ServerImportError})"
|
||||||
)
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# Function to register command-line arguments for the webserver
|
||||||
def register_parser(parser: argparse.ArgumentParser) -> None:
|
def register_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
parser.add_argument("--port", type=int, default=2979, help="Port to listen on")
|
parser.add_argument("--port", type=int, default=2979, help="Port to listen on")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -69,10 +99,10 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
type=str,
|
type=str,
|
||||||
default="/",
|
default="/",
|
||||||
nargs="?",
|
nargs="?",
|
||||||
help="Sub url to open in the browser",
|
help="Sub URL to open in the browser",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set the args.func variable in args
|
# Set the args.func variable in args based on whether FastAPI is installed
|
||||||
if start_server is None:
|
if start_server is None:
|
||||||
parser.set_defaults(func=fastapi_is_not_installed)
|
parser.set_defaults(func=fastapi_is_not_installed)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,55 +1,46 @@
|
|||||||
|
# Imports
|
||||||
import logging
|
import logging
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
# import for sql
|
# Import FastAPI components and SQLAlchemy related modules
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.routing import APIRoute
|
from fastapi.routing import APIRoute
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
|
# Import configs
|
||||||
|
from ..config import cors_ports, cors_url
|
||||||
|
|
||||||
|
# Import custom modules and classes
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from . import sql_models
|
from . import sql_models
|
||||||
from .assets import asset_path
|
from .assets import asset_path
|
||||||
from .error_handlers import clan_error_handler, sql_error_handler
|
from .error_handlers import clan_error_handler, sql_error_handler
|
||||||
from .routers import endpoints, health, root, socket_manager2 # sql router hinzufügen
|
from .routers import endpoints, health, root
|
||||||
from .sql_db import engine
|
from .sql_db import engine
|
||||||
from .tags import tags_metadata
|
from .tags import tags_metadata
|
||||||
|
|
||||||
cors_url = [
|
|
||||||
"http://localhost",
|
|
||||||
"http://127.0.0.1",
|
|
||||||
"http://0.0.0.0",
|
|
||||||
"http://[::]",
|
|
||||||
]
|
|
||||||
cors_ports = [2979, 3000]
|
|
||||||
cors_whitelist = []
|
cors_whitelist = []
|
||||||
for u in cors_url:
|
for u in cors_url:
|
||||||
for p in cors_ports:
|
for p in cors_ports:
|
||||||
cors_whitelist.append(f"{u}:{p}")
|
cors_whitelist.append(f"{u}:{p}")
|
||||||
|
|
||||||
|
|
||||||
# Logging setup
|
# Logging setup
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
# Function to set up and configure the FastAPI application
|
||||||
async def lifespan(app: FastAPI) -> Any:
|
|
||||||
await socket_manager2.brd.connect()
|
|
||||||
yield
|
|
||||||
await socket_manager2.brd.disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
def setup_app() -> FastAPI:
|
def setup_app() -> FastAPI:
|
||||||
# bind sql engine
|
# Uncomment the following line to drop existing tables during startup (if needed)
|
||||||
# TODO comment aut and add flag to run with pupulated data rm *.sql run pytest with marked then start clan webui
|
|
||||||
# https://docs.pytest.org/en/7.1.x/example/markers.html
|
|
||||||
# sql_models.Base.metadata.drop_all(engine)
|
# sql_models.Base.metadata.drop_all(engine)
|
||||||
|
|
||||||
|
# Create tables in the database using SQLAlchemy
|
||||||
sql_models.Base.metadata.create_all(bind=engine)
|
sql_models.Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan, swagger_ui_parameters={"tryItOutEnabled": True})
|
# Initialize FastAPI application with lifespan management
|
||||||
|
app = FastAPI(swagger_ui_parameters={"tryItOutEnabled": True})
|
||||||
|
|
||||||
|
# Configure CORS middleware
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=cors_whitelist,
|
allow_origins=cors_whitelist,
|
||||||
@@ -58,31 +49,35 @@ def setup_app() -> FastAPI:
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Include routers for various endpoints and components
|
||||||
app.include_router(health.router)
|
app.include_router(health.router)
|
||||||
# sql methodes
|
|
||||||
app.include_router(endpoints.router)
|
app.include_router(endpoints.router)
|
||||||
|
|
||||||
app.include_router(socket_manager2.router)
|
# Needs to be last in registration due to wildcard route
|
||||||
|
|
||||||
# Needs to be last in register. Because of wildcard route
|
|
||||||
app.include_router(root.router)
|
app.include_router(root.router)
|
||||||
|
|
||||||
|
# Add custom exception handlers
|
||||||
app.add_exception_handler(ClanError, clan_error_handler) # type: ignore
|
app.add_exception_handler(ClanError, clan_error_handler) # type: ignore
|
||||||
app.add_exception_handler(SQLAlchemyError, sql_error_handler) # type: ignore
|
app.add_exception_handler(SQLAlchemyError, sql_error_handler) # type: ignore
|
||||||
|
|
||||||
|
# Mount the "static" route for serving static files
|
||||||
app.mount("/static", StaticFiles(directory=asset_path()), name="static")
|
app.mount("/static", StaticFiles(directory=asset_path()), name="static")
|
||||||
|
|
||||||
# Add tag descriptions to the OpenAPI schema
|
# Add tag descriptions to the OpenAPI schema
|
||||||
app.openapi_tags = tags_metadata
|
app.openapi_tags = tags_metadata
|
||||||
|
|
||||||
|
# Assign operation IDs to API routes
|
||||||
for route in app.routes:
|
for route in app.routes:
|
||||||
if isinstance(route, APIRoute):
|
if isinstance(route, APIRoute):
|
||||||
route.operation_id = route.name # in this case, 'read_items'
|
route.operation_id = route.name # in this case, 'read_items'
|
||||||
log.debug(f"Registered route: {route}")
|
log.debug(f"Registered route: {route}")
|
||||||
|
|
||||||
|
# Log registered exception handlers
|
||||||
for i in app.exception_handlers.items():
|
for i in app.exception_handlers.items():
|
||||||
log.debug(f"Registered exception handler: {i}")
|
log.debug(f"Registered exception handler: {i}")
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
# Create an instance of the FastAPI application
|
||||||
app = setup_app()
|
app = setup_app()
|
||||||
|
|||||||
1
pkgs/clan-cli/clan_cli/webui/routers/README.md
Normal file
1
pkgs/clan-cli/clan_cli/webui/routers/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
In the <endpoints.py> are the api endpoints implemented which could be used of the user/s.
|
||||||
@@ -27,11 +27,21 @@ router = APIRouter()
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# API Endpoints for all tables
|
||||||
|
# see the default api documentation under:
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/DefaultApi.md
|
||||||
|
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
# #
|
# #
|
||||||
# Service #
|
# Service #
|
||||||
# #
|
# #
|
||||||
#########################
|
#########################
|
||||||
|
# see the corresponding documentation under:
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/Service.md
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/ServiceCreate.md
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/ServiceUsageCreate.md
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/ServicesApi.md
|
||||||
@router.post("/api/v1/service", response_model=Service, tags=[Tags.services])
|
@router.post("/api/v1/service", response_model=Service, tags=[Tags.services])
|
||||||
def create_service(
|
def create_service(
|
||||||
service: ServiceCreate, db: Session = Depends(sql_db.get_db)
|
service: ServiceCreate, db: Session = Depends(sql_db.get_db)
|
||||||
@@ -134,6 +144,10 @@ def delete_service(
|
|||||||
# Entity #
|
# Entity #
|
||||||
# #
|
# #
|
||||||
#########################
|
#########################
|
||||||
|
# see the corresponding documentation under:
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/Entity.md
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/EntityCreate.md
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/EntitiesApi.md
|
||||||
@router.post("/api/v1/entity", response_model=Entity, tags=[Tags.entities])
|
@router.post("/api/v1/entity", response_model=Entity, tags=[Tags.entities])
|
||||||
def create_entity(
|
def create_entity(
|
||||||
entity: EntityCreate, db: Session = Depends(sql_db.get_db)
|
entity: EntityCreate, db: Session = Depends(sql_db.get_db)
|
||||||
@@ -298,6 +312,9 @@ def get_rpc_by_role(db: Session, role: Role, path: str) -> Any:
|
|||||||
# Resolution #
|
# Resolution #
|
||||||
# #
|
# #
|
||||||
#########################
|
#########################
|
||||||
|
# see the corresponding documentation under:
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/Resolution.md
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/ResolutionApi.md
|
||||||
@router.get(
|
@router.get(
|
||||||
"/api/v1/resolutions", response_model=List[Resolution], tags=[Tags.resolutions]
|
"/api/v1/resolutions", response_model=List[Resolution], tags=[Tags.resolutions]
|
||||||
)
|
)
|
||||||
@@ -312,6 +329,8 @@ def get_all_resolutions(
|
|||||||
# Repository #
|
# Repository #
|
||||||
# #
|
# #
|
||||||
#########################
|
#########################
|
||||||
|
# see the corresponding documentation under:
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/RepositoriesApi.md
|
||||||
@router.get(
|
@router.get(
|
||||||
"/api/v1/repositories", tags=[Tags.repositories], response_model=List[Service]
|
"/api/v1/repositories", tags=[Tags.repositories], response_model=List[Service]
|
||||||
)
|
)
|
||||||
@@ -326,6 +345,10 @@ def get_all_repositories(
|
|||||||
# Eventmessage #
|
# Eventmessage #
|
||||||
# #
|
# #
|
||||||
#########################
|
#########################
|
||||||
|
# see the corresponding documentation under:
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/Eventmessage.md
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/EventmessageCreate.md
|
||||||
|
### pkgs/clan-cli/tests/openapi_client/docs/EventmessageApi.md
|
||||||
@router.post(
|
@router.post(
|
||||||
"/api/v1/event_message", response_model=Eventmessage, tags=[Tags.eventmessages]
|
"/api/v1/event_message", response_model=Eventmessage, tags=[Tags.eventmessages]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
# Requires: `starlette`, `uvicorn`, `jinja2`
|
|
||||||
# Run with `uvicorn example:app`
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
import anyio
|
|
||||||
from broadcaster import Broadcast
|
|
||||||
from fastapi import APIRouter, WebSocket
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
brd = Broadcast("memory://")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/ws2_example")
|
|
||||||
async def get() -> HTMLResponse:
|
|
||||||
html = open(f"{os.getcwd()}/webui/routers/messenger.html").read()
|
|
||||||
return HTMLResponse(html)
|
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/ws2")
|
|
||||||
async def chatroom_ws(websocket: WebSocket) -> None:
|
|
||||||
await websocket.accept()
|
|
||||||
|
|
||||||
async with anyio.create_task_group() as task_group:
|
|
||||||
# run until first is complete
|
|
||||||
async def run_chatroom_ws_receiver() -> None:
|
|
||||||
await chatroom_ws_receiver(websocket=websocket)
|
|
||||||
task_group.cancel_scope.cancel()
|
|
||||||
|
|
||||||
task_group.start_soon(run_chatroom_ws_receiver)
|
|
||||||
log.warning("Started chatroom_ws_sender")
|
|
||||||
|
|
||||||
await chatroom_ws_sender(websocket)
|
|
||||||
|
|
||||||
|
|
||||||
async def chatroom_ws_receiver(websocket: WebSocket) -> None:
|
|
||||||
async for message in websocket.iter_text():
|
|
||||||
log.warning(f"Received message: {message}")
|
|
||||||
await brd.publish(channel="chatroom", message=message)
|
|
||||||
|
|
||||||
|
|
||||||
async def chatroom_ws_sender(websocket: WebSocket) -> None:
|
|
||||||
async with brd.subscribe(channel="chatroom") as subscriber:
|
|
||||||
if subscriber is None:
|
|
||||||
log.error("Subscriber is None")
|
|
||||||
return
|
|
||||||
async for event in subscriber: # type: ignore
|
|
||||||
await websocket.send_text(event.message)
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# Imports
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List
|
from typing import List
|
||||||
@@ -7,14 +8,23 @@ from pydantic import BaseModel, Field, validator
|
|||||||
from . import sql_models
|
from . import sql_models
|
||||||
from .db_types import Role, Status
|
from .db_types import Role, Status
|
||||||
|
|
||||||
|
# Set logger
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# create basemodel
|
||||||
class Machine(BaseModel):
|
class Machine(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
status: Status
|
status: Status
|
||||||
|
|
||||||
|
|
||||||
|
### Create database schema for sql
|
||||||
|
# each section will represent an own table
|
||||||
|
# Entity, Service, Resolution, Eventmessages
|
||||||
|
# The relation between them is as follows:
|
||||||
|
# one Entity can have many Services
|
||||||
|
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
# #
|
# #
|
||||||
# Entity #
|
# Entity #
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# Imports
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
@@ -10,7 +11,6 @@ from pathlib import Path
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
# XXX: can we dynamically load this using nix develop?
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from pydantic import AnyUrl, IPvAnyAddress
|
from pydantic import AnyUrl, IPvAnyAddress
|
||||||
from pydantic.tools import parse_obj_as
|
from pydantic.tools import parse_obj_as
|
||||||
@@ -19,9 +19,11 @@ import clan_cli.config as config
|
|||||||
from clan_cli.emulate_fastapi import apps, get_health
|
from clan_cli.emulate_fastapi import apps, get_health
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
|
|
||||||
|
# Setting up logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Function to open the browser for a specified URL
|
||||||
def open_browser(base_url: AnyUrl, sub_url: str) -> None:
|
def open_browser(base_url: AnyUrl, sub_url: str) -> None:
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
try:
|
try:
|
||||||
@@ -33,6 +35,7 @@ def open_browser(base_url: AnyUrl, sub_url: str) -> None:
|
|||||||
_open_browser(url)
|
_open_browser(url)
|
||||||
|
|
||||||
|
|
||||||
|
# Helper function to open a web browser for a given URL using available browsers
|
||||||
def _open_browser(url: AnyUrl) -> subprocess.Popen:
|
def _open_browser(url: AnyUrl) -> subprocess.Popen:
|
||||||
for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
|
for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
|
||||||
if shutil.which(browser):
|
if shutil.which(browser):
|
||||||
@@ -52,6 +55,7 @@ def _open_browser(url: AnyUrl) -> subprocess.Popen:
|
|||||||
raise ClanError("No browser found")
|
raise ClanError("No browser found")
|
||||||
|
|
||||||
|
|
||||||
|
# Context manager to spawn the Node.js development server
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def spawn_node_dev_server(host: IPvAnyAddress, port: int) -> Iterator[None]:
|
def spawn_node_dev_server(host: IPvAnyAddress, port: int) -> Iterator[None]:
|
||||||
log.info("Starting node dev server...")
|
log.info("Starting node dev server...")
|
||||||
@@ -78,6 +82,7 @@ def spawn_node_dev_server(host: IPvAnyAddress, port: int) -> Iterator[None]:
|
|||||||
proc.terminate()
|
proc.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
# Main function to start the server
|
||||||
def start_server(args: argparse.Namespace) -> None:
|
def start_server(args: argparse.Namespace) -> None:
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
headers: list[tuple[str, str]] = []
|
headers: list[tuple[str, str]] = []
|
||||||
@@ -115,6 +120,7 @@ def start_server(args: argparse.Namespace) -> None:
|
|||||||
sql_models.Base.metadata.drop_all(engine)
|
sql_models.Base.metadata.drop_all(engine)
|
||||||
|
|
||||||
if args.populate:
|
if args.populate:
|
||||||
|
# pre populate the server with some test data
|
||||||
test_dir = Path(__file__).parent.parent.parent / "tests"
|
test_dir = Path(__file__).parent.parent.parent / "tests"
|
||||||
|
|
||||||
if not test_dir.is_dir():
|
if not test_dir.is_dir():
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# Imports
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
@@ -7,6 +8,8 @@ from sqlalchemy.sql.expression import true
|
|||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from . import schemas, sql_models
|
from . import schemas, sql_models
|
||||||
|
|
||||||
|
# Functions to manipulate the tables of the database
|
||||||
|
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
# #
|
# #
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# Imports
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
@@ -7,6 +8,8 @@ from sqlalchemy.orm import Session, declarative_base, sessionmaker
|
|||||||
|
|
||||||
URL = "sqlite:///./sql_app.db"
|
URL = "sqlite:///./sql_app.db"
|
||||||
|
|
||||||
|
# Create db engine
|
||||||
|
|
||||||
engine = create_engine(URL, connect_args={"check_same_thread": False})
|
engine = create_engine(URL, connect_args={"check_same_thread": False})
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ from sqlalchemy.orm import relationship
|
|||||||
from .db_types import Role
|
from .db_types import Role
|
||||||
from .sql_db import Base
|
from .sql_db import Base
|
||||||
|
|
||||||
# Relationsship example
|
|
||||||
# https://dev.to/freddiemazzilli/flask-sqlalchemy-relationships-exploring-relationship-associations-igo
|
|
||||||
|
|
||||||
|
|
||||||
|
# SQLAlchemy model for the "entities" table
|
||||||
class Entity(Base):
|
class Entity(Base):
|
||||||
__tablename__ = "entities"
|
__tablename__ = "entities"
|
||||||
|
|
||||||
## Queryable body ##
|
## Queryable body ##
|
||||||
|
# Primary Key
|
||||||
did = Column(String, primary_key=True, index=True)
|
did = Column(String, primary_key=True, index=True)
|
||||||
|
# Indexed Columns
|
||||||
name = Column(String, index=True, unique=True)
|
name = Column(String, index=True, unique=True)
|
||||||
ip = Column(String, index=True)
|
ip = Column(String, index=True)
|
||||||
network = Column(String, index=True)
|
network = Column(String, index=True)
|
||||||
@@ -21,67 +21,85 @@ class Entity(Base):
|
|||||||
stop_health_task = Column(Boolean)
|
stop_health_task = Column(Boolean)
|
||||||
|
|
||||||
## Non queryable body ##
|
## Non queryable body ##
|
||||||
# In here we deposit: Not yet defined stuff
|
# JSON field for additional non-queryable data
|
||||||
other = Column(JSON)
|
other = Column(JSON)
|
||||||
|
|
||||||
## Relations ##
|
## Relations ##
|
||||||
|
# One-to-Many relationship with "services" table
|
||||||
services = relationship("Service", back_populates="entity")
|
services = relationship("Service", back_populates="entity")
|
||||||
|
# One-to-Many relationship with "entity_roles" table
|
||||||
roles = relationship("EntityRoles", back_populates="entity")
|
roles = relationship("EntityRoles", back_populates="entity")
|
||||||
|
# One-to-Many relationship with "service_usage" table
|
||||||
consumes = relationship("ServiceUsage", back_populates="consumer_entity")
|
consumes = relationship("ServiceUsage", back_populates="consumer_entity")
|
||||||
|
|
||||||
|
|
||||||
|
# SQLAlchemy model for the "entity_roles" table
|
||||||
class EntityRoles(Base):
|
class EntityRoles(Base):
|
||||||
__tablename__ = "entity_roles"
|
__tablename__ = "entity_roles"
|
||||||
|
|
||||||
## Queryable body ##
|
## Queryable body ##
|
||||||
|
# Primary Key
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
# Foreign Key
|
||||||
entity_did = Column(String, ForeignKey("entities.did"))
|
entity_did = Column(String, ForeignKey("entities.did"))
|
||||||
|
# Enum field for role
|
||||||
role = Column(Enum(Role), index=True, nullable=False) # type: ignore
|
role = Column(Enum(Role), index=True, nullable=False) # type: ignore
|
||||||
|
|
||||||
## Relations ##
|
## Relations ##
|
||||||
|
# Many-to-One relationship with "entities" table
|
||||||
entity = relationship("Entity", back_populates="roles")
|
entity = relationship("Entity", back_populates="roles")
|
||||||
|
|
||||||
|
|
||||||
|
# SQLAlchemy model for the "service_usage" table
|
||||||
class ServiceUsage(Base):
|
class ServiceUsage(Base):
|
||||||
__tablename__ = "service_usage"
|
__tablename__ = "service_usage"
|
||||||
|
|
||||||
## Queryable body ##
|
## Queryable body ##
|
||||||
|
# Primary Key
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
# Foreign Key
|
||||||
consumer_entity_did = Column(String, ForeignKey("entities.did"))
|
consumer_entity_did = Column(String, ForeignKey("entities.did"))
|
||||||
|
# Many-to-One relationship with "entities" table
|
||||||
consumer_entity = relationship("Entity", back_populates="consumes")
|
consumer_entity = relationship("Entity", back_populates="consumes")
|
||||||
times_consumed = Column(Integer, index=True, nullable=False)
|
times_consumed = Column(Integer, index=True, nullable=False)
|
||||||
|
|
||||||
service_uuid = Column(String, ForeignKey("services.uuid"))
|
service_uuid = Column(String, ForeignKey("services.uuid"))
|
||||||
|
# Many-to-One relationship with "services" table
|
||||||
service = relationship("Service", back_populates="usage")
|
service = relationship("Service", back_populates="usage")
|
||||||
|
|
||||||
|
|
||||||
|
# SQLAlchemy model for the "services" table
|
||||||
class Service(Base):
|
class Service(Base):
|
||||||
__tablename__ = "services"
|
__tablename__ = "services"
|
||||||
|
|
||||||
# Queryable body
|
# Queryable body
|
||||||
|
# Primary Key
|
||||||
uuid = Column(Text(length=36), primary_key=True, index=True)
|
uuid = Column(Text(length=36), primary_key=True, index=True)
|
||||||
service_name = Column(String, index=True)
|
service_name = Column(String, index=True)
|
||||||
service_type = Column(String, index=True)
|
service_type = Column(String, index=True)
|
||||||
endpoint_url = Column(String, index=True)
|
endpoint_url = Column(String, index=True)
|
||||||
|
|
||||||
## Non queryable body ##
|
## Non queryable body ##
|
||||||
# In here we deposit: Action
|
# JSON fields for additional non-queryable data
|
||||||
other = Column(JSON)
|
other = Column(JSON)
|
||||||
status = Column(JSON, index=True)
|
status = Column(JSON, index=True)
|
||||||
action = Column(JSON, index=True)
|
action = Column(JSON, index=True)
|
||||||
|
|
||||||
## Relations ##
|
## Relations ##
|
||||||
# One entity can have many services
|
# One-to-Many relationship with "entities" table
|
||||||
entity = relationship("Entity", back_populates="services")
|
entity = relationship("Entity", back_populates="services")
|
||||||
entity_did = Column(String, ForeignKey("entities.did"))
|
entity_did = Column(String, ForeignKey("entities.did"))
|
||||||
|
|
||||||
|
# One-to-Many relationship with "service_usage" table
|
||||||
usage = relationship("ServiceUsage", back_populates="service")
|
usage = relationship("ServiceUsage", back_populates="service")
|
||||||
|
|
||||||
|
|
||||||
|
# SQLAlchemy model for the "eventmessages" table
|
||||||
class Eventmessage(Base):
|
class Eventmessage(Base):
|
||||||
__tablename__ = "eventmessages"
|
__tablename__ = "eventmessages"
|
||||||
|
|
||||||
## Queryable body ##
|
## Queryable body ##
|
||||||
|
# Primary Key
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
timestamp = Column(Integer, unique=True, index=True)
|
timestamp = Column(Integer, unique=True, index=True)
|
||||||
group = Column(Integer, index=True)
|
group = Column(Integer, index=True)
|
||||||
@@ -91,8 +109,10 @@ class Eventmessage(Base):
|
|||||||
des_did = Column(String, index=True)
|
des_did = Column(String, index=True)
|
||||||
|
|
||||||
## Non queryable body ##
|
## Non queryable body ##
|
||||||
# In here we deposit: Network, Roles, Visible, etc.
|
# JSON field for additional non-queryable data
|
||||||
msg = Column(JSON)
|
msg = Column(JSON)
|
||||||
|
|
||||||
## Relations ##
|
## Relations ##
|
||||||
|
# One-to-Many relationship with "entities" table
|
||||||
# One entity can send many messages
|
# One entity can send many messages
|
||||||
|
# (Note: The comment is incomplete and can be extended based on the relationship)
|
||||||
|
|||||||
Reference in New Issue
Block a user