Files
ipwhitelist-app/views/ui-login.ejs
T
naeel c85a438ba2 feat: UI-слой через /api/v1/* (ui/, views/ui-login.ejs, views/error.ejs)
- ui/api-client.js: HTTP-клиент к /api/v1/* (token из session.token)
- ui/routes/auth.js: /login (mock-пресеты), /login-token (Bearer paste), /logout
- ui/routes/entries.js: /, /add, /edit/:id, /delete/:id через API
- ui/routes/admin.js: /admin, /admin/limit/:id, /audit через API
- ui/routes/export.js: /export → проксирует /api/v1/entries/export
- ui/index.js: createUiRouter() — requireToken + resolveUser middleware
- views/ui-login.ejs: страница входа (mock + token)
- views/error.ejs: шаблон для 403/404/500

API расширения:
- GET /api/v1/entries теперь возвращает limit и used
- GET /api/v1/entries?company=<id> (admin): записи другой компании
- POST/PATCH/DELETE /api/v1/entries?company=<id> (admin): мутации для другой компании
- GET /api/v1/entries/export: агрегированный CIDR (перенесён из монолита)
- GET /api/v1/audit?company=<id>: журнал (перенесён из монолита)

server.js: заменены src/routes/* на ui/createUiRouter(), убран CSRF
2026-05-30 19:02:51 +03:00

104 lines
4.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Вход — IP WhiteList</title>
<link rel="icon" href="/favicon.png" type="image/png">
<style>
:root {
--bg: #f5f5f5; --card: #ffffff; --text: #1a1a1a; --muted: #6b7280;
--border: #d1d5db; --blue: #2563eb; --blue-h: #1d4ed8;
--amber: #d97706; --amber-bg: #fef3c7; --amber-border: #fde68a;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg); color: var(--text); font-size: 14px; line-height: 1.5;
display: flex; align-items: center; justify-content: center; min-height: 100vh;
}
.card {
background: var(--card); border: 1px solid var(--border); border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,.06); width: 440px; padding: 2rem;
}
h1 { font-size: 1.2rem; margin-bottom: .25rem; }
.sub { color: var(--muted); font-size: .85rem; margin-bottom: 1.25rem; }
.warn {
padding: .75rem 1rem; background: var(--amber-bg); border: 1px solid var(--amber-border);
border-radius: 8px; font-size: .82rem; color: var(--amber); margin-bottom: 1.5rem;
}
.warn strong { display: block; margin-bottom: .25rem; font-size: .9rem; }
hr { border: none; border-top: 1px solid var(--border); margin: 1.25rem 0; }
.section-title {
font-size: .75rem; font-weight: 600; color: var(--muted);
text-transform: uppercase; letter-spacing: .5px; margin-bottom: .75rem;
}
.field { display: flex; flex-direction: column; gap: .25rem; margin-bottom: .85rem; }
.field label { font-size: .8rem; font-weight: 500; color: var(--muted); text-transform: uppercase; }
.field select, .field input[type=text], .field textarea {
padding: .5rem .75rem; border: 1px solid var(--border); border-radius: 6px;
font-size: .9rem; outline: none; background: #fff; width: 100%;
}
.field select:focus, .field input:focus, .field textarea:focus {
border-color: var(--blue); box-shadow: 0 0 0 3px rgba(37,99,235,.1);
}
.field textarea { resize: vertical; min-height: 60px; font-family: monospace; font-size: .82rem; }
.btn {
width: 100%; padding: .6rem; border: none; border-radius: 6px; font-size: .9rem;
font-weight: 500; cursor: pointer; background: var(--blue); color: #fff;
transition: background .15s;
}
.btn:hover { background: var(--blue-h); }
.error { color: #dc2626; font-size: .85rem; margin-bottom: 1rem; }
</style>
</head>
<body>
<div class="card">
<h1>IP WhiteList</h1>
<p class="sub">Вход в систему</p>
<% if (error) { %><div class="error"><%= error %></div><% } %>
<% if (devMode && MOCK_USERS && MOCK_USERS.length > 0) { %>
<%# ── Блок mock-пользователей (только DEV_MODE=true) ── %>
<div class="warn">
<strong>⚠ ТЕСТОВЫЙ РЕЖИМ</strong>
Вход без проверки пароля. Только для разработки.
</div>
<p class="section-title">Быстрый вход (mock)</p>
<form method="POST" action="/login">
<input type="hidden" name="returnTo" value="<%= returnTo || '/' %>">
<div class="field">
<label>Пользователь</label>
<select name="clientId" required>
<option value="">— выберите —</option>
<% MOCK_USERS.forEach(u => { %>
<option value="<%= u.clientId %>"><%= u.clientId %> — <%= u.companyName || u.clientId %> (<%= u.isAdmin ? 'admin' : 'user' %>)</option>
<% }) %>
</select>
</div>
<button class="btn" type="submit">Войти как mock-пользователь</button>
</form>
<hr>
<% } %>
<%# ── Блок вставки Bearer токена (всегда виден) ── %>
<p class="section-title">Войти с Bearer токеном</p>
<form method="POST" action="/login-token">
<input type="hidden" name="returnTo" value="<%= returnTo || '/' %>">
<div class="field">
<label>Bearer токен</label>
<textarea name="token" placeholder="eyJ..." rows="3" autocomplete="off"></textarea>
</div>
<button class="btn" type="submit">Войти по токену</button>
</form>
</div>
<footer style="position:fixed;bottom:.75rem;right:1.5rem;font-size:.72rem;color:var(--muted);">
v<%= appVersion %>
</footer>
</body>
</html>