feat: categorias de integracion (pagina-web/sam/profesores/otras-apps) + badge prod/dev

This commit is contained in:
Nurfog committed 2026-09-08 16:29:01 -03:00
1 parent 3787152ca0
commit 66ed027043
3 files changed
+59 -19

No files matched your search

+9 -4
View File
@@ -4,6 +4,7 @@ import { useSyncExternalStore } from "react";
import Link from "next/link"; import Link from "next/link";
import { import {
displayName, displayName,
envOf,
getModuleMeta, getModuleMeta,
getStatusInfo, getStatusInfo,
moduleUrl, moduleUrl,
@@ -74,7 +75,7 @@ export default function Home() {
const bySection: Record<string, ModuleState[]> = {}; const bySection: Record<string, ModuleState[]> = {};
modules.forEach((m) => { modules.forEach((m) => {
const s = sectionOf(m.name); const s = sectionOf(m.name, m.category);
if (!bySection[s]) bySection[s] = []; if (!bySection[s]) bySection[s] = [];
bySection[s].push(m); bySection[s].push(m);
}); });
@@ -82,6 +83,7 @@ export default function Home() {
const renderModuleCard = (m: ModuleState) => { const renderModuleCard = (m: ModuleState) => {
const meta = getModuleMeta(m.name); const meta = getModuleMeta(m.name);
const status = getStatusInfo(m); const status = getStatusInfo(m);
const env = envOf(m.branch);
const url = moduleUrl(m); const url = moduleUrl(m);
const name = displayName(m.name); const name = displayName(m.name);
return ( return (
@@ -107,6 +109,9 @@ export default function Home() {
<small className="text-muted" style={{ fontSize: "0.75rem" }}> <small className="text-muted" style={{ fontSize: "0.75rem" }}>
{status.label} · :{m.port} · {m.branch} {status.label} · :{m.port} · {m.branch}
</small> </small>
<span className={`badge ${env.cls} badge-respaldo`}>
{env.label}
</span>
</div> </div>
</td> </td>
<td className="text-end pe-3" style={{ width: 40 }}> <td className="text-end pe-3" style={{ width: 40 }}>
@@ -202,8 +207,8 @@ export default function Home() {
{SECTIONS.map((section) => { {SECTIONS.map((section) => {
const list = bySection[section.id] ?? []; const list = bySection[section.id] ?? [];
const isSystem = section.id === "system"; const isReview = section.id === "otras-apps";
if (!isSystem && list.length === 0) return null; if (!isReview && list.length === 0) return null;
return ( return (
<div className="col-12" key={section.id}> <div className="col-12" key={section.id}>
<div className="row row-cards"> <div className="row row-cards">
@@ -224,7 +229,7 @@ export default function Home() {
{list.map(renderModuleCard)} {list.map(renderModuleCard)}
{isSystem && backend && ( {isReview && backend && (
<> <>
<div className="col-md-6"> <div className="col-md-6">
<div className="app-card p-2"> <div className="app-card p-2">
+30 -2
View File
@@ -10,11 +10,14 @@ export default function UploadPage() {
branch: "main", branch: "main",
port: "", port: "",
subdomain: "", subdomain: "",
category: "otras-apps",
}); });
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null); const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
setForm({ ...form, [e.target.name]: e.target.value }); setForm({ ...form, [e.target.name]: e.target.value });
}; };
@@ -27,6 +30,7 @@ export default function UploadPage() {
name: form.name.trim().toLowerCase(), name: form.name.trim().toLowerCase(),
repoUrl: form.repoUrl.trim(), repoUrl: form.repoUrl.trim(),
branch: form.branch.trim() || "main", branch: form.branch.trim() || "main",
category: form.category,
}; };
if (form.port) payload.port = parseInt(form.port, 10); if (form.port) payload.port = parseInt(form.port, 10);
if (form.subdomain) payload.subdomain = form.subdomain.trim(); if (form.subdomain) payload.subdomain = form.subdomain.trim();
@@ -43,7 +47,7 @@ export default function UploadPage() {
ok: true, ok: true,
message: `Módulo "${payload.name}" registrado. Deploy: ${data.webhook ? "webhook creado" : "sin webhook"}${data.deployLog ? " · " + data.deployLog.slice(0, 80) : ""}`, message: `Módulo "${payload.name}" registrado. Deploy: ${data.webhook ? "webhook creado" : "sin webhook"}${data.deployLog ? " · " + data.deployLog.slice(0, 80) : ""}`,
}); });
setForm({ name: "", repoUrl: "", branch: "main", port: "", subdomain: "" }); setForm({ name: "", repoUrl: "", branch: "main", port: "", subdomain: "", category: "otras-apps" });
} else { } else {
setResult({ ok: false, message: data.error || `Error HTTP ${res.status}` }); setResult({ ok: false, message: data.error || `Error HTTP ${res.status}` });
} }
@@ -188,6 +192,27 @@ export default function UploadPage() {
</div> </div>
</details> </details>
<div className="col-md-6">
<label className="form-label fw-semibold">
¿A qué corresponde? <span className="text-danger">*</span>
</label>
<select
name="category"
value={form.category}
onChange={handleChange}
required
className="form-select"
>
<option value="pagina-web">Página Web</option>
<option value="sam">SAM</option>
<option value="profesores">Profesores</option>
<option value="otras-apps">Otras Aplicaciones</option>
</select>
<div className="form-text">
Define en qué sección del índice SAM aparece el módulo.
</div>
</div>
<div className="col-md-6"> <div className="col-md-6">
<label className="form-label fw-semibold">Rama</label> <label className="form-label fw-semibold">Rama</label>
<input <input
@@ -197,6 +222,9 @@ export default function UploadPage() {
placeholder="main" placeholder="main"
className="form-control" className="form-control"
/> />
<div className="form-text">
main = producción, otra rama = desarrollo.
</div>
</div> </div>
<div className="col-12 mt-2"> <div className="col-12 mt-2">
+20 -13
View File
@@ -19,6 +19,7 @@ export interface ModuleState {
subdomain: string; subdomain: string;
port: number; port: number;
active: boolean; active: boolean;
category?: string;
lastDeploy?: LastDeploy; lastDeploy?: LastDeploy;
container?: string | null; container?: string | null;
} }
@@ -184,20 +185,26 @@ export interface Section {
} }
export const SECTIONS: Section[] = [ export const SECTIONS: Section[] = [
{ id: "apps", title: "Selección de Aplicación", icon: "bx-grid-alt" }, { id: "pagina-web", title: "Página Web", icon: "bx-globe" },
{ id: "system", title: "Backend & Sistema", icon: "bx-server" }, { id: "sam", title: "SAM", icon: "bx-layer" },
{ id: "profesores", title: "Profesores", icon: "bx-pencil" },
{ id: "otras-apps", title: "Otras Aplicaciones", icon: "bx-cube" },
]; ];
const MODULE_SECTION: Record<string, string> = { // Categoría que pregunta la integración (default del deployer: otras-apps).
// Ejemplos: // "Otras Aplicaciones" concentra la revisión prod/dev: módulos
// contabilidad: "system", // integrados + backend + gestión.
// pagos: "system", export function sectionOf(name: string, category?: string): string {
}; const c = (category || "").trim().toLowerCase();
if (c && SECTIONS.some((s) => s.id === c)) return c;
void name;
return "otras-apps";
}
export function sectionOf(name: string): string { // Rama main/master = producción, resto = desarrollo.
const key = name.toLowerCase(); export function envOf(branch: string) {
if (MODULE_SECTION[key]) return MODULE_SECTION[key]; const b = (branch || "").trim().toLowerCase();
const known = SECTIONS.some((s) => s.id === key); return b === "main" || b === "master" || b === "prod" || b === "production"
if (known) return key; ? { label: "Prod", cls: "bg-success" }
return "apps"; : { label: "Dev", cls: "bg-warning text-dark" };
} }