|
|
<!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> |