Files
yarmarka/templates/mobile_debug_simple.html
2026-03-16 18:57:22 +03:00

962 lines
34 KiB
HTML
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">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Mobile Debug - Rabota.Today</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
body {
background: #0b1c34;
padding: 15px;
color: #333;
}
.container {
max-width: 500px;
margin: 0 auto;
}
.card {
background: white;
border-radius: 24px;
padding: 20px;
margin-bottom: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
h1 {
font-size: 22px;
margin-bottom: 15px;
color: #0b1c34;
}
h2 {
font-size: 18px;
margin-bottom: 12px;
color: #1f3f60;
}
h3 {
font-size: 16px;
margin: 10px 0;
color: #1f3f60;
}
.info-row {
background: #f5f9ff;
padding: 12px;
border-radius: 12px;
margin-bottom: 8px;
font-size: 14px;
word-break: break-word;
}
.label {
font-weight: 600;
color: #1f3f60;
margin-bottom: 4px;
}
.value {
color: #333;
font-family: monospace;
font-size: 13px;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
}
.success { color: #10b981; font-weight: 600; }
.error { color: #ef4444; font-weight: 600; }
.warning { color: #f59e0b; font-weight: 600; }
.info { color: #3b82f6; font-weight: 600; }
button {
background: #0b1c34;
color: white;
border: none;
padding: 14px 16px;
border-radius: 40px;
font-weight: 600;
font-size: 15px;
width: 100%;
margin: 8px 0;
cursor: pointer;
transition: 0.2s;
-webkit-tap-highlight-color: transparent;
}
button:active {
transform: scale(0.98);
background: #1b3f6b;
}
button.secondary {
background: #e5e7eb;
color: #1f3f60;
}
button.secondary:active {
background: #d1d5db;
}
button.small {
padding: 8px 12px;
font-size: 13px;
width: auto;
margin: 5px;
}
.test-result {
margin-top: 15px;
padding: 15px;
background: #f5f5f5;
border-radius: 16px;
font-size: 13px;
max-height: 300px;
overflow-y: auto;
display: none;
white-space: pre-wrap;
word-wrap: break-word;
border: 1px solid #dee9f5;
}
.loader {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid #0b1c34;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.flex {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
}
.flex-start {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 10px;
flex-wrap: wrap;
}
.ip-input {
width: 100%;
padding: 12px 16px;
border: 2px solid #dee9f5;
border-radius: 30px;
font-size: 15px;
margin-bottom: 10px;
}
.protocol-selector {
display: flex;
gap: 10px;
margin: 15px 0;
background: #f0f7ff;
padding: 5px;
border-radius: 40px;
}
.protocol-btn {
flex: 1;
border: none;
background: transparent;
padding: 10px;
border-radius: 40px;
font-weight: 600;
font-size: 14px;
color: #385073;
cursor: pointer;
}
.protocol-btn.active {
background: white;
color: #0b1c34;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.share-link {
background: #e8f0fe;
padding: 15px;
border-radius: 16px;
margin-top: 15px;
word-break: break-all;
font-size: 13px;
border: 1px solid #3b82f6;
}
.share-link a {
color: #3b82f6;
text-decoration: none;
font-weight: 600;
}
.log-entry {
padding: 6px;
border-bottom: 1px solid #dee9f5;
font-size: 12px;
color: #4f7092;
}
.status-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
margin-left: 8px;
}
.status-badge.success {
background: #d1fae5;
color: #065f46;
}
.status-badge.error {
background: #fee2e2;
color: #b91c1c;
}
.status-badge.warning {
background: #fef3c7;
color: #92400e;
}
.json-view {
background: #1e293b;
color: #e2e8f0;
padding: 12px;
border-radius: 12px;
font-family: monospace;
font-size: 11px;
overflow-x: auto;
white-space: pre-wrap;
margin-top: 10px;
}
.protocol-info {
background: #dbeafe;
padding: 12px;
border-radius: 12px;
margin: 10px 0;
font-size: 13px;
border-left: 4px solid #3b82f6;
}
</style>
</head>
<body>
<div class="container">
<!-- Заголовок -->
<div class="card">
<h1>📱 Rabota.Today Mobile Debug</h1>
<div class="protocol-info" id="protocolInfo">
<strong>🔄 Определение протокола:</strong> Загрузка...
</div>
</div>
<!-- Информация об устройстве -->
<div class="card">
<h2>📱 Информация об устройстве</h2>
<div class="info-row">
<div class="label">🌐 Текущий протокол:</div>
<div class="value" id="currentProtocol"></div>
</div>
<div class="info-row">
<div class="label">🔗 Базовый URL API:</div>
<div class="value" id="baseApiUrl"></div>
</div>
<div class="info-row">
<div class="label">📱 Устройство:</div>
<div class="value" id="deviceInfo"></div>
</div>
<div class="info-row">
<div class="label">🌍 Текущий URL:</div>
<div class="value" id="currentUrl"></div>
</div>
<div class="info-row">
<div class="label">📶 Статус сети:</div>
<div class="value" id="networkStatus"></div>
</div>
</div>
<!-- Выбор протокола -->
<div class="card">
<h2>🔧 Настройки подключения</h2>
<div class="protocol-selector" id="protocolSelector">
<button class="protocol-btn active" data-protocol="auto" onclick="setProtocol('auto')">🔄 Авто</button>
<button class="protocol-btn" data-protocol="https" onclick="setProtocol('https')">🔒 HTTPS</button>
<button class="protocol-btn" data-protocol="http" onclick="setProtocol('http')">⚠️ HTTP</button>
</div>
<div class="flex-start" style="margin-top: 10px;">
<button class="secondary small" onclick="copyDebugInfo()">📋 Копировать информацию</button>
<button class="secondary small" onclick="saveIp()">💾 Сохранить настройки</button>
</div>
</div>
<!-- Основные тесты -->
<div class="card">
<h2>🔍 Диагностика API</h2>
<button onclick="runAllTests()" id="runAllBtn">▶️ Запустить все тесты</button>
<div class="flex" style="margin: 10px 0;">
<button class="secondary" onclick="testConnection()" style="flex: 1;">🔌 Тест соединения</button>
<button class="secondary" onclick="testCORS()" style="flex: 1;">🌐 Тест CORS</button>
</div>
<div class="flex" style="margin: 10px 0;">
<button class="secondary" onclick="testHealth()" style="flex: 1;">🏥 Health API</button>
<button class="secondary" onclick="testRegister()" style="flex: 1;">📝 Тест регистрации</button>
</div>
<div class="flex" style="margin: 10px 0;">
<button class="secondary" onclick="testLogin()" style="flex: 1;">🔑 Тест входа</button>
<button class="secondary" onclick="testAuth()" style="flex: 1;">👤 Тест авторизации</button>
</div>
<div id="mainResult" class="test-result"></div>
</div>
<!-- Результаты тестов -->
<div class="card" id="resultsCard" style="display: none;">
<h2>📊 Полные результаты</h2>
<div id="results" class="json-view"></div>
<div id="shareLink" class="share-link" style="display: none;">
<div>🔗 Результаты готовы:</div>
<textarea id="resultsText" style="width: 100%; height: 100px; margin: 10px 0; padding: 8px; border-radius: 8px; border: 1px solid #3b82f6;" readonly></textarea>
<button onclick="copyResults()" class="secondary">📋 Копировать результаты</button>
</div>
</div>
<!-- Лог ошибок -->
<div class="card">
<h2>📝 Лог событий</h2>
<div id="log" style="max-height: 200px; overflow-y: auto; font-size: 12px; background: #f5f5f5; padding: 10px; border-radius: 12px;">
<div class="log-entry">✅ Диагностика запущена</div>
</div>
<div class="flex" style="margin-top: 10px;">
<button class="secondary small" onclick="clearLog()">Очистить лог</button>
<button class="secondary small" onclick="downloadLog()">📥 Скачать лог</button>
</div>
</div>
<!-- Информация о помощи -->
<div class="card">
<h2>🆘 Помощь</h2>
<div class="info-row">
<div class="label">Если тесты не работают:</div>
<ul style="margin-left: 20px; margin-top: 5px;">
<li>Проверьте, что сервер запущен</li>
<li>Убедитесь, что вы в одной сети</li>
<li>Проверьте firewall (порт 8000)</li>
<li>Попробуйте HTTP вместо HTTPS</li>
</ul>
</div>
<div class="info-row">
<div class="label">Быстрые ссылки:</div>
<div class="flex-start">
<a href="/" class="secondary small" style="padding: 8px 12px;">🏠 Главная</a>
<a href="/register" class="secondary small" style="padding: 8px 12px;">📝 Регистрация</a>
<a href="/login" class="secondary small" style="padding: 8px 12px;">🔑 Вход</a>
</div>
</div>
</div>
</div>
<script>
// ==================== КОНФИГУРАЦИЯ ====================
// Определяем базовый URL динамически
const currentProtocol = window.location.protocol; // http: или https:
const currentHost = window.location.host; // yarmarka.rabota.today или IP:порт
let API_BASE_URL = `${currentProtocol}//${currentHost}/api`;
// Хранилище результатов
let testResults = {
device: {},
connection: [],
health: {},
cors: {},
register: {},
login: {},
auth: {},
timestamp: new Date().toISOString()
};
let currentProtocolMode = 'auto'; // auto, https, http
// ==================== ИНИЦИАЛИЗАЦИЯ ====================
function init() {
// Обновляем информацию
updateDeviceInfo();
updateProtocolInfo();
// Загружаем сохраненные настройки
const savedMode = localStorage.getItem('protocolMode');
if (savedMode) {
setProtocol(savedMode);
}
addLog('✅ Диагностика инициализирована');
addLog(`📡 API URL: ${API_BASE_URL}`);
}
function updateDeviceInfo() {
const info = {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
screen: `${window.screen.width}x${window.screen.height}`,
online: navigator.onLine,
vendor: navigator.vendor,
cookieEnabled: navigator.cookieEnabled
};
document.getElementById('deviceInfo').textContent =
`${info.platform}, ${info.screen}, ${info.language}`;
document.getElementById('currentUrl').textContent = window.location.href;
document.getElementById('currentProtocol').textContent = currentProtocol;
document.getElementById('baseApiUrl').textContent = API_BASE_URL;
document.getElementById('networkStatus').textContent =
info.online ? '✅ Онлайн' : '❌ Офлайн';
testResults.device = info;
}
function updateProtocolInfo() {
const protocolInfo = document.getElementById('protocolInfo');
const isHttps = currentProtocol === 'https:';
if (isHttps) {
protocolInfo.innerHTML = `
<strong>🔄 Текущий протокол: HTTPS (защищенный)</strong><br>
<span class="info">⚠️ API запросы должны идти через HTTPS или относительные пути</span>
`;
} else {
protocolInfo.innerHTML = `
<strong>🔄 Текущий протокол: HTTP (незащищенный)</strong><br>
<span class="success">✅ API запросы будут работать напрямую</span>
`;
}
}
// ==================== УПРАВЛЕНИЕ ПРОТОКОЛОМ ====================
function setProtocol(mode) {
currentProtocolMode = mode;
// Обновляем кнопки
document.querySelectorAll('.protocol-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.protocol === mode) {
btn.classList.add('active');
}
});
// Сохраняем настройку
localStorage.setItem('protocolMode', mode);
// Перестраиваем API URL в зависимости от режима
if (mode === 'https') {
API_BASE_URL = `https://${currentHost}/api`;
} else if (mode === 'http') {
// Для HTTP нужно убедиться, что есть порт 8000
const hostWithoutPort = currentHost.split(':')[0];
API_BASE_URL = `http://${hostWithoutPort}:8000/api`;
} else {
// Auto - используем текущий протокол
API_BASE_URL = `${currentProtocol}//${currentHost}/api`;
}
document.getElementById('baseApiUrl').textContent = API_BASE_URL;
addLog(`🔄 Протокол изменен на ${mode}, API URL: ${API_BASE_URL}`);
}
// ==================== ЛОГИРОВАНИЕ ====================
function addLog(message, type = 'info') {
const logDiv = document.getElementById('log');
const entry = document.createElement('div');
entry.className = 'log-entry';
let icon = '📌';
if (type === 'success') icon = '✅';
if (type === 'error') icon = '❌';
if (type === 'warning') icon = '⚠️';
entry.textContent = `[${new Date().toLocaleTimeString()}] ${icon} ${message}`;
logDiv.insertBefore(entry, logDiv.firstChild);
// Ограничиваем количество записей
if (logDiv.children.length > 20) {
logDiv.removeChild(logDiv.lastChild);
}
}
function clearLog() {
document.getElementById('log').innerHTML = '<div class="log-entry">✅ Лог очищен</div>';
}
function downloadLog() {
const logEntries = [];
document.querySelectorAll('#log .log-entry').forEach(entry => {
logEntries.push(entry.textContent);
});
const logText = logEntries.join('\n');
const blob = new Blob([logText], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `debug-log-${new Date().toISOString()}.txt`;
a.click();
}
// ==================== ОТОБРАЖЕНИЕ РЕЗУЛЬТАТОВ ====================
function showResult(title, content, isSuccess = true) {
const resultDiv = document.getElementById('mainResult');
resultDiv.style.display = 'block';
resultDiv.innerHTML = `
<div class="${isSuccess ? 'success' : 'error'}" style="margin-bottom: 10px;">
${isSuccess ? '✅' : '❌'} ${title}
</div>
<pre style="white-space: pre-wrap; font-size: 12px; background: white; padding: 10px; border-radius: 8px;">${JSON.stringify(content, null, 2)}</pre>
`;
}
function updateFullResults() {
const resultsDiv = document.getElementById('results');
const resultsText = document.getElementById('resultsText');
const resultsCard = document.getElementById('resultsCard');
resultsCard.style.display = 'block';
const resultsJson = JSON.stringify(testResults, null, 2);
resultsDiv.textContent = resultsJson;
resultsText.value = resultsJson;
document.getElementById('shareLink').style.display = 'block';
}
function copyResults() {
const resultsText = document.getElementById('resultsText');
resultsText.select();
document.execCommand('copy');
addLog('✅ Результаты скопированы');
alert('Результаты скопированы в буфер обмена!');
}
function copyDebugInfo() {
const info = {
url: window.location.href,
protocol: currentProtocol,
apiUrl: API_BASE_URL,
device: testResults.device,
timestamp: new Date().toISOString()
};
const textarea = document.createElement('textarea');
textarea.value = JSON.stringify(info, null, 2);
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
addLog('✅ Информация скопирована');
}
function saveIp() {
addLog('✅ Настройки сохранены');
alert('Настройки сохранены');
}
// ==================== ТЕСТЫ API ====================
async function testConnection() {
addLog('🔄 Тест соединения...');
const urls = [
`${API_BASE_URL}/health`,
`${API_BASE_URL.replace('/api', '')}/health`,
`${window.location.protocol}//${window.location.host}/api/health`,
`http://${window.location.hostname}:8000/api/health`
];
let results = [];
for (const url of urls) {
try {
const start = Date.now();
const response = await fetch(url, {
method: 'GET',
headers: { 'Accept': 'application/json' },
mode: 'cors'
});
const time = Date.now() - start;
let data = null;
try {
data = await response.json();
} catch {
data = await response.text();
}
const result = {
url,
status: response.status,
time: `${time}ms`,
ok: response.ok,
data: data
};
results.push(result);
if (response.ok) {
addLog(`✅ Доступно: ${url} (${time}ms)`);
} else {
addLog(`⚠️ Ошибка ${response.status}: ${url}`);
}
} catch (e) {
results.push({
url,
error: e.message,
ok: false
});
addLog(`❌ Недоступно: ${url} - ${e.message}`, 'error');
}
}
testResults.connection = results;
// Проверяем, есть ли успешные подключения
const hasSuccess = results.some(r => r.ok);
showResult('Тест соединения', results, hasSuccess);
return results;
}
async function testHealth() {
addLog('🔄 Тест Health API...');
try {
const url = `${API_BASE_URL}/health`;
const response = await fetch(url, {
headers: { 'Accept': 'application/json' }
});
let data;
try {
data = await response.json();
} catch {
data = await response.text();
}
testResults.health = {
status: response.status,
ok: response.ok,
data,
url
};
if (response.ok) {
addLog('✅ Health API работает');
showResult('Health API', data, true);
} else {
addLog(`❌ Health API ошибка: ${response.status}`);
showResult('Health API ошибка', { status: response.status, data }, false);
}
return { ok: response.ok, data };
} catch (error) {
testResults.health = { error: error.message };
addLog(`❌ Health API: ${error.message}`, 'error');
showResult('Health API ошибка', { error: error.message }, false);
return { ok: false, error: error.message };
}
}
async function testCORS() {
addLog('🔄 Тест CORS...');
try {
const url = `${API_BASE_URL}/health`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
const corsHeader = response.headers.get('access-control-allow-origin');
const corsCredentials = response.headers.get('access-control-allow-credentials');
testResults.cors = {
allowed: corsHeader === '*' || corsHeader === window.location.origin,
header: corsHeader,
credentials: corsCredentials,
url: url
};
if (corsHeader) {
addLog(`✅ CORS заголовок: ${corsHeader}`);
showResult('CORS тест', testResults.cors, true);
} else {
addLog('⚠️ Нет CORS заголовка', 'warning');
showResult('CORS предупреждение', testResults.cors, false);
}
} catch (error) {
testResults.cors = { error: error.message };
addLog(`❌ CORS тест: ${error.message}`, 'error');
showResult('CORS ошибка', { error: error.message }, false);
}
}
async function testRegister() {
addLog('🔄 Тест регистрации...');
const testData = {
full_name: "Тест Тестов",
email: `test${Date.now()}@example.com`,
phone: "+79991234567",
telegram: null,
password: "password123",
role: "employee"
};
try {
const url = `${API_BASE_URL}/register`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(testData)
});
let data;
try {
data = await response.json();
} catch {
data = await response.text();
}
testResults.register = {
status: response.status,
ok: response.ok,
data: data,
url: url
};
if (response.ok) {
addLog(`✅ Регистрация работает, создан пользователь ID: ${data.user_id}`);
showResult('Регистрация', {
success: true,
user_id: data.user_id,
email: testData.email
}, true);
} else {
addLog(`❌ Регистрация ошибка: ${response.status}`);
showResult('Ошибка регистрации', { status: response.status, data }, false);
}
return { ok: response.ok, data };
} catch (error) {
testResults.register = { error: error.message };
addLog(`❌ Регистрация: ${error.message}`, 'error');
showResult('Ошибка регистрации', { error: error.message }, false);
return { ok: false, error: error.message };
}
}
async function testLogin() {
addLog('🔄 Тест входа...');
try {
const url = `${API_BASE_URL}/login`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
email: "admin@rabota.today",
password: "admin123"
})
});
let data;
try {
data = await response.json();
} catch {
data = await response.text();
}
testResults.login = {
status: response.status,
ok: response.ok,
data: data,
url: url
};
if (response.ok) {
addLog(`✅ Вход работает, получен токен для ${data.full_name}`);
showResult('Вход в систему', {
success: true,
user: data.full_name,
role: data.role
}, true);
// Сохраняем токен для теста авторизации
if (data.access_token) {
localStorage.setItem('test_token', data.access_token);
}
} else {
addLog(`❌ Вход ошибка: ${response.status}`);
showResult('Ошибка входа', { status: response.status, data }, false);
}
return { ok: response.ok, data };
} catch (error) {
testResults.login = { error: error.message };
addLog(`❌ Вход: ${error.message}`, 'error');
showResult('Ошибка входа', { error: error.message }, false);
return { ok: false, error: error.message };
}
}
async function testAuth() {
addLog('🔄 Тест авторизации...');
const token = localStorage.getItem('test_token');
if (!token) {
addLog('⚠️ Нет токена для теста авторизации', 'warning');
showResult('Тест авторизации', { error: 'Сначала выполните вход' }, false);
return;
}
try {
const url = `${API_BASE_URL}/user`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
});
let data;
try {
data = await response.json();
} catch {
data = await response.text();
}
testResults.auth = {
status: response.status,
ok: response.ok,
data: data
};
if (response.ok) {
addLog(`✅ Авторизация работает, пользователь: ${data.full_name}`);
showResult('Авторизация', {
success: true,
user: data.full_name,
email: data.email
}, true);
} else {
addLog(`❌ Авторизация ошибка: ${response.status}`);
showResult('Ошибка авторизации', { status: response.status, data }, false);
}
} catch (error) {
testResults.auth = { error: error.message };
addLog(`❌ Авторизация: ${error.message}`, 'error');
showResult('Ошибка авторизации', { error: error.message }, false);
}
}
async function runAllTests() {
const btn = document.getElementById('runAllBtn');
btn.disabled = true;
btn.innerHTML = '<div class="loader"></div> Выполнение тестов...';
// Очищаем предыдущие результаты
testResults = {
device: testResults.device,
timestamp: new Date().toISOString()
};
addLog('🚀 Запуск всех тестов...');
// Последовательное выполнение тестов
await testConnection();
await testHealth();
await testCORS();
await testRegister();
await testLogin();
await testAuth();
// Обновляем полные результаты
updateFullResults();
btn.disabled = false;
btn.innerHTML = '▶️ Запустить все тесты';
addLog('✅ Все тесты завершены');
}
// ==================== ЗАПУСК ====================
// Запускаем инициализацию при загрузке
window.addEventListener('load', () => {
init();
// Автоматически запускаем базовые тесты через 1 секунду
setTimeout(() => {
testConnection();
}, 1000);
});
</script>
</body>
</html>