Ir al contenido

Panel de administración

Panel de administración interno de Lade. React 19 + Vite + TypeScript + React Router 7 + Zustand + Tailwind v4, siguiendo los patrones de lade-app (rutas centralizadas, sidebar declarativo, un store por dominio, tokens de diseño oklch + Inter/JetBrains Mono).

Desplegado desde el golden path vite-cloudfront-template (S3 + CloudFront + dominio propio) en admin.lade.com.mxprod-único a propósito (herramienta interna, sin dev/qa).

Ventana de terminal
cd app
npm install
cp .env.local.example .env.local # completar con `terraform -chdir=../infra output` (ver Login)
npm run dev # http://localhost:5173
npm run build # tsc -b && vite build
npm run lint
Ventana de terminal
make bootstrap-remote # una sola vez: bucket S3 de state
make tf-apply # crea CloudFront + dominio + Cognito
make deploy # build + sync + invalidation

En CI: push de un tag vX.Y.Z dispara deploy.yml — un solo job (Terraform apply → build de la SPA con los outputs de Cognito como VITE_* → sync a S3 + invalidation), gated por el GitHub Environment prod.

El panel tiene su propio user pool de Cognito (infra/cognito.tf), separado del de lade-app: sin auto-registro (admin_create_user_config), MFA (TOTP) obligatorio (mfa_configuration = "ON"). No hay UI de alta — los usuarios se crean a mano:

Ventana de terminal
aws cognito-idp admin-create-user \
--user-pool-id $(terraform -chdir=infra output -raw cognito_user_pool_id) \
--username persona@lade.com.mx \
--user-attributes Name=email,Value=persona@lade.com.mx Name=email_verified,Value=true \
--temporary-password '<temporal>'

El primer login pide cambiar la contraseña temporal y después configurar TOTP (se muestra el secret para cargar en Google Authenticator/Authy — sin QR todavía, ver LoginPage.tsx). Login normal en adelante: email + contraseña + código de 6 dígitos.

Implementado con aws-amplify (Auth) — src/lib/amplifyConfig.ts + src/core/store/useAuthStore.ts + src/pages/Login/LoginPage.tsx + src/router/ProtectedRoute.tsx. Los IDs del pool/client se inyectan en build time (VITE_COGNITO_USER_POOL_ID/VITE_COGNITO_CLIENT_ID) — ver .env.local.example para desarrollo local.

Primera sección real del panel (/golden-paths): dispara el workflow scaffold.yml para crear proyectos nuevos desde los templates de lade-templates (astro-cloudflare, vite-cloudfront, terraform-lambda).

Token de GitHub para disparar scaffolds — MVP deliberado, separado del login

Sección titulada «Token de GitHub para disparar scaffolds — MVP deliberado, separado del login»

El login de Cognito protege el panel; para efectivamente disparar un scaffold, el usuario ADEMÁS pega en Configuración un fine-grained PAT de GitHub scoped ÚNICAMENTE al repo lade-group/lade-factory (permisos Actions: Read and write + Metadata: Read-only). El token vive solo en sessionStorage del navegador — nunca en localStorage, .env, ni en el bundle — y se pierde al cerrar la pestaña.

Esto es intencional para el MVP: el blast radius de una fuga está acotado a un solo repo interno. El reemplazo correcto (proxy backend con GitHub App, sin tokens en el browser) es la historia F7.5 en Notion (Lade HQ) — no lo implementes acá sin revisar esa decisión primero.

infra/ Terraform: S3 + CloudFront + dominio propio + Cognito (vite-cloudfront-template)
bootstrap/ Bucket S3 de state (una sola vez, make bootstrap-remote)
envs/ prod.tfvars (custom_domain, cloudflare_zone_id) — prod-único
app/ La SPA (Vite) — todo el código de React vive acá
src/
lib/amplifyConfig.ts Amplify.configure (Cognito)
router/ProtectedRoute.tsx Guard: sin sesión → /login
pages/Login/LoginPage.tsx Credenciales → NEW_PASSWORD → TOTP setup → TOTP challenge
constants/routes.ts ROUTES (rutas centralizadas)
constants/SidebarRoutes.tsx Sidebar declarativo, con secciones
layouts/ AppLayout + Sidebar (secciones + logout)
core/api/githubClient.ts Cliente fetch contra la API de GitHub (scoped a lade-factory)
core/store/ Zustand: useAuthStore (Cognito), useGithubTokenStore (sessionStorage), useScaffoldStore
pages/Dashboard/
Home/
GoldenPaths/ ScaffoldForm + RunsList (polling por run_key)
Settings/

Para agregar una página nueva: clave en ROUTES, entrada en el items de la sección correspondiente de SIDEBAR_SECTIONS (o una sección nueva si no encaja en ninguna existente), ruta en Router.tsx, página en app/src/pages/Dashboard/<Seccion>/.