diff --git a/src/app/m/[name]/page.tsx b/src/app/m/[name]/page.tsx
index 5978f67..9991975 100644
--- a/src/app/m/[name]/page.tsx
+++ b/src/app/m/[name]/page.tsx
@@ -1,67 +1,77 @@
"use client";
-import { useEffect, useMemo, useState, useSyncExternalStore } from "react";
-import { useParams } from "next/navigation";
import Link from "next/link";
import SamShell from "@/components/SamShell";
+import { useModules, useStats } from "@/lib/store";
+import { useParams } from "next/navigation";
import {
displayName,
+ envOf,
getModuleFunctions,
+ getStatusInfo,
moduleUrl,
- type ApiResponse,
- type ModuleState,
} from "@/lib/sam";
+import {
+ memUsed,
+ pct,
+ shortUptime,
+ statFor,
+} from "@/lib/stats";
const API_URL =
process.env.NEXT_PUBLIC_API_URL || "https://api.devops.norteamericano.cl";
-function subscribeHash(cb: () => void) {
- window.addEventListener("hashchange", cb);
- return () => window.removeEventListener("hashchange", cb);
-}
-
-function snapshotHash() {
- return window.location.hash.slice(1);
+function Bar({ value, color }: { value: number; color: string }) {
+ return (
+
+ );
}
+// Tablero funcional del módulo: estado + carga de trabajo +
+// funciones API en el sidebar (plantilla SamShell).
export default function ModuleBoard() {
const params = useParams();
const name = Array.isArray(params.name) ? params.name[0] : params.name ?? "";
- const [data, setData] = useState(null);
- const [loading, setLoading] = useState(true);
- const hash =
- useSyncExternalStore(subscribeHash, snapshotHash, () => "") || null;
+ const { data } = useModules();
+ const stats = useStats();
+ const loading = data === null;
- useEffect(() => {
- fetch("/api/modules", { cache: "no-store" })
- .then((r) => r.json())
- .then((j) => setData(j))
- .catch(() => setData(null))
- .finally(() => setLoading(false));
- }, []);
-
- const mod: ModuleState | undefined = useMemo(
- () => data?.modules.find((m) => m.name.toLowerCase() === name.toLowerCase()),
- [data, name]
+ const mod = data?.modules.find(
+ (m) => m.name.toLowerCase() === name.toLowerCase()
);
-
- const functions = useMemo(() => getModuleFunctions(name), [name]);
- const activePath = hash || functions[0]?.path || null;
- const activeFn = functions.find((f) => f.path === activePath) ?? functions[0];
+ const functions = getModuleFunctions(name);
+ const stat = mod ? statFor(stats, `${mod.name}-frontend`) : undefined;
+ const status = mod ? getStatusInfo(mod) : null;
+ const env = mod ? envOf(mod.branch) : null;
+ const cpu = pct(stat?.cpu);
+ const mem = pct(stat?.memPerc);
const title = mod ? displayName(mod.name) : displayName(name || "Módulo");
- const menu = functions.map((f) => ({
- label: f.label,
- icon: f.icon,
- href: `#${f.path}`,
- }));
+ const menu = [
+ { label: "Estado", icon: "bx-grid-alt", href: "#estado" },
+ ...functions.map((f) => ({
+ label: f.label,
+ icon: f.icon,
+ href: `/m/${name}#${f.path}`,
+ })),
+ { label: "Aplicaciones", icon: "bx-layer", href: "/" },
+ ];
return (
)}
- {!loading && mod && activeFn && (
+ {!loading && mod && status && env && (
<>
-
-
-
+
-
-
- Funciones disponibles de la API
-
+
+
+
+ Estado y carga de trabajo
+
+
+
+ {status.label}
+
+ {env.label}
+
+
+
+
+
+ Uptime
+ {shortUptime(stat?.status ?? mod.container)}
+
+
+ Puerto
+ :{mod.port}
+
+
+ Rama
+ {mod.branch}
+
+
+ Red
+ {stat?.netIO ?? "—"}
+
+
+
+
+ CPU
+ {stat?.cpu ?? "—"}
+
+
+
+
+
+ MEM {memUsed(stat)}
+ {stat?.memPerc ?? "—"}
+
+
+
+
+
+
+
+
+
+ Funciones disponibles de la API
+
+
+
+
{functions.map((f) => (
-
+ GET {f.path}
+
))}
-
- Las funciones se consumen desde {API_URL}. Usa «Abrir
- aplicación» para operar en {moduleUrl(mod)}.
-
>
)}
-
);
}