feat(admin): apix-admin UI at admin.api-index.org
Deploy to Production / deploy (push) Failing after 47s
Deploy to Production / deploy (push) Failing after 47s
Internal maintainer dashboard: global stats, sandbox list with
tier-filter and pagination, sandbox drill-down with D3 world map,
tier-promotion form. API-key auth via HttpOnly cookie. Port 8084,
rolling a/b deploy alongside the existing service pairs.
- apix-common: AdminSandboxSummary, AdminSandboxListResponse, AdminStatsResponse DTOs
- apix-registry: AdminResource (4 endpoints), SandboxService.listAllSandboxes + getGlobalStats
- apix-registry: SandboxResource GET /{uuid}/dashboard endpoint (was missing — fixes portal 500)
- apix-portal: RegistryClient path corrected to /dashboard
- apix-admin: new Quarkus module — auth filter, registry REST client, 3 resources, 6 Qute templates, admin.js
- infra: Dockerfile.admin, docker-compose admin-a/b services, Caddyfile admin.api-index.org block
- scripts: deploy-bluegreen.sh extended for admin-a/b
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>APIX Admin — Dashboard</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { background: #0d1117; color: #c9d1d9; font-family: 'SF Mono','Consolas','Fira Code',monospace; font-size: 14px; }
|
||||
a { color: #58a6ff; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
nav {
|
||||
display: flex; align-items: center; gap: 1.5rem;
|
||||
padding: 0.75rem 1.5rem; border-bottom: 1px solid #21262d;
|
||||
background: #161b22;
|
||||
}
|
||||
.nav-logo { color: #8b949e; font-size: 0.75rem; }
|
||||
.nav-logo span { color: #e6edf3; font-weight: 600; font-size: 0.9rem; }
|
||||
.nav-link { font-size: 0.8rem; color: #8b949e; }
|
||||
.nav-link.active, .nav-link:hover { color: #e6edf3; }
|
||||
.nav-right { margin-left: auto; }
|
||||
|
||||
.stats-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 1px; background: #21262d; border-bottom: 1px solid #21262d;
|
||||
}
|
||||
.stat { background: #0d1117; padding: 1.2rem 1.5rem; }
|
||||
.stat-label { font-size: 0.65rem; color: #484f58; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 0.3rem; }
|
||||
.stat-value { font-size: 1.8rem; color: #e6edf3; font-weight: 700; }
|
||||
.stat-sub { font-size: 0.7rem; color: #484f58; margin-top: 0.2rem; }
|
||||
|
||||
.layout { display: grid; grid-template-columns: 1fr 320px; gap: 1px; background: #21262d; }
|
||||
.panel { background: #0d1117; padding: 1.5rem; }
|
||||
.panel-title { font-size: 0.7rem; color: #484f58; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 1rem; }
|
||||
|
||||
#map-wrap { background: #060d18; border-bottom: 1px solid #21262d; }
|
||||
#world-map { display: block; width: 100%; }
|
||||
|
||||
.donut-wrap { display: flex; align-items: center; gap: 1.5rem; }
|
||||
#donut { width: 120px; height: 120px; flex-shrink: 0; }
|
||||
.tier-list { list-style: none; }
|
||||
.tier-list li { display: flex; align-items: center; gap: 0.5rem; font-size: 0.8rem; margin-bottom: 0.4rem; }
|
||||
.tier-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
||||
|
||||
.footer { padding: 0.75rem 1.5rem; border-top: 1px solid #21262d; font-size: 0.7rem; color: #484f58; display: flex; justify-content: space-between; }
|
||||
|
||||
@keyframes star-glow {
|
||||
0%,100% { filter: drop-shadow(0 0 3px #ffd700) drop-shadow(0 0 6px #ffd70066); }
|
||||
50% { filter: drop-shadow(0 0 8px #ffd700) drop-shadow(0 0 18px #ffd700aa); }
|
||||
}
|
||||
.registrar-star { font-size: 12px; fill: #ffd700; dominant-baseline: central; text-anchor: middle; animation: star-glow 2.4s ease-in-out infinite; cursor: default; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div class="nav-logo">APIX · <span>Admin</span></div>
|
||||
<a class="nav-link active" href="/">Dashboard</a>
|
||||
<a class="nav-link" href="/sandboxes">Sandboxes</a>
|
||||
<div class="nav-right"><a class="nav-link" href="/login/logout">Sign out</a></div>
|
||||
</nav>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat">
|
||||
<div class="stat-label">Total Sandboxes</div>
|
||||
<div class="stat-value">{stats.totalSandboxes}</div>
|
||||
<div class="stat-sub">{stats.activeSandboxes} active · {stats.expiredSandboxes} expired</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Services registered</div>
|
||||
<div class="stat-value">{stats.totalServices}</div>
|
||||
<div class="stat-sub">across all sandboxes</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Total Requests</div>
|
||||
<div class="stat-value" id="req-fmt">—</div>
|
||||
<div class="stat-sub">cumulative</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map-wrap"><svg id="world-map"></svg></div>
|
||||
|
||||
<div class="layout">
|
||||
<div class="panel">
|
||||
<div class="panel-title">Tier distribution</div>
|
||||
<div class="donut-wrap">
|
||||
<svg id="donut" viewBox="0 0 120 120"></svg>
|
||||
<ul class="tier-list" id="tier-legend"></ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-title">Quick links</div>
|
||||
<p style="font-size:0.8rem;color:#8b949e;line-height:1.8">
|
||||
<a href="/sandboxes">All sandboxes →</a><br>
|
||||
<a href="/sandboxes?tier=COMMUNITY">COMMUNITY tier →</a><br>
|
||||
<a href="/sandboxes?tier=FREE">FREE tier →</a><br>
|
||||
<a href="/sandboxes?tier=FOUNDER">FOUNDER tier →</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>APIX Admin · internal</span>
|
||||
<span id="refresh-ts"></span>
|
||||
</div>
|
||||
|
||||
<script>var __D = {statsJson.raw};</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/topojson-client@3/dist/topojson-client.min.js"></script>
|
||||
<script src="/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user