feat: upload dentro de la plantilla SamShell (sin sidenav propio)

This commit is contained in:
Nurfog committed 2026-09-09 10:36:23 -03:00
1 parent 70df8e95a9
commit 3d9685adeb
1 file changed
+185 -209
+185 -209
View File
@@ -1,9 +1,12 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import SamShell from "@/components/SamShell";
import { useModules } from "@/lib/store";
import { itMenu } from "@/lib/sam";
export default function UploadPage() {
const { data } = useModules();
const [form, setForm] = useState({
name: "",
repoUrl: "",
@@ -59,222 +62,195 @@ export default function UploadPage() {
};
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>
<SamShell
brand="IT"
subtitle="Agregar módulo"
menu={itMenu(data?.modules ?? [])}
activeHref="/upload"
topUser="SAM"
backHref="/"
>
<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="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 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="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-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/user/mi-proyecto.git"
required
className="form-control"
/>
<div className="form-text">Debe ser accesible desde el deployer (https://git.norteamericano.cl).</div>
</div>
<div className="col-12">
<div className="alert alert-light border py-2 small">
<i className="bx bx-cog me-1" />
<strong>Configuración automática:</strong> puerto y subdominio se asignan automáticamente
{form.name && (
<span>
{" "}
→ <code>https://{form.subdomain || form.name}.devops.norteamericano.cl</code>
{form.port ? ` :${form.port}` : " (puerto auto)"}
</span>
)}
. Se crean los registros en Caddy y DNS.
</div>
</div>
<details className="col-12">
<summary className="small text-muted" style={{ cursor: "pointer" }}>
Opciones avanzadas (puerto / subdominio manual)
</summary>
<div className="row g-3 mt-1">
<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-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>
</details>
<div className="col-md-6">
<label className="form-label fw-semibold">
¿A qué corresponde? <span className="text-danger">*</span>
</label>
<select
name="category"
value={form.category}
onChange={handleChange}
required
className="form-select"
>
<option value="pagina-web">Página Web</option>
<option value="sam">SAM</option>
<option value="profesores">Profesores</option>
<option value="otras-apps">Otras Aplicaciones</option>
</select>
<div className="form-text">
Define en qué sección del índice SAM aparece el módulo.
</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 className="form-text">
main = producción, otra rama = desarrollo.
</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 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/user/mi-proyecto.git"
required
className="form-control"
/>
<div className="form-text">Debe ser accesible desde el deployer (https://git.norteamericano.cl).</div>
</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 className="col-12">
<div className="alert alert-light border py-2 small">
<i className="bx bx-cog me-1" />
<strong>Configuración automática:</strong> puerto y subdominio se asignan automáticamente
{form.name && (
<span>
{" "}
→ <code>https://{form.subdomain || form.name}.devops.norteamericano.cl</code>
{form.port ? ` :${form.port}` : " (puerto auto)"}
</span>
)}
. Se crean los registros en Caddy y DNS.
</div>
</div>
<details className="col-12">
<summary className="small text-muted" style={{ cursor: "pointer" }}>
Opciones avanzadas (puerto / subdominio manual)
</summary>
<div className="row g-3 mt-1">
<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-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>
</details>
<div className="col-md-6">
<label className="form-label fw-semibold">
¿A qué corresponde? <span className="text-danger">*</span>
</label>
<select
name="category"
value={form.category}
onChange={handleChange}
required
className="form-select"
>
<option value="pagina-web">Página Web</option>
<option value="sam">SAM</option>
<option value="profesores">Profesores</option>
<option value="otras-apps">Otras Aplicaciones</option>
</select>
<div className="form-text">
Define en qué sección del índice SAM aparece el módulo.
</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 className="form-text">
main = producción, otra rama = desarrollo.
</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>
</div>
</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>
</SamShell>
);
}