lastest changes

This commit is contained in:
buenosairesam
2025-12-31 12:44:30 -03:00
parent cccc6b5a93
commit 9319d0ade6
7 changed files with 2231 additions and 159458 deletions

3
.gitignore vendored
View File

@@ -11,3 +11,6 @@ venv/
# Generated runnable instance (entirely gitignored - regenerate with build.py) # Generated runnable instance (entirely gitignored - regenerate with build.py)
gen/ gen/
# Database dumps (sensitive data)
cfg/*/dumps/*.sql

1500
artery/index.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,26 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Album · Pawprint</title> <title>Atlas · 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 48 48' fill='%2315803d'%3E%3Cpath d='M4 8 C4 8 12 6 24 10 L24 42 C12 38 4 40 4 40 Z' opacity='0.3'/%3E%3Cpath d='M44 8 C44 8 36 6 24 10 L24 42 C36 38 44 40 44 40 Z' opacity='0.5'/%3E%3Cpath d='M4 8 C4 8 12 6 24 10 M44 8 C44 8 36 6 24 10' fill='none' stroke='%2315803d' stroke-width='2'/%3E%3Cpath d='M4 40 C4 40 12 38 24 42 M44 40 C44 40 36 38 24 42' fill='none' stroke='%2315803d' stroke-width='2'/%3E%3Cline x1='24' y1='10' x2='24' y2='42' stroke='%2315803d' stroke-width='2'/%3E%3C/svg%3E"> <link
rel="icon"
type="image/svg+xml"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='%2315803d'%3E%3Cpath d='M4 8 C4 8 12 6 24 10 L24 42 C12 38 4 40 4 40 Z' opacity='0.3'/%3E%3Cpath d='M44 8 C44 8 36 6 24 10 L24 42 C36 38 44 40 44 40 Z' opacity='0.5'/%3E%3Cpath d='M4 8 C4 8 12 6 24 10 M44 8 C44 8 36 6 24 10' fill='none' stroke='%2315803d' stroke-width='2'/%3E%3Cpath d='M4 40 C4 40 12 38 24 42 M44 40 C44 40 36 38 24 42' fill='none' stroke='%2315803d' stroke-width='2'/%3E%3Cline x1='24' y1='10' x2='24' y2='42' stroke='%2315803d' stroke-width='2'/%3E%3C/svg%3E"
/>
<style> <style>
* { box-sizing: border-box; } * {
html { background: #0a0a0a; } box-sizing: border-box;
}
html {
background: #0a0a0a;
}
body { body {
font-family: system-ui, -apple-system, sans-serif; font-family:
system-ui,
-apple-system,
sans-serif;
max-width: 960px; max-width: 960px;
margin: 0 auto; margin: 0 auto;
padding: 2rem 1rem; padding: 2rem 1rem;
@@ -23,8 +34,16 @@
gap: 1rem; gap: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.logo { width: 64px; height: 64px; color: white; } .logo {
h1 { font-size: 2.5rem; margin: 0; color: white; } width: 64px;
height: 64px;
color: white;
}
h1 {
font-size: 2.5rem;
margin: 0;
color: white;
}
.tagline { .tagline {
color: rgba(255, 255, 255, 0.85); color: rgba(255, 255, 255, 0.85);
margin-bottom: 2rem; margin-bottom: 2rem;
@@ -48,8 +67,16 @@
padding: 1rem; padding: 1rem;
border-radius: 12px; border-radius: 12px;
} }
.composition h3 { margin: 0 0 0.75rem 0; font-size: 1.1rem; color: #86efac; } .composition h3 {
.composition > p { margin: 0 0 1rem 0; font-size: 0.9rem; color: #a3a3a3; } margin: 0 0 0.75rem 0;
font-size: 1.1rem;
color: #86efac;
}
.composition > p {
margin: 0 0 1rem 0;
font-size: 0.9rem;
color: #a3a3a3;
}
.components { .components {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
@@ -61,8 +88,16 @@
padding: 0.75rem; padding: 0.75rem;
border-radius: 8px; border-radius: 8px;
} }
.component h4 { margin: 0 0 0.25rem 0; font-size: 0.95rem; color: #86efac; } .component h4 {
.component p { margin: 0; font-size: 0.85rem; color: #a3a3a3; } margin: 0 0 0.25rem 0;
font-size: 0.95rem;
color: #86efac;
}
.component p {
margin: 0;
font-size: 0.85rem;
color: #a3a3a3;
}
.books { .books {
list-style: none; list-style: none;
padding: 0; padding: 0;
@@ -75,9 +110,17 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.books li:last-child { border-bottom: none; } .books li:last-child {
.books a { color: #86efac; text-decoration: none; font-weight: 500; } border-bottom: none;
.books a:hover { text-decoration: underline; } }
.books a {
color: #86efac;
text-decoration: none;
font-weight: 500;
}
.books a:hover {
text-decoration: underline;
}
.status { .status {
display: none; display: none;
} }
@@ -88,31 +131,72 @@
font-size: 0.85rem; font-size: 0.85rem;
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
footer a { color: white; } footer a {
footer .disabled { opacity: 0.5; } color: white;
}
footer .disabled {
opacity: 0.5;
}
</style> </style>
</head> </head>
<body> <body>
<header style="position:relative;"> <header style="position: relative">
<!-- Open book --> <!-- Open book -->
<svg class="logo" viewBox="0 0 48 48" fill="currentColor"> <svg class="logo" viewBox="0 0 48 48" fill="currentColor">
<path d="M4 8 C4 8 12 6 24 10 L24 42 C12 38 4 40 4 40 Z" opacity="0.3"/> <path
<path d="M44 8 C44 8 36 6 24 10 L24 42 C36 38 44 40 44 40 Z" opacity="0.5"/> d="M4 8 C4 8 12 6 24 10 L24 42 C12 38 4 40 4 40 Z"
<path d="M4 8 C4 8 12 6 24 10 M44 8 C44 8 36 6 24 10" fill="none" stroke="currentColor" stroke-width="2"/> opacity="0.3"
<path d="M4 40 C4 40 12 38 24 42 M44 40 C44 40 36 38 24 42" fill="none" stroke="currentColor" stroke-width="2"/> />
<line x1="24" y1="10" x2="24" y2="42" stroke="currentColor" stroke-width="2"/> <path
d="M44 8 C44 8 36 6 24 10 L24 42 C36 38 44 40 44 40 Z"
opacity="0.5"
/>
<path
d="M4 8 C4 8 12 6 24 10 M44 8 C44 8 36 6 24 10"
fill="none"
stroke="currentColor"
stroke-width="2"
/>
<path
d="M4 40 C4 40 12 38 24 42 M44 40 C44 40 36 38 24 42"
fill="none"
stroke="currentColor"
stroke-width="2"
/>
<line
x1="24"
y1="10"
x2="24"
y2="42"
stroke="currentColor"
stroke-width="2"
/>
</svg> </svg>
<h1>Album</h1> <h1>Album</h1>
{% if pawprint_url %}<a href="{{ pawprint_url }}" style="position:absolute;right:0;top:50%;transform:translateY(-50%);color:rgba(255,255,255,0.7);font-size:0.85rem;">← Pawprint</a>{% endif %} {% if soleprint_url %}<a
href="{{ soleprint_url }}"
style="
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
color: rgba(255, 255, 255, 0.7);
font-size: 0.85rem;
"
>← Soleprint</a
>{% endif %}
</header> </header>
<p class="tagline">conocimiento y toma de decisiones <!-- Actionable documentation --></p> <p class="tagline">
conocimiento y toma de decisiones
<!-- Actionable documentation -->
</p>
<section> <section>
<div class="composition"> <div class="composition">
<h3>Books</h3> <h3>Books</h3>
<div class="components"> <div class="components">
<div class="component"><h4>Template</h4></div> <div class="component"><h4>Template</h4></div>
<div class="component"><h4>Larder</h4></div> <div class="component"><h4>Depot</h4></div>
</div> </div>
</div> </div>
</section> </section>
@@ -123,10 +207,16 @@
{% for book in books %} {% for book in books %}
<li> <li>
<a href="/book/{{ book.slug }}/">{{ book.title }}</a> <a href="/book/{{ book.slug }}/">{{ book.title }}</a>
<span class="status {% if book.status == 'ready' %}ready{% elif book.status == 'building' %}building{% endif %}">{{ book.status | capitalize }}</span> <span
class="status {% if book.status == 'ready' %}ready{% elif book.status == 'building' %}building{% endif %}"
>{{ book.status | capitalize }}</span
>
</li> </li>
{% else %} {% else %}
<li><span style="color:#86efac;font-weight:500;">--</span><span class="status">Pending</span></li> <li>
<span style="color: #86efac; font-weight: 500">--</span
><span class="status">Pending</span>
</li>
{% endfor %} {% endfor %}
</ul> </ul>
</section> </section>
@@ -135,26 +225,46 @@
<h2>Templates</h2> <h2>Templates</h2>
<ul class="books"> <ul class="books">
{% for template in templates %} {% for template in templates %}
<li><a href="/template/{{ template.slug }}/">{{ template.title }}</a><span class="status {% if template.status == 'ready' %}ready{% elif template.status == 'building' %}building{% endif %}">{{ template.status | capitalize }}</span></li> <li>
<a href="/template/{{ template.slug }}/"
>{{ template.title }}</a
><span
class="status {% if template.status == 'ready' %}ready{% elif template.status == 'building' %}building{% endif %}"
>{{ template.status | capitalize }}</span
>
</li>
{% else %} {% else %}
<li><span style="color:#86efac;font-weight:500;">--</span><span class="status">Pending</span></li> <li>
<span style="color: #86efac; font-weight: 500">--</span
><span class="status">Pending</span>
</li>
{% endfor %} {% endfor %}
</ul> </ul>
</section> </section>
<section> <section>
<h2>Larders</h2> <h2>Depots</h2>
<ul class="books"> <ul class="books">
{% for larder in larders %} {% for depot in depots %}
<li><a href="/book/feature-form-samples/larder/">{{ larder.title }}</a><span class="status {% if larder.status == 'ready' %}ready{% elif larder.status == 'building' %}building{% endif %}">{{ larder.status | capitalize }}</span></li> <li>
<a href="/depot/{{ depot.slug }}/">{{ depot.title }}</a
><span
class="status {% if depot.status == 'ready' %}ready{% elif depot.status == 'building' %}building{% endif %}"
>{{ depot.status | capitalize }}</span
>
</li>
{% else %} {% else %}
<li><span style="color:#86efac;font-weight:500;">--</span><span class="status">Pending</span></li> <li>
<span style="color: #86efac; font-weight: 500">--</span
><span class="status">Pending</span>
</li>
{% endfor %} {% endfor %}
</ul> </ul>
</section> </section>
<footer> <footer>
{% if pawprint_url %}<a href="{{ pawprint_url }}">Pawprint</a>{% else %}<span class="disabled">← Pawprint</span>{% endif %} {% if soleprint_url %}<a href="{{ soleprint_url }}">Soleprint</a
>{% else %}<span class="disabled">← Soleprint</span>{% endif %}
</footer> </footer>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# View core_room logs # View mainroom logs
# #
# Usage: # Usage:
# ./logs.sh # All logs # ./logs.sh # All logs
@@ -11,9 +11,11 @@ set -e
# Change to parent directory (services are in ../service_name) # Change to parent directory (services are in ../service_name)
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
# Export core_room/.env vars # Export mainroom/.env vars
if [ -f ".env" ]; then if [ -f ".env" ]; then
export $(grep -v '^#' .env | grep -v '^$' | xargs) set -a
source .env
set +a
fi fi
TARGET=${1:-all} TARGET=${1:-all}
@@ -27,19 +29,21 @@ for dir in */; do
fi fi
done done
# ROOM_NAME comes from core_room/.env
ROOM_NAME=${ROOM_NAME:-core_room}
if [[ " ${SERVICE_DIRS[@]} " =~ " ${TARGET} " ]]; then if [[ " ${SERVICE_DIRS[@]} " =~ " ${TARGET} " ]]; then
# Service directory logs # Service directory logs
cd "$TARGET" && docker compose logs -f cd "$TARGET" && docker compose logs -f
elif [ "$TARGET" = "all" ]; then elif [ "$TARGET" = "all" ]; then
# All containers matching ROOM_NAME # All containers from all services
docker logs -f $(docker ps -q --filter "name=${ROOM_NAME}") 2>/dev/null || \ echo "Tailing logs for: ${SERVICE_DIRS[*]}"
echo "No ${ROOM_NAME} containers running" for service in "${SERVICE_DIRS[@]}"; do
cd "$service"
docker compose logs -f &
cd ..
done
wait
else else
# Specific container name # Specific container name - try exact match
docker logs -f "${ROOM_NAME}_$TARGET" 2>/dev/null || \
docker logs -f "$TARGET" 2>/dev/null || \ docker logs -f "$TARGET" 2>/dev/null || \
echo "Container not found: $TARGET" echo "Container not found: $TARGET"
echo "Use service name (e.g., ./logs.sh soleprint) or full container name"
fi fi

View File

@@ -11,6 +11,7 @@ Usage:
This is for soleprint development only, not for managed rooms (use docker for those). This is for soleprint development only, not for managed rooms (use docker for those).
""" """
import json
import os import os
import sys import sys
from pathlib import Path from pathlib import Path
@@ -27,8 +28,18 @@ app = FastAPI(title="Soleprint (dev)", version="0.1.0")
templates = Jinja2Templates(directory=Path(__file__).parent) templates = Jinja2Templates(directory=Path(__file__).parent)
# Base path for systems # Base path for systems (gen/ directory where this runs from)
SPR_ROOT = Path(__file__).parent.parent SPR_ROOT = Path(__file__).parent
DATA_DIR = SPR_ROOT / "data"
def load_data(filename: str) -> list[dict]:
"""Load data from JSON file in data/ directory."""
path = DATA_DIR / filename
if path.exists():
data = json.loads(path.read_text())
return data.get("items", [])
return []
def scan_directory(base_path: Path, pattern: str = "*") -> list[dict]: def scan_directory(base_path: Path, pattern: str = "*") -> list[dict]:
@@ -68,16 +79,59 @@ def health():
# === Artery === # === Artery ===
@app.get("/artery") @app.get("/artery", response_class=HTMLResponse)
def artery_index(): def artery_index(request: Request):
"""List installed veins.""" """Artery landing page."""
html_path = SPR_ROOT / "artery" / "index.html"
if html_path.exists():
# Load from data files
veins = load_data("veins.json")
pulses = load_data("pulses.json")
# Scan directories for items not in data files
shunts = scan_directory(SPR_ROOT / "artery" / "shunts")
for s in shunts:
s["slug"] = s["name"]
s["title"] = s["name"].replace("-", " ").title()
s["status"] = "ready"
depots = scan_directory(SPR_ROOT / "artery" / "depots")
for d in depots:
d["slug"] = d["name"]
d["title"] = d["name"].replace("-", " ").title()
d["status"] = "ready"
rooms = scan_directory(SPR_ROOT / "artery" / "room")
for r in rooms:
r["slug"] = r["name"]
r["title"] = r["name"].replace("-", " ").title()
r["status"] = "ready"
from jinja2 import Template
template = Template(html_path.read_text())
return HTMLResponse(
template.render(
request=request,
veins=veins,
rooms=rooms,
pulses=pulses,
shunts=shunts,
depots=depots,
plexuses=[], # Placeholder - whatsapp planned
soleprint_url="/",
pawprint_url="/",
)
)
veins_path = SPR_ROOT / "artery" / "veins" veins_path = SPR_ROOT / "artery" / "veins"
veins = scan_directory(veins_path) veins = scan_directory(veins_path)
return { return JSONResponse(
{
"system": "artery", "system": "artery",
"tagline": "Todo lo vital", "tagline": "Todo lo vital",
"veins": veins, "veins": veins,
} }
)
@app.get("/artery/{path:path}") @app.get("/artery/{path:path}")
@@ -89,16 +143,35 @@ def artery_route(path: str):
# === Atlas === # === Atlas ===
@app.get("/atlas") @app.get("/atlas", response_class=HTMLResponse)
def atlas_index(): def atlas_index(request: Request):
"""List installed templates.""" """Atlas landing page."""
templates_path = SPR_ROOT / "atlas" / "templates" html_path = SPR_ROOT / "atlas" / "index.html"
tpls = scan_directory(templates_path) if html_path.exists():
return { # Load books from data file (includes template info for templated vs original)
books = load_data("books.json")
templates = load_data("templates.json")
depots = load_data("depots.json")
from jinja2 import Template
template = Template(html_path.read_text())
return HTMLResponse(
template.render(
request=request,
books=books,
templates=templates,
depots=depots,
soleprint_url="/",
)
)
return JSONResponse(
{
"system": "atlas", "system": "atlas",
"tagline": "Documentacion accionable", "tagline": "Documentacion accionable",
"templates": tpls, "books": [],
} }
)
@app.get("/atlas/{path:path}") @app.get("/atlas/{path:path}")
@@ -110,16 +183,49 @@ def atlas_route(path: str):
# === Station === # === Station ===
@app.get("/station") @app.get("/station", response_class=HTMLResponse)
def station_index(): def station_index(request: Request):
"""List installed tools.""" """Station landing page."""
html_path = SPR_ROOT / "station" / "index.html"
if html_path.exists():
tools = scan_directory(SPR_ROOT / "station" / "tools")
for t in tools:
t["slug"] = t["name"]
t["title"] = t["name"].replace("-", " ").title()
t["status"] = "ready"
monitors = scan_directory(SPR_ROOT / "station" / "monitors")
for m in monitors:
m["slug"] = m["name"]
m["title"] = m["name"].replace("-", " ").title()
m["status"] = "ready"
desks = scan_directory(SPR_ROOT / "station" / "desks")
for d in desks:
d["slug"] = d["name"]
d["title"] = d["name"].replace("-", " ").title()
d["status"] = "ready"
from jinja2 import Template
template = Template(html_path.read_text())
return HTMLResponse(
template.render(
request=request,
tools=tools,
monitors=monitors,
cabinets=desks,
tables=[],
soleprint_url="/",
pawprint_url="/",
)
)
tools_path = SPR_ROOT / "station" / "tools" tools_path = SPR_ROOT / "station" / "tools"
tools = scan_directory(tools_path) tools = scan_directory(tools_path)
return { return JSONResponse(
{
"system": "station", "system": "station",
"tagline": "Monitores, Entornos y Herramientas", "tagline": "Monitores, Entornos y Herramientas",
"tools": tools, "tools": tools,
} }
)
@app.get("/station/{path:path}") @app.get("/station/{path:path}")

317
station/index.html Normal file
View File

@@ -0,0 +1,317 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ward · 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 48 48' fill='%231d4ed8'%3E%3Ccircle cx='24' cy='10' r='8'/%3E%3Cellipse cx='24' cy='32' rx='12' ry='14'/%3E%3Ccircle cx='20' cy='8' r='1.5' fill='white'/%3E%3Ccircle cx='28' cy='8' r='1.5' fill='white'/%3E%3Cellipse cx='24' cy='13' rx='2' ry='1' fill='white'/%3E%3Crect x='18' y='28' width='12' height='8' rx='2' fill='white' opacity='0.5'/%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: #1d4ed8;
}
header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.logo {
width: 64px;
height: 64px;
color: white;
}
h1 {
font-size: 2.5rem;
margin: 0;
color: white;
}
.tagline {
color: rgba(255, 255, 255, 0.85);
margin-bottom: 2rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
padding-bottom: 2rem;
}
section {
background: #0a0a0a;
padding: 1.5rem;
margin: 1.5rem 0;
border-radius: 12px;
}
section h2 {
margin: 0 0 1rem 0;
font-size: 1.2rem;
color: #93c5fd;
}
.composition {
background: #1a1a1a;
border: 2px solid #1d4ed8;
padding: 1rem;
border-radius: 12px;
}
.composition h3 {
margin: 0 0 0.75rem 0;
font-size: 1.1rem;
color: #93c5fd;
}
.composition > p {
margin: 0 0 1rem 0;
font-size: 0.9rem;
color: #a3a3a3;
}
.components {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 0.75rem;
}
.component {
background: #0a0a0a;
border: 1px solid #3f3f3f;
padding: 0.75rem;
border-radius: 8px;
}
.component h4 {
margin: 0 0 0.25rem 0;
font-size: 0.95rem;
color: #93c5fd;
}
.component p {
margin: 0;
font-size: 0.85rem;
color: #a3a3a3;
}
.tables {
list-style: none;
padding: 0;
margin: 0;
}
.tables li {
padding: 0.75rem 0;
border-bottom: 1px solid #3f3f3f;
display: flex;
justify-content: space-between;
align-items: center;
}
.tables li:last-child {
border-bottom: none;
}
.tables .name {
font-weight: 500;
text-decoration: none;
color: #e5e5e5;
}
.tables a.name:hover {
color: #93c5fd;
}
.status {
font-size: 0.75rem;
padding: 0.2rem 0.5rem;
border-radius: 4px;
text-transform: uppercase;
background: #2a2a2a;
color: #a3a3a3;
}
.health {
display: inline-block;
margin-top: 1rem;
padding: 0.5rem 1rem;
background: #1a1a1a;
border: 1px solid #3f3f3f;
border-radius: 4px;
font-family: monospace;
color: #93c5fd;
text-decoration: none;
}
.health:hover {
background: #2a2a2a;
}
footer {
margin-top: 3rem;
padding-top: 1.5rem;
border-top: 1px solid rgba(255, 255, 255, 0.3);
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.7);
}
footer a {
color: white;
}
footer .disabled {
opacity: 0.5;
}
</style>
</head>
<body>
<header style="position: relative">
<!-- Operation game patient -->
<svg class="logo" viewBox="0 0 48 48" fill="currentColor">
<circle cx="24" cy="10" r="8" />
<ellipse cx="24" cy="32" rx="12" ry="14" />
<circle cx="20" cy="8" r="1.5" fill="#1d4ed8" />
<circle cx="28" cy="8" r="1.5" fill="#1d4ed8" />
<ellipse cx="24" cy="13" rx="2" ry="1" fill="#1d4ed8" />
<rect
x="18"
y="28"
width="12"
height="8"
rx="2"
fill="#1d4ed8"
opacity="0.5"
/>
</svg>
<h1>Ward</h1>
{% if pawprint_url %}<a
href="{{ pawprint_url }}"
style="
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
color: rgba(255, 255, 255, 0.7);
font-size: 0.85rem;
"
>← Soleprint</a
>{% endif %}
</header>
<p class="tagline">
Monitores, herramientas y pruebas<!-- Monitors & Tools -->
</p>
<section>
<div class="composition">
<h3>Table</h3>
<div class="components">
<div
class="component"
style="
display: flex;
flex-direction: column;
gap: 0.5rem;
"
>
<h4 style="margin-bottom: 0.25rem">Cabinet</h4>
<div
style="
background: #1a1a1a;
border: 1px solid #3f3f3f;
padding: 0.5rem;
border-radius: 6px;
font-size: 0.85rem;
color: #93c5fd;
"
>
Monitor
</div>
<div
style="
background: #1a1a1a;
border: 1px solid #3f3f3f;
padding: 0.5rem;
border-radius: 6px;
font-size: 0.85rem;
color: #93c5fd;
"
>
Tool
</div>
</div>
<div class="component"><h4>Room</h4></div>
<div class="component"><h4>Depot</h4></div>
</div>
</div>
</section>
<section>
<h2>Tables</h2>
<ul class="tables">
{% for table in tables %}
<li>
<span class="name">{{ table.title }}</span
><span class="status">{{ table.status }}</span>
</li>
{% else %}
<li><span class="name">--</span></li>
{% endfor %}
</ul>
</section>
<section>
<h2>Cabinets</h2>
<ul class="tables">
{% for cabinet in cabinets %}
<li>
<span class="name">{{ cabinet.title }}</span
><span class="status">{{ cabinet.status }}</span>
</li>
{% else %}
<li><span class="name">--</span></li>
{% endfor %}
</ul>
</section>
<section>
<h2>Monitors</h2>
<ul class="tables">
{% for monitor in monitors %}
<li>
<a href="/monitor/{{ monitor.slug }}/" class="name"
>{{ monitor.title }}</a
><span class="status">{{ monitor.status }}</span>
</li>
{% else %}
<li><span class="name">--</span></li>
{% endfor %}
</ul>
</section>
<section>
<h2>Tools</h2>
<ul class="tables">
{% for tool in tools %}
<li>
{% if tool.type == 'app' and tool.url %}
<a href="{{ tool.url }}" class="name">{{ tool.title }}</a>
{% else %}
<span class="name">{{ tool.title }}</span>
{% if tool.cli %}<code
style="
font-size: 0.75rem;
color: #666;
margin-left: 0.5rem;
"
>{{ tool.cli }}</code
>{% endif %} {% endif %}
<span class="status">{{ tool.status }}</span>
</li>
{% else %}
<li><span class="name">--</span></li>
{% endfor %}
</ul>
</section>
<a href="/health" class="health">/health</a>
<footer>
{% if pawprint_url %}<a href="{{ pawprint_url }}">← Soleprint</a>{%
else %}<span class="disabled">← Soleprint</span>{% endif %}
</footer>
</body>
</html>