| | <!DOCTYPE html> |
| | <html lang="en"> |
| | {{template "views/partials/head" .}} |
| |
|
| | <body class="bg-[#101827] text-[#E5E7EB]"> |
| | <div class="flex flex-col min-h-screen"> |
| |
|
| | {{template "views/partials/navbar" .}} |
| |
|
| | <div class="container mx-auto px-4 py-8 flex-grow flex items-center justify-center"> |
| | |
| | <div class="max-w-md w-full bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl overflow-hidden"> |
| | <div class="animation-container"> |
| | <div class="text-overlay"> |
| | <img src="static/logo.png" alt="LocalAI Logo" class="h-32 drop-shadow-[0_0_15px_rgba(56,189,248,0.3)]"> |
| | </div> |
| | </div> |
| | |
| | <div class="p-8"> |
| | <div class="text-center mb-6"> |
| | <h2 class="h2"> |
| | Authorization Required |
| | </h2> |
| | <p class="text-[#94A3B8] mt-2">Please enter your access token to continue</p> |
| | </div> |
| | |
| | <form id="login-form" class="space-y-6" onsubmit="login(); return false;"> |
| | <div> |
| | <label for="token" class="block text-sm font-medium text-[#94A3B8] mb-2">Access Token</label> |
| | <div class="relative"> |
| | <div class="absolute inset-y-0 start-0 flex items-center ps-4 pointer-events-none z-10"> |
| | <i class="fas fa-key text-[#38BDF8]"></i> |
| | </div> |
| | <input |
| | type="password" |
| | id="token" |
| | name="token" |
| | placeholder="Enter your token" |
| | class="bg-[#101827] border border-[#1E293B] text-[#E5E7EB] placeholder-[#94A3B8] text-sm rounded-lg focus:ring-[#38BDF8] focus:border-[#38BDF8] focus:ring-2 block w-full p-2.5 transition-all" |
| | style="padding-left: 3.5rem !important;" |
| | required |
| | /> |
| | </div> |
| | </div> |
| | |
| | <div> |
| | <button |
| | type="submit" |
| | class="w-full flex items-center justify-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-3 px-6 rounded-lg transition-colors" |
| | > |
| | <i class="fas fa-sign-in-alt mr-2"></i> |
| | <span>Login</span> |
| | </button> |
| | </div> |
| | </form> |
| | |
| | <div class="mt-8 pt-6 border-t border-[#1E293B] text-center text-sm text-[#94A3B8]"> |
| | <div class="flex items-center justify-center mb-2"> |
| | <i class="fas fa-shield-alt mr-2 text-[#38BDF8]"></i> |
| | <span>Instance is token protected</span> |
| | </div> |
| | <p>Current time (UTC): <span id="current-time">{{.CurrentDate}}</span></p> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | {{template "views/partials/footer" .}} |
| | </div> |
| |
|
| | <script> |
| | function login() { |
| | const token = document.getElementById('token').value; |
| | if (!token.trim()) { |
| | |
| | const form = document.getElementById('login-form'); |
| | const errorMsg = document.createElement('div'); |
| | errorMsg.className = 'p-3 mt-4 bg-red-500/10 text-red-300 rounded-lg border border-red-500/30 text-sm flex items-center'; |
| | errorMsg.innerHTML = '<i class="fas fa-exclamation-circle mr-2"></i> Please enter a valid token'; |
| | |
| | |
| | const existingError = form.querySelector('[class*="bg-red-"]'); |
| | if (existingError) form.removeChild(existingError); |
| | |
| | |
| | form.appendChild(errorMsg); |
| | setTimeout(() => { |
| | errorMsg.style.opacity = '0'; |
| | errorMsg.style.transition = 'opacity 0.5s ease'; |
| | setTimeout(() => errorMsg.remove(), 500); |
| | }, 3000); |
| | return; |
| | } |
| | |
| | var date = new Date(); |
| | date.setTime(date.getTime() + (24*60*60*1000)); |
| | document.cookie = `token=${token}; expires=${date.toGMTString()}; path=/`; |
| | |
| | |
| | const button = document.querySelector('button[type="submit"]'); |
| | const originalContent = button.innerHTML; |
| | button.disabled = true; |
| | button.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Authenticating...'; |
| | button.classList.add('opacity-75'); |
| | |
| | |
| | setTimeout(() => { |
| | window.location.reload(); |
| | }, 800); |
| | } |
| | |
| | |
| | function updateCurrentTime() { |
| | const timeElement = document.getElementById('current-time'); |
| | if (timeElement) { |
| | const now = new Date(); |
| | const year = now.getUTCFullYear(); |
| | const month = String(now.getUTCMonth() + 1).padStart(2, '0'); |
| | const day = String(now.getUTCDate()).padStart(2, '0'); |
| | const hours = String(now.getUTCHours()).padStart(2, '0'); |
| | const minutes = String(now.getUTCMinutes()).padStart(2, '0'); |
| | const seconds = String(now.getUTCSeconds()).padStart(2, '0'); |
| | timeElement.textContent = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | } |
| | } |
| | |
| | |
| | updateCurrentTime(); |
| | setInterval(updateCurrentTime, 1000); |
| | |
| | |
| | document.addEventListener('DOMContentLoaded', function() { |
| | const animContainer = document.querySelector('.animation-container'); |
| | if (animContainer) { |
| | const canvas = document.createElement('canvas'); |
| | animContainer.appendChild(canvas); |
| | |
| | const ctx = canvas.getContext('2d'); |
| | canvas.width = animContainer.offsetWidth; |
| | canvas.height = animContainer.offsetHeight; |
| | |
| | |
| | const particles = []; |
| | const particleCount = 30; |
| | |
| | for (let i = 0; i < particleCount; i++) { |
| | particles.push({ |
| | x: Math.random() * canvas.width, |
| | y: Math.random() * canvas.height, |
| | radius: Math.random() * 3 + 1, |
| | color: `rgba(${Math.random() * 50 + 50}, ${Math.random() * 100 + 100}, ${Math.random() * 155 + 100}, ${Math.random() * 0.4 + 0.1})`, |
| | speedX: Math.random() * 0.5 - 0.25, |
| | speedY: Math.random() * 0.5 - 0.25 |
| | }); |
| | } |
| | |
| | |
| | function animate() { |
| | requestAnimationFrame(animate); |
| | ctx.clearRect(0, 0, canvas.width, canvas.height); |
| | |
| | particles.forEach(particle => { |
| | particle.x += particle.speedX; |
| | particle.y += particle.speedY; |
| | |
| | |
| | if (particle.x < 0 || particle.x > canvas.width) { |
| | particle.speedX = -particle.speedX; |
| | } |
| | |
| | if (particle.y < 0 || particle.y > canvas.height) { |
| | particle.speedY = -particle.speedY; |
| | } |
| | |
| | |
| | ctx.beginPath(); |
| | ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2); |
| | ctx.fillStyle = particle.color; |
| | ctx.fill(); |
| | }); |
| | |
| | |
| | for (let i = 0; i < particles.length; i++) { |
| | for (let j = i + 1; j < particles.length; j++) { |
| | const dx = particles[i].x - particles[j].x; |
| | const dy = particles[i].y - particles[j].y; |
| | const distance = Math.sqrt(dx * dx + dy * dy); |
| | |
| | if (distance < 100) { |
| | ctx.beginPath(); |
| | ctx.moveTo(particles[i].x, particles[i].y); |
| | ctx.lineTo(particles[j].x, particles[j].y); |
| | ctx.strokeStyle = `rgba(100, 150, 255, ${0.1 * (1 - distance / 100)})`; |
| | ctx.lineWidth = 1; |
| | ctx.stroke(); |
| | } |
| | } |
| | } |
| | } |
| | |
| | |
| | animate(); |
| | |
| | |
| | window.addEventListener('resize', () => { |
| | canvas.width = animContainer.offsetWidth; |
| | canvas.height = animContainer.offsetHeight; |
| | }); |
| | } |
| | }); |
| | </script> |
| |
|
| | </body> |
| | </html> |