2026-04-15-4
This commit is contained in:
+87
-23
@@ -11,13 +11,18 @@ const app = {
|
||||
},
|
||||
|
||||
init: async () => {
|
||||
console.log('🚀 Dashboard Initializing...');
|
||||
console.log('🚀 Dashboard Initializing Components...');
|
||||
app.bindEvents();
|
||||
app.handleRouting();
|
||||
await app.fetchData();
|
||||
lucide.createIcons();
|
||||
},
|
||||
|
||||
onAuthenticated: async () => {
|
||||
console.log('🔑 User Authenticated, Fetching Data...');
|
||||
await app.fetchData();
|
||||
app.updateStats();
|
||||
},
|
||||
|
||||
bindEvents: () => {
|
||||
window.addEventListener('hashchange', app.handleRouting);
|
||||
|
||||
@@ -50,25 +55,31 @@ const app = {
|
||||
playground.init(app.state.keys);
|
||||
} else if (hash === 'analytics') {
|
||||
analytics.init();
|
||||
} else if (hash === 'billing') {
|
||||
billing.init();
|
||||
} else if (hash === 'refinement') {
|
||||
refinement.init();
|
||||
} else if (hash === 'docs') {
|
||||
docs.init();
|
||||
}
|
||||
},
|
||||
|
||||
fetchData: async () => {
|
||||
const headers = auth.getAuthHeader();
|
||||
try {
|
||||
// Fetch Tenant
|
||||
const tenantRes = await fetch('/api/auth/management/me');
|
||||
const tenantRes = await fetch('/api/auth/management/me', { headers });
|
||||
if (tenantRes.ok) {
|
||||
app.state.tenant = await tenantRes.data || await tenantRes.json();
|
||||
app.state.tenant = 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 (app.state.tenant) {
|
||||
const keysRes = await fetch('/api/auth/management/keys', { headers });
|
||||
if (keysRes.ok) {
|
||||
app.state.keys = await keysRes.json();
|
||||
app.renderKeysTable();
|
||||
app.updateStats();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -80,21 +91,73 @@ const app = {
|
||||
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}`;
|
||||
document.getElementById('welcome-msg').textContent = `Welcome back, ${app.state.tenant.name.split(' ')[0]}`;
|
||||
|
||||
// Role-based UI visibility
|
||||
if (app.state.tenant.role === 'ADMIN') {
|
||||
const auditLink = document.getElementById('nav-refinement');
|
||||
if (auditLink) auditLink.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Update user avatar if photoUrl exists
|
||||
if (app.state.tenant.photoUrl) {
|
||||
const avatarContainer = document.querySelector('.w-10.h-10.rounded-full.bg-slate-900');
|
||||
if (avatarContainer) {
|
||||
avatarContainer.innerHTML = `<img src="${app.state.tenant.photoUrl}" class="w-full h-full rounded-full object-cover border border-blue-500/20" alt="Profile">`;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateStats: () => {
|
||||
document.getElementById('active-keys-count').textContent = app.state.keys.length;
|
||||
updateStats: async () => {
|
||||
try {
|
||||
const headers = auth.getAuthHeader();
|
||||
const res = await fetch('/api/usage/summary', { headers });
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
|
||||
// Update KPI Cards
|
||||
document.getElementById('active-keys-count').textContent = app.state.keys.length;
|
||||
|
||||
// Detailed Stats from getUsageSummary
|
||||
if (document.getElementById('stat-total-req')) {
|
||||
document.getElementById('stat-total-req').textContent = data.monthlyUsage.toLocaleString();
|
||||
}
|
||||
if (document.getElementById('stat-success-percent')) {
|
||||
document.getElementById('stat-success-percent').textContent = `${data.successRate}%`;
|
||||
document.getElementById('stat-success-rate').textContent = data.successRate >= 95 ? 'Excellent' : 'Stable';
|
||||
}
|
||||
if (document.getElementById('stat-avg-latency')) {
|
||||
document.getElementById('stat-avg-latency').textContent = `${data.avgLatency}ms`;
|
||||
document.getElementById('stat-latency-val').textContent = `-${Math.round(data.avgLatency * 0.1)}ms`;
|
||||
}
|
||||
|
||||
// Update Progress bar & Quota card
|
||||
const bar = document.getElementById('usage-progress-bar');
|
||||
const label = document.getElementById('usage-percentage-label');
|
||||
const limitText = document.getElementById('usage-limit-text');
|
||||
|
||||
if (bar) bar.style.width = `${data.percentage}%`;
|
||||
if (label) label.textContent = `${data.percentage}% USED`;
|
||||
if (limitText) {
|
||||
const remaining = Math.max(0, data.limit - data.monthlyUsage);
|
||||
limitText.textContent = `${remaining.toLocaleString()} requests left`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update stats', error);
|
||||
}
|
||||
|
||||
// Inject random bars for traffic
|
||||
// Inject sample bars for visual flair if chart not ready
|
||||
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 => {
|
||||
// High-density bars for a tech/premium feel
|
||||
const values = [40, 60, 55, 80, 70, 45, 90, 85, 60, 40, 30, 55, 75, 40, 60, 55, 80, 70, 45, 90, 85, 60, 40, 30, 55, 75, 50, 65, 80, 70, 30];
|
||||
values.forEach((h, index) => {
|
||||
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.className = 'flex-1 bg-gradient-to-t from-blue-600/10 to-blue-400/60 rounded-t-[2px] relative group hover:to-blue-300 transition-all duration-500';
|
||||
bar.style.height = `${h}%`;
|
||||
bar.style.transitionDelay = `${index * 20}ms`;
|
||||
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);
|
||||
});
|
||||
@@ -112,12 +175,10 @@ const app = {
|
||||
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';
|
||||
}
|
||||
// Enable create button (removed previous limit restrictiveness)
|
||||
createBtn.disabled = false;
|
||||
createBtn.title = "Create a new API key";
|
||||
createBtn.innerHTML = '<i data-lucide="plus" class="w-4 h-4"></i> Create Key';
|
||||
|
||||
tbody.innerHTML = app.state.keys.map(key => {
|
||||
const isVisible = app.state.showKeys[key.id];
|
||||
@@ -195,9 +256,12 @@ const app = {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Creating...';
|
||||
|
||||
const res = await fetch(`/api/auth/management/keys/${app.state.tenant.id}`, {
|
||||
const res = await fetch('/api/auth/management/keys', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...auth.getAuthHeader()
|
||||
},
|
||||
body: JSON.stringify({ name, rateLimit: 100 })
|
||||
});
|
||||
|
||||
@@ -217,5 +281,5 @@ const app = {
|
||||
}
|
||||
};
|
||||
|
||||
// Start app
|
||||
// Start app components
|
||||
document.addEventListener('DOMContentLoaded', app.init);
|
||||
|
||||
Reference in New Issue
Block a user