feat: portal SAM - Next.js 15 + Bootstrap, integración con deployer API

This commit is contained in:
Nurfog committed 2026-09-08 14:06:40 -03:00
1 parent 229cbd074a
commit 7691c01ff1
9 files changed
+1005 -107

No files matched your search

+231
View File
@@ -0,0 +1,231 @@
"use client";
import { useState } from "react";
import Link from "next/link";
export default function UploadPage() {
const [form, setForm] = useState({
name: "",
repoUrl: "",
branch: "main",
port: "",
subdomain: "",
});
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setForm({ ...form, [e.target.name]: e.target.value });
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setResult(null);
const payload: Record<string, unknown> = {
name: form.name.trim().toLowerCase(),
repoUrl: form.repoUrl.trim(),
branch: form.branch.trim() || "main",
};
if (form.port) payload.port = parseInt(form.port, 10);
if (form.subdomain) payload.subdomain = form.subdomain.trim();
try {
const res = await fetch("/api/modules", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await res.json();
if (res.ok) {
setResult({
ok: true,
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: "" });
} else {
setResult({ ok: false, message: data.error || `Error HTTP ${res.status}` });
}
} catch (err) {
setResult({ ok: false, message: err instanceof Error ? err.message : "Error de red" });
} finally {
setLoading(false);
}
};
return (
<>
<div className="sidenav">
<div className="row text-light login-main-text">
<div className="col-12 text-center">
<div
className="mx-auto d-flex align-items-center justify-content-center bg-white rounded-3"
style={{ width: 80, height: 80 }}
>
<i className="bx bx-upload text-primary" style={{ fontSize: 48 }} />
</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">Subir Proyecto</h4>
</div>
<div className="col-12 text-center mt-4">
<Link href="/" className="btn btn-outline-light btn-sm px-4">
<i className="bx bx-arrow-back me-1" /> Volver
</Link>
</div>
</div>
</div>
<div className="main">
<div className="row">
<div className="col-lg-1" />
<div className="col-lg-10 mt-4">
<div className="alert alert-light section-header mb-3 py-2 px-3">
<i className="bx bx-cloud-upload me-1" /> Integrar Nuevo Módulo
</div>
<div className="app-card p-4">
<p className="text-muted mb-4">
Registra un proyecto <strong>Next.js 15 + Bootstrap</strong> desde un repositorio
Gitea. Se clonará, desplegará automáticamente y quedará disponible en{" "}
<code>https://{"{subdominio}"}.devops.norteamericano.cl</code>. Cada push futuro
actualiza el módulo vía webhook.
</p>
{result && (
<div className={`alert ${result.ok ? "alert-success" : "alert-danger"}`}>
<i className={`bx ${result.ok ? "bx-check-circle" : "bx-error-circle"} me-2`} />
{result.message}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="row g-3">
<div className="col-md-6">
<label className="form-label fw-semibold">
Nombre del módulo <span className="text-danger">*</span>
</label>
<input
name="name"
value={form.name}
onChange={handleChange}
placeholder="ej: ventas"
pattern="^[a-z][a-z0-9-]+$"
required
className="form-control"
/>
<div className="form-text">Minúsculas, guiones, empieza con letra.</div>
</div>
<div className="col-md-6">
<label className="form-label fw-semibold">
Subdominio <small className="text-muted">(opcional)</small>
</label>
<input
name="subdomain"
value={form.subdomain}
onChange={handleChange}
placeholder="ej: ventas (default: nombre)"
className="form-control"
/>
<div className="form-text">https://{form.subdomain || form.name || "subdominio"}.devops.norteamericano.cl</div>
</div>
<div className="col-12">
<label className="form-label fw-semibold">
URL del repositorio Gitea <span className="text-danger">*</span>
</label>
<input
name="repoUrl"
value={form.repoUrl}
onChange={handleChange}
placeholder="https://git.norteamericano.cl/Fimm/mi-frontend.git"
required
className="form-control"
/>
<div className="form-text">Debe ser accesible desde el deployer (https://git.norteamericano.cl).</div>
</div>
<div className="col-md-6">
<label className="form-label fw-semibold">Rama</label>
<input
name="branch"
value={form.branch}
onChange={handleChange}
placeholder="main"
className="form-control"
/>
</div>
<div className="col-md-6">
<label className="form-label fw-semibold">
Puerto <small className="text-muted">(opcional)</small>
</label>
<input
name="port"
type="number"
value={form.port}
onChange={handleChange}
placeholder="auto (3011+)"
min={3000}
max={4000}
className="form-control"
/>
<div className="form-text">Se asigna automáticamente si se deja vacío.</div>
</div>
<div className="col-12 mt-2">
<div className="alert alert-info py-2 small mb-0">
<i className="bx bx-info-circle me-1" />
El repositorio debe ser un proyecto <strong>Next.js 15</strong> (con{" "}
<code>package.json</code> en raíz o en <code>frontend/</code>) y usar{" "}
<strong>Bootstrap</strong>. Si no trae <code>Dockerfile</code>, se genera uno automáticamente.
</div>
</div>
<div className="col-12 mt-3 d-flex gap-2">
<button type="submit" disabled={loading} className="btn btn-primary px-4">
{loading ? (
<>
<span className="spinner-border spinner-border-sm me-2" /> Desplegando...
</>
) : (
<>
<i className="bx bx-rocket me-1" /> Registrar y Desplegar
</>
)}
</button>
<Link href="/" className="btn btn-outline-secondary">
Cancelar
</Link>
</div>
</div>
</form>
</div>
<div className="app-card p-3 mt-3">
<h6 className="fw-semibold mb-2">
<i className="bx bx-book-open me-1" /> Requisitos del repositorio
</h6>
<ul className="small text-muted mb-0">
<li>
Proyecto Next.js 15 con <code>next build</code> / <code>next start</code>
</li>
<li>Bootstrap 5 (via CDN o npm) + Boxicons opcional</li>
<li>
Variable <code>NEXT_PUBLIC_API_URL=https://api.devops.norteamericano.cl</code>{" "}
para consumir el backend
</li>
<li>Branch por defecto: main (configurable)</li>
</ul>
</div>
</div>
</div>
</div>
</>
);
}