308 lines
9.8 KiB
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>
|