Added more security and metrics
This commit is contained in:
@@ -1896,3 +1896,181 @@ textarea.field-input {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
/* Dashboard Styles */
|
||||
.dashboard-panel {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 58px;
|
||||
bottom: 0;
|
||||
width: 400px;
|
||||
background: var(--bg-1);
|
||||
border-left: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 100;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
|
||||
.dashboard-header h2 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-0);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
color: var(--text-1);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: var(--text-0);
|
||||
}
|
||||
|
||||
.dashboard-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.dashboard-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
border-color: var(--accent);
|
||||
background: var(--bg-3);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-2);
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.stat-value.error-red {
|
||||
color: var(--m-delete);
|
||||
}
|
||||
|
||||
.metric-chart {
|
||||
background: var(--bg-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.recent-requests {
|
||||
background: var(--bg-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.request-item {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.request-item:hover {
|
||||
background: var(--bg-3);
|
||||
}
|
||||
|
||||
.request-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.request-method {
|
||||
font-weight: 600;
|
||||
font-family: var(--font-mono);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.request-status-success {
|
||||
color: var(--m-get);
|
||||
}
|
||||
|
||||
.request-status-error {
|
||||
color: var(--m-delete);
|
||||
}
|
||||
|
||||
.request-time {
|
||||
color: var(--text-2);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
#app-shell.with-dashboard {
|
||||
grid-template-columns: var(--nav-w) 1fr var(--try-w) 400px;
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
.dashboard-panel {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
#app-shell.with-dashboard {
|
||||
grid-template-columns: var(--nav-w) 1fr 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-panel {
|
||||
width: 100%;
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1386,6 +1386,9 @@
|
||||
const exportDoxaApiBtn = document.getElementById("exportDoxaApiBtn");
|
||||
const exportOpenApiBtn = document.getElementById("exportOpenApiBtn");
|
||||
const exportSwaggerBtn = document.getElementById("exportSwaggerBtn");
|
||||
const dashboardBtn = document.getElementById("dashboardBtn");
|
||||
const closeDashboard = document.getElementById("closeDashboard");
|
||||
const dashboardPanel = document.getElementById("dashboardPanel");
|
||||
|
||||
importBtn?.addEventListener("click", () => importFile.click());
|
||||
|
||||
@@ -1443,6 +1446,10 @@
|
||||
exportOpenApiBtn?.addEventListener("click", () => download("openapi.json", "openapi.json"));
|
||||
exportSwaggerBtn?.addEventListener("click", () => download("swagger.json", "swagger.json"));
|
||||
|
||||
// Dashboard functionality
|
||||
dashboardBtn?.addEventListener("click", () => openDashboard());
|
||||
closeDashboard?.addEventListener("click", () => closeDashboardPanel());
|
||||
|
||||
const authBtn = document.getElementById("authBtn");
|
||||
authBtn?.addEventListener("click", () => openAuthModal());
|
||||
|
||||
@@ -1578,5 +1585,93 @@
|
||||
return escapeHtml(str);
|
||||
}
|
||||
|
||||
// Dashboard functions
|
||||
async function openDashboard() {
|
||||
const dashboardPanel = document.getElementById("dashboardPanel");
|
||||
const dashboard = document.querySelector(".dashboard-panel");
|
||||
|
||||
if (!dashboardPanel) return;
|
||||
dashboardPanel.style.display = "block";
|
||||
|
||||
try {
|
||||
const res = await fetch("analytics");
|
||||
if (!res.ok) throw new Error("Failed to load analytics");
|
||||
|
||||
const analyticsData = await res.json();
|
||||
updateDashboard(analyticsData);
|
||||
|
||||
// Refresh every 5 seconds
|
||||
if (window.dashboardInterval) clearInterval(window.dashboardInterval);
|
||||
window.dashboardInterval = setInterval(async () => {
|
||||
const res = await fetch("analytics");
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
updateDashboard(data);
|
||||
}
|
||||
}, 5000);
|
||||
} catch (err) {
|
||||
console.error("Dashboard load error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
function closeDashboardPanel() {
|
||||
const dashboardPanel = document.getElementById("dashboardPanel");
|
||||
if (dashboardPanel) dashboardPanel.style.display = "none";
|
||||
|
||||
if (window.dashboardInterval) {
|
||||
clearInterval(window.dashboardInterval);
|
||||
window.dashboardInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateDashboard(data) {
|
||||
document.getElementById("totalEndpoints").textContent = data.totalEndpoints || "0";
|
||||
document.getElementById("totalRequests").textContent = data.totalRequests || "0";
|
||||
document.getElementById("totalErrors").textContent = data.totalErrors || "0";
|
||||
document.getElementById("avgResponse").textContent = Math.round((data.averageResponseTime || 0)) + "ms";
|
||||
|
||||
// Update top endpoints
|
||||
const topEndpointsList = (data.topEndpoints || []).map(ep => `
|
||||
<div class="request-item">
|
||||
<div>
|
||||
<span class="request-method">${ep.method}</span>
|
||||
<code style="font-size: 11px; color: var(--text-1);">${ep.path}</code>
|
||||
</div>
|
||||
<div class="request-time">${ep.requestCount} req</div>
|
||||
</div>
|
||||
`).join("") || "<div style='padding: 20px; color: var(--text-2); text-align: center;'>No data yet</div>";
|
||||
|
||||
document.getElementById("topEndpointsChart").innerHTML = `
|
||||
<div style="width: 100%;">
|
||||
<h3 style="margin: 0 0 12px 0; font-size: 13px; font-weight: 600;">Top Endpoints</h3>
|
||||
${topEndpointsList}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Update recent requests
|
||||
const recentRequestsList = (data.recentRequests || []).map(req => {
|
||||
const statusClass = req.statusCode >= 400 ? "request-status-error" : "request-status-success";
|
||||
return `
|
||||
<div class="request-item">
|
||||
<div>
|
||||
<span class="request-method">${req.method}</span>
|
||||
<code style="font-size: 11px; color: var(--text-1);">${req.path}</code>
|
||||
</div>
|
||||
<div>
|
||||
<span class="request-time ${statusClass}">${req.statusCode}</span>
|
||||
<span class="request-time" style="margin-left: 8px;">${req.responseTimeMs}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join("") || "<div style='padding: 20px; color: var(--text-2); text-align: center;'>No requests yet</div>";
|
||||
|
||||
document.getElementById("recentRequestsTable").innerHTML = `
|
||||
<div style="padding: 16px; background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-md);">
|
||||
<h3 style="margin: 0 0 12px 0; font-size: 13px; font-weight: 600;">Recent Requests</h3>
|
||||
${recentRequestsList}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
init();
|
||||
})();
|
||||
@@ -33,7 +33,10 @@
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<div class="action-group">
|
||||
<button id="authBtn" class="action-btn icon-btn-badge" title="Authorization">
|
||||
<button id="dashboardBtn" class="action-btn" title="View Analytics Dashboard">
|
||||
<svg viewBox="0 0 24 24" fill="none" width="15" height="15" style="vertical-align:-2px;margin-right:4px;"><rect x="3" y="3" width="7" height="7" stroke="currentColor" stroke-width="2"/><rect x="14" y="3" width="7" height="7" stroke="currentColor" stroke-width="2"/><rect x="3" y="14" width="7" height="7" stroke="currentColor" stroke-width="2"/><rect x="14" y="14" width="7" height="7" stroke="currentColor" stroke-width="2"/></svg>Dashboard
|
||||
</button>
|
||||
<button id="authBtn" class="action-btn" title="Manage authentication">
|
||||
<svg viewBox="0 0 24 24" fill="none" width="15" height="15" style="vertical-align:-2px;margin-right:4px;"><rect x="5" y="11" width="14" height="9" rx="2" stroke="currentColor" stroke-width="2" /><path d="M8 11V7a4 4 0 018 0v4" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>Auth
|
||||
<span class="badge-dot" id="authBadgeDot" style="display:none;"></span>
|
||||
</button>
|
||||
@@ -71,6 +74,36 @@
|
||||
<aside class="try-pane" id="tryPane">
|
||||
<div id="tryContent" class="try-content"></div>
|
||||
</aside>
|
||||
|
||||
<!-- Dashboard Panel (initially hidden) -->
|
||||
<div id="dashboardPanel" class="dashboard-panel" style="display: none;">
|
||||
<div class="dashboard-header">
|
||||
<h2>API Analytics</h2>
|
||||
<button id="closeDashboard" class="close-btn" title="Close Dashboard">×</button>
|
||||
</div>
|
||||
<div class="dashboard-content">
|
||||
<div class="dashboard-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Total Endpoints</div>
|
||||
<div class="stat-value" id="totalEndpoints">0</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Total Requests</div>
|
||||
<div class="stat-value" id="totalRequests">0</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Errors</div>
|
||||
<div class="stat-value error-red" id="totalErrors">0</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Avg Response</div>
|
||||
<div class="stat-value" id="avgResponse">0ms</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="topEndpointsChart" class="metric-chart"></div>
|
||||
<div id="recentRequestsTable" class="recent-requests"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user