feat: secciones configurables del indice (apps/system, manifiesto MODULE_SECTION)
This commit is contained in:
1 parent
5c6d6bc46e
commit
3787152ca0
2 files changed
+175
-119
No files matched your search
+141
-119
@@ -7,7 +7,10 @@ import {
|
||||
getModuleMeta,
|
||||
getStatusInfo,
|
||||
moduleUrl,
|
||||
sectionOf,
|
||||
SECTIONS,
|
||||
type ApiResponse,
|
||||
type ModuleState,
|
||||
} from "@/lib/sam";
|
||||
|
||||
// Store externo: evita setState en efectos (regla react-hooks)
|
||||
@@ -69,6 +72,63 @@ export default function Home() {
|
||||
const modules = data?.modules ?? [];
|
||||
const backend = data?.backend;
|
||||
|
||||
const bySection: Record<string, ModuleState[]> = {};
|
||||
modules.forEach((m) => {
|
||||
const s = sectionOf(m.name);
|
||||
if (!bySection[s]) bySection[s] = [];
|
||||
bySection[s].push(m);
|
||||
});
|
||||
|
||||
const renderModuleCard = (m: ModuleState) => {
|
||||
const meta = getModuleMeta(m.name);
|
||||
const status = getStatusInfo(m);
|
||||
const url = moduleUrl(m);
|
||||
const name = displayName(m.name);
|
||||
return (
|
||||
<div className="col-md-6" key={m.name}>
|
||||
<div className="app-card p-2">
|
||||
<table className="table table-borderless mb-0 align-middle">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="ps-3" style={{ width: 56 }}>
|
||||
<i
|
||||
className={`bx ${meta.icon} bx-sm bx-border text-light ${meta.bg}`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
href={`/m/${m.name}`}
|
||||
className="text-decoration-none text-dark d-block py-2 stretched-link"
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<span className={`status-dot ${status.cls}`} />
|
||||
<small className="text-muted" style={{ fontSize: "0.75rem" }}>
|
||||
{status.label} · :{m.port} · {m.branch}
|
||||
</small>
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-end pe-3" style={{ width: 40 }}>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={`Abrir ${url}`}
|
||||
className="position-relative"
|
||||
style={{ zIndex: 2 }}
|
||||
>
|
||||
<i className="bx bx-window-open text-muted" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="sidenav">
|
||||
@@ -126,18 +186,7 @@ export default function Home() {
|
||||
|
||||
{!loading && !error && (
|
||||
<div className="row row-cards">
|
||||
<div className="col-12">
|
||||
<div className="alert alert-light section-header mb-3 py-2 px-3 d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
<i className="bx bx-grid-alt me-1" /> Selección de Aplicación
|
||||
</span>
|
||||
<span className="badge bg-primary rounded-pill">
|
||||
{modules.length} módulo{modules.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{modules.length === 0 ? (
|
||||
{modules.length === 0 && (
|
||||
<div className="col-12">
|
||||
<div className="app-card p-4 text-center">
|
||||
<i className="bx bx-inbox fs-1 text-muted" />
|
||||
@@ -149,117 +198,90 @@ export default function Home() {
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
modules.map((m) => {
|
||||
const meta = getModuleMeta(m.name);
|
||||
const status = getStatusInfo(m);
|
||||
const url = moduleUrl(m);
|
||||
const name = displayName(m.name);
|
||||
return (
|
||||
<div className="col-md-6" key={m.name}>
|
||||
<div className="app-card p-2">
|
||||
<table className="table table-borderless mb-0 align-middle">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="ps-3" style={{ width: 56 }}>
|
||||
<i
|
||||
className={`bx ${meta.icon} bx-sm bx-border text-light ${meta.bg}`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
href={`/m/${m.name}`}
|
||||
className="text-decoration-none text-dark d-block py-2 stretched-link"
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<span className={`status-dot ${status.cls}`} />
|
||||
<small className="text-muted" style={{ fontSize: "0.75rem" }}>
|
||||
{status.label} · :{m.port} · {m.branch}
|
||||
</small>
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-end pe-3" style={{ width: 40 }}>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={`Abrir ${url}`}
|
||||
className="position-relative"
|
||||
style={{ zIndex: 2 }}
|
||||
>
|
||||
<i className="bx bx-window-open text-muted" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
||||
{backend && (
|
||||
<>
|
||||
<div className="col-12 mt-4 mb-2">
|
||||
<div className="alert alert-light section-header mb-3 py-2 px-3">
|
||||
<i className="bx bx-server me-1" /> Backend & Sistema
|
||||
{SECTIONS.map((section) => {
|
||||
const list = bySection[section.id] ?? [];
|
||||
const isSystem = section.id === "system";
|
||||
if (!isSystem && list.length === 0) return null;
|
||||
return (
|
||||
<div className="col-12" key={section.id}>
|
||||
<div className="row row-cards">
|
||||
<div className="col-12">
|
||||
<div className="alert alert-light section-header mb-3 py-2 px-3 d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
<i className={`bx ${section.icon} me-1`} />{" "}
|
||||
{section.title}
|
||||
</span>
|
||||
{list.length > 0 && (
|
||||
<span className="badge bg-primary rounded-pill">
|
||||
{list.length} módulo
|
||||
{list.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{list.map(renderModuleCard)}
|
||||
|
||||
{isSystem && backend && (
|
||||
<>
|
||||
<div className="col-md-6">
|
||||
<div className="app-card p-2">
|
||||
<table className="table table-borderless mb-0 align-middle">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="ps-3" style={{ width: 56 }}>
|
||||
<i className="bx bx-code-alt bx-sm bx-border text-light bg-dark" />
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href="https://api.devops.norteamericano.cl/health"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-decoration-none text-dark d-block py-2 stretched-link"
|
||||
>
|
||||
API Backend
|
||||
</a>
|
||||
<small className="text-muted" style={{ fontSize: "0.75rem" }}>
|
||||
{backend.container ? "Activo" : "Detenido"} · {backend.branch}
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="app-card p-2">
|
||||
<table className="table table-borderless mb-0 align-middle">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="ps-3" style={{ width: 56 }}>
|
||||
<i className="bx bx-cog bx-sm bx-border text-light bg-secondary" />
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
href="/upload"
|
||||
className="text-decoration-none text-dark d-block py-2 stretched-link"
|
||||
>
|
||||
Gestionar Módulos
|
||||
</Link>
|
||||
<small className="text-muted" style={{ fontSize: "0.75rem" }}>
|
||||
Subir / actualizar proyectos
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="app-card p-2">
|
||||
<table className="table table-borderless mb-0 align-middle">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="ps-3" style={{ width: 56 }}>
|
||||
<i className="bx bx-code-alt bx-sm bx-border text-light bg-dark" />
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href="https://api.devops.norteamericano.cl/health"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-decoration-none text-dark d-block py-2 stretched-link"
|
||||
>
|
||||
API Backend
|
||||
</a>
|
||||
<small className="text-muted" style={{ fontSize: "0.75rem" }}>
|
||||
{backend.container ? "Activo" : "Detenido"} · {backend.branch}
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="app-card p-2">
|
||||
<table className="table table-borderless mb-0 align-middle">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="ps-3" style={{ width: 56 }}>
|
||||
<i className="bx bx-cog bx-sm bx-border text-light bg-secondary" />
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
href="/upload"
|
||||
className="text-decoration-none text-dark d-block py-2 stretched-link"
|
||||
>
|
||||
Gestionar Módulos
|
||||
</Link>
|
||||
<small className="text-muted" style={{ fontSize: "0.75rem" }}>
|
||||
Subir / actualizar proyectos
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="col-12 mt-4 mb-4 text-center">
|
||||
<small className="text-muted">
|
||||
|
||||
@@ -167,3 +167,37 @@ export function displayName(name: string) {
|
||||
export function moduleUrl(m: ModuleState) {
|
||||
return `https://${m.subdomain}.devops.norteamericano.cl`;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Secciones del índice: dónde aparece cada módulo.
|
||||
// - Todo módulo nuevo cae en "apps" (Aplicaciones) automáticamente.
|
||||
// - Para mover un módulo a otra sección, agrégalo a MODULE_SECTION.
|
||||
// - Para crear una sección futura: agrega una entrada en SECTIONS
|
||||
// (id, título, icono) y la regla en MODULE_SECTION o en
|
||||
// sectionOf(). El índice la renderiza sola si tiene módulos.
|
||||
// ============================================================
|
||||
|
||||
export interface Section {
|
||||
id: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export const SECTIONS: Section[] = [
|
||||
{ id: "apps", title: "Selección de Aplicación", icon: "bx-grid-alt" },
|
||||
{ id: "system", title: "Backend & Sistema", icon: "bx-server" },
|
||||
];
|
||||
|
||||
const MODULE_SECTION: Record<string, string> = {
|
||||
// Ejemplos:
|
||||
// contabilidad: "system",
|
||||
// pagos: "system",
|
||||
};
|
||||
|
||||
export function sectionOf(name: string): string {
|
||||
const key = name.toLowerCase();
|
||||
if (MODULE_SECTION[key]) return MODULE_SECTION[key];
|
||||
const known = SECTIONS.some((s) => s.id === key);
|
||||
if (known) return key;
|
||||
return "apps";
|
||||
}
|
||||
Reference in new issue
Block a user