Updated UI

This commit is contained in:
2026-06-23 20:12:51 +02:00
parent cb2d111ad7
commit b8905206d9
4 changed files with 2350 additions and 1981 deletions
File diff suppressed because it is too large Load Diff
+1685 -1499
View File
File diff suppressed because it is too large Load Diff
+19 -3
View File
@@ -1383,9 +1383,25 @@
function bindGlobalEvents() {
const importBtn = document.getElementById("importBtn");
const importFile = document.getElementById("importFile");
const exportToggle = document.querySelector(".export-toggle");
const exportDropdown = document.querySelector(".export-dropdown");
const exportMenu = document.getElementById("exportMenu");
const exportDoxaApiBtn = document.getElementById("exportDoxaApiBtn");
const exportOpenApiBtn = document.getElementById("exportOpenApiBtn");
const exportSwaggerBtn = document.getElementById("exportSwaggerBtn");
exportToggle?.addEventListener("click", (event) => {
event.stopPropagation();
const isOpen = exportMenu?.classList.toggle("open");
exportToggle.setAttribute("aria-expanded", String(!!isOpen));
});
document.addEventListener("click", (event) => {
if (!exportDropdown?.contains(event.target)) {
exportMenu?.classList.remove("open");
exportToggle?.setAttribute("aria-expanded", "false");
}
});
const dashboardBtn = document.getElementById("dashboardBtn");
const closeDashboard = document.getElementById("closeDashboard");
const dashboardPanel = document.getElementById("dashboardPanel");
@@ -1398,7 +1414,7 @@
const text = await file.text();
const res = await fetch("import", {
const res = await fetch(new URL("import", document.baseURI), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: text
@@ -1435,7 +1451,7 @@
function download(url, filename) {
const a = document.createElement("a");
a.href = url;
a.href = new URL(url, document.baseURI);
a.download = filename;
document.body.appendChild(a);
a.click();
@@ -1475,7 +1491,7 @@
const normalized = parsed.groups
? parsed
: await (async () => {
const res = await fetch("import", {
const res = await fetch(new URL("import", document.baseURI), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: text
+90 -84
View File
@@ -3,112 +3,118 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SampleApi</title>
<title>DoxaApi</title>
<base href="__DOXA_API_BASE__" />
<link rel="stylesheet" href="app.css" />
<style>
:root {
--accent: #6366f1;
}
</style>
</head>
<body>
<div id="app" class="app-shell">
<!-- Top bar -->
<header class="topbar">
<div class="topbar-brand">
<span class="brand-mark">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 6L10 12L4 18" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" />
<path d="M13 18H20" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" />
</svg>
</span>
<span class="brand-title" id="brandTitle">SampleApi</span>
<span class="brand-version" id="brandVersion"></span>
</div>
<div class="api-switcher"><select id="apiSelector"><option>Default API</option></select></div><div class="topbar-search">
<svg viewBox="0 0 24 24" fill="none" class="search-icon"><circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2" /><path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>
<input id="searchInput" type="text" placeholder="Search endpoints (press /)" autocomplete="off" />
<kbd>/</kbd>
</div>
<div class="topbar-actions">
<div class="action-group">
<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>
<button id="consoleBtn" class="action-btn" title="Open API console">
<svg viewBox="0 0 24 24" fill="none" width="15" height="15" style="vertical-align:-2px;margin-right:4px;"><path d="M4 5h16v14H4z" stroke="currentColor" stroke-width="2" /><path d="M7 9l3 3-3 3M13 15h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>Console
</button>
<button id="importBtn" class="action-btn action-btn-primary">⬆ Import</button>
<button id="compareBtn" class="action-btn" title="Compare against another spec">⇄ Compare</button>
<button id="exportDoxaApiBtn" class="action-btn">DoxaApi</button>
<button id="exportOpenApiBtn" class="action-btn">OpenAPI</button>
<button id="exportSwaggerBtn" class="action-btn">Swagger</button>
<div class="topbar-left">
<div class="brand">
<div class="brand-icon">
<svg viewBox="0 0 20 20" fill="none"><path d="M3 5L9 11L3 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /><path d="M11 17H17" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>
</div>
<div class="brand-text">
<span class="brand-name" id="brandTitle">DoxaApi</span>
<span class="brand-ver" id="brandVersion"></span>
</div>
</div>
<input id="importFile" type="file" accept=".json" hidden />
<input id="diffFile" type="file" accept=".json" hidden />
<button id="themeToggle" class="icon-btn" title="Toggle theme" aria-label="Toggle theme">
<svg class="icon-sun" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="4" stroke="currentColor" stroke-width="2" /><path d="M12 2V4M12 20V22M4 12H2M22 12H20M19.07 4.93L17.66 6.34M6.34 17.66L4.93 19.07M19.07 19.07L17.66 17.66M6.34 6.34L4.93 4.93" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>
<svg class="icon-moon" viewBox="0 0 24 24" fill="none"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" /></svg>
<div class="api-switcher-wrap">
<select id="apiSelector" class="api-select"></select>
</div>
</div>
<div class="topbar-center">
<div class="search-wrap">
<svg viewBox="0 0 20 20" fill="none" class="search-ico"><circle cx="9" cy="9" r="6" stroke="currentColor" stroke-width="1.8" /><path d="M17 17L13.5 13.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /></svg>
<input id="searchInput" type="text" placeholder="Search endpoints, methods, paths…" autocomplete="off" spellcheck="false" />
<kbd class="search-kbd">/</kbd>
</div>
</div>
<div class="topbar-right">
<button id="dashboardBtn" class="tb-btn" title="Analytics">
<svg viewBox="0 0 20 20" fill="none"><rect x="2" y="2" width="7" height="7" rx="1.5" stroke="currentColor" stroke-width="1.8" /><rect x="11" y="2" width="7" height="7" rx="1.5" stroke="currentColor" stroke-width="1.8" /><rect x="2" y="11" width="7" height="7" rx="1.5" stroke="currentColor" stroke-width="1.8" /><rect x="11" y="11" width="7" height="7" rx="1.5" stroke="currentColor" stroke-width="1.8" /></svg>
<span>Dashboard</span>
</button>
<button id="authBtn" class="tb-btn" title="Auth">
<svg viewBox="0 0 20 20" fill="none"><rect x="4" y="9" width="12" height="9" rx="2" stroke="currentColor" stroke-width="1.8" /><path d="M7 9V6a3 3 0 016 0v3" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /></svg>
<span>Auth</span>
<span class="auth-dot" id="authBadgeDot" style="display:none"></span>
</button>
<button id="consoleBtn" class="tb-btn" title="Console">
<svg viewBox="0 0 20 20" fill="none"><rect x="2" y="3" width="16" height="14" rx="2" stroke="currentColor" stroke-width="1.8" /><path d="M6 8l4 4-4 4M12 16h4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" /></svg>
<span>Console</span>
</button>
<div class="tb-divider"></div>
<div class="export-group">
<button id="importBtn" class="tb-btn tb-btn-accent">
<svg viewBox="0 0 20 20" fill="none"><path d="M10 3v10M6 9l4 4 4-4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" /><path d="M3 15v1a1 1 0 001 1h12a1 1 0 001-1v-1" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /></svg>
Import
</button>
<div class="export-dropdown">
<button class="tb-btn export-toggle">
<svg viewBox="0 0 20 20" fill="none"><path d="M10 17V7M6 11l4-4 4 4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" /><path d="M3 15v1a1 1 0 001 1h12a1 1 0 001-1v-1" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /></svg>
Export
<svg viewBox="0 0 20 20" fill="none" class="chevdown"><path d="M5 8l5 5 5-5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" /></svg>
</button>
<div class="export-menu" id="exportMenu">
<button id="exportDoxaApiBtn" class="export-item"><span class="export-fmt">DoxaApi</span><span class="export-desc">.json</span></button>
<button id="exportOpenApiBtn" class="export-item"><span class="export-fmt">OpenAPI 3</span><span class="export-desc">.json</span></button>
<button id="exportSwaggerBtn" class="export-item"><span class="export-fmt">Swagger 2</span><span class="export-desc">.json</span></button>
</div>
</div>
<button id="compareBtn" class="tb-btn">
<svg viewBox="0 0 20 20" fill="none"><path d="M6 5h8M6 10h8M6 15h4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /><path d="M15 12l3 3-3 3" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" /></svg>
Diff
</button>
</div>
<div class="tb-divider"></div>
<button id="themeToggle" class="icon-btn" title="Toggle theme">
<svg class="icon-sun" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="3.5" stroke="currentColor" stroke-width="1.8" /><path d="M10 2v2M10 16v2M2 10h2M16 10h2M4.93 4.93l1.41 1.41M13.66 13.66l1.41 1.41M4.93 15.07l1.41-1.41M13.66 6.34l1.41-1.41" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /></svg>
<svg class="icon-moon" viewBox="0 0 20 20" fill="none"><path d="M17.5 11.5A7.5 7.5 0 018.5 2.5a7.5 7.5 0 100 15 7.5 7.5 0 009-6z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" /></svg>
</button>
</div>
</header>
<div class="body-grid">
<!-- Left nav: groups + endpoint list -->
<nav class="nav-pane" id="navPane">
<div id="navContent" class="nav-content"></div>
<div class="layout">
<nav class="sidebar" id="sidebar">
<div class="sidebar-inner" id="navContent"></div>
</nav>
<!-- Middle: endpoint detail -->
<main class="detail-pane" id="detailPane">
<div id="detailContent" class="detail-content"></div>
<main class="content-area">
<div class="detail-pane" id="detailPane">
<div id="detailContent" class="detail-content"></div>
</div>
<aside class="try-pane" id="tryPane">
<div id="tryContent" class="try-content"></div>
</aside>
</main>
</div>
<!-- Right: try it out / response -->
<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">&times;</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>
<!-- Dashboard slide-over -->
<div id="dashboardPanel" class="drawer" style="display:none">
<div class="drawer-header">
<span class="drawer-title">API Analytics</span>
<button id="closeDashboard" class="icon-btn"><svg viewBox="0 0 20 20" fill="none"><path d="M5 5l10 10M15 5L5 15" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /></svg></button>
</div>
<div class="drawer-body">
<div class="stat-grid">
<div class="stat-card"><div class="stat-val" id="totalEndpoints"></div><div class="stat-lbl">Endpoints</div></div>
<div class="stat-card"><div class="stat-val" id="totalRequests"></div><div class="stat-lbl">Requests</div></div>
<div class="stat-card stat-card-err"><div class="stat-val" id="totalErrors"></div><div class="stat-lbl">Errors</div></div>
<div class="stat-card"><div class="stat-val" id="avgResponse"></div><div class="stat-lbl">Avg. latency</div></div>
</div>
<div id="topEndpointsChart" class="dash-section"></div>
<div id="recentRequestsTable" class="dash-section"></div>
</div>
</div>
</div>
<div id="modalRoot"></div>
<input id="importFile" type="file" accept=".json" hidden />
<input id="diffFile" type="file" accept=".json" hidden />
<script>window.__DOXA_API_SPEC_URL__ = "__DOXA_API_SPEC_PATH__";</script>
<script src="app.js"></script>
</body>