feat: Add API URL input field to login form for dynamic endpoint configuration

This commit is contained in:
2025-09-02 07:33:48 +00:00
parent 210569ad37
commit fbaf775e2a
2 changed files with 48 additions and 24 deletions

View File

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OCDP Application Manager</title> <title>OCDP Application Manager</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<!-- Lucide icons for UI elements -->
<script src="https://unpkg.com/lucide@latest"></script> <script src="https://unpkg.com/lucide@latest"></script>
<style> <style>
body { body {
@ -49,7 +48,6 @@
</head> </head>
<body class="bg-gray-900 text-gray-200"> <body class="bg-gray-900 text-gray-200">
<!-- Main Application Manager -->
<div id="mainAppPage" class="container mx-auto p-4 md:p-8"> <div id="mainAppPage" class="container mx-auto p-4 md:p-8">
<header class="mb-8 flex flex-col sm:flex-row justify-between items-center gap-4"> <header class="mb-8 flex flex-col sm:flex-row justify-between items-center gap-4">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
@ -60,6 +58,7 @@
</div> </div>
</div> </div>
<div class="w-full sm:w-auto mt-4 sm:mt-0"> <div class="w-full sm:w-auto mt-4 sm:mt-0">
<input type="text" id="apiBaseUrlInput" placeholder="Enter API Base URL (e.g., http://localhost:8000)" class="w-full sm:w-80 bg-gray-900/50 text-white border border-gray-600 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition mb-2">
<input type="text" id="tokenInput" placeholder="Enter JWT Token" class="w-full sm:w-80 bg-gray-900/50 text-white border border-gray-600 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"> <input type="text" id="tokenInput" placeholder="Enter JWT Token" class="w-full sm:w-80 bg-gray-900/50 text-white border border-gray-600 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition">
</div> </div>
</header> </header>
@ -71,19 +70,16 @@
</div> </div>
<div id="contentAvailable" class="tab-content"> <div id="contentAvailable" class="tab-content">
<!-- Loading indicator for available apps -->
<p id="availableLoading" class="text-center text-blue-400 hidden">Loading available applications...</p> <p id="availableLoading" class="text-center text-blue-400 hidden">Loading available applications...</p>
<div id="availableAppsList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"></div> <div id="availableAppsList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"></div>
</div> </div>
<div id="contentInstalled" class="tab-content hidden"> <div id="contentInstalled" class="tab-content hidden">
<!-- Loading indicator for installed apps -->
<p id="installedLoading" class="text-center text-blue-400 hidden">Loading installed applications...</p> <p id="installedLoading" class="text-center text-blue-400 hidden">Loading installed applications...</p>
<div id="installedAppsList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"></div> <div id="installedAppsList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"></div>
</div> </div>
</main> </main>
</div> </div>
<!-- Modals -->
<div id="installModal" class="fixed inset-0 modal hidden justify-center items-center"> <div id="installModal" class="fixed inset-0 modal hidden justify-center items-center">
<div class="card p-8 w-full max-w-lg"> <div class="card p-8 w-full max-w-lg">
<h3 class="text-2xl font-bold text-white mb-4">Install Application</h3> <h3 class="text-2xl font-bold text-white mb-4">Install Application</h3>
@ -134,10 +130,11 @@
const API_ORCHESTRATION_PATH = "/api/v1/orchestration"; const API_ORCHESTRATION_PATH = "/api/v1/orchestration";
// Global variables // Global variables
let API_BASE_URL = "http://10.6.14.233:8000"; let API_BASE_URL = "http://10.6.14.233:8000"; // Default value
let TOKEN = ""; let TOKEN = "";
// DOM Elements // DOM Elements
const apiBaseUrlInput = document.getElementById('apiBaseUrlInput');
const tokenInput = document.getElementById('tokenInput'); const tokenInput = document.getElementById('tokenInput');
const tabAvailable = document.getElementById('tabAvailable'); const tabAvailable = document.getElementById('tabAvailable');
const tabInstalled = document.getElementById('tabInstalled'); const tabInstalled = document.getElementById('tabInstalled');
@ -165,11 +162,11 @@
renderAvailableApps(apps); renderAvailableApps(apps);
} catch (error) { } catch (error) {
console.error("Error fetching available apps:", error); console.error("Error fetching available apps:", error);
let errorMessage = `Failed to load data: ${error.message}`; let errorMessage = `Failed to load data from ${API_BASE_URL}: ${error.message}`;
if (error instanceof TypeError && error.message === 'Failed to fetch') { if (error instanceof TypeError && error.message === 'Failed to fetch') {
errorMessage = 'Network Error: Failed to fetch. Please ensure your API server is running and has CORS enabled.'; errorMessage = `Network Error: Failed to fetch from ${API_BASE_URL}. Please check the URL and ensure the API server is running with CORS enabled.`;
} }
availableAppsList.innerHTML = `<p class="text-center text-red-400">${errorMessage}</p>`; availableAppsList.innerHTML = `<p class="text-center text-red-400 col-span-full">${errorMessage}</p>`;
} finally { } finally {
// Hide loading indicator // Hide loading indicator
availableLoading.classList.add('hidden'); availableLoading.classList.add('hidden');
@ -193,11 +190,11 @@
renderInstalledApps(apps); renderInstalledApps(apps);
} catch (error) { } catch (error) {
console.error("Error fetching installed apps:", error); console.error("Error fetching installed apps:", error);
let errorMessage = `Failed to load data: ${error.message}`; let errorMessage = `Failed to load data from ${API_BASE_URL}: ${error.message}`;
if (error instanceof TypeError && error.message === 'Failed to fetch') { if (error instanceof TypeError && error.message === 'Failed to fetch') {
errorMessage = 'Network Error: Failed to fetch. Please ensure your API server is running and has CORS enabled.'; errorMessage = `Network Error: Failed to fetch from ${API_BASE_URL}. Please check the URL and ensure the API server is running with CORS enabled.`;
} }
installedAppsList.innerHTML = `<p class="text-center text-red-400">${errorMessage}</p>`; installedAppsList.innerHTML = `<p class="text-center text-red-400 col-span-full">${errorMessage}</p>`;
} finally { } finally {
// Hide loading indicator // Hide loading indicator
installedLoading.classList.add('hidden'); installedLoading.classList.add('hidden');
@ -224,7 +221,6 @@
} }
async function uninstallRelease(namespace, app_template_name, mode) { async function uninstallRelease(namespace, app_template_name, mode) {
// Use a custom modal instead of alert/confirm
if (!confirm(`Are you sure you want to uninstall the Helm release for ${app_template_name}?`)) return; if (!confirm(`Are you sure you want to uninstall the Helm release for ${app_template_name}?`)) return;
try { try {
const response = await fetch(`${API_BASE_URL}${API_ORCHESTRATION_PATH}/application-instances/${namespace}/${app_template_name}?mode=${mode}`, { const response = await fetch(`${API_BASE_URL}${API_ORCHESTRATION_PATH}/application-instances/${namespace}/${app_template_name}?mode=${mode}`, {
@ -243,7 +239,6 @@
} }
async function deleteNamespace(namespace) { async function deleteNamespace(namespace) {
// Use a custom modal instead of alert/confirm
if (!confirm(`WARNING: This will permanently delete the entire namespace '${namespace}' and all its resources.'`)) return; if (!confirm(`WARNING: This will permanently delete the entire namespace '${namespace}' and all its resources.'`)) return;
try { try {
const response = await fetch(`${API_BASE_URL}${API_ORCHESTRATION_PATH}/application-instances/${namespace}`, { const response = await fetch(`${API_BASE_URL}${API_ORCHESTRATION_PATH}/application-instances/${namespace}`, {
@ -370,7 +365,6 @@
try { try {
userOverrides = JSON.parse(userOverridesText); userOverrides = JSON.parse(userOverridesText);
} catch (error) { } catch (error) {
// Use a custom modal instead of alert
showResultModal("Invalid JSON for user overrides.", "Please ensure your JSON is correctly formatted.", "error"); showResultModal("Invalid JSON for user overrides.", "Please ensure your JSON is correctly formatted.", "error");
return; return;
} }
@ -440,6 +434,8 @@
function viewStatusDetails(namespace, app_template_name, mode) { function viewStatusDetails(namespace, app_template_name, mode) {
document.getElementById('statusContent').innerHTML = `<p class="text-center text-blue-400">Loading status...</p>`; document.getElementById('statusContent').innerHTML = `<p class="text-center text-blue-400">Loading status...</p>`;
statusModal.classList.remove('hidden');
statusModal.classList.add('flex');
fetchAndRenderStatus(namespace, app_template_name, mode); fetchAndRenderStatus(namespace, app_template_name, mode);
} }
@ -462,21 +458,45 @@
tabAvailable.addEventListener('click', () => switchTab('tabAvailable')); tabAvailable.addEventListener('click', () => switchTab('tabAvailable'));
tabInstalled.addEventListener('click', () => switchTab('tabInstalled')); tabInstalled.addEventListener('click', () => switchTab('tabInstalled'));
// --- Added: Real-time token update on input --- apiBaseUrlInput.addEventListener('input', (e) => {
API_BASE_URL = e.target.value;
localStorage.setItem('apiBaseUrl', API_BASE_URL);
// Refresh the current tab's data when URL changes
if (tabAvailable.classList.contains('active')) {
fetchAvailableApps();
} else {
fetchInstalledApps();
}
});
tokenInput.addEventListener('input', (e) => { tokenInput.addEventListener('input', (e) => {
TOKEN = e.target.value; TOKEN = e.target.value;
localStorage.setItem('jwtToken', TOKEN);
// Refresh installed apps when token changes, as it's required for auth
if (tabInstalled.classList.contains('active')) {
fetchInstalledApps();
}
}); });
// Initial page load check // Initial page load
window.onload = () => { window.onload = () => {
// The API_BASE_URL is now a default value and can be manually changed. // Load saved API URL or use default
// We'll also remove the token from localStorage for a clean start. const savedApiUrl = localStorage.getItem('apiBaseUrl');
localStorage.removeItem('jwtToken'); if (savedApiUrl) {
localStorage.removeItem('apiBaseUrl'); API_BASE_URL = savedApiUrl;
}
apiBaseUrlInput.value = API_BASE_URL;
// Load saved token
const savedToken = localStorage.getItem('jwtToken');
if (savedToken) {
TOKEN = savedToken;
tokenInput.value = TOKEN;
}
switchTab('tabAvailable'); // Default to the "Available Applications" tab switchTab('tabAvailable'); // Default to the "Available Applications" tab
}; };
</script> </script>
</body> </body>
</html> </html>

View File

@ -41,6 +41,10 @@
</header> </header>
<form id="loginForm" class="space-y-4"> <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> <div>
<label for="username" class="block text-sm font-medium text-gray-300">Username</label> <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> <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>
@ -73,9 +77,8 @@
</div> </div>
<script> <script>
const API_URL = "http://10.6.14.233:8000/api/v1/auth/login";
const loginForm = document.getElementById('loginForm'); const loginForm = document.getElementById('loginForm');
const apiUrlInput = document.getElementById('apiUrl');
const usernameInput = document.getElementById('username'); const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password'); const passwordInput = document.getElementById('password');
const resultContainer = document.getElementById('resultContainer'); const resultContainer = document.getElementById('resultContainer');
@ -87,6 +90,7 @@
loginForm.addEventListener('submit', async (e) => { loginForm.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const API_URL = apiUrlInput.value;
const username = usernameInput.value; const username = usernameInput.value;
const password = passwordInput.value; const password = passwordInput.value;