restructure: console→client-console, add admin-console skeleton, move docs to doc/
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user