File size: 7,543 Bytes
cb5d9d0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | <!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Panel de Control SaaS</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-100 text-slate-900">
<div class="max-w-6xl mx-auto p-6 space-y-6">
<header
class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between"
>
<div>
<h1 class="text-3xl font-bold">Panel de Control SaaS</h1>
<p class="text-slate-600">
Desde aquí puedes ver usuarios activos, generar nuevas API keys y
obtener el script de integración.
</p>
</div>
<div class="space-x-2">
<a
href="/preview"
class="inline-block px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>Vista previa</a
>
<button
id="open-app-button"
class="inline-block px-4 py-2 bg-slate-200 text-slate-900 rounded-lg hover:bg-slate-300"
>
Abrir app
</button>
</div>
</header>
<section class="grid gap-6 lg:grid-cols-2">
<div class="bg-white rounded-3xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">Generar nueva API key</h2>
<div class="space-y-4">
<label class="block">
<span class="text-sm font-medium text-slate-700"
>Nombre del cliente</span
>
<input
id="client-name"
type="text"
placeholder="Ej. Tienda Sur"
class="mt-2 w-full rounded-2xl border border-slate-300 px-4 py-3 focus:border-blue-500 focus:outline-none"
/>
</label>
<button
id="generate-key"
class="w-full px-5 py-3 bg-green-600 text-white rounded-2xl hover:bg-green-700"
>
Generar API key
</button>
<div
id="generate-result"
class="hidden rounded-2xl border border-slate-200 bg-slate-50 p-4 text-slate-900"
></div>
</div>
</div>
<div class="bg-white rounded-3xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">
Instrucciones de integración
</h2>
<p class="text-slate-600 mb-4">
El desarrollador debe insertar un contenedor y el script del widget
en su HTML:
</p>
<pre
class="rounded-2xl bg-slate-950 p-4 text-slate-100 overflow-x-auto"
><code id="static-snippet"></code></pre>
<script>
</script>
<p class="text-sm text-slate-500 mt-4">
Reemplaza <strong>ID_UNICO_DEL_CLIENTE_001</strong> por la API key
generada.
</p>
</div>
</section>
<section class="grid gap-6 lg:grid-cols-2">
<div class="bg-white rounded-3xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">Clientes registrados</h2>
<div id="clients-list" class="space-y-3 text-slate-700"></div>
</div>
<div class="bg-white rounded-3xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">Usuarios activos</h2>
<div id="active-list" class="space-y-3 text-slate-700"></div>
</div>
</section>
</div>
<script>
async function loadData() {
const [keysRes, activeRes] = await Promise.all([
fetch("/api/keys"),
fetch("/api/active-sessions"),
]);
const keysData = await keysRes.json();
const activeData = await activeRes.json();
const clientsList = document.getElementById("clients-list");
clientsList.innerHTML = keysData.keys
.map(
(key) => `
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
<div class="font-semibold">${key.nombre}</div>
<div class="text-sm text-slate-500">API key: <code>${key.client_id}</code></div>
<div class="text-sm text-slate-500">Color primario: ${key.color_primario}</div>
<div class="text-sm text-slate-500">Creado: ${key.created_at}</div>
</div>
`,
)
.join("");
const activeList = document.getElementById("active-list");
if (activeData.active_sessions.length === 0) {
activeList.innerHTML =
'<p class="text-sm text-slate-500">No hay sesiones activas aún.</p>';
} else {
activeList.innerHTML = activeData.active_sessions
.map(
(item) => `
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
<div class="font-semibold">${item.client_id}</div>
<div class="text-sm text-slate-500">Último acceso: ${item.last_seen}</div>
</div>
`,
)
.join("");
}
}
document
.getElementById("generate-key")
.addEventListener("click", async () => {
const nameInput = document.getElementById("client-name");
const name = nameInput.value.trim();
if (!name) {
alert("Ingresa el nombre del cliente antes de generar la API key.");
return;
}
const formData = new URLSearchParams();
formData.append("nombre", name);
const response = await fetch("/api/generate-key", {
method: "POST",
body: formData,
});
const result = await response.json();
const resultBox = document.getElementById("generate-result");
resultBox.classList.remove("hidden");
const escapedSnippet = result.snippet
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
resultBox.innerHTML = `
<p class="font-semibold text-slate-900">API key generada:</p>
<p class="mt-2"><code>${result.client_id}</code></p>
<p class="mt-3 font-semibold text-slate-900">Snippet para el cliente:</p>
<pre class="mt-2 rounded-2xl bg-slate-950 p-4 text-slate-100 overflow-x-auto"><code>${escapedSnippet}</code></pre>
`;
nameInput.value = "";
await loadData();
});
loadData();
document
.getElementById("open-app-button")
.addEventListener("click", async () => {
const clientId = "ID_UNICO_DEL_CLIENTE_001";
try {
const res = await fetch("/api/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `client_id=${encodeURIComponent(clientId)}`,
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "No se pudo generar token.");
}
window.open(
`/app?token=${encodeURIComponent(data.token)}`,
"_blank",
);
} catch (error) {
alert("No se pudo abrir el app: " + error.message);
}
});
</script>
</body>
</html>
|