feat: portal SAM - Next.js 15 + Bootstrap, integración con deployer API
This commit is contained in:
1 parent
229cbd074a
commit
7691c01ff1
9 files changed
+1005
-107
No files matched your search
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user