fix: update paths and add static files support for Docker
This commit is contained in:
35
app/static/js/error.js
Normal file
35
app/static/js/error.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// Theme toggle functionality
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement;
|
||||
const currentTheme = html.getAttribute('data-theme');
|
||||
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
|
||||
html.setAttribute('data-theme', newTheme);
|
||||
localStorage.setItem('lb_theme', newTheme);
|
||||
|
||||
// Update theme toggle button
|
||||
const themeButton = document.querySelector('.theme-toggle');
|
||||
if (themeButton) {
|
||||
themeButton.textContent = newTheme === 'light' ? '🌙' : '☀️';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize theme on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const savedTheme = localStorage.getItem('lb_theme') || 'dark';
|
||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||
|
||||
// Update theme toggle button
|
||||
const themeButton = document.querySelector('.theme-toggle');
|
||||
if (themeButton) {
|
||||
themeButton.textContent = savedTheme === 'light' ? '🌙' : '☀️';
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard shortcut for theme toggle (Ctrl+T)
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey && e.key === 't') {
|
||||
e.preventDefault();
|
||||
toggleTheme();
|
||||
}
|
||||
});
|
||||
5685
app/static/js/index.js
Normal file
5685
app/static/js/index.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,105 +1,106 @@
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
// Theme toggle
|
||||
(function initTheme(){
|
||||
const saved = localStorage.lb_theme || 'dark';
|
||||
document.documentElement.setAttribute('data-theme', saved);
|
||||
document.getElementById('themeSwitch').checked = (saved==='light');
|
||||
document.getElementById('themeSwitch').addEventListener('change', ()=>{
|
||||
const t = document.getElementById('themeSwitch').checked ? 'light' : 'dark';
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
localStorage.lb_theme = t;
|
||||
});
|
||||
})();
|
||||
|
||||
.form-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
// Password toggle
|
||||
document.getElementById('passwordToggle').addEventListener('click', function() {
|
||||
const passwordInput = document.getElementById('password');
|
||||
const icon = this.querySelector('i');
|
||||
|
||||
if (passwordInput.type === 'password') {
|
||||
passwordInput.type = 'text';
|
||||
icon.className = 'fas fa-eye-slash';
|
||||
} else {
|
||||
passwordInput.type = 'password';
|
||||
icon.className = 'fas fa-eye';
|
||||
}
|
||||
});
|
||||
|
||||
.form-input {
|
||||
background: var(--chip);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
color: var(--fg);
|
||||
transition: all 0.2s ease;
|
||||
font-family: inherit;
|
||||
// Login form
|
||||
document.getElementById('loginForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const username = document.getElementById('username').value.trim();
|
||||
const password = document.getElementById('password').value;
|
||||
const loginButton = document.getElementById('loginButton');
|
||||
const buttonText = document.getElementById('buttonText');
|
||||
const loadingSpinner = document.getElementById('loadingSpinner');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
|
||||
// Validation
|
||||
if (!username || !password) {
|
||||
showError('Пожалуйста, заполните все поля');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
loginButton.disabled = true;
|
||||
buttonText.textContent = 'Вход...';
|
||||
loadingSpinner.classList.add('show');
|
||||
hideError();
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: password
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
|
||||
// Store token in localStorage
|
||||
localStorage.setItem('access_token', data.access_token);
|
||||
|
||||
// Redirect to main page
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
showError(errorData.detail || 'Ошибка входа в систему');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
showError('Ошибка соединения с сервером');
|
||||
} finally {
|
||||
// Reset loading state
|
||||
loginButton.disabled = false;
|
||||
buttonText.textContent = 'Войти';
|
||||
loadingSpinner.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(122, 162, 247, 0.1);
|
||||
}
|
||||
function showError(message) {
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
errorMessage.textContent = message;
|
||||
errorMessage.classList.add('show');
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: var(--muted);
|
||||
}
|
||||
function hideError() {
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
errorMessage.classList.remove('show');
|
||||
}
|
||||
|
||||
.password-input-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
// Auto-focus on username field
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('username').focus();
|
||||
});
|
||||
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: 8px; /* Ближе к краю для всех устройств */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
color: var(--fg);
|
||||
background: var(--chip);
|
||||
}
|
||||
|
||||
.password-toggle:active {
|
||||
transform: translateY(-50%) scale(0.95);
|
||||
}
|
||||
|
||||
.password-input-wrapper .form-input {
|
||||
padding-right: 40px; /* Место для кнопки */
|
||||
width: 100%; /* Поле на всю ширину */
|
||||
}
|
||||
|
||||
.login-button {
|
||||
background: var(--accent);
|
||||
color: #0b0d12;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 14px 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-family: inherit;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.login-button:hover {
|
||||
background: #6b8fd8;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(122, 162, 247, 0.3);
|
||||
}
|
||||
|
||||
.login-button:disabled {
|
||||
background: var(--muted);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: rgba(247, 118, 142, 0.1);
|
||||
border: 1px solid var(--err);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
// Handle Enter key in password field
|
||||
document.getElementById('password').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
document.getElementById('loginForm').dispatchEvent(new Event('submit'));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user