Files
apix-mvp/apix-admin/src/main/resources/templates/SandboxListResource/list.html
T
Carsten Rehfeld ad19c8f20f
Deploy to Production / deploy (push) Successful in 3m14s
fix(admin): relax Qute type-safety for arithmetic in list.html, fix JS template literals
requireTypeSafeExpressions=false allows {page+1} and (page+1)*size
in pagination conditions. JS template literals with ${days} replaced
with string concatenation to avoid Qute consuming the {days} token.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 21:30:29 +02:00

125 lines
4.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sandboxes · APIX Admin</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: 13px; }
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; }
.toolbar {
padding: 0.75rem 1.5rem; border-bottom: 1px solid #21262d;
display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap;
}
.toolbar-label { font-size: 0.7rem; color: #484f58; }
.filter-link {
font-size: 0.75rem; color: #8b949e;
padding: 0.2rem 0.6rem; border: 1px solid #30363d; border-radius: 12px;
}
.filter-link:hover, .filter-link.active { color: #e6edf3; border-color: #58a6ff; }
.count { margin-left: auto; font-size: 0.75rem; color: #484f58; }
table { width: 100%; border-collapse: collapse; }
th {
text-align: left; padding: 0.6rem 1.5rem;
font-size: 0.65rem; color: #484f58; text-transform: uppercase; letter-spacing: 0.06em;
border-bottom: 1px solid #21262d; background: #161b22;
}
td { padding: 0.55rem 1.5rem; border-bottom: 1px solid #161b22; vertical-align: middle; }
tr:hover td { background: #161b22; }
.tier-badge {
background: #0d1117; border: 1px solid #30363d;
color: #8b949e; padding: 0.1rem 0.4rem; border-radius: 10px;
font-size: 0.65rem; letter-spacing: 0.04em;
}
.tier-COMMUNITY { border-color: #1f6feb; color: #58a6ff; }
.tier-FOUNDER { border-color: #9e6a03; color: #e3b341; }
.tier-DEMO { border-color: #388bfd; color: #79c0ff; }
.expired { color: #f85149; }
.ok { color: #3fb950; }
.pagination { padding: 1rem 1.5rem; display: flex; gap: 1rem; align-items: center; border-top: 1px solid #21262d; }
.page-link { font-size: 0.8rem; color: #58a6ff; }
.page-info { font-size: 0.75rem; color: #484f58; }
</style>
</head>
<body>
<nav>
<div class="nav-logo">APIX · <span>Admin</span></div>
<a class="nav-link" href="/">Dashboard</a>
<a class="nav-link active" href="/sandboxes">Sandboxes</a>
<div class="nav-right"><a class="nav-link" href="/login/logout">Sign out</a></div>
</nav>
<div class="toolbar">
<span class="toolbar-label">Filter:</span>
<a class="filter-link {#if tier == ''}active{/if}" href="/sandboxes">All</a>
<a class="filter-link {#if tier == 'FREE'}active{/if}" href="/sandboxes?tier=FREE">FREE</a>
<a class="filter-link {#if tier == 'COMMUNITY'}active{/if}" href="/sandboxes?tier=COMMUNITY">COMMUNITY</a>
<a class="filter-link {#if tier == 'FOUNDER'}active{/if}" href="/sandboxes?tier=FOUNDER">FOUNDER</a>
<a class="filter-link {#if tier == 'DEMO'}active{/if}" href="/sandboxes?tier=DEMO">DEMO</a>
<span class="count">{result.total} total</span>
</div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Tier</th>
<th>Services</th>
<th>Requests</th>
<th>Location</th>
<th>Expires</th>
</tr>
</thead>
<tbody>
{#for sb in result.sandboxes}
<tr>
<td><a href="/sandbox/{sb.sandboxId}">{sb.name}</a>
<div style="font-size:0.65rem;color:#484f58;margin-top:2px">{sb.sandboxId}</div>
</td>
<td><span class="tier-badge tier-{sb.tier}">{sb.tier}</span></td>
<td>{sb.serviceCount}</td>
<td>{sb.totalRequests}</td>
<td>{#if sb.location != null}{sb.location}{#else}<span style="color:#484f58"></span>{/if}</td>
<td class="{#if sb.expired}expired{#else}ok{/if}" data-expires="{sb.expiresAt}">{sb.expiresAt}</td>
</tr>
{/for}
</tbody>
</table>
<div class="pagination">
{#if page > 0}
<a class="page-link" href="/sandboxes?page={page - 1}&size={size}&tier={tier}">← Previous</a>
{/if}
<span class="page-info">Page {page + 1} · {result.sandboxes.size} of {result.total}</span>
{#if (page + 1) * size < result.total}
<a class="page-link" href="/sandboxes?page={page + 1}&size={size}&tier={tier}">Next →</a>
{/if}
</div>
<script>
document.querySelectorAll('[data-expires]').forEach(td => {
const d = new Date(td.dataset.expires);
const now = new Date();
const days = Math.round((d - now) / 86400000);
td.textContent = days < 0 ? ('expired ' + (-days) + 'd ago') : ('in ' + days + 'd');
});
</script>
</body>
</html>