130 lines
5.4 KiB
HTML
130 lines
5.4 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="it">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Loogle MCP Dashboard</title>
|
|
<style>
|
|
:root { --bg:#0f172a; --card:#1e293b; --text:#e2e8f0; --muted:#94a3b8; --accent:#2563eb; }
|
|
* { box-sizing: border-box; }
|
|
body { font-family: system-ui, sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 1rem; }
|
|
h1 { font-size: 1.5rem; margin-bottom: .25rem; }
|
|
.sub { color: var(--muted); margin-bottom: 1.5rem; }
|
|
.card { background: var(--card); border-radius: 12px; padding: 1rem 1.25rem; margin-bottom: 1rem; }
|
|
button { background: var(--accent); color: #fff; border: none; border-radius: 8px; padding: .55rem 1rem; cursor: pointer; }
|
|
button.secondary { background: #334155; }
|
|
input { width: 100%; padding: .5rem; border-radius: 6px; border: 1px solid #334155; background: var(--bg); color: var(--text); margin: .35rem 0 .75rem; }
|
|
table { width: 100%; border-collapse: collapse; font-size: .9rem; }
|
|
th, td { text-align: left; padding: .45rem .25rem; border-bottom: 1px solid #334155; }
|
|
.hidden { display: none; }
|
|
.error { color: #f87171; }
|
|
code { background: #0b1220; padding: .15rem .35rem; border-radius: 4px; font-size: .85rem; }
|
|
ul { padding-left: 1.2rem; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Loogle MCP Hub</h1>
|
|
<p class="sub">Archivio contesto e knowledge base locale per AI famiglia</p>
|
|
|
|
<div id="loginView" class="card">
|
|
<h2>Accedi</h2>
|
|
<label>Utente</label>
|
|
<input id="loginUser" autocomplete="username">
|
|
<label>Password</label>
|
|
<input id="loginPass" type="password" autocomplete="current-password">
|
|
<button onclick="doLogin()">Entra</button>
|
|
<p id="loginErr" class="error hidden"></p>
|
|
</div>
|
|
|
|
<div id="appView" class="hidden">
|
|
<div class="card">
|
|
<strong id="welcome"></strong>
|
|
<button class="secondary" onclick="doLogout()" style="float:right">Esci</button>
|
|
<p>MCP URL: <code>https://mcp.loogle.it/mcp</code></p>
|
|
</div>
|
|
|
|
<div class="card" id="pwdCard">
|
|
<h3>Cambia password</h3>
|
|
<label>Password attuale</label><input id="oldPwd" type="password">
|
|
<label>Nuova password</label><input id="newPwd" type="password">
|
|
<button onclick="changePwd()">Salva</button>
|
|
<p id="pwdMsg"></p>
|
|
</div>
|
|
|
|
<div class="card hidden" id="adminCard">
|
|
<h3>Admin — revoca refresh token</h3>
|
|
<label>Refresh token</label><input id="revokeToken">
|
|
<button onclick="revokeRefresh()">Revoca</button>
|
|
<p id="revokeMsg"></p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Progetti</h3>
|
|
<div id="projects"></div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Audit log recente</h3>
|
|
<table><thead><tr><th>Ora</th><th>Utente</th><th>Tool</th><th>Risorsa</th></tr></thead><tbody id="auditBody"></tbody></table>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Collegamenti AI</h3>
|
|
<ul>
|
|
<li><strong>ChatGPT:</strong> Impostazioni → Developer → Add MCP Connector → URL sopra</li>
|
|
<li><strong>Claude:</strong> Settings → Connectors → Add remote MCP server</li>
|
|
<li><strong>Gemini CLI:</strong> configura server remoto in settings MCP</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
async function api(path, opts={}) {
|
|
const r = await fetch(path, { credentials: 'same-origin', headers: {'Content-Type':'application/json', ...(opts.headers||{})}, ...opts });
|
|
if (!r.ok) throw new Error(await r.text());
|
|
return r.json();
|
|
}
|
|
function show(el, on) { document.getElementById(el).classList.toggle('hidden', !on); }
|
|
async function boot() {
|
|
try {
|
|
const me = await api('/api/me');
|
|
show('loginView', false); show('appView', true);
|
|
document.getElementById('welcome').textContent = 'Ciao ' + me.username + (me.is_admin ? ' (admin)' : '');
|
|
if (me.is_admin) document.getElementById('adminCard').classList.remove('hidden');
|
|
loadProjects(); loadAudit();
|
|
} catch { show('loginView', true); show('appView', false); }
|
|
}
|
|
async function doLogin() {
|
|
try {
|
|
await api('/api/login', { method:'POST', body: JSON.stringify({ username: loginUser.value, password: loginPass.value }) });
|
|
boot();
|
|
} catch (e) {
|
|
loginErr.textContent = 'Credenziali non valide'; loginErr.classList.remove('hidden');
|
|
}
|
|
}
|
|
async function doLogout() { await api('/api/logout', { method:'POST', body:'{}' }); boot(); }
|
|
async function changePwd() {
|
|
try {
|
|
await api('/api/password', { method:'POST', body: JSON.stringify({ old_password: oldPwd.value, new_password: newPwd.value }) });
|
|
pwdMsg.textContent = 'Password aggiornata';
|
|
} catch (e) { pwdMsg.textContent = 'Errore: password attuale errata'; }
|
|
}
|
|
async function loadProjects() {
|
|
const rows = await api('/api/projects');
|
|
projects.innerHTML = rows.length ? rows.map(p => `<div><strong>${p.title}</strong> <small>${p.id}</small> — agg. ${p.updated_at||p.created_at}</div>`).join('') : '<em>Nessun progetto</em>';
|
|
}
|
|
async function loadAudit() {
|
|
const rows = await api('/api/audit?limit=30');
|
|
auditBody.innerHTML = rows.map(r => `<tr><td>${r.created_at}</td><td>${r.username}</td><td>${r.tool_name}</td><td>${r.resource_id||''}</td></tr>`).join('');
|
|
}
|
|
async function revokeRefresh() {
|
|
try {
|
|
await api('/api/admin/revoke-refresh', { method:'POST', body: JSON.stringify({ refresh_token: revokeToken.value }) });
|
|
revokeMsg.textContent = 'Token revocato';
|
|
} catch (e) { revokeMsg.textContent = 'Errore revoca'; }
|
|
}
|
|
boot();
|
|
</script>
|
|
</body>
|
|
</html>
|