feat: indice estilo lanzador sam.norteamericano.cl, sidebar solo en IT + rewrite it.sam
This commit is contained in:
1 parent
af638d4154
commit
347d8510b4
3 files changed
+205
-171
No files matched your search
+168
-165
@@ -1,9 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import SamShell from "@/components/SamShell";
|
import { useModules, refreshModules } from "@/lib/store";
|
||||||
import TableroView from "@/components/TableroView";
|
|
||||||
import { useModules, refreshModules, useSiteSection } from "@/lib/store";
|
|
||||||
import {
|
import {
|
||||||
displayName,
|
displayName,
|
||||||
envOf,
|
envOf,
|
||||||
@@ -14,35 +12,14 @@ import {
|
|||||||
sectionOf,
|
sectionOf,
|
||||||
SECTIONS,
|
SECTIONS,
|
||||||
type ModuleState,
|
type ModuleState,
|
||||||
|
type RefApp,
|
||||||
} from "@/lib/sam";
|
} from "@/lib/sam";
|
||||||
|
|
||||||
const sectionMenu = [
|
|
||||||
...SECTIONS.map((s) => ({ label: s.title, icon: s.icon, href: `/#${s.id}` })),
|
|
||||||
{ label: "Tablero IT", icon: "bx-pie-chart-alt-2", href: "/tablero" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { data, error } = useModules();
|
const { data, error } = useModules();
|
||||||
const site = useSiteSection();
|
|
||||||
const loading = data === null && error === null;
|
const loading = data === null && error === null;
|
||||||
const fetchModules = refreshModules;
|
const fetchModules = refreshModules;
|
||||||
|
|
||||||
// it.sam.devops... aterriza directo en el tablero
|
|
||||||
if (site === "it") {
|
|
||||||
return (
|
|
||||||
<SamShell
|
|
||||||
brand="IT"
|
|
||||||
subtitle="Tablero de gestión"
|
|
||||||
menu={sectionMenu}
|
|
||||||
activeHref="/tablero"
|
|
||||||
topUser="SAM"
|
|
||||||
backHref="/"
|
|
||||||
>
|
|
||||||
<TableroView />
|
|
||||||
</SamShell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const modules = data?.modules ?? [];
|
const modules = data?.modules ?? [];
|
||||||
const backend = data?.backend;
|
const backend = data?.backend;
|
||||||
|
|
||||||
@@ -107,13 +84,7 @@ export default function Home() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderRefCard = (app: {
|
const renderRefCard = (app: RefApp) => (
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
icon: string;
|
|
||||||
bg: string;
|
|
||||||
url: string;
|
|
||||||
}) => (
|
|
||||||
<div className="col-md-6" key={`ref-${app.name}`}>
|
<div className="col-md-6" key={`ref-${app.name}`}>
|
||||||
<div className="app-card p-2">
|
<div className="app-card p-2">
|
||||||
<table className="table table-borderless mb-0 align-middle">
|
<table className="table table-borderless mb-0 align-middle">
|
||||||
@@ -150,149 +121,181 @@ export default function Home() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SamShell
|
<>
|
||||||
brand="SAM"
|
<div className="sidenav">
|
||||||
subtitle="Sistema de Administración de Matrículas"
|
<div className="row text-light login-main-text">
|
||||||
menu={sectionMenu}
|
<div className="col-12 text-center">
|
||||||
topUser="SAM"
|
<div
|
||||||
>
|
className="mx-auto d-flex align-items-center justify-content-center bg-white rounded-3 overflow-hidden"
|
||||||
{loading && (
|
style={{ width: 80, height: 80 }}
|
||||||
<div className="text-center py-5">
|
|
||||||
<div className="spinner-border text-primary" role="status" />
|
|
||||||
<p className="mt-2 text-muted">Cargando módulos...</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<div className="alert alert-danger d-flex align-items-center">
|
|
||||||
<i className="bx bx-error-circle me-2 fs-5" />
|
|
||||||
<div>
|
|
||||||
<strong>Error:</strong> {error}
|
|
||||||
<button
|
|
||||||
onClick={fetchModules}
|
|
||||||
className="btn btn-sm btn-outline-danger ms-3"
|
|
||||||
>
|
>
|
||||||
Reintentar
|
<img src="/logo-sam.png" alt="SAM" style={{ width: 64, height: 64, objectFit: "contain" }} />
|
||||||
</button>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 text-center mt-3">
|
||||||
|
<h1 className="fw-bold fs-2">Sistema SAM</h1>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<h4 className="fw-semibold opacity-75">Aplicaciones</h4>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 text-center mt-4 d-flex flex-column gap-2 align-items-center">
|
||||||
|
<Link href="/tablero" className="btn btn-light btn-sm px-4">
|
||||||
|
<i className="bx bx-pie-chart-alt-2 me-1" /> Tablero IT
|
||||||
|
</Link>
|
||||||
|
<Link href="/upload" className="btn btn-outline-light btn-sm px-4">
|
||||||
|
<i className="bx bx-plus me-1" /> Nuevo Módulo
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 text-center mt-3">
|
||||||
|
<small className="opacity-50">Portal de integración</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
{!loading && !error && (
|
<div className="main">
|
||||||
<div className="row row-cards">
|
<div className="row" id="modulos">
|
||||||
{modules.length === 0 && (
|
<div className="col-lg-1" />
|
||||||
<div className="col-12">
|
<div className="col-lg-10 mt-4">
|
||||||
<div className="app-card p-4 text-center">
|
{loading && (
|
||||||
<i className="bx bx-inbox fs-1 text-muted" />
|
<div className="text-center py-5">
|
||||||
<p className="text-muted mt-2 mb-3">
|
<div className="spinner-border text-primary" role="status" />
|
||||||
No hay módulos integrados. ¡Sube tu primer proyecto!
|
<p className="mt-2 text-muted">Cargando módulos...</p>
|
||||||
</p>
|
|
||||||
<Link href="/upload" className="btn btn-primary btn-sm">
|
|
||||||
<i className="bx bx-upload me-1" /> Subir Proyecto
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{SECTIONS.map((section) => {
|
{error && (
|
||||||
const list = bySection[section.id] ?? [];
|
<div className="alert alert-danger d-flex align-items-center">
|
||||||
const isReview = section.id === "otras-apps";
|
<i className="bx bx-error-circle me-2 fs-5" />
|
||||||
if (!isReview && list.length === 0) return null;
|
<div>
|
||||||
return (
|
<strong>Error:</strong> {error}
|
||||||
<div className="col-12" key={section.id} id={section.id}>
|
<button
|
||||||
<div className="row row-cards">
|
onClick={fetchModules}
|
||||||
<div className="col-12">
|
className="btn btn-sm btn-outline-danger ms-3"
|
||||||
<div className="alert alert-light section-header mb-3 py-2 px-3 d-flex justify-content-between align-items-center">
|
>
|
||||||
<span>
|
Reintentar
|
||||||
<i className={`bx ${section.icon} me-1`} />{" "}
|
</button>
|
||||||
{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)}
|
|
||||||
|
|
||||||
{section.id === "sam" &&
|
|
||||||
REFERENCE_APPS.filter(
|
|
||||||
(a) =>
|
|
||||||
!list.some((m) => m.name.toLowerCase() === a.name)
|
|
||||||
).map(renderRefCard)}
|
|
||||||
|
|
||||||
{isReview && 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>
|
</div>
|
||||||
);
|
)}
|
||||||
})}
|
|
||||||
|
|
||||||
<div className="col-12 mt-4 mb-4 text-center">
|
{!loading && !error && (
|
||||||
<small className="text-muted">
|
<div className="row row-cards">
|
||||||
Auto-actualizado cada 15s ·{" "}
|
{modules.length === 0 && (
|
||||||
<a href="#" onClick={(e) => { e.preventDefault(); fetchModules(); }}>
|
<div className="col-12">
|
||||||
actualizar ahora
|
<div className="app-card p-4 text-center">
|
||||||
</a>
|
<i className="bx bx-inbox fs-1 text-muted" />
|
||||||
</small>
|
<p className="text-muted mt-2 mb-3">
|
||||||
|
No hay módulos integrados. ¡Sube tu primer proyecto!
|
||||||
|
</p>
|
||||||
|
<Link href="/upload" className="btn btn-primary btn-sm">
|
||||||
|
<i className="bx bx-upload me-1" /> Subir Proyecto
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{SECTIONS.map((section) => {
|
||||||
|
const list = bySection[section.id] ?? [];
|
||||||
|
const isReview = section.id === "otras-apps";
|
||||||
|
if (!isReview && list.length === 0) return null;
|
||||||
|
return (
|
||||||
|
<div className="col-12" key={section.id} id={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)}
|
||||||
|
|
||||||
|
{section.id === "sam" &&
|
||||||
|
REFERENCE_APPS.filter(
|
||||||
|
(a) =>
|
||||||
|
!list.some((m) => m.name.toLowerCase() === a.name)
|
||||||
|
).map(renderRefCard)}
|
||||||
|
|
||||||
|
{isReview && 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-12 mt-4 mb-4 text-center">
|
||||||
|
<small className="text-muted">
|
||||||
|
Auto-actualizado cada 15s ·{" "}
|
||||||
|
<a href="#" onClick={(e) => { e.preventDefault(); fetchModules(); }}>
|
||||||
|
actualizar ahora
|
||||||
|
</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</SamShell>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,31 @@
|
|||||||
|
|
||||||
import SamShell from "@/components/SamShell";
|
import SamShell from "@/components/SamShell";
|
||||||
import TableroView from "@/components/TableroView";
|
import TableroView from "@/components/TableroView";
|
||||||
|
import { useModules } from "@/lib/store";
|
||||||
|
import { displayName, getModuleMeta } from "@/lib/sam";
|
||||||
|
|
||||||
const menu = [
|
// App IT: sidebar con todas sus opciones (resumen, aplicaciones,
|
||||||
{ label: "Resumen", icon: "bx-grid-alt", href: "#resumen" },
|
// tableros por módulo y gestión). Se sirve en /tablero y en
|
||||||
{ label: "Aplicaciones", icon: "bx-layer", href: "/" },
|
// it.sam.devops.norteamericano.cl
|
||||||
];
|
|
||||||
|
|
||||||
export default function Tablero() {
|
export default function Tablero() {
|
||||||
|
const { data } = useModules();
|
||||||
|
const modules = data?.modules ?? [];
|
||||||
|
|
||||||
|
const menu = [
|
||||||
|
{ label: "Resumen", icon: "bx-grid-alt", href: "#resumen" },
|
||||||
|
{ label: "Aplicaciones", icon: "bx-layer", href: "/" },
|
||||||
|
...modules.map((m) => ({
|
||||||
|
label: displayName(m.name),
|
||||||
|
icon: getModuleMeta(m.name).icon,
|
||||||
|
href: `/m/${m.name}`,
|
||||||
|
})),
|
||||||
|
{ label: "Nuevo Módulo", icon: "bx-plus", href: "/upload" },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SamShell
|
<SamShell
|
||||||
brand="IT"
|
brand="IT"
|
||||||
subtitle="Tablero de gestión"
|
subtitle="Gestión de aplicaciones"
|
||||||
menu={menu}
|
menu={menu}
|
||||||
activeHref="#resumen"
|
activeHref="#resumen"
|
||||||
topUser="SAM"
|
topUser="SAM"
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import type { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
// it.sam.devops... muestra el tablero IT manteniendo la URL.
|
||||||
|
export function middleware(req: NextRequest) {
|
||||||
|
const host = (req.headers.get("host") || "").toLowerCase();
|
||||||
|
if (host.startsWith("it.sam.") && req.nextUrl.pathname === "/") {
|
||||||
|
const url = req.nextUrl.clone();
|
||||||
|
url.pathname = "/tablero";
|
||||||
|
return NextResponse.rewrite(url);
|
||||||
|
}
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ["/"],
|
||||||
|
};
|
||||||
Reference in new issue
Block a user