Files
loogle-scripts/services/loogle-mcp/app/static/dashboard.html
T

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>