feat: kind backend/frontend + visible con toggle en gestion

This commit is contained in:
Nurfog committed 2026-09-09 11:27:39 -03:00
1 parent 3d9685adeb
commit 222fdac54d
8 files changed
+207 -48

No files matched your search

+105 -38
View File
@@ -14,6 +14,9 @@ export default function UploadPage() {
port: "",
subdomain: "",
category: "otras-apps",
kind: "frontend",
visible: true,
targetPort: "",
});
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null);
@@ -21,7 +24,13 @@ export default function UploadPage() {
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
setForm({ ...form, [e.target.name]: e.target.value });
const { name, value } = e.target;
setForm((f) => ({ ...f, [name]: value }));
};
const handleCheck = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, checked } = e.target;
setForm((f) => ({ ...f, [name]: checked }));
};
const handleSubmit = async (e: React.FormEvent) => {
@@ -34,9 +43,12 @@ export default function UploadPage() {
repoUrl: form.repoUrl.trim(),
branch: form.branch.trim() || "main",
category: form.category,
kind: form.kind,
visible: form.visible,
};
if (form.port) payload.port = parseInt(form.port, 10);
if (form.subdomain) payload.subdomain = form.subdomain.trim();
if (form.targetPort) payload.targetPort = parseInt(form.targetPort, 10);
try {
const res = await fetch("/api/modules", {
@@ -50,7 +62,7 @@ export default function UploadPage() {
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: "", category: "otras-apps" });
setForm({ name: "", repoUrl: "", branch: "main", port: "", subdomain: "", category: "otras-apps", kind: "frontend", visible: true, targetPort: "" });
} else {
setResult({ ok: false, message: data.error || `Error HTTP ${res.status}` });
}
@@ -155,45 +167,100 @@ export default function UploadPage() {
/>
<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 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-md-6">
<label className="form-label fw-semibold">
Puerto interno <small className="text-muted">(backend)</small>
</label>
<input
name="targetPort"
type="number"
value={form.targetPort}
onChange={handleChange}
placeholder="3000 frontend / 5000 backend"
min={1}
max={65535}
className="form-control"
/>
<div className="form-text">Puerto que expone el contenedor.</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">
¿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">
Tipo <span className="text-danger">*</span>
</label>
<select
name="kind"
value={form.kind}
onChange={handleChange}
required
className="form-select"
>
<option value="frontend">Frontend (Next.js)</option>
<option value="backend">Backend (Dockerfile propio)</option>
</select>
<div className="form-text">
El backend queda interno salvo subdominio explícito.
</div>
</div>
<div className="col-12">
<div className="form-check form-switch">
<input
className="form-check-input"
type="checkbox"
role="switch"
id="visibleSwitch"
name="visible"
checked={form.visible}
onChange={handleCheck}
/>
<label className="form-check-label" htmlFor="visibleSwitch">
Visible en el portal{" "}
<small className="text-muted">
(si no, solo se gestiona desde IT)
</small>
</label>
</div>
</div>
<div className="col-md-6">
<label className="form-label fw-semibold">Rama</label>