1.1 changes
This commit is contained in:
186
soleprint/dataloader/__init__.py
Normal file
186
soleprint/dataloader/__init__.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Soleprint Data Loader
|
||||
|
||||
Loads JSON data files and provides typed access via Pydantic models.
|
||||
JSON files live in data/ directory (content only, no code).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
# When symlinked, __file__ resolves to actual location (hub/dataloader)
|
||||
# but we need to find models/ in the runtime directory (gen/)
|
||||
# Use cwd as the base since we always run from gen/
|
||||
_runtime_dir = Path.cwd()
|
||||
_file_parent = Path(__file__).resolve().parent.parent
|
||||
|
||||
# Try runtime dir first (gen/), then fall back to file's parent (hub/)
|
||||
if (_runtime_dir / "models").exists():
|
||||
sys.path.insert(0, str(_runtime_dir))
|
||||
else:
|
||||
sys.path.insert(0, str(_file_parent))
|
||||
|
||||
from models.pydantic import (
|
||||
Book,
|
||||
BookCollection,
|
||||
Depot,
|
||||
DepotCollection,
|
||||
Desk,
|
||||
DeskCollection,
|
||||
Pulse,
|
||||
PulseCollection,
|
||||
Room,
|
||||
RoomCollection,
|
||||
Status,
|
||||
Template,
|
||||
TemplateCollection,
|
||||
Tool,
|
||||
ToolCollection,
|
||||
Vein,
|
||||
VeinCollection,
|
||||
)
|
||||
|
||||
# Data directory - try runtime dir first, then file's parent
|
||||
_default_data = (
|
||||
_runtime_dir / "data" if (_runtime_dir / "data").exists() else _file_parent / "data"
|
||||
)
|
||||
DATA_DIR = Path(os.getenv("SOLEPRINT_DATA_DIR", _default_data)).resolve()
|
||||
|
||||
|
||||
def _load_json(filename: str) -> dict:
|
||||
"""Load a JSON file from the data directory."""
|
||||
filepath = DATA_DIR / filename
|
||||
if filepath.exists():
|
||||
with open(filepath) as f:
|
||||
return json.load(f)
|
||||
return {"items": []}
|
||||
|
||||
|
||||
def _save_json(filename: str, data: dict):
|
||||
"""Save data to a JSON file in the data directory."""
|
||||
filepath = DATA_DIR / filename
|
||||
with open(filepath, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
|
||||
# === Collection Loaders ===
|
||||
|
||||
|
||||
def get_veins() -> List[Vein]:
|
||||
data = _load_json("veins.json")
|
||||
return VeinCollection(**data).items
|
||||
|
||||
|
||||
def get_rooms() -> List[Room]:
|
||||
data = _load_json("rooms.json")
|
||||
return RoomCollection(**data).items
|
||||
|
||||
|
||||
def get_depots() -> List[Depot]:
|
||||
data = _load_json("depots.json")
|
||||
return DepotCollection(**data).items
|
||||
|
||||
|
||||
def get_templates() -> List[Template]:
|
||||
data = _load_json("templates.json")
|
||||
return TemplateCollection(**data).items
|
||||
|
||||
|
||||
def get_tools() -> List[Tool]:
|
||||
data = _load_json("tools.json")
|
||||
return ToolCollection(**data).items
|
||||
|
||||
|
||||
def get_cabinets() -> list:
|
||||
"""Load cabinets (simple dict for now)."""
|
||||
data = _load_json("cabinets.json")
|
||||
return data.get("items", [])
|
||||
|
||||
|
||||
def get_monitors() -> list:
|
||||
"""Load monitors (simple dict for now)."""
|
||||
data = _load_json("monitors.json")
|
||||
return data.get("items", [])
|
||||
|
||||
|
||||
def get_pulses() -> List[Pulse]:
|
||||
data = _load_json("pulses.json")
|
||||
return PulseCollection(**data).items
|
||||
|
||||
|
||||
def get_books() -> List[Book]:
|
||||
data = _load_json("books.json")
|
||||
return BookCollection(**data).items
|
||||
|
||||
|
||||
def get_desks() -> List[Desk]:
|
||||
data = _load_json("desks.json")
|
||||
return DeskCollection(**data).items
|
||||
|
||||
|
||||
# === Single Item Helpers ===
|
||||
|
||||
|
||||
def get_vein(name: str) -> Optional[Vein]:
|
||||
for v in get_veins():
|
||||
if v.name == name:
|
||||
return v
|
||||
return None
|
||||
|
||||
|
||||
def get_room(name: str) -> Optional[Room]:
|
||||
for r in get_rooms():
|
||||
if r.name == name:
|
||||
return r
|
||||
return None
|
||||
|
||||
|
||||
def get_depot(name: str) -> Optional[Depot]:
|
||||
for d in get_depots():
|
||||
if d.name == name:
|
||||
return d
|
||||
return None
|
||||
|
||||
|
||||
def get_tool(name: str) -> Optional[Tool]:
|
||||
for t in get_tools():
|
||||
if t.name == name:
|
||||
return t
|
||||
return None
|
||||
|
||||
|
||||
# === System Data (for frontend rendering) ===
|
||||
|
||||
|
||||
def get_artery_data() -> dict:
|
||||
"""Data for artery frontend."""
|
||||
return {
|
||||
"veins": [v.model_dump() for v in get_veins()],
|
||||
"rooms": [r.model_dump() for r in get_rooms()],
|
||||
"depots": [d.model_dump() for d in get_depots()],
|
||||
"pulses": [p.model_dump() for p in get_pulses()],
|
||||
}
|
||||
|
||||
|
||||
def get_atlas_data() -> dict:
|
||||
"""Data for atlas frontend."""
|
||||
return {
|
||||
"templates": [t.model_dump() for t in get_templates()],
|
||||
"depots": [d.model_dump() for d in get_depots()],
|
||||
"books": [b.model_dump() for b in get_books()],
|
||||
}
|
||||
|
||||
|
||||
def get_station_data() -> dict:
|
||||
"""Data for station frontend."""
|
||||
return {
|
||||
"tools": [t.model_dump() for t in get_tools()],
|
||||
"monitors": get_monitors(),
|
||||
"cabinets": get_cabinets(),
|
||||
"rooms": [r.model_dump() for r in get_rooms()],
|
||||
"depots": [d.model_dump() for d in get_depots()],
|
||||
"desks": [d.model_dump() for d in get_desks()],
|
||||
}
|
||||
174
soleprint/index.html
Normal file
174
soleprint/index.html
Normal file
@@ -0,0 +1,174 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>soleprint</title>
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='%23e5e5e5'%3E%3Cg transform='rotate(-15 18 38)'%3E%3Cellipse cx='18' cy='32' rx='7' ry='13'/%3E%3Cellipse cx='18' cy='48' rx='6' ry='7'/%3E%3C/g%3E%3Cg transform='rotate(15 46 28)'%3E%3Cellipse cx='46' cy='22' rx='7' ry='13'/%3E%3Cellipse cx='46' cy='38' rx='6' ry='7'/%3E%3C/g%3E%3C/svg%3E">
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html { background: #0a0a0a; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
line-height: 1.6;
|
||||
color: #e5e5e5;
|
||||
background: #0a0a0a;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.logo { width: 64px; height: 64px; }
|
||||
h1 { font-size: 2.5rem; margin: 0; color: white; }
|
||||
.tagline {
|
||||
color: #a3a3a3;
|
||||
margin-bottom: 2rem;
|
||||
border-bottom: 1px solid #333;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
.mission {
|
||||
background: #1a1a1a;
|
||||
border-left: 3px solid #d4a574;
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 2rem 0;
|
||||
border-radius: 0 8px 8px 0;
|
||||
color: #d4a574;
|
||||
}
|
||||
.systems {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.system {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
text-decoration: none;
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
transition: transform 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
.system:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
.system.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.system svg { width: 48px; height: 48px; flex-shrink: 0; }
|
||||
.system-info h2 { margin: 0 0 0.25rem 0; font-size: 1.2rem; }
|
||||
.system-info p { margin: 0; font-size: 0.9rem; color: #a3a3a3; }
|
||||
|
||||
.artery { background: #1a1a1a; border: 1px solid #b91c1c; }
|
||||
.artery h2 { color: #fca5a5; }
|
||||
.artery svg { color: #b91c1c; }
|
||||
|
||||
.atlas { background: #1a1a1a; border: 1px solid #15803d; }
|
||||
.atlas h2 { color: #86efac; }
|
||||
.atlas svg { color: #15803d; }
|
||||
|
||||
.station { background: #1a1a1a; border: 1px solid #1d4ed8; }
|
||||
.station h2 { color: #93c5fd; }
|
||||
.station svg { color: #1d4ed8; }
|
||||
|
||||
footer {
|
||||
margin-top: 3rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid #333;
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<!-- Two shoe prints walking -->
|
||||
<svg class="logo" viewBox="0 0 64 64" fill="currentColor">
|
||||
<!-- Left shoe print (back, lower) -->
|
||||
<g transform="rotate(-15 18 38)">
|
||||
<!-- Sole -->
|
||||
<ellipse cx="18" cy="32" rx="7" ry="13"/>
|
||||
<!-- Heel -->
|
||||
<ellipse cx="18" cy="48" rx="6" ry="7"/>
|
||||
</g>
|
||||
<!-- Right shoe print (front, higher) -->
|
||||
<g transform="rotate(15 46 28)">
|
||||
<!-- Sole -->
|
||||
<ellipse cx="46" cy="22" rx="7" ry="13"/>
|
||||
<!-- Heel -->
|
||||
<ellipse cx="46" cy="38" rx="6" ry="7"/>
|
||||
</g>
|
||||
</svg>
|
||||
<h1>soleprint</h1>
|
||||
</header>
|
||||
<p class="tagline">Cada paso deja huella</p>
|
||||
|
||||
<p class="mission" style="display:none;"><!-- placeholder for session alerts --></p>
|
||||
|
||||
<div class="systems">
|
||||
<a {% if artery %}href="{{ artery }}"{% endif %} class="system artery{% if not artery %} disabled{% endif %}">
|
||||
<!-- Flux capacitor style -->
|
||||
<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
<path d="M24 4 L24 20 M24 20 L8 40 M24 20 L40 40"/>
|
||||
<circle cx="24" cy="4" r="3" fill="currentColor"/>
|
||||
<circle cx="8" cy="40" r="3" fill="currentColor"/>
|
||||
<circle cx="40" cy="40" r="3" fill="currentColor"/>
|
||||
<circle cx="24" cy="20" r="5" fill="none"/>
|
||||
<circle cx="24" cy="20" r="2" fill="currentColor"/>
|
||||
</svg>
|
||||
<div class="system-info">
|
||||
<h2>Artery</h2>
|
||||
<p>Todo lo vital</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a {% if atlas %}href="{{ atlas }}"{% endif %} class="system atlas{% if not atlas %} disabled{% endif %}">
|
||||
<!-- Map/Atlas with compass rose -->
|
||||
<svg viewBox="0 0 48 48" fill="currentColor">
|
||||
<!-- Map fold lines -->
|
||||
<path d="M4 8 L44 8 M4 16 L44 16 M4 24 L44 24 M4 32 L44 32 M4 40 L44 40" stroke="currentColor" stroke-width="1.5" opacity="0.3" fill="none"/>
|
||||
<path d="M16 4 L16 44 M32 4 L32 44" stroke="currentColor" stroke-width="1.5" opacity="0.3" fill="none"/>
|
||||
<!-- Compass rose in center -->
|
||||
<circle cx="24" cy="24" r="8" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M24 16 L24 32 M16 24 L32 24" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M24 16 L26 20 L24 24 L22 20 Z" fill="currentColor"/><!-- North arrow -->
|
||||
</svg>
|
||||
<div class="system-info">
|
||||
<h2>Atlas</h2>
|
||||
<p>Documentación accionable</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a {% if station %}href="{{ station }}"{% endif %} class="system station{% if not station %} disabled{% endif %}">
|
||||
<!-- Control panel with knobs and meters -->
|
||||
<svg viewBox="0 0 48 48" fill="currentColor">
|
||||
<!-- Panel frame -->
|
||||
<rect x="4" y="8" width="40" height="32" rx="2" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||
<!-- Knobs -->
|
||||
<circle cx="14" cy="18" r="5"/>
|
||||
<circle cx="14" cy="18" r="2" fill="white"/>
|
||||
<circle cx="34" cy="18" r="5"/>
|
||||
<circle cx="34" cy="18" r="2" fill="white"/>
|
||||
<!-- Meter displays -->
|
||||
<rect x="10" y="28" width="8" height="6" rx="1" fill="white" opacity="0.6"/>
|
||||
<rect x="30" y="28" width="8" height="6" rx="1" fill="white" opacity="0.6"/>
|
||||
<!-- Indicator lights -->
|
||||
<circle cx="24" cy="14" r="2" fill="white" opacity="0.8"/>
|
||||
</svg>
|
||||
<div class="system-info">
|
||||
<h2>Station</h2>
|
||||
<p>Monitores, Entornos y Herramientas</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<footer>soleprint</footer>
|
||||
</body>
|
||||
</html>
|
||||
127
soleprint/main.py
Normal file
127
soleprint/main.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Soleprint - Overview and routing hub.
|
||||
|
||||
Development workflow and documentation system
|
||||
👣 Mapping development footprints
|
||||
|
||||
Systems:
|
||||
💉 Artery (artery) - Todo lo vital
|
||||
🗺️ Atlas (atlas) - Documentación accionable
|
||||
🎛️ Station (station) - Monitores, Entornos y Herramientas
|
||||
|
||||
Routes:
|
||||
/ → index
|
||||
/health → health check
|
||||
/api/data/artery → artery data
|
||||
/api/data/atlas → atlas data
|
||||
/api/data/station → station data
|
||||
/artery/* → proxy to artery service
|
||||
/atlas/* → proxy to atlas service
|
||||
/station/* → proxy to station service
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Import data functions
|
||||
from dataloader import get_artery_data, get_atlas_data, get_station_data
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
app = FastAPI(title="Soleprint", version="0.1.0")
|
||||
|
||||
templates = Jinja2Templates(directory=Path(__file__).parent)
|
||||
|
||||
# Service URLs (internal for API calls)
|
||||
ARTERY_URL = os.getenv("ARTERY_URL", "http://localhost:12001")
|
||||
ATLAS_URL = os.getenv("ATLAS_URL", "http://localhost:12002")
|
||||
STATION_URL = os.getenv("STATION_URL", "http://localhost:12003")
|
||||
|
||||
# External URLs (for frontend links, falls back to internal)
|
||||
ARTERY_EXTERNAL_URL = os.getenv("ARTERY_EXTERNAL_URL", ARTERY_URL)
|
||||
ATLAS_EXTERNAL_URL = os.getenv("ATLAS_EXTERNAL_URL", ATLAS_URL)
|
||||
STATION_EXTERNAL_URL = os.getenv("STATION_EXTERNAL_URL", STATION_URL)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {
|
||||
"status": "ok",
|
||||
"service": "soleprint",
|
||||
"subsystems": {
|
||||
"artery": ARTERY_URL,
|
||||
"atlas": ATLAS_URL,
|
||||
"station": STATION_URL,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# === Data API ===
|
||||
|
||||
|
||||
@app.get("/api/data/artery")
|
||||
def api_artery_data():
|
||||
"""Data for artery service."""
|
||||
return get_artery_data()
|
||||
|
||||
|
||||
@app.get("/api/data/atlas")
|
||||
def api_atlas_data():
|
||||
"""Data for atlas service."""
|
||||
return get_atlas_data()
|
||||
|
||||
|
||||
@app.get("/api/data/station")
|
||||
def api_station_data():
|
||||
"""Data for station service."""
|
||||
return get_station_data()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def index(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
"index.html",
|
||||
{
|
||||
"request": request,
|
||||
"artery": ARTERY_EXTERNAL_URL,
|
||||
"atlas": ATLAS_EXTERNAL_URL,
|
||||
"station": STATION_EXTERNAL_URL,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# === Cross-system redirects ===
|
||||
# These allow soleprint to act as a hub, redirecting to subsystem routes
|
||||
|
||||
|
||||
@app.get("/artery")
|
||||
@app.get("/artery/{path:path}")
|
||||
def artery_redirect(path: str = ""):
|
||||
"""Redirect to artery service."""
|
||||
return RedirectResponse(url=f"{ARTERY_EXTERNAL_URL}/{path}")
|
||||
|
||||
|
||||
@app.get("/atlas")
|
||||
@app.get("/atlas/{path:path}")
|
||||
def atlas_redirect(path: str = ""):
|
||||
"""Redirect to atlas service."""
|
||||
return RedirectResponse(url=f"{ATLAS_EXTERNAL_URL}/{path}")
|
||||
|
||||
|
||||
@app.get("/station")
|
||||
@app.get("/station/{path:path}")
|
||||
def station_redirect(path: str = ""):
|
||||
"""Redirect to station service."""
|
||||
return RedirectResponse(url=f"{STATION_EXTERNAL_URL}/{path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=int(os.getenv("PORT", "12000")),
|
||||
reload=os.getenv("DEV", "").lower() in ("1", "true"),
|
||||
)
|
||||
5
soleprint/requirements.txt
Normal file
5
soleprint/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
fastapi>=0.104.0
|
||||
uvicorn[standard]>=0.24.0
|
||||
pydantic>=2.5.0
|
||||
httpx>=0.25.0
|
||||
jinja2>=3.1.0
|
||||
164
soleprint/run.py
Normal file
164
soleprint/run.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
Soleprint - Bare-metal single-port development server.
|
||||
|
||||
Serves everything on a single port for basic testing without docker/nginx.
|
||||
Routes /artery/*, /atlas/*, /station/* internally instead of redirecting.
|
||||
|
||||
Usage:
|
||||
python run.py # Serves on :12000 with all subsystems
|
||||
PORT=8080 python run.py # Custom port
|
||||
|
||||
This is for soleprint development only, not for managed rooms (use docker for those).
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
app = FastAPI(title="Soleprint (dev)", version="0.1.0")
|
||||
|
||||
templates = Jinja2Templates(directory=Path(__file__).parent)
|
||||
|
||||
# Base path for systems
|
||||
SPR_ROOT = Path(__file__).parent.parent
|
||||
|
||||
|
||||
def scan_directory(base_path: Path, pattern: str = "*") -> list[dict]:
|
||||
"""Scan a directory and return list of items with metadata."""
|
||||
items = []
|
||||
if base_path.exists():
|
||||
for item in sorted(base_path.iterdir()):
|
||||
if item.is_dir() and not item.name.startswith(("_", ".")):
|
||||
readme = item / "README.md"
|
||||
description = ""
|
||||
if readme.exists():
|
||||
# Get first non-empty, non-header line
|
||||
for line in readme.read_text().split("\n"):
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#"):
|
||||
description = line[:100]
|
||||
break
|
||||
items.append(
|
||||
{
|
||||
"name": item.name,
|
||||
"path": str(item.relative_to(SPR_ROOT)),
|
||||
"description": description,
|
||||
}
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {
|
||||
"status": "ok",
|
||||
"service": "soleprint-dev",
|
||||
"mode": "bare-metal",
|
||||
}
|
||||
|
||||
|
||||
# === Artery ===
|
||||
|
||||
|
||||
@app.get("/artery")
|
||||
def artery_index():
|
||||
"""List installed veins."""
|
||||
veins_path = SPR_ROOT / "artery" / "veins"
|
||||
veins = scan_directory(veins_path)
|
||||
return {
|
||||
"system": "artery",
|
||||
"tagline": "Todo lo vital",
|
||||
"veins": veins,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/artery/{path:path}")
|
||||
def artery_route(path: str):
|
||||
"""Artery sub-routes."""
|
||||
return {"system": "artery", "path": path, "message": "Artery route placeholder"}
|
||||
|
||||
|
||||
# === Atlas ===
|
||||
|
||||
|
||||
@app.get("/atlas")
|
||||
def atlas_index():
|
||||
"""List installed templates."""
|
||||
templates_path = SPR_ROOT / "atlas" / "templates"
|
||||
tpls = scan_directory(templates_path)
|
||||
return {
|
||||
"system": "atlas",
|
||||
"tagline": "Documentacion accionable",
|
||||
"templates": tpls,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/atlas/{path:path}")
|
||||
def atlas_route(path: str):
|
||||
"""Atlas sub-routes."""
|
||||
return {"system": "atlas", "path": path, "message": "Atlas route placeholder"}
|
||||
|
||||
|
||||
# === Station ===
|
||||
|
||||
|
||||
@app.get("/station")
|
||||
def station_index():
|
||||
"""List installed tools."""
|
||||
tools_path = SPR_ROOT / "station" / "tools"
|
||||
tools = scan_directory(tools_path)
|
||||
return {
|
||||
"system": "station",
|
||||
"tagline": "Monitores, Entornos y Herramientas",
|
||||
"tools": tools,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/station/{path:path}")
|
||||
def station_route(path: str):
|
||||
"""Station sub-routes."""
|
||||
return {"system": "station", "path": path, "message": "Station route placeholder"}
|
||||
|
||||
|
||||
# === Main ===
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def index(request: Request):
|
||||
"""Landing page with links to subsystems."""
|
||||
return templates.TemplateResponse(
|
||||
"index.html",
|
||||
{
|
||||
"request": request,
|
||||
# In bare-metal mode, all routes are internal
|
||||
"artery": "/artery",
|
||||
"atlas": "/atlas",
|
||||
"station": "/station",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
port = int(os.getenv("PORT", "12000"))
|
||||
print(f"Soleprint bare-metal dev server starting on http://localhost:{port}")
|
||||
print(" /artery - Connectors (veins)")
|
||||
print(" /atlas - Documentation (templates)")
|
||||
print(" /station - Tools")
|
||||
print()
|
||||
|
||||
uvicorn.run(
|
||||
"run:app",
|
||||
host="0.0.0.0",
|
||||
port=port,
|
||||
reload=True,
|
||||
)
|
||||
Reference in New Issue
Block a user