AI_Portal / static /admin.html
Moncey10's picture
Update static/admin.html
59670e7 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Admin Panel — New Age Portal</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #F5F4F0; --surface: #FFFFFF; --border: #E2E0DA;
--text: #1A1916; --muted: #6B6A65; --hint: #9E9C97;
--accent: #1A1916; --radius: 10px; --radius-sm: 6px;
--shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 16px rgba(0,0,0,0.06);
}
body { font-family: 'DM Sans', sans-serif; background: var(--bg); min-height: 100vh; }
body::before {
content: ''; position: fixed; inset: 0;
background-image: radial-gradient(circle, #C8C6BF 1px, transparent 1px);
background-size: 24px 24px; opacity: 0.45; pointer-events: none; z-index: 0;
}
.navbar {
position: relative; z-index: 10;
background: var(--surface); border-bottom: 1px solid var(--border);
padding: 0 2rem; height: 56px;
display: flex; align-items: center; justify-content: space-between;
}
.nav-brand { font-size: 0.9375rem; font-weight: 600; color: var(--text); }
.nav-brand span { color: var(--hint); font-weight: 400; margin-left: 6px; font-size: 0.8125rem; }
.nav-right a {
font-size: 0.8125rem; color: var(--muted); text-decoration: none;
border: 1px solid var(--border); border-radius: var(--radius-sm);
padding: 6px 12px; transition: color 0.15s;
}
.nav-right a:hover { color: var(--text); }
.container { position: relative; z-index: 1; max-width: 900px; margin: 2rem auto; padding: 0 1.5rem; }
h1 { font-size: 1.25rem; font-weight: 600; color: var(--text); margin-bottom: 0.25rem; }
.subtitle { font-size: 0.875rem; color: var(--muted); margin-bottom: 1.5rem; }
.tabs { display: flex; gap: 4px; margin-bottom: 1.5rem; }
.tab {
padding: 7px 16px; border-radius: var(--radius-sm); font-size: 0.8125rem;
font-weight: 500; cursor: pointer; border: 1px solid var(--border);
background: var(--surface); color: var(--muted); transition: all 0.15s;
font-family: 'DM Sans', sans-serif;
}
.tab.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
table { width: 100%; border-collapse: collapse; }
thead th { padding: 12px 16px; text-align: left; font-size: 0.75rem; font-weight: 600; color: var(--hint); text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid var(--border); background: #FAFAF8; }
tbody td { padding: 14px 16px; font-size: 0.875rem; color: var(--text); border-bottom: 1px solid var(--border); }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover td { background: #FAFAF8; }
.badge {
display: inline-flex; align-items: center; gap: 5px;
padding: 3px 9px; border-radius: 99px; font-size: 0.75rem; font-weight: 600;
}
.badge.pending { background: #FEF9C3; color: #854D0E; }
.badge.approved { background: #DCFCE7; color: #166534; }
.badge.rejected { background: #FEE2E2; color: #991B1B; }
.btn-approve, .btn-reject {
padding: 5px 12px; border-radius: var(--radius-sm); font-size: 0.8125rem;
font-weight: 500; cursor: pointer; border: 1px solid; font-family: 'DM Sans', sans-serif;
transition: opacity 0.15s;
}
.btn-approve { background: #DCFCE7; color: #166534; border-color: #BBF7D0; }
.btn-approve:hover { opacity: 0.8; }
.btn-reject { background: #FEE2E2; color: #991B1B; border-color: #FECACA; margin-left: 6px; }
.btn-reject:hover { opacity: 0.8; }
.empty { padding: 3rem; text-align: center; color: var(--hint); font-size: 0.875rem; }
.toast {
position: fixed; bottom: 2rem; right: 2rem; z-index: 999;
background: var(--accent); color: #fff;
padding: 12px 20px; border-radius: var(--radius-sm);
font-size: 0.875rem; font-weight: 500;
animation: slideIn 0.2s ease; display: none;
}
@keyframes slideIn { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
</style>
<!-- Microsoft Clarity -->
<script type="text/javascript">
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "x13hxsbvnw");
</script>
</head>
<body>
<nav class="navbar">
<div class="nav-brand">New Age Portal <span>Admin Panel</span></div>
<div class="nav-right"><a href="/chat">← Back to Chat</a></div>
</nav>
<div class="container">
<h1>User Management</h1>
<p class="subtitle">Approve or reject employee access requests</p>
<div class="tabs">
<button class="tab active" onclick="filterUsers('Pending')">Pending</button>
<button class="tab" onclick="filterUsers('Approved')">Approved</button>
<button class="tab" onclick="filterUsers('Rejected')">Rejected</button>
<button class="tab" onclick="filterUsers('All')">All</button>
</div>
<div class="card" id="usersCard">
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Requested</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="usersTable">
<tr><td colspan="6" class="empty">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- ── KNOWLEDGE BASE SECTION ── -->
<div class="container" style="margin-top:2rem;">
<h1>Knowledge Base</h1>
<p class="subtitle">Add content the AI bot will use to answer employee questions</p>
<!-- Add Entry Form -->
<div class="card" style="padding:1.5rem; margin-bottom:1.5rem;">
<div style="display:flex; flex-direction:column; gap:1rem;">
<div>
<label style="font-size:0.8125rem; font-weight:500; color:var(--text); display:block; margin-bottom:5px;">Title *</label>
<input type="text" id="kTitle" placeholder="e.g. Leave Policy, Product Info" style="width:100%; height:40px; padding:0 12px; font-family:'DM Sans',sans-serif; font-size:0.875rem; border:1px solid var(--border); border-radius:6px; outline:none; color:var(--text);" />
</div>
<div>
<label style="font-size:0.8125rem; font-weight:500; color:var(--text); display:block; margin-bottom:5px;">URL (optional — auto fetches content)</label>
<input type="url" id="kUrl" placeholder="https://notion.so/your-page or any public URL" style="width:100%; height:40px; padding:0 12px; font-family:'DM Sans',sans-serif; font-size:0.875rem; border:1px solid var(--border); border-radius:6px; outline:none; color:var(--text);" />
</div>
<div>
<label style="font-size:0.8125rem; font-weight:500; color:var(--text); display:block; margin-bottom:5px;">Content (paste text if no URL)</label>
<textarea id="kContent" placeholder="Paste the knowledge content here..." rows="5" style="width:100%; padding:10px 12px; font-family:'DM Sans',sans-serif; font-size:0.875rem; border:1px solid var(--border); border-radius:6px; outline:none; color:var(--text); resize:vertical;"></textarea>
</div>
<button onclick="addKnowledge()" style="height:40px; background:var(--accent); color:#fff; border:none; border-radius:6px; font-family:'DM Sans',sans-serif; font-size:0.875rem; font-weight:600; cursor:pointer; transition:opacity 0.15s;" onmouseover="this.style.opacity=0.85" onmouseout="this.style.opacity=1">
+ Add to Knowledge Base
</button>
<div style="border-top:1px solid var(--border); padding-top:1rem; margin-top:0.5rem;">
<label style="font-size:0.8125rem; font-weight:600; color:var(--text); display:block; margin-bottom:8px;">📄 Or Upload a PDF</label>
<div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
<input type="text" id="pdfTitle" placeholder="PDF title (e.g. Job Leveling)" style="flex:1; min-width:180px; height:40px; padding:0 12px; font-family:'DM Sans',sans-serif; font-size:0.875rem; border:1px solid var(--border); border-radius:6px; outline:none; color:var(--text);" />
<input type="file" id="pdfFile" accept=".pdf" style="flex:1; min-width:180px; font-family:'DM Sans',sans-serif; font-size:0.8125rem; color:var(--muted);" />
<button onclick="uploadPdf()" style="height:40px; padding:0 20px; background:#1A1916; color:#fff; border:none; border-radius:6px; font-family:'DM Sans',sans-serif; font-size:0.875rem; font-weight:600; cursor:pointer; white-space:nowrap;" onmouseover="this.style.opacity=0.85" onmouseout="this.style.opacity=1">
Upload PDF
</button>
</div>
</div>
</div>
</div>
<!-- Knowledge Entries List -->
<div class="card" id="knowledgeList">
<table style="width:100%; border-collapse:collapse;">
<thead>
<tr>
<th style="padding:12px 16px; text-align:left; font-size:0.75rem; font-weight:600; color:var(--hint); text-transform:uppercase; letter-spacing:0.05em; border-bottom:1px solid var(--border); background:#FAFAF8;">Title</th>
<th style="padding:12px 16px; text-align:left; font-size:0.75rem; font-weight:600; color:var(--hint); text-transform:uppercase; letter-spacing:0.05em; border-bottom:1px solid var(--border); background:#FAFAF8;">Content Preview</th>
<th style="padding:12px 16px; text-align:left; font-size:0.75rem; font-weight:600; color:var(--hint); text-transform:uppercase; letter-spacing:0.05em; border-bottom:1px solid var(--border); background:#FAFAF8;">Added</th>
<th style="padding:12px 16px; text-align:left; font-size:0.75rem; font-weight:600; color:var(--hint); text-transform:uppercase; letter-spacing:0.05em; border-bottom:1px solid var(--border); background:#FAFAF8;">Actions</th>
</tr>
</thead>
<tbody id="kTableBody">
<tr><td colspan="4" style="padding:3rem; text-align:center; color:var(--hint); font-size:0.875rem;">No knowledge entries yet.</td></tr>
</tbody>
</table>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
let allUsers = [];
let currentFilter = 'Pending';
async function loadUsers() {
const res = await fetch('/api/admin/users');
const data = await res.json();
allUsers = data.users || [];
renderTable(currentFilter);
}
function filterUsers(status) {
currentFilter = status;
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
renderTable(status);
}
function renderTable(filter) {
const tbody = document.getElementById('usersTable');
const users = filter === 'All' ? allUsers : allUsers.filter(u => u.status === filter);
if (users.length === 0) {
tbody.innerHTML = `<tr><td colspan="6" class="empty">No ${filter.toLowerCase()} users found.</td></tr>`;
return;
}
tbody.innerHTML = users.map(u => `
<tr>
<td><strong>${u.name}</strong></td>
<td>${u.email}</td>
<td>${u.role}</td>
<td>${u.created_at}</td>
<td><span class="badge ${u.status.toLowerCase()}">${u.status}</span></td>
<td>
${u.status !== 'Approved' ? `<button class="btn-approve" onclick="updateUser('${u.email}','Approved')">✓ Approve</button>` : ''}
${u.status !== 'Rejected' ? `<button class="btn-reject" onclick="updateUser('${u.email}','Rejected')">✕ Reject</button>` : ''}
</td>
</tr>
`).join('');
}
async function updateUser(email, action) {
const res = await fetch('/api/admin/approve', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, action })
});
const data = await res.json();
if (data.success) {
showToast(`User ${action.toLowerCase()} successfully`);
loadUsers();
}
}
function showToast(msg) {
const t = document.getElementById('toast');
t.textContent = msg;
t.style.display = 'block';
setTimeout(() => t.style.display = 'none', 2500);
}
loadUsers();
loadKnowledge();
async function addKnowledge() {
const title = document.getElementById('kTitle').value.trim();
const url = document.getElementById('kUrl').value.trim();
const content = document.getElementById('kContent').value.trim();
if (!title) return showToast('Please enter a title');
if (!url && !content) return showToast('Please enter a URL or content');
showToast('Adding entry...');
const res = await fetch('/api/knowledge/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, url, content })
});
const data = await res.json();
showToast(data.message);
if (data.success) {
document.getElementById('kTitle').value = '';
document.getElementById('kUrl').value = '';
document.getElementById('kContent').value = '';
loadKnowledge();
}
}
async function loadKnowledge() {
const res = await fetch('/api/knowledge');
const data = await res.json();
const tbody = document.getElementById('kTableBody');
if (!data.entries || data.entries.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" style="padding:3rem; text-align:center; color:var(--hint); font-size:0.875rem;">No knowledge entries yet.</td></tr>';
return;
}
tbody.innerHTML = data.entries.map(e => `
<tr>
<td style="padding:14px 16px; font-size:0.875rem; font-weight:600; color:var(--text); border-bottom:1px solid var(--border);">${e.title}</td>
<td style="padding:14px 16px; font-size:0.8125rem; color:var(--muted); border-bottom:1px solid var(--border); max-width:300px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${e.content.slice(0, 100)}...</td>
<td style="padding:14px 16px; font-size:0.8125rem; color:var(--hint); border-bottom:1px solid var(--border);">${e.created_at}</td>
<td style="padding:14px 16px; border-bottom:1px solid var(--border);">
<button onclick="deleteKnowledge('${e.id}')" style="padding:5px 12px; border-radius:6px; font-size:0.8125rem; font-weight:500; cursor:pointer; background:#FEE2E2; color:#991B1B; border:1px solid #FECACA; font-family:'DM Sans',sans-serif;">✕ Delete</button>
</td>
</tr>
`).join('');
}
async function uploadPdf() {
const title = document.getElementById('pdfTitle').value.trim();
const file = document.getElementById('pdfFile').files[0];
if (!title) return showToast('Please enter a title for the PDF');
if (!file) return showToast('Please select a PDF file');
showToast('Uploading PDF...');
const formData = new FormData();
formData.append('pdf', file);
formData.append('title', title);
const res = await fetch('/api/knowledge/upload-pdf', { method: 'POST', body: formData });
const data = await res.json();
showToast(data.message);
if (data.success) {
document.getElementById('pdfTitle').value = '';
document.getElementById('pdfFile').value = '';
loadKnowledge();
}
}
async function deleteKnowledge(id) {
if (!confirm('Delete this knowledge entry?')) return;
const res = await fetch('/api/knowledge/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id })
});
const data = await res.json();
showToast(data.message);
if (data.success) loadKnowledge();
}
</script>
</body>
</html>