(() => { 'use strict'; // --- OpenTelemetry Frontend Instrumentation --- // Загружаем OTel только на продакшене (не на localhost) if (!['localhost', '127.0.0.1'].includes(window.location.hostname)) { import('https://esm.sh/@opentelemetry/sdk-trace-web').then(async ({ WebTracerProvider, BatchSpanProcessor }) => { const { OTLPTraceExporter } = await import('https://esm.sh/@opentelemetry/exporter-trace-otlp-http'); const { getWebAutoInstrumentations } = await import('https://esm.sh/@opentelemetry/auto-instrumentations-web'); const { registerInstrumentations } = await import('https://esm.sh/@opentelemetry/instrumentation'); const { Resource } = await import('https://esm.sh/@opentelemetry/resources'); const exporter = new OTLPTraceExporter({ url: window.location.origin + '/otel/v1/traces' }); const provider = new WebTracerProvider({ resource: new Resource({ 'service.name': 'magistr-frontend' }), }); provider.addSpanProcessor(new BatchSpanProcessor(exporter)); provider.register(); registerInstrumentations({ instrumentations: [getWebAutoInstrumentations()] }); console.log("SigNoz (OpenTelemetry) инициализирован во фронтенде."); }).catch(e => console.error("Ошибка загрузки OTel:", e)); } // ---------------------------------------------- const form = document.getElementById('login-form'); const usernameInput = document.getElementById('username'); const passwordInput = document.getElementById('password'); const usernameError = document.getElementById('username-error'); const passwordError = document.getElementById('password-error'); const formAlert = document.getElementById('form-alert'); const btnSubmit = document.getElementById('btn-submit'); const btnText = btnSubmit.querySelector('.btn-text'); const btnLoader = btnSubmit.querySelector('.btn-loader'); const togglePassword = document.getElementById('toggle-password'); // Ripple effect btnSubmit.addEventListener('click', function(e) { const rect = this.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const ripple = document.createElement('span'); ripple.classList.add('ripple'); ripple.style.left = `${x}px`; ripple.style.top = `${y}px`; this.appendChild(ripple); setTimeout(() => { ripple.remove(); }, 600); }); togglePassword.addEventListener('click', () => { const isPassword = passwordInput.type === 'password'; passwordInput.type = isPassword ? 'text' : 'password'; togglePassword.setAttribute('aria-label', isPassword ? 'Скрыть пароль' : 'Показать пароль'); }); usernameInput.addEventListener('input', () => clearFieldError(usernameInput, usernameError)); passwordInput.addEventListener('input', () => clearFieldError(passwordInput, passwordError)); function clearFieldError(input, errorEl) { input.closest('.form-group').classList.remove('has-error'); errorEl.textContent = ''; } function setFieldError(input, errorEl, message) { const group = input.closest('.form-group'); group.classList.add('has-error'); errorEl.textContent = message; // Shake animation group.classList.remove('shake'); void group.offsetWidth; // trigger reflow group.classList.add('shake'); } function showAlert(message, type) { formAlert.className = 'form-alert ' + type; formAlert.textContent = message; } function hideAlert() { formAlert.className = 'form-alert'; formAlert.textContent = ''; } function setLoading(loading) { btnSubmit.disabled = loading; btnText.hidden = loading; btnLoader.hidden = !loading; } function validate() { let valid = true; hideAlert(); if (!usernameInput.value.trim()) { setFieldError(usernameInput, usernameError, 'Введите имя пользователя'); valid = false; } if (!passwordInput.value) { setFieldError(passwordInput, passwordError, 'Введите пароль'); valid = false; } else if (passwordInput.value.length < 4) { setFieldError(passwordInput, passwordError, 'Минимум 4 символа'); valid = false; } return valid; } form.addEventListener('submit', async (e) => { e.preventDefault(); if (!validate()) return; setLoading(true); hideAlert(); try { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: usernameInput.value.trim(), password: passwordInput.value, }), }); const data = await response.json(); if (response.ok) { showAlert('Вход выполнен успешно!', 'success'); if (data.token) localStorage.setItem('token', data.token); if (data.role) localStorage.setItem('role', data.role); const redirect = data.redirect || '/'; setTimeout(() => { window.location.href = redirect; }, 400); } else { showAlert(data.message || 'Неверное имя пользователя или пароль', 'error'); } } catch (err) { showAlert('Ошибка соединения с сервером', 'error'); } finally { setLoading(false); } }); })();