Deleted everything webui
All checks were successful
checks / test (pull_request) Successful in 58s
checks-impure / test (pull_request) Successful in 1m5s

This commit is contained in:
Luis Hebendanz 2023-12-14 18:47:14 +01:00
parent 1a36ef242f
commit 7dc2c21517
172 changed files with 4 additions and 57045 deletions

View File

@ -10,15 +10,6 @@
treefmt.flakeCheck = true;
treefmt.flakeFormatter = true;
treefmt.programs.shellcheck.enable = true;
treefmt.programs.prettier.enable = true;
# TODO: add custom prettier package, that uses our ui/node_modules
# treefmt.programs.prettier.settings.plugins = [
# "${self'.packages.prettier-plugin-tailwindcss}/lib/node_modules/prettier-plugin-tailwindcss/dist/index.mjs"
# ];
treefmt.settings.formatter.prettier.excludes = [
"secrets.yaml"
"key.json"
];
treefmt.programs.mypy.enable = true;
treefmt.programs.mypy.directories = {
@ -39,7 +30,6 @@
"--" # this argument is ignored by bash
];
includes = [ "*.nix" ];
excludes = [ "pkgs/node-packages/*.nix" ];
};
treefmt.settings.formatter.python = {
command = "sh";

View File

@ -1,6 +1,6 @@
# clan-cli
The clan-cli contains the command line interface as well as the graphical webui through the `clan webui` command.
The clan-cli contains the command line interface
## Hacking on the cli
@ -17,43 +17,6 @@ After you can use the local bin wrapper to test things in the cli:
./bin/clan
```
## Hacking on the webui
By default the webui is build from a tarball available https://git.clan.lol/clan/-/packages/generic/ui/.
To start a local developement environment instead, use the `--dev` flag:
```
./bin/clan webui --dev
```
This will spawn two webserver, a python one to for the api and a nodejs one that rebuilds the ui on the fly.
## Run webui directly
Useful for vscode run and debug option
```bash
python -m clan_cli.webui --reload --no-open
```
Add this `launch.json` to your .vscode directory to have working breakpoints in your vscode editor.
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Clan Webui",
"type": "python",
"request": "launch",
"module": "clan_cli.webui",
"justMyCode": true,
"args": ["--reload", "--no-open", "--log-level", "debug"]
}
]
}
```
## Run locally single-threaded for debugging
By default tests run in parallel using pytest-parallel.

View File

@ -1,37 +0,0 @@
#!/usr/bin/env python
import argparse
import json
import sys
from pathlib import Path
from uvicorn.importer import import_from_string
def main() -> None:
parser = argparse.ArgumentParser(prog="gen-openapi")
parser.add_argument(
"app", help='App import string. Eg. "main:app"', default="main:app"
)
parser.add_argument("--app-dir", help="Directory containing the app", default=None)
parser.add_argument(
"--out", help="Output file ending in .json", default="openapi.json"
)
args = parser.parse_args()
if args.app_dir is not None:
print(f"adding {args.app_dir} to sys.path")
sys.path.insert(0, args.app_dir)
print(f"importing app from {args.app}")
app = import_from_string(args.app)
openapi = app.openapi()
version = openapi.get("openapi", "unknown version")
print(f"writing openapi spec v{version}")
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(openapi, indent=2))
if __name__ == "__main__":
main()

View File

@ -6,7 +6,7 @@ from pathlib import Path
from types import ModuleType
from typing import Any
from . import backups, config, flakes, history, machines, secrets, vms, webui
from . import backups, config, flakes, history, machines, secrets, vms
from .custom_logger import setup_logging
from .dirs import get_clan_flake_toplevel, is_clan_flake
from .ssh import cli as ssh_cli
@ -105,9 +105,6 @@ def create_parser(prog: str | None = None) -> argparse.ArgumentParser:
)
machines.register_parser(parser_machine)
parser_webui = subparsers.add_parser("webui", help="start webui")
webui.register_parser(parser_webui)
parser_vms = subparsers.add_parser("vms", help="manage virtual machines")
vms.register_parser(parser_vms)

View File

@ -1,64 +0,0 @@
import argparse
from collections.abc import Callable
from typing import NoReturn
start_server: Callable | None = None
ServerImportError: ImportError | None = None
try:
from .server import start_server
except ImportError as e:
ServerImportError = e
def fastapi_is_not_installed(_: argparse.Namespace) -> NoReturn:
assert ServerImportError is not None
print(
f"Dependencies for the webserver is not installed. The webui command has been disabled ({ServerImportError})"
)
exit(1)
def register_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument("--port", type=int, default=2979, help="Port to listen on")
parser.add_argument(
"--host", type=str, default="localhost", help="Host to listen on"
)
parser.add_argument(
"--no-open", action="store_true", help="Don't open the browser", default=False
)
parser.add_argument(
"--dev", action="store_true", help="Run in development mode", default=False
)
parser.add_argument(
"--dev-port",
type=int,
default=3000,
help="Port to listen on for the dev server",
)
parser.add_argument(
"--dev-host", type=str, default="localhost", help="Host to listen on"
)
parser.add_argument(
"--reload", action="store_true", help="Don't reload on changes", default=False
)
parser.add_argument(
"--log-level",
type=str,
default="info",
help="Log level",
choices=["critical", "error", "warning", "info", "debug", "trace"],
)
parser.add_argument(
"sub_url",
type=str,
default="/",
nargs="?",
help="Sub url to open in the browser",
)
# Set the args.func variable in args
if start_server is None:
parser.set_defaults(func=fastapi_is_not_installed)
else:
parser.set_defaults(func=start_server)

View File

@ -1,15 +0,0 @@
import argparse
from . import register_parser
if __name__ == "__main__":
# this is use in our integration test
parser = argparse.ArgumentParser()
# call the register_parser function, which adds arguments to the parser
register_parser(parser)
args = parser.parse_args()
# call the function that is stored
# in the func attribute of args, and pass args as the argument
# look into register_parser to see how this is done
args.func(args)

View File

@ -1,10 +0,0 @@
import logging
from pydantic import BaseModel
log = logging.getLogger(__name__)
class MissingClanImports(BaseModel):
missing_clan_imports: list[str] = []
msg: str = "Some requested clan modules could not be found"

View File

@ -1,20 +0,0 @@
import logging
from pydantic import AnyUrl, BaseModel, Extra, parse_obj_as
from ..flakes.create import DEFAULT_URL
log = logging.getLogger(__name__)
class FlakeCreateInput(BaseModel):
url: AnyUrl = parse_obj_as(AnyUrl, DEFAULT_URL)
class MachineConfig(BaseModel):
clanImports: list[str] = [] # noqa: N815
clan: dict = {}
# allow extra fields to cover the full spectrum of a nixos config
class Config:
extra = Extra.allow

View File

@ -1,68 +0,0 @@
from enum import Enum
from pydantic import BaseModel, Extra, Field
from ..async_cmd import CmdOut
class Status(Enum):
ONLINE = "online"
OFFLINE = "offline"
UNKNOWN = "unknown"
class ClanModulesResponse(BaseModel):
clan_modules: list[str]
class Machine(BaseModel):
name: str
status: Status
class MachinesResponse(BaseModel):
machines: list[Machine]
class MachineResponse(BaseModel):
machine: Machine
class ConfigResponse(BaseModel):
clanImports: list[str] = [] # noqa: N815
clan: dict = {}
# allow extra fields to cover the full spectrum of a nixos config
class Config:
extra = Extra.allow
class SchemaResponse(BaseModel):
schema_: dict = Field(alias="schema")
class VerifyMachineResponse(BaseModel):
success: bool
error: str | None
class FlakeAttrResponse(BaseModel):
flake_attrs: list[str]
class FlakeAction(BaseModel):
id: str
uri: str
class FlakeListResponse(BaseModel):
flakes: list[str]
class FlakeCreateResponse(BaseModel):
cmd_out: dict[str, CmdOut]
class FlakeResponse(BaseModel):
content: str
actions: list[FlakeAction]

View File

@ -1,56 +0,0 @@
import logging
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.routing import APIRoute
from fastapi.staticfiles import StaticFiles
from .assets import asset_path
from .error_handlers import clan_error_handler
from .routers import clan_modules, flake, health, machines, root
from .settings import settings
from .tags import tags_metadata
# Logging setup
log = logging.getLogger(__name__)
def setup_app() -> FastAPI:
app = FastAPI()
if settings.env.is_development():
# Allow CORS in development mode for nextjs dev server
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(clan_modules.router)
app.include_router(flake.router)
app.include_router(health.router)
app.include_router(machines.router)
# Needs to be last in register. Because of wildcard route
app.include_router(root.router)
app.add_exception_handler(Exception, clan_error_handler)
app.mount("/static", StaticFiles(directory=asset_path()), name="static")
# Add tag descriptions to the OpenAPI schema
app.openapi_tags = tags_metadata
for route in app.routes:
if isinstance(route, APIRoute):
route.operation_id = route.name # in this case, 'read_items'
log.debug(f"Registered route: {route}")
for i in app.exception_handlers.items():
log.debug(f"Registered exception handler: {i}")
return app
app = setup_app()

View File

@ -1,39 +0,0 @@
import functools
import logging
from pathlib import Path
log = logging.getLogger(__name__)
def get_hash(string: str) -> str:
"""
This function takes a string like '/nix/store/kkvk20b8zh8aafdnfjp6dnf062x19732-source'
and returns the hash part 'kkvk20b8zh8aafdnfjp6dnf062x19732' after '/nix/store/' and before '-source'.
"""
# Split the string by '/' and get the last element
last_element = string.split("/")[-1]
# Split the last element by '-' and get the first element
hash_part = last_element.split("-")[0]
# Return the hash part
return hash_part
def check_divergence(path: Path) -> None:
p = path.resolve()
log.info("Absolute web asset path: %s", p)
if not p.is_dir():
raise FileNotFoundError(p)
# Get the hash part of the path
gh = get_hash(str(p))
log.debug(f"Serving webui asset with hash {gh}")
@functools.cache
def asset_path() -> Path:
path = Path(__file__).parent / "assets"
log.debug("Serving assets from: %s", path)
check_divergence(path)
return path

View File

@ -1,54 +0,0 @@
import logging
from fastapi import Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from ..errors import ClanError, ClanHttpError
from .settings import settings
log = logging.getLogger(__name__)
def clan_error_handler(request: Request, exc: Exception) -> JSONResponse:
headers = {}
if settings.env.is_development():
headers["Access-Control-Allow-Origin"] = "*"
headers["Access-Control-Allow-Methods"] = "*"
if isinstance(exc, ClanHttpError):
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder(dict(detail={"msg": exc.msg})),
headers=headers,
)
elif isinstance(exc, ClanError):
log.error(f"ClanError: {exc}")
detail = [
{
"loc": [],
"msg": str(exc),
}
]
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder(dict(detail=detail)),
headers=headers,
)
else:
log.exception(f"Unhandled Exception: {exc}")
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content=jsonable_encoder(
dict(
detail=[
{
"loc": [],
"msg": str(exc),
}
]
)
),
headers=headers,
)

View File

@ -1,952 +0,0 @@
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/api/clan_modules": {
"get": {
"tags": ["modules"],
"summary": "List Clan Modules",
"operationId": "list_clan_modules",
"parameters": [
{
"name": "flake_dir",
"in": "query",
"required": true,
"schema": {
"title": "Flake Dir",
"type": "string",
"format": "path"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ClanModulesResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/flake/history": {
"post": {
"tags": ["flake"],
"summary": "Flake History Append",
"operationId": "flake_history_append",
"parameters": [
{
"name": "flake_dir",
"in": "query",
"required": true,
"schema": {
"title": "Flake Dir",
"type": "string",
"format": "path"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
},
"get": {
"tags": ["flake"],
"summary": "Flake History List",
"operationId": "flake_history_list",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"title": "Response Flake History List Api Flake History Get",
"type": "array",
"items": {
"type": "string",
"format": "path"
}
}
}
}
}
}
}
},
"/api/flake/attrs": {
"get": {
"tags": ["flake"],
"summary": "Inspect Flake Attrs",
"operationId": "inspect_flake_attrs",
"parameters": [
{
"name": "url",
"in": "query",
"required": true,
"schema": {
"title": "Url",
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 65536,
"format": "uri"
},
{
"type": "string",
"format": "path"
}
]
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FlakeAttrResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/flake/inspect": {
"get": {
"tags": ["flake"],
"summary": "Inspect Flake",
"operationId": "inspect_flake",
"parameters": [
{
"name": "url",
"in": "query",
"required": true,
"schema": {
"title": "Url",
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 65536,
"format": "uri"
},
{
"type": "string",
"format": "path"
}
]
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FlakeResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/flake/create": {
"post": {
"tags": ["flake"],
"summary": "Create Flake",
"operationId": "create_flake",
"parameters": [
{
"name": "flake_dir",
"in": "query",
"required": true,
"schema": {
"title": "Flake Dir",
"type": "string",
"format": "path"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FlakeCreateInput"
}
}
}
},
"responses": {
"201": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FlakeCreateResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/machines": {
"get": {
"tags": ["machine"],
"summary": "List Machines",
"operationId": "list_machines",
"parameters": [
{
"name": "flake_dir",
"in": "query",
"required": true,
"schema": {
"title": "Flake Dir",
"type": "string",
"format": "path"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MachinesResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/machines/{name}": {
"get": {
"tags": ["machine"],
"summary": "Get Machine",
"operationId": "get_machine",
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"title": "Name",
"type": "string"
}
},
{
"name": "flake_dir",
"in": "query",
"required": true,
"schema": {
"title": "Flake Dir",
"type": "string",
"format": "path"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MachineResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/machines/{name}/config": {
"get": {
"tags": ["machine"],
"summary": "Get Machine Config",
"operationId": "get_machine_config",
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"title": "Name",
"type": "string"
}
},
{
"name": "flake_dir",
"in": "query",
"required": true,
"schema": {
"title": "Flake Dir",
"type": "string",
"format": "path"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConfigResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
},
"put": {
"tags": ["machine"],
"summary": "Set Machine Config",
"operationId": "set_machine_config",
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"title": "Name",
"type": "string"
}
},
{
"name": "flake_dir",
"in": "query",
"required": true,
"schema": {
"title": "Flake Dir",
"type": "string",
"format": "path"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MachineConfig"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/schema": {
"put": {
"tags": ["machine"],
"summary": "Get Machine Schema",
"operationId": "get_machine_schema",
"parameters": [
{
"name": "flake_dir",
"in": "query",
"required": true,
"schema": {
"title": "Flake Dir",
"type": "string",
"format": "path"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"title": "Config",
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SchemaResponse"
}
}
}
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MissingClanImports"
}
}
},
"description": "Bad Request"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/machines/{name}/verify": {
"get": {
"tags": ["machine"],
"summary": "Get Verify Machine Config",
"operationId": "get_verify_machine_config",
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"title": "Name",
"type": "string"
}
},
{
"name": "flake_dir",
"in": "query",
"required": true,
"schema": {
"title": "Flake Dir",
"type": "string",
"format": "path"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifyMachineResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
},
"put": {
"tags": ["machine"],
"summary": "Put Verify Machine Config",
"operationId": "put_verify_machine_config",
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"title": "Name",
"type": "string"
}
},
{
"name": "flake_dir",
"in": "query",
"required": true,
"schema": {
"title": "Flake Dir",
"type": "string",
"format": "path"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"title": "Config",
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifyMachineResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"ClanModulesResponse": {
"properties": {
"clan_modules": {
"items": {
"type": "string"
},
"type": "array",
"title": "Clan Modules"
}
},
"type": "object",
"required": ["clan_modules"],
"title": "ClanModulesResponse"
},
"CmdOut": {
"properties": {
"stdout": {
"type": "string",
"title": "Stdout"
},
"stderr": {
"type": "string",
"title": "Stderr"
},
"cwd": {
"type": "string",
"format": "path",
"title": "Cwd"
}
},
"type": "object",
"required": ["stdout", "stderr", "cwd"],
"title": "CmdOut"
},
"ConfigResponse": {
"properties": {
"clanImports": {
"items": {
"type": "string"
},
"type": "array",
"title": "Clanimports",
"default": []
},
"clan": {
"type": "object",
"title": "Clan",
"default": {}
}
},
"type": "object",
"title": "ConfigResponse"
},
"FlakeAction": {
"properties": {
"id": {
"type": "string",
"title": "Id"
},
"uri": {
"type": "string",
"title": "Uri"
}
},
"type": "object",
"required": ["id", "uri"],
"title": "FlakeAction"
},
"FlakeAttrResponse": {
"properties": {
"flake_attrs": {
"items": {
"type": "string"
},
"type": "array",
"title": "Flake Attrs"
}
},
"type": "object",
"required": ["flake_attrs"],
"title": "FlakeAttrResponse"
},
"FlakeCreateInput": {
"properties": {
"url": {
"type": "string",
"maxLength": 65536,
"minLength": 1,
"format": "uri",
"title": "Url",
"default": "git+https://git.clan.lol/clan/clan-core?new-clan"
}
},
"type": "object",
"title": "FlakeCreateInput"
},
"FlakeCreateResponse": {
"properties": {
"cmd_out": {
"additionalProperties": {
"items": [
{
"type": "string",
"title": "Stdout"
},
{
"type": "string",
"title": "Stderr"
},
{
"type": "string",
"format": "path",
"title": "Cwd"
}
],
"type": "array",
"maxItems": 3,
"minItems": 3
},
"type": "object",
"title": "Cmd Out"
}
},
"type": "object",
"required": ["cmd_out"],
"title": "FlakeCreateResponse"
},
"FlakeResponse": {
"properties": {
"content": {
"type": "string",
"title": "Content"
},
"actions": {
"items": {
"$ref": "#/components/schemas/FlakeAction"
},
"type": "array",
"title": "Actions"
}
},
"type": "object",
"required": ["content", "actions"],
"title": "FlakeResponse"
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"Machine": {
"properties": {
"name": {
"type": "string",
"title": "Name"
},
"status": {
"$ref": "#/components/schemas/Status"
}
},
"type": "object",
"required": ["name", "status"],
"title": "Machine"
},
"MachineConfig": {
"properties": {
"clanImports": {
"items": {
"type": "string"
},
"type": "array",
"title": "Clanimports",
"default": []
},
"clan": {
"type": "object",
"title": "Clan",
"default": {}
}
},
"type": "object",
"title": "MachineConfig"
},
"MachineResponse": {
"properties": {
"machine": {
"$ref": "#/components/schemas/Machine"
}
},
"type": "object",
"required": ["machine"],
"title": "MachineResponse"
},
"MachinesResponse": {
"properties": {
"machines": {
"items": {
"$ref": "#/components/schemas/Machine"
},
"type": "array",
"title": "Machines"
}
},
"type": "object",
"required": ["machines"],
"title": "MachinesResponse"
},
"MissingClanImports": {
"properties": {
"missing_clan_imports": {
"items": {
"type": "string"
},
"type": "array",
"title": "Missing Clan Imports",
"default": []
},
"msg": {
"type": "string",
"title": "Msg",
"default": "Some requested clan modules could not be found"
}
},
"type": "object",
"title": "MissingClanImports"
},
"SchemaResponse": {
"properties": {
"schema": {
"type": "object",
"title": "Schema"
}
},
"type": "object",
"required": ["schema"],
"title": "SchemaResponse"
},
"Status": {
"enum": ["online", "offline", "unknown"],
"title": "Status",
"description": "An enumeration."
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": ["loc", "msg", "type"],
"title": "ValidationError"
},
"VerifyMachineResponse": {
"properties": {
"success": {
"type": "boolean",
"title": "Success"
},
"error": {
"type": "string",
"title": "Error"
}
},
"type": "object",
"required": ["success"],
"title": "VerifyMachineResponse"
}
}
},
"tags": [
{
"name": "flake",
"description": "Operations on a flake.",
"externalDocs": {
"description": "What is a flake?",
"url": "https://www.tweag.io/blog/2020-05-25-flakes/"
}
},
{
"name": "machine",
"description": "Manage physical machines. Instances of a flake"
},
{
"name": "vm",
"description": "Manage virtual machines. Instances of a flake"
},
{
"name": "modules",
"description": "Manage cLAN modules of a flake"
}
]
}

View File

@ -1,23 +0,0 @@
# Logging setup
import logging
from pathlib import Path
from fastapi import APIRouter, HTTPException
from clan_cli.clan_modules import get_clan_module_names
from ..api_outputs import (
ClanModulesResponse,
)
from ..tags import Tags
log = logging.getLogger(__name__)
router = APIRouter()
@router.get("/api/clan_modules", tags=[Tags.modules])
async def list_clan_modules(flake_dir: Path) -> ClanModulesResponse:
module_names, error = get_clan_module_names(flake_dir)
if error is not None:
raise HTTPException(status_code=400, detail=error)
return ClanModulesResponse(clan_modules=module_names)

View File

@ -1,93 +0,0 @@
import json
from json.decoder import JSONDecodeError
from pathlib import Path
from typing import Annotated
from fastapi import APIRouter, Body, HTTPException, status
from pydantic import AnyUrl
from clan_cli.webui.api_inputs import (
FlakeCreateInput,
)
from clan_cli.webui.api_outputs import (
FlakeAction,
FlakeAttrResponse,
FlakeCreateResponse,
FlakeResponse,
)
from ...async_cmd import run
from ...flakes import create
from ...nix import nix_command, nix_flake_show
from ..tags import Tags
router = APIRouter()
# TODO: Check for directory traversal
async def get_attrs(url: AnyUrl | Path) -> list[str]:
cmd = nix_flake_show(url)
out = await run(cmd)
data: dict[str, dict] = {}
try:
data = json.loads(out.stdout)
except JSONDecodeError:
raise HTTPException(status_code=422, detail="Could not load flake.")
nixos_configs = data.get("nixosConfigurations", {})
flake_attrs = list(nixos_configs.keys())
if not flake_attrs:
raise HTTPException(
status_code=422, detail="No entry or no attribute: nixosConfigurations"
)
return flake_attrs
# TODO: Check for directory traversal
@router.get("/api/flake/attrs", tags=[Tags.flake])
async def inspect_flake_attrs(url: AnyUrl | Path) -> FlakeAttrResponse:
return FlakeAttrResponse(flake_attrs=await get_attrs(url))
# TODO: Check for directory traversal
@router.get("/api/flake/inspect", tags=[Tags.flake])
async def inspect_flake(
url: AnyUrl | Path,