137 lines
6.4 KiB
HTML
137 lines
6.4 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>User Login</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<style>
|
|
/* Custom styles for glassmorphism and animations */
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
background: linear-gradient(135deg, #1a202c 0%, #2d3748 50%, #4a5568 100%);
|
|
background-size: 400% 400%;
|
|
animation: gradientBG 15s ease infinite;
|
|
}
|
|
|
|
@keyframes gradientBG {
|
|
0% { background-position: 0% 50%; }
|
|
50% { background-position: 100% 50%; }
|
|
100% { background-position: 0% 50%; }
|
|
}
|
|
|
|
.card {
|
|
background: rgba(31, 41, 55, 0.5);
|
|
backdrop-filter: blur(12px);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
border-radius: 1rem;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
transition: all 0.3s ease;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="flex items-center justify-center min-h-screen bg-gray-900 text-gray-200">
|
|
<div class="container mx-auto p-4 md:p-8">
|
|
<div class="max-w-md mx-auto card p-8">
|
|
<header class="mb-6 text-center">
|
|
<i data-lucide="log-in" class="w-12 h-12 text-blue-400 mx-auto mb-2"></i>
|
|
<h1 class="text-3xl font-bold text-white">Login</h1>
|
|
<p class="text-gray-400 mt-2">Access your application manager.</p>
|
|
</header>
|
|
|
|
<form id="loginForm" class="space-y-4">
|
|
<div>
|
|
<label for="apiUrl" class="block text-sm font-medium text-gray-300">API URL</label>
|
|
<input type="text" id="apiUrl" name="apiUrl" value="http://127.0.0.1:8000/api/v1/auth/login" class="mt-1 block w-full bg-gray-900/50 border border-gray-600 rounded-md shadow-sm p-2 text-white focus:ring-blue-500 focus:border-blue-500" placeholder="e.g., http://your-domain.com/api/v1/auth/login" required>
|
|
</div>
|
|
<div>
|
|
<label for="username" class="block text-sm font-medium text-gray-300">Username</label>
|
|
<input type="text" id="username" name="username" class="mt-1 block w-full bg-gray-900/50 border border-gray-600 rounded-md shadow-sm p-2 text-white focus:ring-blue-500 focus:border-blue-500" required>
|
|
</div>
|
|
<div>
|
|
<label for="password" class="block text-sm font-medium text-gray-300">Password</label>
|
|
<input type="password" id="password" name="password" class="mt-1 block w-full bg-gray-900/50 border border-gray-600 rounded-md shadow-sm p-2 text-white focus:ring-blue-500 focus:border-blue-500" required>
|
|
</div>
|
|
<button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg flex items-center justify-center gap-2 transition">
|
|
<i data-lucide="log-in" class="w-4 h-4"></i> Sign In
|
|
</button>
|
|
</form>
|
|
|
|
<div id="resultContainer" class="mt-6 p-4 rounded-lg hidden">
|
|
<h3 class="text-lg font-semibold text-white mb-2">Login Successful!</h3>
|
|
<p class="text-gray-400">Your Access Token:</p>
|
|
<div class="relative mt-2">
|
|
<textarea id="tokenDisplay" rows="4" class="w-full bg-gray-900/50 text-green-400 border border-gray-600 rounded-md p-2" readonly></textarea>
|
|
<button id="copyTokenBtn" class="absolute top-2 right-2 p-1 rounded-md bg-gray-700/50 text-gray-400 hover:bg-gray-600/50 transition">
|
|
<i data-lucide="clipboard" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
<p class="text-sm mt-2 text-gray-500">This token is valid for a limited time. Use it for authenticated API requests.</p>
|
|
</div>
|
|
|
|
<div id="errorContainer" class="mt-6 p-4 bg-red-800/20 text-red-400 rounded-lg hidden">
|
|
<p id="errorMessage" class="font-semibold"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const loginForm = document.getElementById('loginForm');
|
|
const apiUrlInput = document.getElementById('apiUrl');
|
|
const usernameInput = document.getElementById('username');
|
|
const passwordInput = document.getElementById('password');
|
|
const resultContainer = document.getElementById('resultContainer');
|
|
const tokenDisplay = document.getElementById('tokenDisplay');
|
|
const copyTokenBtn = document.getElementById('copyTokenBtn');
|
|
const errorContainer = document.getElementById('errorContainer');
|
|
const errorMessage = document.getElementById('errorMessage');
|
|
|
|
loginForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const API_URL = apiUrlInput.value;
|
|
const username = usernameInput.value;
|
|
const password = passwordInput.value;
|
|
|
|
// Clear previous results and errors
|
|
resultContainer.classList.add('hidden');
|
|
errorContainer.classList.add('hidden');
|
|
|
|
try {
|
|
const response = await fetch(API_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(result.detail || 'Login failed. Please check your credentials.');
|
|
}
|
|
|
|
const token = result.access_token;
|
|
tokenDisplay.value = `Bearer ${token}`;
|
|
resultContainer.classList.remove('hidden');
|
|
|
|
} catch (error) {
|
|
console.error("Login failed:", error);
|
|
errorMessage.textContent = error.message;
|
|
errorContainer.classList.remove('hidden');
|
|
}
|
|
});
|
|
|
|
copyTokenBtn.addEventListener('click', () => {
|
|
tokenDisplay.select();
|
|
document.execCommand('copy');
|
|
alert('Token copied to clipboard!');
|
|
});
|
|
|
|
// Initialize icons
|
|
window.onload = () => {
|
|
lucide.createIcons();
|
|
};
|
|
</script>
|
|
</body>
|
|
</html> |