155 lines
6.4 KiB
HTML
155 lines
6.4 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Fission Admin Console v0.1.0</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body { font-family: system-ui, sans-serif; background: #0f1117; color: #e0e0e0; }
|
|
header { background: #1a1d27; padding: 16px 24px; border-bottom: 1px solid #2a2d3a;
|
|
display: flex; align-items: center; gap: 12px; }
|
|
header h1 { font-size: 18px; font-weight: 600; color: #fff; }
|
|
header .badge { background: #e53e3e; color: #fff; font-size: 10px; font-weight: 700;
|
|
padding: 2px 6px; border-radius: 4px; letter-spacing: .5px; }
|
|
#login { display: flex; align-items: center; justify-content: center; height: calc(100vh - 57px); }
|
|
#login form { background: #1a1d27; border: 1px solid #2a2d3a; border-radius: 8px;
|
|
padding: 32px; display: flex; flex-direction: column; gap: 12px; width: 320px; }
|
|
#login h2 { font-size: 16px; color: #fff; margin-bottom: 4px; }
|
|
input { background: #0f1117; border: 1px solid #2a2d3a; border-radius: 6px;
|
|
color: #e0e0e0; padding: 10px 12px; font-size: 14px; outline: none; }
|
|
input:focus { border-color: #4a90d9; }
|
|
button { background: #2563eb; color: #fff; border: none; border-radius: 6px;
|
|
padding: 10px; font-size: 14px; cursor: pointer; font-weight: 600; }
|
|
button:hover { background: #1d4ed8; }
|
|
#app { display: none; }
|
|
nav { background: #1a1d27; border-bottom: 1px solid #2a2d3a;
|
|
display: flex; gap: 4px; padding: 0 24px; }
|
|
nav button { background: none; border: none; color: #9ca3af; font-size: 14px;
|
|
padding: 12px 16px; cursor: pointer; border-bottom: 2px solid transparent; }
|
|
nav button.active { color: #fff; border-bottom-color: #2563eb; }
|
|
main { padding: 24px; }
|
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
th { color: #6b7280; font-weight: 500; text-align: left; padding: 8px 12px;
|
|
border-bottom: 1px solid #2a2d3a; }
|
|
td { padding: 10px 12px; border-bottom: 1px solid #1a1d27; }
|
|
tr:hover td { background: #1a1d27; }
|
|
.pill { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; }
|
|
.pill-blue { background: #1e3a5f; color: #60a5fa; }
|
|
.err { color: #f87171; font-size: 13px; padding: 16px 0; }
|
|
.loader { color: #6b7280; font-size: 13px; padding: 16px 0; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>Fission Admin Console</h1>
|
|
<span class="badge">ADMIN</span>
|
|
</header>
|
|
|
|
<div id="login">
|
|
<form id="loginForm">
|
|
<h2>Вход в Admin Console</h2>
|
|
<input type="password" id="tokenInput" placeholder="Admin token" autocomplete="current-password">
|
|
<button type="submit">Войти</button>
|
|
<div id="loginErr" class="err" style="display:none"></div>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="app">
|
|
<nav>
|
|
<button class="active" onclick="showTab('namespaces', this)">Клиенты</button>
|
|
<button onclick="showTab('usage', this)">Использование</button>
|
|
<button onclick="showTab('users', this)">Пользователи</button>
|
|
</nav>
|
|
<main id="content"></main>
|
|
</div>
|
|
|
|
<script>
|
|
const API = '/admin/api';
|
|
let TOKEN = '';
|
|
|
|
document.getElementById('loginForm').addEventListener('submit', async e => {
|
|
e.preventDefault();
|
|
TOKEN = document.getElementById('tokenInput').value.trim();
|
|
const r = await fetch(`${API}/namespaces`, { headers: { Authorization: `Bearer ${TOKEN}` } });
|
|
if (r.ok) {
|
|
document.getElementById('login').style.display = 'none';
|
|
document.getElementById('app').style.display = 'block';
|
|
showTab('namespaces', document.querySelector('nav button'));
|
|
} else {
|
|
const err = document.getElementById('loginErr');
|
|
err.style.display = 'block';
|
|
err.textContent = 'Неверный токен';
|
|
}
|
|
});
|
|
|
|
function showTab(tab, btn) {
|
|
document.querySelectorAll('nav button').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
loadTab(tab);
|
|
}
|
|
|
|
async function loadTab(tab) {
|
|
const content = document.getElementById('content');
|
|
content.innerHTML = '<p class="loader">Загрузка…</p>';
|
|
try {
|
|
const r = await fetch(`${API}/${tab}`, { headers: { Authorization: `Bearer ${TOKEN}` } });
|
|
const data = await r.json();
|
|
if (!r.ok) { content.innerHTML = `<p class="err">${data.error}</p>`; return; }
|
|
content.innerHTML = renderTab(tab, data);
|
|
} catch(e) {
|
|
content.innerHTML = `<p class="err">Ошибка: ${e.message}</p>`;
|
|
}
|
|
}
|
|
|
|
function renderTab(tab, data) {
|
|
if (tab === 'namespaces') return renderNamespaces(data.namespaces);
|
|
if (tab === 'usage') return renderUsage(data.usage);
|
|
if (tab === 'users') return renderUsers(data.users);
|
|
return '';
|
|
}
|
|
|
|
function renderNamespaces(rows) {
|
|
if (!rows?.length) return '<p class="loader">Нет namespace-ов</p>';
|
|
return `<table>
|
|
<tr><th>Namespace</th><th>Email</th><th>Функции</th><th>Пакеты</th><th>Создан</th></tr>
|
|
${rows.map(n => `<tr>
|
|
<td><span class="pill pill-blue">${n.name}</span></td>
|
|
<td>${n.user_email || '—'}</td>
|
|
<td>${n.functions}</td>
|
|
<td>${n.packages}</td>
|
|
<td>${n.created_at ? n.created_at.slice(0,10) : '—'}</td>
|
|
</tr>`).join('')}
|
|
</table>`;
|
|
}
|
|
|
|
function renderUsage(rows) {
|
|
if (!rows?.length) return '<p class="loader">Нет данных</p>';
|
|
return `<table>
|
|
<tr><th>Namespace</th><th>Email</th><th>Функции</th><th>Вызовы</th><th>Хранилище GB</th><th>Период</th></tr>
|
|
${rows.map(u => `<tr>
|
|
<td><span class="pill pill-blue">${u.namespace}</span></td>
|
|
<td>${u.user_email || '—'}</td>
|
|
<td>${u.functions}</td>
|
|
<td>${u.invocations_total}</td>
|
|
<td>${u.storage_gb.toFixed(2)}</td>
|
|
<td>${u.period_start} — ${u.period_end}</td>
|
|
</tr>`).join('')}
|
|
</table>`;
|
|
}
|
|
|
|
function renderUsers(rows) {
|
|
if (!rows?.length) return '<p class="loader">Нет пользователей</p>';
|
|
return `<table>
|
|
<tr><th>Email</th><th>Namespace</th><th>Создан</th></tr>
|
|
${rows.map(u => `<tr>
|
|
<td>${u.email || '—'}</td>
|
|
<td><span class="pill pill-blue">${u.namespace}</span></td>
|
|
<td>${u.created_at ? u.created_at.slice(0,10) : '—'}</td>
|
|
</tr>`).join('')}
|
|
</table>`;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|