Загрузить файлы в «static»

This commit is contained in:
2026-07-02 14:25:38 +00:00
parent b2d10ec669
commit f5f2f9e3f9
5 changed files with 1759 additions and 0 deletions

32
static/index.html Normal file
View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Robo Arena — РСУ</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="auth-wrap">
<div class="auth-panel">
<div class="robot-icon">🤖</div>
<div class="sys-label">Robo Arena / РСУ</div>
<h1>Удалённая система управления боевыми роботами</h1>
<p>Прототип личного кабинета для мониторинга арены, дистанционного управления роботами и ведения боевого журнала в реальном времени.</p>
<div class="arena-stats">
<div class="arena-stat">⚔ Управление роботами <span>ONLINE</span></div>
<div class="arena-stat">📡 Телеметрия арены <span>LIVE</span></div>
<div class="arena-stat">🛡 Защищённый доступ <span>TLS</span></div>
</div>
</div>
<div class="auth-form-area">
<div class="auth-box">
<h2>Добро пожаловать, пилот</h2>
<p class="sub">Войдите или зарегистрируйтесь, чтобы управлять своими роботами на арене</p>
<a class="btn btn-primary" href="/static/login.html">Войти в систему</a>
<a class="btn btn-ghost" style="margin-top:12px;width:100%;display:flex;align-items:center;justify-content:center;" href="/static/register.html">Стать пилотом</a>
</div>
</div>
</div>
</body>
</html>

79
static/login.html Normal file
View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Вход — Robo Arena</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="auth-wrap">
<div class="auth-panel">
<div class="robot-icon"></div>
<div class="sys-label">Robo Arena / РСУ</div>
<h1>Вход пилота</h1>
<p>Управляйте боевыми роботами, следите за телеметрией и ведите журнал сражений из любой точки мира.</p>
<div class="arena-stats">
<div class="arena-stat">🤖 Роботы в арене <span>READY</span></div>
<div class="arena-stat">📊 Журнал боёв <span>LOG</span></div>
</div>
</div>
<div class="auth-form-area">
<div class="auth-box">
<h2>Авторизация</h2>
<p class="sub">Введите данные своего аккаунта пилота</p>
<div class="field">
<label for="email">Электронная почта</label>
<input type="email" id="email" placeholder="pilot@arena.ru" autocomplete="email">
</div>
<div class="field">
<label for="password">Пароль</label>
<input type="password" id="password" placeholder="••••••••" autocomplete="current-password">
</div>
<button class="btn btn-primary" id="submit-btn">Войти на арену</button>
<p class="err-msg" id="error-text"></p>
<div class="link-row">Нет аккаунта? <a href="/static/register.html">Стать пилотом</a></div>
</div>
</div>
</div>
<script>
const errEl = document.getElementById("error-text");
const submitBtn = document.getElementById("submit-btn");
submitBtn.addEventListener("click", async () => {
errEl.textContent = "";
const email = document.getElementById("email").value.trim();
const password = document.getElementById("password").value;
if (!email || !password) { errEl.textContent = "Заполните все поля"; return; }
submitBtn.disabled = true;
submitBtn.textContent = "Проверяем…";
try {
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ email, password })
});
const data = await res.json();
if (!res.ok) { errEl.textContent = data.detail || "Неверная почта или пароль"; return; }
window.location.href = "/static/dashboard.html";
} catch {
errEl.textContent = "Не удалось связаться с сервером";
} finally {
submitBtn.disabled = false;
submitBtn.textContent = "Войти на арену";
}
});
document.addEventListener("keydown", e => { if (e.key === "Enter") submitBtn.click(); });
</script>
</body>
</html>

234
static/profile.html Normal file
View File

@@ -0,0 +1,234 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Robo Arena — Профиль пилота</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body class="page-app page-profile">
<nav class="topbar">
<div class="topbar-brand">
<span class="logo-icon">🤖</span>
<span>Robo Arena</span> / РСУ
</div>
<span class="topbar-user" id="topbar-email"></span>
<a class="btn btn-sm active-nav" href="/static/profile.html">Профиль</a>
<a class="btn btn-sm" href="/static/dashboard.html">Арена</a>
<button class="btn btn-sm" id="logout-btn">Выйти</button>
</nav>
<div class="main main--profile">
<div class="profile-layout">
<!-- Боковая панель -->
<aside class="profile-sidebar">
<div class="profile-sidebar-glow"></div>
<div class="profile-avatar" id="profile-avatar">👤</div>
<h2 class="profile-sidebar-name" id="sidebar-name">Пилот</h2>
<p class="profile-sidebar-email" id="sidebar-email"></p>
<div class="profile-status" id="profile-status">
<div class="profile-status-label">Заполненность профиля</div>
<div class="profile-progress-wrap">
<div class="profile-progress-bar" id="progress-bar" style="width:0%"></div>
</div>
<div class="profile-status-text" id="status-text">0 из 4 полей</div>
</div>
<div class="profile-sidebar-hints">
<div class="hint-item">
<span class="hint-icon">🛡</span>
<span>Данные нужны для идентификации пилота на арене</span>
</div>
<div class="hint-item">
<span class="hint-icon">🔒</span>
<span>Информация хранится только в вашем аккаунте</span>
</div>
</div>
</aside>
<!-- Форма -->
<div class="profile-form-card">
<div class="profile-form-header">
<div class="profile-badge">⚔ Профиль пилота</div>
<h2>Данные оператора</h2>
<p class="sub">Заполните личную информацию для участия в системе управления боевыми роботами</p>
</div>
<form id="profile-form" autocomplete="on">
<div class="form-section">
<div class="form-section-title">
<span class="form-section-icon">📋</span>
Личные данные
</div>
<div class="form-field">
<label for="full_name">ФИО</label>
<div class="input-wrap">
<span class="input-icon">👤</span>
<input type="text" id="full_name" placeholder="Иванов Иван Иванович" autocomplete="name">
</div>
</div>
<div class="form-row">
<div class="form-field">
<label for="birth_date">Дата рождения</label>
<div class="input-wrap">
<span class="input-icon">🎂</span>
<input type="date" id="birth_date">
</div>
</div>
<div class="form-field">
<label for="gender">Пол</label>
<div class="input-wrap">
<span class="input-icon"></span>
<select id="gender">
<option value="">— выберите —</option>
<option value="male">Мужской</option>
<option value="female">Женский</option>
<option value="other">Не указан</option>
</select>
</div>
</div>
</div>
</div>
<div class="form-section">
<div class="form-section-title">
<span class="form-section-icon">📞</span>
Контакты
</div>
<div class="form-field">
<label for="phone">Телефон</label>
<div class="input-wrap">
<span class="input-icon">📱</span>
<input type="tel" id="phone" placeholder="+7 (999) 123-45-67" autocomplete="tel">
</div>
<span class="field-hint">Формат: +7 или 8, минимум 7 цифр</span>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="save-btn">
<span>💾 Сохранить профиль</span>
</button>
<a class="btn btn-ghost btn-back" href="/static/dashboard.html">На арену</a>
</div>
<p class="err-msg" id="error-text"></p>
<p class="ok-msg" id="ok-text"></p>
</form>
</div>
</div>
</div>
<script>
const errEl = document.getElementById("error-text");
const okEl = document.getElementById("ok-text");
const saveBtn = document.getElementById("save-btn");
function updateSidebar(u) {
document.getElementById("topbar-email").textContent = u.email;
document.getElementById("sidebar-email").textContent = u.email;
if (u.full_name) {
document.getElementById("sidebar-name").textContent = u.full_name;
document.getElementById("profile-avatar").textContent = u.full_name.trim()[0].toUpperCase();
}
const fields = [u.full_name, u.birth_date, u.phone, u.gender];
const filled = fields.filter(Boolean).length;
const pct = Math.round((filled / 4) * 100);
document.getElementById("progress-bar").style.width = pct + "%";
document.getElementById("status-text").textContent = `${filled} из 4 полей`;
const statusEl = document.getElementById("profile-status");
if (filled === 4) {
statusEl.classList.add("profile-status--complete");
document.getElementById("status-text").textContent = "Профиль заполнен ✓";
} else {
statusEl.classList.remove("profile-status--complete");
}
}
async function loadMe() {
const res = await fetch("/api/me", { credentials: "include" });
if (!res.ok) { window.location.href = "/static/login.html"; return; }
const u = await res.json();
document.getElementById("full_name").value = u.full_name || "";
document.getElementById("birth_date").value = u.birth_date || "";
document.getElementById("phone").value = u.phone || "";
document.getElementById("gender").value = u.gender || "";
updateSidebar(u);
}
["full_name", "birth_date", "phone", "gender"].forEach(id => {
document.getElementById(id).addEventListener("input", () => {
updateSidebar({
full_name: document.getElementById("full_name").value.trim(),
birth_date: document.getElementById("birth_date").value,
phone: document.getElementById("phone").value.trim(),
gender: document.getElementById("gender").value,
email: document.getElementById("sidebar-email").textContent,
});
});
document.getElementById(id).addEventListener("change", () => {
document.getElementById(id).dispatchEvent(new Event("input"));
});
});
document.getElementById("profile-form").addEventListener("submit", async e => {
e.preventDefault();
errEl.textContent = "";
okEl.textContent = "";
const payload = {
full_name: document.getElementById("full_name").value.trim(),
birth_date: document.getElementById("birth_date").value,
phone: document.getElementById("phone").value.trim(),
gender: document.getElementById("gender").value,
};
saveBtn.disabled = true;
saveBtn.querySelector("span").textContent = "Сохраняем…";
try {
const res = await fetch("/api/me/profile", {
method: "PUT",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(payload)
});
const data = await res.json();
if (!res.ok) {
errEl.textContent = data.detail || "Не удалось сохранить профиль";
return;
}
okEl.textContent = "✓ Профиль сохранён! Переход на арену…";
updateSidebar(data);
setTimeout(() => { window.location.href = "/static/dashboard.html"; }, 1400);
} catch {
errEl.textContent = "Не удалось связаться с сервером";
} finally {
saveBtn.disabled = false;
saveBtn.querySelector("span").textContent = "💾 Сохранить профиль";
}
});
document.getElementById("logout-btn").addEventListener("click", async () => {
await fetch("/api/logout", { method: "POST", credentials: "include" });
window.location.href = "/static/index.html";
});
loadMe();
</script>
</body>
</html>

79
static/register.html Normal file
View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Регистрация — Robo Arena</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="auth-wrap">
<div class="auth-panel">
<div class="robot-icon">🛡</div>
<div class="sys-label">Robo Arena / РСУ</div>
<h1>Регистрация пилота</h1>
<p>Один аккаунт — неограниченное количество боевых роботов под вашим управлением на арене.</p>
<div class="arena-stats">
<div class="arena-stat">⚡ Развёртывание роботов <span>INSTANT</span></div>
<div class="arena-stat">🔧 Управление мощностью <span>0100%</span></div>
</div>
</div>
<div class="auth-form-area">
<div class="auth-box">
<h2>Новый пилот</h2>
<p class="sub">Создайте аккаунт — после регистрации заполните профиль</p>
<div class="field">
<label for="email">Электронная почта</label>
<input type="email" id="email" placeholder="pilot@arena.ru" autocomplete="email">
</div>
<div class="field">
<label for="password">Пароль <span style="color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">(не менее 6 символов)</span></label>
<input type="password" id="password" placeholder="••••••••" autocomplete="new-password">
</div>
<button class="btn btn-primary" id="submit-btn">Зарегистрироваться</button>
<p class="err-msg" id="error-text"></p>
<div class="link-row">Уже есть аккаунт? <a href="/static/login.html">Войти</a></div>
</div>
</div>
</div>
<script>
const errEl = document.getElementById("error-text");
const submitBtn = document.getElementById("submit-btn");
submitBtn.addEventListener("click", async () => {
errEl.textContent = "";
const email = document.getElementById("email").value.trim();
const password = document.getElementById("password").value;
if (!email || !password) { errEl.textContent = "Заполните все поля"; return; }
submitBtn.disabled = true;
submitBtn.textContent = "Подождите…";
try {
const res = await fetch("/api/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ email, password })
});
const data = await res.json();
if (!res.ok) { errEl.textContent = data.detail || "Ошибка регистрации"; return; }
window.location.href = "/static/profile.html";
} catch {
errEl.textContent = "Не удалось связаться с сервером";
} finally {
submitBtn.disabled = false;
submitBtn.textContent = "Зарегистрироваться";
}
});
document.addEventListener("keydown", e => { if (e.key === "Enter") submitBtn.click(); });
</script>
</body>
</html>

1335
static/style.css Normal file

File diff suppressed because it is too large Load Diff