266 lines
13 KiB
HTML
266 lines
13 KiB
HTML
<!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="Логин"><br>
|
||
<input type="password" id="password" placeholder="Пароль"><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";
|
||
|
||
async function hashPassword(password) {
|
||
const msgBuffer = new TextEncoder().encode(password);
|
||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
||
return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
|
||
}
|
||
|
||
function getHeaders() {
|
||
return { 'Content-Type': 'application/json', 'X-Token': localStorage.getItem('token') };
|
||
}
|
||
|
||
// Функция сворачивания/разворачивания блоков
|
||
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;
|
||
|
||
const hashedPassword = await hashPassword(pass);
|
||
|
||
try {
|
||
const response = await fetch(`${API_URL}/${action}`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ username: user, password: hashedPassword })
|
||
});
|
||
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 = "Рега успешна. Жми Войти.";
|
||
}
|
||
} 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');
|
||
}
|
||
|
||
async function changePassword() {
|
||
const newPwd = document.getElementById('new-password-input').value;
|
||
const msgEl = document.getElementById('change-pwd-msg');
|
||
if(!newPwd) return;
|
||
|
||
const hashedNewPwd = await hashPassword(newPwd);
|
||
const res = await fetch(`${API_URL}/change-password`, {
|
||
method: 'POST',
|
||
headers: getHeaders(),
|
||
body: JSON.stringify({ new_password: hashedNewPwd })
|
||
});
|
||
const data = await res.json();
|
||
if(res.ok) {
|
||
msgEl.innerText = "Пароль изменен!";
|
||
msgEl.style.color = "#00ff00";
|
||
switchTab('profile');
|
||
} else {
|
||
msgEl.innerText = "Ошибка";
|
||
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(event) 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>
|