Files
evento/public/admin.html

308 lines
9.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Admin Panel</title>
<style>
body {
font-family: system-ui, sans-serif;
padding: 2rem;
max-width: 900px;
margin: 0 auto;
}
h1 {
margin-bottom: 0.5rem;
}
.module {
border: 1px solid #ccc;
padding: 1rem;
border-radius: 10px;
margin-bottom: 1rem;
}
.badge {
display: inline-block;
padding: 0.2rem 0.6rem;
background: #e5e7eb;
border-radius: 9999px;
font-size: 0.8rem;
color: #111827;
}
.field {
display: flex;
flex-direction: column;
margin-bottom: 0.5rem;
}
.field input {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 6px;
}
button {
padding: 0.5rem 0.75rem;
border: 1px solid #ccc;
border-radius: 6px;
background: #f7f7f8;
cursor: pointer;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.error {
color: #b91c1c;
margin-top: 0.5rem;
}
.success {
color: #047857;
margin-top: 0.5rem;
}
</style>
</head>
<body>
<h1>Admin Panel</h1>
<p>Interfaz de administración (Vue 3 + JWT).</p>
<div id="app">
<div v-if="!token" class="module">
<h2>Iniciar sesión</h2>
<div class="field">
<label>Email</label>
<input v-model="login.email" type="email" placeholder="admin@local" />
</div>
<div class="field">
<label>Contraseña</label>
<input v-model="login.password" type="password" placeholder="••••••" />
</div>
<div class="field">
<label>Tenant</label>
<input v-model="login.tenantId" placeholder="default" />
</div>
<button :disabled="loading" @click="loginUser">Iniciar sesión</button>
<div :class="{ error: loginError, success: loginSuccess }">{{ loginMessage }}</div>
<hr />
<h2>Registrar usuario</h2>
<div class="field">
<label>Nombre</label>
<input v-model="register.name" placeholder="Nombre (opcional)" />
</div>
<div class="field">
<label>Email</label>
<input v-model="register.email" type="email" placeholder="admin@local" />
</div>
<div class="field">
<label>Contraseña</label>
<input v-model="register.password" type="password" placeholder="••••••" />
</div>
<div class="field">
<label>Tenant</label>
<input v-model="register.tenantId" placeholder="default" />
</div>
<button :disabled="loading" @click="registerUser">Registrar</button>
<div :class="{ error: registerError, success: registerSuccess }">{{ registerMessage }}</div>
</div>
<div v-else class="module">
<div style="display:flex; align-items:center; gap:1rem; margin-bottom:1rem;">
<div>
<strong>Autenticado como:</strong> {{ user?.name || user?.email }}
</div>
<button @click="logout">Cerrar sesión</button>
</div>
<label>
Tenant ID:
<input v-model="tenantId" />
</label>
<button @click="loadModules" style="margin-left:0.5rem;">Actualizar módulos</button>
<span style="margin-left:1rem; color:#555;">{{ status }}</span>
<div v-if="modules.length" style="margin-top:1rem;">
<div v-for="mod in modules" :key="mod.key" class="module">
<h2>{{ mod.name }}</h2>
<p><span class="badge">{{ mod.key }}</span> {{ mod.description }}</p>
<button @click="selectModule(mod.key)">Administrar</button>
</div>
</div>
<div v-if="selectedModule" class="module">
<h2>Administrar: {{ selectedModule }}</h2>
<div v-html="moduleHtml"></div>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script>
const { createApp, reactive, ref } = Vue;
createApp({
setup() {
const apiRoot = '/api/admin';
const authRoot = '/api/auth';
const tokenKey = 'planner_admin_token';
const token = ref(localStorage.getItem(tokenKey) || '');
const user = ref(null);
const tenantId = ref('default');
const status = ref('');
const modules = ref([]);
const selectedModule = ref('');
const moduleHtml = ref('');
const loading = ref(false);
const login = reactive({ email: '', password: '', tenantId: 'default' });
const register = reactive({ name: '', email: '', password: '', tenantId: 'default' });
const loginMessage = ref('');
const loginError = ref(false);
const loginSuccess = ref(false);
const registerMessage = ref('');
const registerError = ref(false);
const registerSuccess = ref(false);
const setStatus = (msg) => (status.value = msg);
const setToken = (t) => {
token.value = t;
if (t) localStorage.setItem(tokenKey, t);
else localStorage.removeItem(tokenKey);
};
const api = async (path, options = {}) => {
setStatus('Consultando ' + path + '...');
const headers = options.headers || {};
if (token.value) headers.Authorization = `Bearer ${token.value}`;
const res = await fetch(`${apiRoot}${path}?tenantId=${encodeURIComponent(tenantId.value)}`, {
...options,
headers,
});
const data = await res.json().catch(() => null);
if (!res.ok) {
const err = data?.message || res.statusText;
setStatus(`Error ${res.status}`);
throw new Error(err);
}
setStatus('OK');
setTimeout(() => setStatus(''), 1500);
return data;
};
const logout = () => {
setToken('');
user.value = null;
selectedModule.value = '';
moduleHtml.value = '';
};
const loginUser = async () => {
loginMessage.value = '';
loginError.value = false;
loginSuccess.value = false;
loading.value = true;
try {
const res = await fetch(`${authRoot}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tenantId: login.tenantId || 'default',
email: login.email,
password: login.password,
}),
});
const data = await res.json();
if (!res.ok || !data.token) {
loginMessage.value = data.message || 'Credenciales inválidas';
loginError.value = true;
return;
}
setToken(data.token);
user.value = { email: login.email, name: data.name };
loginMessage.value = 'Sesión iniciada correctamente';
loginSuccess.value = true;
await loadModules();
} catch (err) {
loginMessage.value = err.message || err;
loginError.value = true;
} finally {
loading.value = false;
}
};
const registerUser = async () => {
registerMessage.value = '';
registerError.value = false;
registerSuccess.value = false;
loading.value = true;
try {
const res = await fetch(`${authRoot}/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tenantId: register.tenantId || 'default',
name: register.name,
email: register.email,
password: register.password,
}),
});
const data = await res.json();
if (!res.ok) {
registerMessage.value = data.message || 'Error al registrar';
registerError.value = true;
return;
}
registerMessage.value = 'Registrado. Inicia sesión.';
registerSuccess.value = true;
} catch (err) {
registerMessage.value = err.message || err;
registerError.value = true;
} finally {
loading.value = false;
}
};
const loadModules = async () => {
if (!token.value) return;
try {
modules.value = await api('/modules');
} catch (err) {
console.error(err);
}
};
const selectModule = (key) => {
selectedModule.value = key;
moduleHtml.value = `<p>Seleccionado <strong>${key}</strong>. Usa los botones del módulo para operar.</p>`;
};
return {
token,
user,
tenantId,
status,
modules,
selectedModule,
moduleHtml,
login,
register,
loginMessage,
loginError,
loginSuccess,
registerMessage,
registerError,
registerSuccess,
loading,
loginUser,
registerUser,
logout,
loadModules,
selectModule,
};
},
}).mount('#app');
</script>
</body>
</html>