coproapi/index.html

274 lines
13 KiB
HTML
Raw Permalink 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>Universe of Bad Code - Секретный Бункер</title>
<style>
body { font-family: monospace; background: #1a1a1a; color: #00ff00; padding: 20px; }
input, textarea { background: #333; color: #fff; border: 1px solid #00ff00; padding: 5px; margin: 5px 0; width: 300px; }
button { background: #00ff00; color: #000; border: none; padding: 5px 10px; cursor: pointer; margin-right: 5px; font-weight: bold; }
.tab-btn { background: #444; color: #fff; }
.tab-btn.active { background: #00ff00; color: #000; }
.hidden { display: none; }
.tab-content { border: 1px solid #00ff00; padding: 20px; margin-top: 10px; background: #222; }
.item-box { border: 1px solid #555; padding: 10px; margin-bottom: 10px; background: #2b2b2b; }
.item-header { display: flex; justify-content: space-between; align-items: center; cursor: pointer; font-weight: bold; background: #333; padding: 5px; }
.item-body { padding-top: 10px; border-top: 1px dashed #555; margin-top: 5px; }
.toggle-icon { color: #00ff00; }
</style>
</head>
<body>
<div id="auth-block">
<h2>Вход в секретный бункер</h2>
<input type="text" id="username" placeholder="Логин" onkeydown="handleKeyPress(event)"><br>
<input type="password" id="password" placeholder="Пароль" onkeydown="handleKeyPress(event)"><br>
<button onclick="auth('login')">Войти</button>
<button onclick="auth('register')">Рега</button>
<p id="auth-response"></p>
</div>
<div id="main-block" class="hidden">
<h2>Система управления бункером. Привет, <span id="user-display"></span>! <button onclick="logout()" style="background:#ff0000; color:#fff;">Выйти</button></h2>
<div id="tabs-header">
<button class="tab-btn active" onclick="switchTab('profile')">Профиль</button>
<button class="tab-btn" onclick="switchTab('objects')">Объекты</button>
<button class="tab-btn hidden" id="admin-users-btn" onclick="switchTab('admin-users')">Все Профили (ROOT)</button>
<button class="tab-btn hidden" id="admin-objects-btn" onclick="switchTab('admin-objects')">Все Объекты (ROOT)</button>
</div>
<div id="tab-profile" class="tab-content">
<h3>Твой Профиль</h3>
<div class="item-box">
<div class="item-header" onclick="toggleCollapse('prof-body', this)">
<span>Пользователь: <span id="prof-username"></span></span>
<span class="toggle-icon">[Развернуть]</span>
</div>
<div id="prof-body" class="item-body hidden">
<p><strong>Argon2 Хэш пароля в БД:</strong></p>
<textarea id="prof-hash" rows="2" readonly style="width:100%; color:#ff00ff;"></textarea>
<p><strong>Сменить пароль:</strong></p>
<input type="password" id="new-password-input" placeholder="Новый пароль"><br>
<button onclick="changePassword()">Сохранить новый пароль</button>
<span id="change-pwd-msg"></span>
</div>
</div>
</div>
<div id="tab-objects" class="tab-content hidden">
<h3>Мои Созданные Объекты</h3>
<input type="text" id="new-obj-title" placeholder="Название объекта"><br>
<textarea id="new-obj-text" placeholder="Введите текст объекта..." rows="3"></textarea><br>
<button onclick="createObject()">Создать объект</button>
<div id="objects-list" style="margin-top:20px;"></div>
</div>
<div id="tab-admin-users" class="tab-content hidden">
<h3>Админка БД: Текстовый дамп database.json</h3>
<div id="admin-users-list"></div>
</div>
<div id="tab-admin-objects" class="tab-content hidden">
<h3>Админка БД: Все объекты из objects.json</h3>
<div id="admin-objects-list"></div>
</div>
</div>
<script>
const API_URL = "https://universeofbadcode.online/api";
function getHeaders() {
return { 'Content-Type': 'application/json', 'X-Token': localStorage.getItem('token') };
}
// Слушаем нажатие клавиш в форме авторизации
function handleKeyPress(event) {
if (event.key === 'Enter') {
auth('login');
}
}
function toggleCollapse(id, headerEl) {
const body = document.getElementById(id);
const icon = headerEl.querySelector('.toggle-icon');
if (body.classList.contains('hidden')) {
body.classList.remove('hidden');
icon.innerText = "[Свернуть]";
} else {
body.classList.add('hidden');
icon.innerText = "[Развернуть]";
}
}
async function auth(action) {
const user = document.getElementById('username').value;
const pass = document.getElementById('password').value;
const resElement = document.getElementById('auth-response');
if(!user || !pass) return;
// Проверка на фронте для удобства UX
if (action === 'register' && pass.length < 6) {
resElement.innerText = "Ошибка: Пароль должен быть не менее 6 символов";
return;
}
try {
const response = await fetch(`${API_URL}/${action}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: user, password: pass }) // Пароль летит чистым
});
const data = await response.json();
if (response.ok) {
if(action === 'login') {
localStorage.setItem('username', data.username);
localStorage.setItem('token', data.token);
initSystem();
} else {
resElement.innerText = "Рега успешна. Жми Войти или Enter.";
}
} else { resElement.innerText = "Ошибка: " + data.detail; }
} catch (err) { resElement.innerText = "Бэк упал."; }
}
function initSystem() {
const username = localStorage.getItem('username');
if (!username) return;
document.getElementById('auth-block').classList.add('hidden');
document.getElementById('main-block').classList.remove('hidden');
document.getElementById('user-display').innerText = username;
if (username === 'root') {
document.getElementById('admin-users-btn').classList.remove('hidden');
document.getElementById('admin-objects-btn').classList.remove('hidden');
} else {
document.getElementById('admin-users-btn').classList.add('hidden');
document.getElementById('admin-objects-btn').classList.add('hidden');
}
switchTab('profile');
}
function logout() {
localStorage.clear();
document.getElementById('main-block').classList.add('hidden');
document.getElementById('auth-block').classList.remove('hidden');
document.getElementById('auth-response').innerText = '';
}
async function changePassword() {
const newPwd = document.getElementById('new-password-input').value;
const msgEl = document.getElementById('change-pwd-msg');
if(!newPwd) return;
if (newPwd.length < 6) {
msgEl.innerText = "Минимум 6 символов!";
msgEl.style.color = "#ff0000";
return;
}
const res = await fetch(`${API_URL}/change-password`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ new_password: newPwd })
});
const data = await res.json();
if(res.ok) {
msgEl.innerText = "Пароль изменен!";
msgEl.style.color = "#00ff00";
switchTab('profile');
} else {
msgEl.innerText = "Ошибка: " + data.detail;
msgEl.style.color = "#ff0000";
}
}
async function switchTab(tabName) {
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active'));
document.getElementById(`tab-${tabName}`).classList.remove('hidden');
if(window.event && window.event.target && window.event.target.classList.contains('tab-btn')) {
window.event.target.classList.add('active');
}
if (tabName === 'profile') {
const res = await fetch(`${API_URL}/profile`, { headers: getHeaders() });
const data = await res.json();
document.getElementById('prof-username').innerText = data.username;
document.getElementById('prof-hash').value = data.hash;
document.getElementById('prof-body').classList.add('hidden');
document.querySelector('#tab-profile .toggle-icon').innerText = "[Развернуть]";
}
if (tabName === 'objects') { loadUserObjects(); }
if (tabName === 'admin-users') {
const res = await fetch(`${API_URL}/admin/users`, { headers: getHeaders() });
const users = await res.json();
document.getElementById('admin-users-list').innerHTML = users.map((u, index) => `
<div class="item-box">
<div class="item-header" onclick="toggleCollapse('admin-u-${index}', this)">
<span>Пользователь: ${u.username}</span>
<span class="toggle-icon">[Развернуть]</span>
</div>
<div id="admin-u-${index}" class="item-body hidden">
<b>Хэш Argon2:</b> <span style="color:#ff00ff;">${u.password}</span>
</div>
</div>
`).join('');
}
if (tabName === 'admin-objects') {
const res = await fetch(`${API_URL}/admin/objects`, { headers: getHeaders() });
const objs = await res.json();
document.getElementById('admin-objects-list').innerHTML = objs.map((o, index) => `
<div class="item-box">
<div class="item-header" onclick="toggleCollapse('admin-o-${index}', this)">
<span>Название: ${o.title} (Автор: ${o.username})</span>
<span class="toggle-icon">[Развернуть]</span>
</div>
<div id="admin-o-${index}" class="item-body hidden">
<b>Текст:</b><br>${o.text}
</div>
</div>
`).join('');
}
}
async function loadUserObjects() {
const res = await fetch(`${API_URL}/objects`, { headers: getHeaders() });
const objs = await res.json();
document.getElementById('objects-list').innerHTML = objs.map((o, index) => `
<div class="item-box">
<div class="item-header" onclick="toggleCollapse('user-o-${index}', this)">
<span>Название: ${o.title}</span>
<span class="toggle-icon">[Развернуть]</span>
</div>
<div id="user-o-${index}" class="item-body hidden">
<b>Текст:</b><br>${o.text}
</div>
</div>
`).join('');
}
async function createObject() {
const title = document.getElementById('new-obj-title').value;
const text = document.getElementById('new-obj-text').value;
if(!title || !text) return;
await fetch(`${API_URL}/objects`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ title: title, text: text })
});
document.getElementById('new-obj-title').value = '';
document.getElementById('new-obj-text').value = '';
loadUserObjects();
}
if(localStorage.getItem('username')) { initSystem(); }
</script>
</body>
</html>