Files
maps-saas/apps/dashboard/js/app.js
T

222 lines
9.0 KiB
JavaScript

/**
* Main App Logic for Intaleq Dashboard (Vanilla Version)
*/
const app = {
state: {
tenant: null,
keys: [],
showKeys: {}, // { id: boolean }
activePage: 'home'
},
init: async () => {
console.log('🚀 Dashboard Initializing...');
app.bindEvents();
app.handleRouting();
await app.fetchData();
lucide.createIcons();
},
bindEvents: () => {
window.addEventListener('hashchange', app.handleRouting);
// Form submission for new key
const createKeyForm = document.getElementById('create-key-form');
if (createKeyForm) {
createKeyForm.addEventListener('submit', async (e) => {
e.preventDefault();
await app.createKey();
});
}
},
handleRouting: () => {
const hash = window.location.hash.replace('#', '') || 'home';
app.state.activePage = hash;
// Update UI
document.querySelectorAll('.page-section').forEach(s => s.classList.remove('active'));
const activeSection = document.getElementById(hash);
if (activeSection) activeSection.classList.add('active');
// Update Nav
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
const activeLink = document.getElementById(`nav-${hash}`);
if (activeLink) activeLink.classList.add('active');
// Specialized page logic
if (hash === 'playground') {
playground.init(app.state.keys);
} else if (hash === 'analytics') {
analytics.init();
}
},
fetchData: async () => {
try {
// Fetch Tenant
const tenantRes = await fetch('/api/auth/management/me');
if (tenantRes.ok) {
app.state.tenant = await tenantRes.data || await tenantRes.json();
app.updateHeader();
}
// Fetch Keys
if (app.state.tenant && app.state.tenant.id) {
const keysRes = await fetch(`/api/auth/management/keys/${app.state.tenant.id}`);
if (keysRes.ok) {
app.state.keys = await keysRes.json();
app.renderKeysTable();
app.updateStats();
}
}
} catch (error) {
console.error('Failed to fetch dashboard data', error);
}
},
updateHeader: () => {
if (!app.state.tenant) return;
document.getElementById('tenant-name').textContent = app.state.tenant.name;
document.getElementById('tenant-email').textContent = app.state.tenant.email;
document.getElementById('welcome-msg').textContent = `Welcome back, ${app.state.tenant.name}`;
},
updateStats: () => {
document.getElementById('active-keys-count').textContent = app.state.keys.length;
// Inject random bars for traffic
const container = document.getElementById('traffic-bars');
if (container) {
container.innerHTML = '';
const values = [40, 60, 55, 80, 70, 45, 90, 85, 60, 40, 30, 55, 75, 40, 60, 55, 80, 70, 45, 90];
values.forEach(h => {
const bar = document.createElement('div');
bar.className = 'flex-1 bg-gradient-to-t from-blue-600/20 to-blue-400/80 rounded-t-sm relative group hover:to-blue-300 transition-all';
bar.style.height = `${h}%`;
bar.innerHTML = `<div class="absolute -top-10 left-1/2 -translate-x-1/2 glass px-2 py-1 rounded text-[10px] font-bold opacity-0 group-hover:opacity-100 transition-opacity z-10">${h}k</div>`;
container.appendChild(bar);
});
}
},
renderKeysTable: () => {
const tbody = document.getElementById('keys-table-body');
const createBtn = document.getElementById('create-key-btn');
if (!tbody) return;
if (app.state.keys.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" class="py-20 text-center text-slate-500 font-medium">No API keys found. Create one to get started.</td></tr>`;
return;
}
// Disable create button if limit reached (per React logic)
if (app.state.keys.length >= 1) {
createBtn.disabled = true;
createBtn.title = "Limit of 1 API key per developer reached";
createBtn.innerHTML = '<i data-lucide="shield-alert" class="w-4 h-4"></i> Limit Reached';
}
tbody.innerHTML = app.state.keys.map(key => {
const isVisible = app.state.showKeys[key.id];
const maskedKey = isVisible ? key.key : "in_••••••••••••••••••••••••";
const eyeIcon = isVisible ? 'eye-off' : 'eye';
return `
<tr class="hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-6 font-medium">${key.name}</td>
<td class="px-6 py-6">
<div class="flex items-center gap-2 bg-slate-900 rounded-lg px-3 py-1.5 w-fit border border-slate-800">
<code class="text-xs text-blue-400 font-mono">${maskedKey}</code>
<div class="flex items-center gap-1 ml-2 border-l border-slate-800 pl-2">
<button onclick="app.toggleKeyVisibility('${key.id}')" class="text-slate-500 hover:text-white">
<i data-lucide="${eyeIcon}" class="w-3.5 h-3.5"></i>
</button>
<button class="text-slate-500 hover:text-white" onclick="app.copyToClipboard('${key.key}')">
<i data-lucide="copy" class="w-3.5 h-3.5"></i>
</button>
</div>
</div>
</td>
<td class="px-6 py-6">
<span class="flex items-center gap-1.5 text-xs font-bold ${key.isActive ? 'text-emerald-400' : 'text-slate-500'}">
<div class="w-1.5 h-1.5 rounded-full ${key.isActive ? 'bg-emerald-400 animate-pulse' : 'bg-slate-500'}"></div>
${key.isActive ? 'Active' : 'Inactive'}
</span>
</td>
<td class="px-6 py-6">
<div class="flex gap-2">
${(key.allowedOrigins && key.allowedOrigins.length > 0) ?
key.allowedOrigins.map(org => `<span class="px-2 py-0.5 rounded-md bg-blue-500/10 text-[10px] uppercase font-black text-blue-400 flex items-center gap-1"><i data-lucide="globe" class="w-2.5 h-2.5"></i> ${org}</span>`).join('') :
`<span class="px-2 py-0.5 rounded-md bg-slate-800 text-[10px] uppercase font-black text-slate-400 flex items-center gap-1"><i data-lucide="globe" class="w-2.5 h-2.5"></i> Universal</span>`
}
</div>
</td>
<td class="px-6 py-6 text-right">
<button class="p-2 text-slate-500 hover:text-white" onclick="app.fetchData()">
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
</button>
</td>
</tr>
`;
}).join('');
lucide.createIcons();
},
toggleKeyVisibility: (id) => {
app.state.showKeys[id] = !app.state.showKeys[id];
app.renderKeysTable();
},
copyToClipboard: (text) => {
navigator.clipboard.writeText(text);
// Simple toast or just feedback
console.log('Copied to clipboard');
},
toggleModal: (id, show) => {
const modal = document.getElementById(id);
if (modal) {
if (show) modal.classList.remove('hidden');
else modal.classList.add('hidden');
}
},
createKey: async () => {
const name = document.getElementById('new-key-name').value;
const btn = document.getElementById('btn-submit-key');
if (!app.state.tenant) return;
try {
btn.disabled = true;
btn.textContent = 'Creating...';
const res = await fetch(`/api/auth/management/keys/${app.state.tenant.id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, rateLimit: 100 })
});
if (res.ok) {
app.toggleModal('create-key-modal', false);
await app.fetchData();
document.getElementById('new-key-name').value = '';
} else {
alert('Failed to create key');
}
} catch (error) {
console.error(error);
} finally {
btn.disabled = false;
btn.textContent = 'Create Key';
}
}
};
// Start app
document.addEventListener('DOMContentLoaded', app.init);